diff --git a/README.md b/README.md index 95a7fe9..00ca339 100644 --- a/README.md +++ b/README.md @@ -55,6 +55,14 @@ Usage of md2gmn: md2gmn is mainly made to facilitate testing the Gemtext renderer but can be used as a standalone program as well. +## Site configuration + +For RSS feeds to use correct URLs, you should define geminiBaseURL in +Hugo's configuration file (config.toml, config.yaml, or config.json). + +Other attributes from this file, such as site title, will also be used +during RSS feed generation if they are defined. + ## License This program is redistributed under the terms and conditions of the GNU diff --git a/cmd/gmnhg/main.go b/cmd/gmnhg/main.go index 472bfa5..78a056a 100644 --- a/cmd/gmnhg/main.go +++ b/cmd/gmnhg/main.go @@ -78,6 +78,26 @@ // (https://github.com/Masterminds/sprig); see the sprig documentation // for more details. // +// RSS will be generated as rss.xml for the root directory and all +// branch directories. Site title and other RSS metadata will be +// loaded from the Hugo configuration file (config.toml, config.yaml, +// or config.json). +// +// A new setting, geminiBaseURL, should be added to the Hugo +// configuration file to ensure that RSS paths are correct. This is +// more or less the same as Hugo's baseURL, but is separate in case +// your Gemini site is deployed to a different server. +// +// RSS templates can be overriden by defining a template in one of +// several places: +// +// * Site-wide: gmnhg/_default/rss.gotmpl +// +// * Site root: gmnhg/rss.gotmpl +// +// * Directories: gmnhg/rss/dirname.gotmpl for a directory "/dirname" +// or gmnhg/rss/dirname/subdir.gotmpl for "/dirname/subdir" +// // One might want to ignore _index.gmi.md files with the following Hugo // config option in config.toml: // @@ -91,6 +111,7 @@ package main import ( "bytes" + "encoding/json" "errors" "flag" "fmt" @@ -103,6 +124,9 @@ import ( "strings" "text/template" + "github.com/BurntSushi/toml" + "gopkg.in/yaml.v2" + gemini "github.com/tdemin/gmnhg" "github.com/tdemin/gmnhg/internal/gmnhg" ) @@ -111,6 +135,7 @@ const ( defaultPageTemplate = "single" indexMdFilename = "_index.gmi.md" indexFilename = "index.gmi" + rssFilename = "rss.xml" ) const ( @@ -128,6 +153,13 @@ var ( var hugoConfigFiles = []string{"config.toml", "config.yaml", "config.json"} +type SiteConfig struct { + GeminiBaseURL string `yaml:"geminiBaseURL"` + Title string `yaml:"title"` + Copyright string `yaml:"copyright"` + LanguageCode string `yaml:"languageCode"` +} + func copyFile(dst, src string) error { input, err := os.Open(src) if err != nil { @@ -200,9 +232,28 @@ func main() { } configFound := false + var siteConf SiteConfig for _, filename := range hugoConfigFiles { if fileInfo, err := os.Stat(filename); !(os.IsNotExist(err) || fileInfo.IsDir()) { configFound = true + buf, err := ioutil.ReadFile(filename) + if err != nil { + panic(err) + } + switch ext := filepath.Ext(filename); ext { + case ".toml": + if err := toml.Unmarshal(buf, &siteConf); err != nil { + panic(err) + } + case ".yaml": + if err := yaml.Unmarshal(buf, &siteConf); err != nil { + panic(err) + } + case ".json": + if err := json.Unmarshal(buf, &siteConf); err != nil { + panic(err) + } + } break } } @@ -294,7 +345,7 @@ func main() { dirs := strings.Split(matches[1], "/") // only include leaf resources pages in leaf index if !isLeafIndex && hasSubPath(leafIndexPaths, path) { - topLevelPosts[matches[1]] = append(topLevelPosts[matches[1]], p) + topLevelPosts["/"+matches[1]] = append(topLevelPosts["/"+matches[1]], p) } else { // include normal pages in all subdirectory indices for i, dir := range dirs { @@ -303,8 +354,9 @@ func main() { } } for _, dir := range dirs { - topLevelPosts[dir] = append(topLevelPosts[dir], p) + topLevelPosts["/"+dir] = append(topLevelPosts["/"+dir], p) } + topLevelPosts["/"] = append(topLevelPosts["/"], p) } } return nil @@ -353,7 +405,11 @@ func main() { } // render indexes for top-level dirs for dirname, posts := range topLevelPosts { - tmpl, hasTmpl := templates["top/"+dirname] + // skip the main index + if dirname == "/" { + continue + } + tmpl, hasTmpl := templates["top"+dirname] if !hasTmpl { continue } @@ -403,6 +459,44 @@ func main() { panic(err) } + // render RSS/Atom feeds + if tmpl, hasTmpl := templates["_default/rss"]; hasTmpl { + defaultRssTemplate = tmpl + } + for dirname, posts := range topLevelPosts { + // do not render RSS for leaf paths + if hasSubPath(leafIndexPaths, path.Join(contentBase, dirname)+"/") { + continue + } + tmpl, hasTmpl := templates["rss"+dirname] + if !hasTmpl { + if rootTmpl, hasTmpl := templates["rss"]; dirname == "/" && hasTmpl { + tmpl = rootTmpl + } else { + tmpl = defaultRssTemplate + } + } + sc := map[string]interface{}{ + "GeminiBaseURL": siteConf.GeminiBaseURL, + "Title": siteConf.Title, + "Copyright": siteConf.Copyright, + "LanguageCode": siteConf.LanguageCode, + } + cnt := map[string]interface{}{ + "Posts": posts, + "Dirname": dirname, + "Link": path.Join(dirname, rssFilename), + "Site": sc, + } + buf := bytes.Buffer{} + if err := tmpl.Execute(&buf, cnt); err != nil { + panic(err) + } + if err := writeFile(path.Join(outputDir, dirname, rssFilename), buf.Bytes()); err != nil { + panic(err) + } + } + // copy page resources to output dir if err := filepath.Walk(contentBase, func(p string, info os.FileInfo, err error) error { if err != nil { diff --git a/cmd/gmnhg/templates.go b/cmd/gmnhg/templates.go index c8256a0..046d9d6 100644 --- a/cmd/gmnhg/templates.go +++ b/cmd/gmnhg/templates.go @@ -44,8 +44,40 @@ var defaultSingleTemplate = mustParseTmpl("single", `# {{ .Metadata.PostTitle }} var defaultIndexTemplate = mustParseTmpl("index", `# Site index {{ with .Content }}{{ printf "%s" . -}}{{ end }} -{{ range $dir, $posts := .PostData }}Index of {{ $dir }}: +{{- range $dir, $posts := .PostData }}{{ if and (ne $dir "/") (eq (dir $dir) "/") }} +Index of {{ trimPrefix "/" $dir }}: -{{ range $p := $posts | sortPosts }}=> {{ $p.Link }} {{ $p.Metadata.PostDate.Format "2006-01-02 15:04" }} - {{ $p.Metadata.PostTitle }} -{{ end }}{{ end }} +{{ range $p := $posts | sortPosts }}=> {{ $p.Link }} {{ $p.Metadata.PostDate.Format "2006-01-02 15:04" }} - {{ if $p.Metadata.PostTitle }}{{ $p.Metadata.PostTitle }}{{else}}{{ $p.Link }}{{end}} +{{ end }}{{ end }}{{ end }} +`) + +var defaultRssTemplate = mustParseTmpl("rss", `{{- $Site := .Site -}} +{{- $Dirname := trimPrefix "/" .Dirname -}} +{{- $DirLink := list (trimSuffix "/" $Site.GeminiBaseURL) $Dirname | join "/" | html -}} +{{- $RssLink := list (trimSuffix "/" $Site.GeminiBaseURL) (trimPrefix "/" .Link) | join "/" | html -}} + + + + {{ if $Site.Title }}{{ html $Site.Title }}{{ else }}Site feed{{ with $Dirname }} for {{ html . }}{{end}}{{end}} + {{ $DirLink }} + Recent content{{ with $Dirname }} in {{ html . }}{{end}}{{ with $Site.Title }} on {{ html . }}{{end}} + gmnhg{{ with $Site.LanguageCode }} + {{ html .}}{{end}}{{ with $Site.Author.email }} + {{ html . }}{{ with $Site.Author.name }} ({{ html . }}){{end}} + {{ html . }}{{ with $Site.Author.name }} ({{ html . }}){{end}}{{end}}{{ with $Site.Copyright }} + {{ html . }}{{end}} + {{ now.Format "Mon, 02 Jan 2006 15:04:05 -0700" }} + {{ printf "" $RssLink }} + {{ range $i, $p := .Posts | sortPosts }}{{ if lt $i 25 }} + {{- $AbsURL := list (trimSuffix "/" $Site.GeminiBaseURL) (trimPrefix "/" $p.Link) | join "/" | html }} + + {{ if $p.Metadata.PostTitle }}{{ html $p.Metadata.PostTitle }}{{ else }}{{ trimPrefix "/" $p.Link | html }}{{end}} + {{ $AbsURL }} + {{ $p.Metadata.PostDate.Format "Mon, 02 Jan 2006 15:04:05 -0700" }} + {{ $AbsURL }} + {{ html $p.Metadata.PostSummary }} + + {{end}}{{end}} + + `) diff --git a/go.mod b/go.mod index 20b7ca2..4fd1b29 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module github.com/tdemin/gmnhg go 1.16 require ( + github.com/BurntSushi/toml v0.4.1 github.com/Masterminds/sprig/v3 v3.2.2 github.com/gomarkdown/markdown v0.0.0-20210514010506-3b9f47219fe7 github.com/mattn/go-runewidth v0.0.13 // indirect diff --git a/go.sum b/go.sum index f67b1a2..40c8c5b 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,5 @@ +github.com/BurntSushi/toml v0.4.1 h1:GaI7EiDXDRfa8VshkTj7Fym7ha+y8/XxIgD2okUIjLw= +github.com/BurntSushi/toml v0.4.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc= diff --git a/render.go b/render.go index f60680d..615867a 100644 --- a/render.go +++ b/render.go @@ -36,6 +36,7 @@ type HugoMetadata struct { PostIsDraft bool `yaml:"draft"` PostLayout string `yaml:"layout"` PostDate time.Time `yaml:"date"` + PostSummary string `yaml:"summary"` IsHeadless bool `yaml:"headless"` }