Add initial implementation of build tasks

This commit is contained in:
adnano 2021-03-20 23:17:58 -04:00
parent f2f4d09146
commit feb0c77d66
7 changed files with 135 additions and 79 deletions

View file

@ -1,7 +1,9 @@
package main package main
import ( import (
"fmt"
"os" "os"
"path"
"text/template" "text/template"
"github.com/pelletier/go-toml" "github.com/pelletier/go-toml"
@ -12,9 +14,27 @@ type Config struct {
Title string `toml:"title"` // site title Title string `toml:"title"` // site title
URLs []string `toml:"urls"` // site URLs URLs []string `toml:"urls"` // site URLs
Feeds map[string]string `toml:"feeds"` // site feeds Feeds map[string]string `toml:"feeds"` // site feeds
Tasks map[string]*Task `toml:"task"` // site tasks
Templates *Templates `toml:"-"` // site templates Templates *Templates `toml:"-"` // site templates
} }
// Task represents a site build task.
type Task struct {
Input string `toml:"input"` // input file extension
Output string `toml:"output"` // output file extension
Template string `toml:"template"` // template file extension
PostProcess string `toml:"postprocess"` // postprocess directive
Destination string `toml:"destination"` // destination directory
postProcess Format
}
func (t Task) Format(p *Page) (string, []byte) {
if t.postProcess == nil {
return path.Join(p.Path, "index"+t.Output), []byte(p.Content)
}
return t.postProcess.Format(p)
}
// LoadConfig loads the configuration from the provided path. // LoadConfig loads the configuration from the provided path.
func LoadConfig(path string) (*Config, error) { func LoadConfig(path string) (*Config, error) {
c := new(Config) c := new(Config)
@ -28,6 +48,28 @@ func LoadConfig(path string) (*Config, error) {
return nil, err return nil, err
} }
// Add default task
// The toml library overwrites the map, so add default values after parsing
if _, ok := c.Tasks["gemini"]; !ok {
c.Tasks["gemini"] = &Task{
Input: ".gmi",
Output: ".gmi",
Template: ".gmi",
Destination: "public",
}
}
for _, task := range c.Tasks {
switch task.PostProcess {
case "":
continue
case "geminiToHTML":
task.postProcess = GeminiToHTML(c)
default:
return nil, fmt.Errorf("unrecognized postprocess directive %q", task.PostProcess)
}
}
return c, nil return c, nil
} }

25
dir.go
View file

