diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6fdcca6 --- /dev/null +++ b/.gitignore @@ -0,0 +1,115 @@ +# Created by https://www.toptal.com/developers/gitignore/api/goland+all,go +# Edit at https://www.toptal.com/developers/gitignore?templates=goland+all,go + +### Go ### +# If you prefer the allow list template instead of the deny list, see community template: +# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore +# +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib + +# Test binary, built with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +# Dependency directories (remove the comment below to include it) +# vendor/ + +# Go workspace file +go.work + +### GoLand+all ### +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/**/usage.statistics.xml +.idea/**/dictionaries +.idea/**/shelf + +# AWS User-specific +.idea/**/aws.xml + +# Generated files +.idea/**/contentModel.xml + +# Sensitive or high-churn files +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml +.idea/**/dbnavigator.xml + +# Gradle +.idea/**/gradle.xml +.idea/**/libraries + +# Gradle and Maven with auto-import +# When using Gradle or Maven with auto-import, you should exclude module files, +# since they will be recreated, and may cause churn. Uncomment if using +# auto-import. +# .idea/artifacts +# .idea/compiler.xml +# .idea/jarRepositories.xml +# .idea/modules.xml +# .idea/*.iml +# .idea/modules +# *.iml +# *.ipr + +# CMake +cmake-build-*/ + +# Mongo Explorer plugin +.idea/**/mongoSettings.xml + +# File-based project format +*.iws + +# IntelliJ +out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Cursive Clojure plugin +.idea/replstate.xml + +# SonarLint plugin +.idea/sonarlint/ + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +# Editor-based Rest Client +.idea/httpRequests + +# Android studio 3.1+ serialized cache file +.idea/caches/build_file_checksums.ser + +### GoLand+all Patch ### +# Ignore everything but code style settings and run configurations +# that are supposed to be shared within teams. + +.idea/* + +!.idea/codeStyles +!.idea/runConfigurations + +# End of https://www.toptal.com/developers/gitignore/api/goland+all,go \ No newline at end of file diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..b74dc55 --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module gositemap + +go 1.20 diff --git a/sitemap.go b/sitemap.go new file mode 100644 index 0000000..89c445d --- /dev/null +++ b/sitemap.go @@ -0,0 +1,102 @@ +package gositemap + +import ( + "encoding/xml" + "fmt" + "math" +) + +type ChangeFrequency string + +const ( + Always ChangeFrequency = "always" + Hourly ChangeFrequency = "hourly" + Daily ChangeFrequency = "daily" + Weekly ChangeFrequency = "weekly" + Monthly ChangeFrequency = "monthly" + Yearly ChangeFrequency = "yearly" + Never ChangeFrequency = "never" +) + +func (f ChangeFrequency) String() string { + return string(f) +} + +type urlSetXML struct { + XMLName xml.Name `xml:"http://www.sitemaps.org/schemas/sitemap/0.9 urlset"` + URLs []*urlXML `xml:"url"` +} + +type urlXML struct { + Loc string `xml:"loc"` + LastMod *string `xml:"lastmod,omitempty"` + ChangeFreq *ChangeFrequency `xml:"changefreq,omitempty"` + Priority *float64 `xml:"priority,omitempty"` +} + +type siteMapIndexXML struct { + XMLName xml.Name `xml:"http://www.sitemaps.org/schemas/sitemap/0.9 sitemapindex"` + SiteMaps []*sitemapXML `xml:"sitemap"` +} + +type sitemapXML struct { + Loc string `xml:"loc"` + LastMod *string `xml:"lastmod,omitempty"` +} + +type SiteMap struct { + urls []*urlXML + BaseURL string +} + +func (m *SiteMap) AddURL(url string, lastMod *string, freq *ChangeFrequency, prio *float64) { + m.urls = append(m.urls, &urlXML{ + Loc: fmt.Sprintf("https://%s/%s", m.BaseURL, url), + LastMod: lastMod, + ChangeFreq: freq, + Priority: prio, + }) +} + +func prefixXML(content []byte) []byte { + return append([]byte(xml.Header), content...) +} + +func (m *SiteMap) SiteMapIndex() ([]byte, error) { + nSiteMaps := int(math.Floor(float64(len(m.urls))/50000 + 1)) + + index := &siteMapIndexXML{} + + for i := 0; i < nSiteMaps; i++ { + index.SiteMaps = append(index.SiteMaps, &sitemapXML{ + Loc: fmt.Sprintf("https://%s/sitemap%d.xml", m.BaseURL, i), + }) + } + + res, err := xml.Marshal(index) + if err != nil { + return nil, err + } + + return prefixXML(res), nil +} + +func (m *SiteMap) SiteMap(id int) ([]byte, error) { + siteMap := &urlSetXML{} + + var urls []*urlXML + if 50000*(id+1) >= len(m.urls) { + urls = m.urls[50000*id:] + } else { + urls = m.urls[50000*id : 50000*(id+1)] + } + + siteMap.URLs = append(siteMap.URLs, urls...) + + res, err := xml.Marshal(siteMap) + if err != nil { + return nil, err + } + + return prefixXML(res), nil +}