~adnano/kiln

f87fa392d50d2846119c28e16bc6d29c6a10f7a9 — Adnan Maolood 2 months ago ec0209f
site: Implement support for task URLs

Resolves #23
7 files changed, 117 insertions(+), 90 deletions(-)

M config.toml
M docs/kiln.1.scd
M main.go
M page.go
M site.go
M templates/_default/atom.xml
M templates/_default/index.gmi
M config.toml => config.toml +2 -1
@@ 1,9 1,10 @@
title = "Example website"

[permalinks]
"/" = "/{{ .Date.Format `2006/01/02` }}/{{ path.Base .Permalink }}/"
"/" = "/{{ .Date.Format `2006/01/02` }}/{{ path.Base .Path }}/"

[[tasks]]
url = "gemini://example.com"
input = [".gmi"]
output = ".gmi"
template = ".gmi"

M docs/kiln.1.scd => docs/kiln.1.scd +61 -45
@@ 195,7 195,7 @@ content/blog/2021-05-12-hello-world.gmi will have a path of

	```
	[permalinks]
	"/blog/" = "/{{ .Date.Format `2006/01/02` }}/{{ path.Base .Permalink }}"
	"/blog/" = "/{{ .Date.Format `2006/01/02` }}/{{ path.Base .Path }}"
	```

For more information on templates, see *TEMPLATES*.