@ -19,6 +19,10 @@ type Dir struct {
Dirs []*Dir // Subdirectories. Dirs []*Dir // Subdirectories.
files map[string][]byte // Static files. files map[string][]byte // Static files.
index *Page // The index page. index *Page // The index page.
inputExt string // input file extension
outputExt string // output file extension
templateExt string // template file extension
} }
// NewDir returns a new Dir with the given path. // NewDir returns a new Dir with the given path.
@ -50,6 +54,9 @@ func (d *Dir) read(srcDir string, path string) error {
if entry.IsDir() { if entry.IsDir() {
// Gather directory data // Gather directory data
dir := NewDir(path) dir := NewDir(path)
dir.inputExt = d.inputExt
dir.outputExt = d.outputExt
dir.templateExt = d.templateExt
if err := dir.read(srcDir, path); err != nil { if err := dir.read(srcDir, path); err != nil {
return err return err
} }
@ -60,9 +67,9 @@ func (d *Dir) read(srcDir string, path string) error {
if err != nil { if err != nil {
return err return err
} }
if pathpkg.Ext(name) == ".gmi" { if ext := pathpkg.Ext(name); ext == d.inputExt {
// Gather page data // Gather page data
if name == "index.gmi" { if strings.TrimSuffix(name, ext) == "index" {
d.index = NewPage(d.Path, content) d.index = NewPage(d.Path, content)
d.Title = d.index.Title d.Title = d.index.Title
d.Content = d.index.Content d.Content = d.index.Content
@ -80,10 +87,11 @@ func (d *Dir) read(srcDir string, path string) error {
// manipulate processes and manipulates the directory's contents. // manipulate processes and manipulates the directory's contents.
func (d *Dir) manipulate(cfg *Config) error { func (d *Dir) manipulate(cfg *Config) error {
if d.templateExt != "" {
// Create index // Create index
if d.index != nil { if d.index != nil {
var b strings.Builder var b strings.Builder
tmpl := cfg.Templates.FindTemplate(d.Path, "index.gmi") tmpl := cfg.Templates.FindTemplate(d.Path, "index"+d.templateExt)
if err := tmpl.Execute(&b, d); err != nil { if err := tmpl.Execute(&b, d); err != nil {
return err return err
} }
@ -93,12 +101,13 @@ func (d *Dir) manipulate(cfg *Config) error {
// Manipulate pages // Manipulate pages
for i := range d.Pages { for i := range d.Pages {
var b strings.Builder var b strings.Builder
tmpl := cfg.Templates.FindTemplate(d.Path, "page.gmi") tmpl := cfg.Templates.FindTemplate(d.Path, "page"+d.templateExt)
if err := tmpl.Execute(&b, d.Pages[i]); err != nil { if err := tmpl.Execute(&b, d.Pages[i]); err != nil {
return err return err
} }
d.Pages[i].Content = b.String() d.Pages[i].Content = b.String()
} }
}
// Feed represents a feed. // Feed represents a feed.
type Feed struct { type Feed struct {
@ -134,7 +143,7 @@ func (d *Dir) manipulate(cfg *Config) error {
} }
// write writes the Dir to the provided destination path. // write writes the Dir to the provided destination path.
func (d *Dir) write(dstDir string, format Format, cfg *Config) error { func (d *Dir) write(dstDir string, format Format) error {
// Create the directory // Create the directory
dirPath := pathpkg.Join(dstDir, d.Path) dirPath := pathpkg.Join(dstDir, d.Path)
if err := os.MkdirAll(dirPath, 0755); err != nil { if err := os.MkdirAll(dirPath, 0755); err != nil {
@ -156,7 +165,7 @@ func (d *Dir) write(dstDir string, format Format, cfg *Config) error {
// Write pages // Write pages
for _, page := range d.Pages { for _, page := range d.Pages {
path, content := format.Format(page, cfg) path, content := format.Format(page)
dstPath := pathpkg.Join(dstDir, path) dstPath := pathpkg.Join(dstDir, path)
dir := pathpkg.Dir(dstPath) dir := pathpkg.Dir(dstPath)
os.MkdirAll(dir, 0755) os.MkdirAll(dir, 0755)
@ -171,7 +180,7 @@ func (d *Dir) write(dstDir string, format Format, cfg *Config) error {
// Write the index file // Write the index file
if d.index != nil { if d.index != nil {
path, content := format.Format(d.index, cfg) path, content := format.Format(d.index)
dstPath := pathpkg.Join(dstDir, path) dstPath := pathpkg.Join(dstDir, path)
dir := pathpkg.Dir(dstPath) dir := pathpkg.Dir(dstPath)
os.MkdirAll(dir, 0755) os.MkdirAll(dir, 0755)
@ -186,7 +195,7 @@ func (d *Dir) write(dstDir string, format Format, cfg *Config) error {
// Write subdirectories // Write subdirectories
for _, dir := range d.Dirs { for _, dir := range d.Dirs {
dir.write(dstDir, format, cfg) dir.write(dstDir, format)
} }
return nil return nil
} }

1
example/.gitignore vendored
View file

@ -1 +1,2 @@
/public /public
/public.html

View file

@ -3,3 +3,16 @@ urls = ["gemini://example.com", "https://example.com"]
[feeds] [feeds]
"/blog/" = "Example Feed" "/blog/" = "Example Feed"
[task.gemini]
input = ".gmi"
output = ".gmi"
template = ".gmi"
destination = "public"
[task.geminiToHTML]
input = ".gmi"
output = ".html"
template = ".gmi"
postprocess = "geminiToHTML"
destination = "public.html"

View file

@ -8,24 +8,18 @@ import (
// Format represents an output format. // Format represents an output format.
type Format interface { type Format interface {
Format(*Page, *Config) (path string, content []byte) Format(*Page) (path string, content []byte)
} }
type FormatFunc func(*Page, *Config) (string, []byte) type FormatFunc func(*Page) (string, []byte)
func (f FormatFunc) Format(p *Page, cfg *Config) (string, []byte) { func (f FormatFunc) Format(p *Page) (string, []byte) {
return f(p, cfg) return f(p)
} }
// FormatGemini formats the page as Gemini text. // GeminiToHTML returns an output format that converts Gemini text to HTML.
func FormatGemini(p *Page, cfg *Config) (path string, content []byte) { func GeminiToHTML(cfg *Config) Format {
path = pathpkg.Join(p.Path, "index.gmi") return FormatFunc(func(p *Page) (path string, content []byte) {
content = []byte(p.Content)
return
}
// FormatHTML formats the page as HTML.
func FormatHTML(p *Page, cfg *Config) (path string, content []byte) {
path = pathpkg.Join(p.Path, "index.html") path = pathpkg.Join(p.Path, "index.html")
r := strings.NewReader(p.Content) r := strings.NewReader(p.Content)
@ -47,4 +41,5 @@ func FormatHTML(p *Page, cfg *Config) (path string, content []byte) {
}) })
content = b.Bytes() content = b.Bytes()
return return
})
} }

23
main.go
View file

@ -1,7 +1,6 @@
package main package main
import ( import (
"flag"
"log" "log"
) )
@ -12,10 +11,6 @@ func main() {
} }
func run() error { func run() error {
var format string
flag.StringVar(&format, "format", "gemini", "output format to use. Supported formats include gemini and html")
flag.Parse()
// Load config // Load config
cfg, err := LoadConfig("config.toml") cfg, err := LoadConfig("config.toml")
if err != nil { if err != nil {
@ -25,18 +20,12 @@ func run() error {
return err return err
} }
var output Format for _, task := range cfg.Tasks {
switch format {
case "gemini":
output = FormatFunc(FormatGemini)
case "html":
output = FormatFunc(FormatHTML)
default:
log.Fatalf("unknown output format %q", format)
}
// Load content // Load content
dir := NewDir("") dir := NewDir("")
dir.inputExt = task.Input
dir.outputExt = task.Output
dir.templateExt = task.Template
if err := dir.read("content", ""); err != nil { if err := dir.read("content", ""); err != nil {
return err return err
} }
@ -46,8 +35,10 @@ func run() error {
return err return err
} }
// Write content // Write content
if err := dir.write("public", output, cfg); err != nil { if err := dir.write(task.Destination, task); err != nil {
return err return err
} }
}
return nil return nil
} }

View file

@ -3,6 +3,7 @@ package main
import ( import (
"embed" "embed"
"io/ioutil" "io/ioutil"
"log"
"os" "os"
pathpkg "path" pathpkg "path"
"path/filepath" "path/filepath"
@ -82,5 +83,9 @@ func (t *Templates) FindTemplate(path string, tmpl string) *template.Template {
if t, ok := t.tmpls[tmplPath]; ok { if t, ok := t.tmpls[tmplPath]; ok {
return t return t
} }
return t.tmpls[pathpkg.Join("/_default", tmpl)] if t, ok := t.tmpls[pathpkg.Join("/_default", tmpl)]; ok {
return t
}
log.Fatalf("failed to find template %q", tmpl)
return nil
} }