From feb0c77d66202e05f50a6cf6f717556f51d5190b Mon Sep 17 00:00:00 2001 From: adnano Date: Sat, 20 Mar 2021 23:17:58 -0400 Subject: [PATCH] Add initial implementation of build tasks --- config.go | 42 ++++++++++++++++++++++++++++++++++ dir.go | 51 ++++++++++++++++++++++++----------------- example/.gitignore | 1 + example/config.toml | 13 +++++++++++ format.go | 55 +++++++++++++++++++++------------------------ main.go | 45 +++++++++++++++---------------------- templates.go | 7 +++++- 7 files changed, 135 insertions(+), 79 deletions(-) diff --git a/config.go b/config.go index aec85bb..34c12b5 100644 --- a/config.go +++ b/config.go @@ -1,7 +1,9 @@ package main import ( + "fmt" "os" + "path" "text/template" "github.com/pelletier/go-toml" @@ -12,9 +14,27 @@ type Config struct { Title string `toml:"title"` // site title URLs []string `toml:"urls"` // site URLs Feeds map[string]string `toml:"feeds"` // site feeds + Tasks map[string]*Task `toml:"task"` // site tasks 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. func LoadConfig(path string) (*Config, error) { c := new(Config) @@ -28,6 +48,28 @@ func LoadConfig(path string) (*Config, error) { 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 } diff --git a/dir.go b/dir.go index 1f0545b..3477c93 100644 --- a/dir.go +++ b/dir.go @@ -19,6 +19,10 @@ type Dir struct { Dirs []*Dir // Subdirectories. files map[string][]byte // Static files. 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. @@ -50,6 +54,9 @@ func (d *Dir) read(srcDir string, path string) error { if entry.IsDir() { // Gather directory data dir := NewDir(path) + dir.inputExt = d.inputExt + dir.outputExt = d.outputExt + dir.templateExt = d.templateExt if err := dir.read(srcDir, path); err != nil { return err } @@ -60,9 +67,9 @@ func (d *Dir) read(srcDir string, path string) error { if err != nil { return err } - if pathpkg.Ext(name) == ".gmi" { + if ext := pathpkg.Ext(name); ext == d.inputExt { // Gather page data - if name == "index.gmi" { + if strings.TrimSuffix(name, ext) == "index" { d.index = NewPage(d.Path, content) d.Title = d.index.Title d.Content = d.index.Content @@ -80,24 +87,26 @@ func (d *Dir) read(srcDir string, path string) error { // manipulate processes and manipulates the directory's contents. func (d *Dir) manipulate(cfg *Config) error { - // Create index - if d.index != nil { - var b strings.Builder - tmpl := cfg.Templates.FindTemplate(d.Path, "index.gmi") - if err := tmpl.Execute(&b, d); err != nil { - return err + if d.templateExt != "" { + // Create index + if d.index != nil { + var b strings.Builder + tmpl := cfg.Templates.FindTemplate(d.Path, "index"+d.templateExt) + if err := tmpl.Execute(&b, d); err != nil { + return err + } + d.index.Content = b.String() } - d.index.Content = b.String() - } - // Manipulate pages - for i := range d.Pages { - var b strings.Builder - tmpl := cfg.Templates.FindTemplate(d.Path, "page.gmi") - if err := tmpl.Execute(&b, d.Pages[i]); err != nil { - return err + // Manipulate pages + for i := range d.Pages { + var b strings.Builder + tmpl := cfg.Templates.FindTemplate(d.Path, "page"+d.templateExt) + if err := tmpl.Execute(&b, d.Pages[i]); err != nil { + return err + } + d.Pages[i].Content = b.String() } - d.Pages[i].Content = b.String() } // Feed represents a feed. @@ -134,7 +143,7 @@ func (d *Dir) manipulate(cfg *Config) error { } // 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 dirPath := pathpkg.Join(dstDir, d.Path) 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 for _, page := range d.Pages { - path, content := format.Format(page, cfg) + path, content := format.Format(page) dstPath := pathpkg.Join(dstDir, path) dir := pathpkg.Dir(dstPath) os.MkdirAll(dir, 0755) @@ -171,7 +180,7 @@ func (d *Dir) write(dstDir string, format Format, cfg *Config) error { // Write the index file if d.index != nil { - path, content := format.Format(d.index, cfg) + path, content := format.Format(d.index) dstPath := pathpkg.Join(dstDir, path) dir := pathpkg.Dir(dstPath) os.MkdirAll(dir, 0755) @@ -186,7 +195,7 @@ func (d *Dir) write(dstDir string, format Format, cfg *Config) error { // Write subdirectories for _, dir := range d.Dirs { - dir.write(dstDir, format, cfg) + dir.write(dstDir, format) } return nil } diff --git a/example/.gitignore b/example/.gitignore index c75eecc..5d97af1 100644 --- a/example/.gitignore +++ b/example/.gitignore @@ -1 +1,2 @@ /public +/public.html diff --git a/example/config.toml b/example/config.toml index 243a36d..8ec4ea1 100644 --- a/example/config.toml +++ b/example/config.toml @@ -3,3 +3,16 @@ urls = ["gemini://example.com", "https://example.com"] [feeds] "/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" diff --git a/format.go b/format.go index e0bb10c..44afc4a 100644 --- a/format.go +++ b/format.go @@ -8,43 +8,38 @@ import ( // Format represents an output format. 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) { - return f(p, cfg) +func (f FormatFunc) Format(p *Page) (string, []byte) { + return f(p) } -// FormatGemini formats the page as Gemini text. -func FormatGemini(p *Page, cfg *Config) (path string, content []byte) { - path = pathpkg.Join(p.Path, "index.gmi") - content = []byte(p.Content) - return -} +// GeminiToHTML returns an output format that converts Gemini text to HTML. +func GeminiToHTML(cfg *Config) Format { + return FormatFunc(func(p *Page) (path string, content []byte) { + path = pathpkg.Join(p.Path, "index.html") -// FormatHTML formats the page as HTML. -func FormatHTML(p *Page, cfg *Config) (path string, content []byte) { - path = pathpkg.Join(p.Path, "index.html") + r := strings.NewReader(p.Content) + content = textToHTML(r) - r := strings.NewReader(p.Content) - content = textToHTML(r) + // html template context + type htmlCtx struct { + Title string // page title + Content string // page HTML contents + } - // html template context - type htmlCtx struct { - Title string // page title - Content string // page HTML contents - } - - var b bytes.Buffer - // clean path to remove trailing slash - dir := pathpkg.Dir(pathpkg.Clean(p.Path)) - tmpl := cfg.Templates.FindTemplate(dir, "output.html") - tmpl.Execute(&b, &htmlCtx{ - Title: p.Title, - Content: string(content), + var b bytes.Buffer + // clean path to remove trailing slash + dir := pathpkg.Dir(pathpkg.Clean(p.Path)) + tmpl := cfg.Templates.FindTemplate(dir, "output.html") + tmpl.Execute(&b, &htmlCtx{ + Title: p.Title, + Content: string(content), + }) + content = b.Bytes() + return }) - content = b.Bytes() - return } diff --git a/main.go b/main.go index 3e0581e..bed9bc3 100644 --- a/main.go +++ b/main.go @@ -1,7 +1,6 @@ package main import ( - "flag" "log" ) @@ -12,10 +11,6 @@ func main() { } 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 cfg, err := LoadConfig("config.toml") if err != nil { @@ -25,29 +20,25 @@ func run() error { return err } - var output Format - switch format { - case "gemini": - output = FormatFunc(FormatGemini) - case "html": - output = FormatFunc(FormatHTML) - default: - log.Fatalf("unknown output format %q", format) + for _, task := range cfg.Tasks { + // Load content + dir := NewDir("") + dir.inputExt = task.Input + dir.outputExt = task.Output + dir.templateExt = task.Template + if err := dir.read("content", ""); err != nil { + return err + } + dir.sort() + // Manipulate content + if err := dir.manipulate(cfg); err != nil { + return err + } + // Write content + if err := dir.write(task.Destination, task); err != nil { + return err + } } - // Load content - dir := NewDir("") - if err := dir.read("content", ""); err != nil { - return err - } - dir.sort() - // Manipulate content - if err := dir.manipulate(cfg); err != nil { - return err - } - // Write content - if err := dir.write("public", output, cfg); err != nil { - return err - } return nil } diff --git a/templates.go b/templates.go index c86a7b4..c3977a5 100644 --- a/templates.go +++ b/templates.go @@ -3,6 +3,7 @@ package main import ( "embed" "io/ioutil" + "log" "os" pathpkg "path" "path/filepath" @@ -82,5 +83,9 @@ func (t *Templates) FindTemplate(path string, tmpl string) *template.Template { if t, ok := t.tmpls[tmplPath]; ok { 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 }