@@ 292,9 292,13 @@ The following configuration options are supported per task:
	output_dir = "public"
	```

*url*
	The base URL to use for page URLs. The base URL should not have trailing
	forward slashes.

*ugly_urls*
	Specifies whether page permalinks will contain file extensions. By default,
	clean URLs without any extension are used.
	Specifies whether page paths will contain file extensions. By default,
	clean paths without any extension are used.

	Example:



@@ 624,14 628,14 @@ All templates have the following functions available to them:

Site metadata contains the following data:

[[ *Variable*
:[ *Description*
|  Title
:  The title of the site.
|  Params
:  Extra parameters specified in configuration.
|  Generated
:  Site generation time.
*Title*
	The title of the site.

*Params*
	Extra parameters specified in configuration.

*Generated*
	Site generation time.

To configure these variables, see *CONFIGURATION*.



@@ 639,51 643,63 @@ To configure these variables, see *CONFIGURATION*.

Page templates are provided with the following data:

[[ *Variable*
:[ *Description*
|  Title
:  The title of the page
|  Date
:  The date of the page
|  Weight
:  The weight of the page
|  Permalink
:  The permanent link to this page
|  FilePath
:  The path of the page file relative to the content directory
|  Content
:  The contents of the page
|  Params
:  Extra parameters specified in frontmatter
|  Prev
:  The previous page in this directory
|  Next
:  The next page in this directory
*Title*
	The title of the page

*Date*
	The date of the page

*Weight*
	The weight of the page

*Path*
	The path to the page

*URL*
	The URL of the page. If no base URL is configured, it is equivalent to
	*Path*.

*FilePath*
	The path of the page file relative to the content directory

*Content*
	The contents of the page

*Params*
	Extra parameters specified in frontmatter

*Prev*
	The previous page in this directory

*Next*
	The next page in this directory

## INDEX TEMPLATES

Index templates are provided with all the data that page templates are provided,
plus the following data:

[[ *Variable*
:[ *Description*
|  Pages
:  List of pages in this directory
|  Dirs
:  List of subdirectories
*Pages*
	List of pages in this directory

*Dirs*
	List of subdirectories

## FEED TEMPLATES

Feed templates are provided with the following data:

[[ *Variable*
:[ *Description*
|  Title
:  Title of the feed
|  Permalink
:  The permanent link to the feed directory
|  Pages
:  List of pages in this feed
*Title*
	Title of the feed

*Path*
	The path to the feed directory

*URL*
	The URL of the feed directory

*Pages*
	List of pages in this feed

## PARTIAL TEMPLATES


M main.go => main.go +1 -1
@@ 71,7 71,7 @@ func (site *Site) run() error {

func (s *Site) runTask(task *Task) error {
	// Read content
	s.root = &Page{Permalink: "/", FilePath: ""}
	s.root = &Page{Path: "/", FilePath: ""}
	if err := s.root.read("content", task, s); err != nil {
		return err
	}

M page.go => page.go +40 -31
@@ 18,19 18,20 @@ import (

// Page represents a page.
type Page struct {
	Title     string
	Date      time.Time
	Weight    int
	Params    map[string]interface{}
	FilePath  string  `yaml:"-"`
	Permalink string  `yaml:"-"`
	Content   string  `yaml:"-"`
	Prev      *Page   `yaml:"-"`
	Next      *Page   `yaml:"-"`
	Pages     []*Page `yaml:"-"`
	Dirs      []*Page `yaml:"-"`
	feeds     map[string][]byte
	index     bool
	Title    string
	Date     time.Time
	Weight   int
	Params   map[string]interface{}
	Path     string  `yaml:"-"`
	FilePath string  `yaml:"-"`
	URL      string  `yaml:"-"`
	Content  string  `yaml:"-"`
	Prev     *Page   `yaml:"-"`
	Next     *Page   `yaml:"-"`
	Pages    []*Page `yaml:"-"`
	Dirs     []*Page `yaml:"-"`
	feeds    map[string][]byte
	index    bool
}

// read reads from a directory and indexes the files and directories within it.


@@ 52,7 53,12 @@ func (p *Page) _read(srcDir, path string, task *Task, cfg *Site) error {
				continue
			}
			// Gather directory data
			dir := &Page{Permalink: "/" + path + "/", FilePath: path}
			dirPath := "/" + path + "/"
			dir := &Page{
				Path:     dirPath,
				FilePath: path,
				URL:      task.URL + dirPath,
			}
			if err := dir._read(srcDir, path, task, cfg); err != nil {
				return err
			}


@@ 134,12 140,13 @@ func (p *Page) _read(srcDir, path string, task *Task, cfg *Site) error {
						path += "/"
					}
				}
				page.Permalink = path
				if permalink, ok := cfg.permalinks[p.Permalink]; ok {
				page.Path = path
				if permalink, ok := cfg.permalinks[p.Path]; ok {
					var b strings.Builder
					permalink.Execute(&b, page)
					page.Permalink = b.String()
					page.Path = b.String()
				}
				page.URL = task.URL + page.Path
				p.Pages = append(p.Pages, page)
			}
		}


@@ 161,7 168,7 @@ func (p *Page) process(cfg *Site, task *Task) error {
	if task.TemplateExt != "" {
		// Create index
		if p.index {
			tmpl, ok := cfg.templates.FindTemplate(p.Permalink, "index"+task.TemplateExt)
			tmpl, ok := cfg.templates.FindTemplate(p.Path, "index"+task.TemplateExt)
			if ok {
				var b strings.Builder
				if err := tmpl.Execute(&b, p); err != nil {


@@ 174,7 181,7 @@ func (p *Page) process(cfg *Site, task *Task) error {
		// Process pages
		for i := range p.Pages {
			var b strings.Builder
			tmpl, ok := cfg.templates.FindTemplate(p.Permalink, "page"+task.TemplateExt)
			tmpl, ok := cfg.templates.FindTemplate(p.Path, "page"+task.TemplateExt)
			if ok {
				if err := tmpl.Execute(&b, p.Pages[i]); err != nil {
					return err


@@ 197,21 204,23 @@ func (p *Page) process(cfg *Site, task *Task) error {
func (p *Page) buildFeed(cfg *Site, feed Feed) ([]byte, error) {
	// Feed represents a feed.
	type Feed struct {
		Title     string
		Permalink string
		Pages     []*Page
		Title string
		Path  string
		URL   string
		Pages []*Page
	}

	tmpl, ok := cfg.templates.FindTemplate(p.Permalink, feed.Template)
	tmpl, ok := cfg.templates.FindTemplate(p.Path, feed.Template)
	if !ok {
		return nil, fmt.Errorf("failed to generate feed %q: missing feed template %q", feed.Title, feed.Template)
	}

	var b bytes.Buffer
	data := Feed{
		Title:     feed.Title,
		Permalink: p.Permalink,
		Pages:     p.Pages,
		Title: feed.Title,
		Path:  p.Path,
		URL:   p.URL,
		Pages: p.Pages,
	}
	if err := tmpl.Execute(&b, data); err != nil {
		return nil, err


@@ 228,11 237,11 @@ func (p *Page) addFeed(name string, content []byte) {

// write writes the directory's contents to the provided destination path.
func (p *Page) write(dstDir string, task *Task) error {
	dirPath := pathpkg.Join(dstDir, p.Permalink)
	dirPath := pathpkg.Join(dstDir, p.Path)

	// Write pages
	for _, page := range p.Pages {
		dstPath := pathpkg.Join(dstDir, page.Permalink)
		dstPath := pathpkg.Join(dstDir, page.Path)
		if !task.UglyURLs {
			dstPath = pathpkg.Join(dstPath, "index"+task.OutputExt)
		}


@@ 242,7 251,7 @@ func (p *Page) write(dstDir string, task *Task) error {
	}
	// Write index page
	if p.index {
		dstPath := pathpkg.Join(dstDir, p.Permalink, "index"+task.OutputExt)
		dstPath := pathpkg.Join(dstDir, p.Path, "index"+task.OutputExt)
		if err := p.writeTo(dstPath, task); err != nil {
			return err
		}


@@ 337,7 346,7 @@ func execute(command string, input io.Reader, output io.Writer) error {

func (p *Page) getPage(path string) *Page {
	// XXX: This is inefficient
	if p.Permalink == path {
	if p.Path == path {
		return p
	}
	for _, page := range p.Pages {


@@ 346,7 355,7 @@ func (p *Page) getPage(path string) *Page {
		}
	}
	for _, dir := range p.Dirs {
		if dir.Permalink == path {
		if dir.Path == path {
			return dir
		}
	}

M site.go => site.go +9 -8
@@ 23,14 23,15 @@ type Site struct {

// Task represents a site build task.
type Task struct {
	Input       []string          `toml:"input"`       // input file suffixes
	OutputExt   string            `toml:"output"`      // output file suffix
	TemplateExt string            `toml:"template"`    // template file suffix
	Preprocess  map[string]string `toml:"preprocess"`  // preprocess commands
	Postprocess string            `toml:"postprocess"` // postprocess command
	StaticDir   string            `toml:"static_dir"`  // static file directory
	OutputDir   string            `toml:"output_dir"`  // output directory
	UglyURLs    bool              `toml:"ugly_urls"`   // whether to use ugly URLs
	Input       []string          `toml:"input"`
	OutputExt   string            `toml:"output"`
	TemplateExt string            `toml:"template"`
	Preprocess  map[string]string `toml:"preprocess"`
	Postprocess string            `toml:"postprocess"`
	StaticDir   string            `toml:"static_dir"`
	OutputDir   string            `toml:"output_dir"`
	URL         string            `toml:"url"`
	UglyURLs    bool              `toml:"ugly_urls"`
	Feeds       []Feed            `toml:"feeds"`
	feeds       map[string][]Feed
}

M templates/_default/atom.xml => templates/_default/atom.xml +3 -3
@@ 1,11 1,11 @@
{{ `<?xml version="1.0" encoding="utf-8"?>` | safeHTML }}
<feed xmlns="http://www.w3.org/2005/Atom">
<id>{{ .Permalink }}</id>
<id>{{ .URL }}</id>
<title>{{ .Title }}</title>
<updated>{{ site.Generated.Format "2006-01-02T15:04:05Z07:00" }}</updated>
<link href="{{ .Permalink }}" rel="alternate"/>
<link href="{{ .URL }}" rel="alternate"/>
{{ range .Pages }}<entry>
	<id>{{ .Permalink }}</id>
	<id>{{ .URL }}</id>
	<title>{{ .Title }}</title>
	<updated>{{ .Date.Format "2006-01-02T15:04:05Z07:00" }}</updated>
</entry>

M templates/_default/index.gmi => templates/_default/index.gmi +1 -1
@@ 1,6 1,6 @@
# {{ .Title }}
{{ if .Content }}
{{ .Content }}{{ end }}
{{ range .Pages }}=> {{ .Permalink }} {{ if not .Date.IsZero -}}
{{ range .Pages }}=> {{ .Path }} {{ if not .Date.IsZero -}}
{{.Date.Format "2006-01-02"}} {{end}}{{.Title}}
{{ end -}}