tasks: add tasks.feeds support

Every task can now have a [[tasks.feeds]] entry to generate one or more feeds.
It must have the following arguments:

[[tasks.feeds]]
input_dir = ""
title = "Example feed"
template = "atom.xml"
output = "atom.xml"
This commit is contained in:
oliverpool 2021-09-02 11:59:08 +02:00 committed by adnano
parent d5f85931ea
commit 2ef8f77d24
6 changed files with 126 additions and 18 deletions

View file

@ -1,9 +1,6 @@
title = "Example website" title = "Example website"
urls = ["gemini://example.com"] urls = ["gemini://example.com"]
[feeds]
"/" = "Example feed"
[permalinks] [permalinks]
"/" = "/{{ .Date.Format `2006/01/02` }}/{{ path.Base .Permalink }}/" "/" = "/{{ .Date.Format `2006/01/02` }}/{{ path.Base .Permalink }}/"
@ -13,3 +10,9 @@ output = ".gmi"
template = ".gmi" template = ".gmi"
static_dir = "static" static_dir = "static"
output_dir = "public" output_dir = "public"
[[tasks.feeds]]
input_dir = ""
title = "Example feed"
template = "atom.xml"
output = "atom.xml"

63
dir.go
View file

@ -22,7 +22,9 @@ type Dir struct {
Pages []*Page Pages []*Page
Dirs []*Dir Dirs []*Dir
index *Page // The index page. index *Page // The index page.
feed []byte // Atom feed. feed []byte // Atom feed (deprecated)
extraFiles map[string][]byte // Atom/RSS feeds
path string // relative to the content dir
} }
// Page represents a page. // Page represents a page.
@ -57,7 +59,7 @@ func (d *Dir) _read(srcDir, path string, task *Task, cfg *Site) error {
continue continue
} }
// Gather directory data // Gather directory data
dir := &Dir{Permalink: "/" + path + "/"} dir := &Dir{Permalink: "/" + path + "/", path: path}
if err := dir._read(srcDir, path, task, cfg); err != nil { if err := dir._read(srcDir, path, task, cfg); err != nil {
return err return err
} }
@ -151,6 +153,21 @@ func (d *Dir) _read(srcDir, path string, task *Task, cfg *Site) error {
// process processes the directory's contents. // process processes the directory's contents.
func (d *Dir) process(cfg *Site, task *Task) error { func (d *Dir) process(cfg *Site, task *Task) error {
// build feeds
// this must happen before the page processing
// to have an unprocessed Page.Content
// 192: d.Pages[i].Content = b.String()
for _, feed := range task.Feeds {
if d.path != feed.InputDir {
continue
}
b, err := d.buildFeed(cfg, feed)
if err != nil {
return err
}
d.addExtraFile(feed.Output, b)
}
if task.TemplateExt != "" { if task.TemplateExt != "" {
// Create index // Create index
if d.index != nil { if d.index != nil {
@ -214,6 +231,39 @@ func (d *Dir) process(cfg *Site, task *Task) error {
return nil return nil
} }
// buildFeed build the feed of the directory
func (d Dir) buildFeed(cfg *Site, feed Feed) ([]byte, error) {
tmpl, ok := cfg.templates.FindTemplate(d.Permalink, feed.Template)
if !ok {
return nil, fmt.Errorf("missing feed template %q to generate %q", feed.Template, feed.Title)
}
var b bytes.Buffer
data := struct {
Title string // Feed title.
Permalink string // Feed permalink.
Updated time.Time // Last updated time.
Pages []*Page // Feed pages.
}{
Title: feed.Title,
Permalink: d.Permalink,
Updated: time.Now(),
Pages: d.Pages,
}
if err := tmpl.Execute(&b, data); err != nil {
return nil, err
}
return b.Bytes(), nil
}
func (d *Dir) addExtraFile(name string, content []byte) {
if d.extraFiles == nil {
d.extraFiles = map[string][]byte{name: content}
} else {
d.extraFiles[name] = content
}
}
// write writes the directory's contents to the provided destination path. // write writes the directory's contents to the provided destination path.
func (d *Dir) write(dstDir string, task *Task) error { func (d *Dir) write(dstDir string, task *Task) error {
dirPath := pathpkg.Join(dstDir, d.Permalink) dirPath := pathpkg.Join(dstDir, d.Permalink)
@ -255,6 +305,15 @@ func (d *Dir) write(dstDir string, task *Task) error {
} }
} }
// Write feeds
for name, content := range d.extraFiles {
dstPath := pathpkg.Join(dstDir, name)
os.MkdirAll(dirPath, 0755)
if err := os.WriteFile(dstPath, content, 0644); err != nil {
return err
}
}
// Write subdirectories // Write subdirectories
for _, dir := range d.Dirs { for _, dir := range d.Dirs {
dir.write(dstDir, task) dir.write(dstDir, task)

View file

@ -187,20 +187,31 @@ site URLs may be specified for sites that are available at multiple locations.
## FEEDS ## FEEDS
Feeds can be specified in the [feeds] table of the configuration file. Keys Feeds can be specified in [[tasks.feeds]] for every task needing them.
denote the path to the feed directory and values denote the title of the feed.
Feeds are written to the output directory plus the feed directory plus
"atom.xml".
Example feed configuration: Example feed configuration:
``` ```
# This will generate a feed which will be written to public/blog/atom.xml # This will generate a feed which will be written to public/blog/atom.xml
[feeds] [[tasks.feeds]]
"/blog/" = "My blog" input_dir = "blog"
title = "My Blog"
template = "atom.xml"
output = "blog/atom.xml"
``` ```
*input_dir*
the content folder for which the feed will be generated
*title*
the title of the feed, accessible via {{ .Title }} in the template
*template*
the template to use for the feed
*output*
the absolute path for the rendered feed
## PERMALINKS ## PERMALINKS
Permalinks can be used to rewrite page paths. Permalinks are specified in the Permalinks can be used to rewrite page paths. Permalinks are specified in the

View file

@ -71,7 +71,7 @@ func (site *Site) run() error {
func (s *Site) runTask(task *Task) error { func (s *Site) runTask(task *Task) error {
// Read content // Read content
s.root = &Dir{Permalink: "/"} s.root = &Dir{Permalink: "/", path: ""}
if err := s.root.read("content", task, s); err != nil { if err := s.root.read("content", task, s); err != nil {
return err return err
} }

35
site.go
View file

@ -2,7 +2,10 @@ package main
import ( import (
"fmt" "fmt"
"log"
"os" "os"
"path"
"strings"
"text/template" "text/template"
"github.com/pelletier/go-toml" "github.com/pelletier/go-toml"
@ -31,6 +34,14 @@ type Task struct {
StaticDir string `toml:"static_dir"` // static file directory StaticDir string `toml:"static_dir"` // static file directory
OutputDir string `toml:"output_dir"` // output directory OutputDir string `toml:"output_dir"` // output directory
UglyURLs bool `toml:"ugly_urls"` // whether to use ugly URLs UglyURLs bool `toml:"ugly_urls"` // whether to use ugly URLs
Feeds []Feed `toml:"feeds"`
}
type Feed struct {
InputDir string `toml:"input_dir"`
Title string `toml:"title"`
Template string `toml:"template"`
Output string `toml:"output"`
} }
func (t *Task) Match(ext string) bool { func (t *Task) Match(ext string) bool {
@ -80,6 +91,30 @@ func LoadSite(config string) (*Site, error) {
return nil, err return nil, err
} }
// deprecate [feeds]
if len(site.Feeds) > 0 {
log.Println("The [feeds] configuration is deprecated")
for _, task := range site.Tasks {
if len(task.Feeds) > 0 {
log.Println("and can't be used along [[tasks.feeds]]")
return nil, fmt.Errorf("please remove (or rename) the [feeds] category")
}
}
log.Println("Please replace it with [[tasks.feeds]] in every task needing feeds:")
for permalink, title := range site.Feeds {
dir := strings.Trim(permalink, "/")
output := path.Join(dir, "atom.xml")
fmt.Fprintf(log.Writer(), `[[tasks.feeds]]
input_dir = %q
title = %q
template = "atom.xml"
output = %q
`, dir, title, output)
}
log.Println("You will also need to change .Entries to .Pages in \"atom.xml\"")
}
return site, nil return site, nil
} }

View file

@ -4,7 +4,7 @@
<title>{{ .Title }}</title> <title>{{ .Title }}</title>
<updated>{{ .Updated.Format "2006-01-02T15:04:05Z07:00" }}</updated> <updated>{{ .Updated.Format "2006-01-02T15:04:05Z07:00" }}</updated>
<link href="{{ index site.URLs 0 | safeURL }}{{ .Permalink }}" rel="alternate"/> <link href="{{ index site.URLs 0 | safeURL }}{{ .Permalink }}" rel="alternate"/>
{{ range .Entries }}<entry> {{ range .Pages }}<entry>
<id>{{ index site.URLs 0 }}{{ .Permalink }}</id> <id>{{ index site.URLs 0 }}{{ .Permalink }}</id>
<title>{{ .Title }}</title> <title>{{ .Title }}</title>
<updated>{{ .Date.Format "2006-01-02T15:04:05Z07:00" }}</updated> <updated>{{ .Date.Format "2006-01-02T15:04:05Z07:00" }}</updated>