pagr

A 'static site generator', built using dati.
Log | Files | Refs | Atom

commit 805c20bce337bb03c110d03129675a29335759b1
parent 694d6018c5f98e84e7a393fa5721b72a54228fcb
Author: gearsix <gearsix@tuta.io>
Date:   Mon, 21 Jun 2021 02:07:51 +0100

gofmt; bugfix to pathing in LoadContentsDir & TestLoadContentsDir

Diffstat:
Mconfig.go | 60++++++++++++++++++++++++++++++------------------------------
Mconfig_test.go | 34+++++++++++++++++-----------------
Mcontent.go | 341++++++++++++++++++++++++++++++++++++++++---------------------------------------
Mcontent_test.go | 127+++++++++++++++++++++++++++++++++++++++++++++----------------------------------
Mpagr.go | 40+++++++++++++++++++---------------------
5 files changed, 312 insertions(+), 290 deletions(-)

diff --git a/config.go b/config.go @@ -1,37 +1,37 @@ package main import ( - "path/filepath" - "os" - "notabug.org/gearsix/suti" + "notabug.org/gearsix/suti" + "os" + "path/filepath" ) type Config struct { - Contents string - Templates string - Output string + Contents string + Templates string + Output string } func (cfg *Config) relPaths(dir string) { - var paths = []string{cfg.Contents, cfg.Templates, cfg.Output} - for i, path := range paths { - if !filepath.IsAbs(path) { - paths[i] = filepath.Join(dir, path) - } - } - cfg.Contents = paths[0] - cfg.Templates = paths[1] - cfg.Output = paths[2] - return + var paths = []string{cfg.Contents, cfg.Templates, cfg.Output} + for i, path := range paths { + if !filepath.IsAbs(path) { + paths[i] = filepath.Join(dir, path) + } + } + cfg.Contents = paths[0] + cfg.Templates = paths[1] + cfg.Output = paths[2] + return } // NewConfig returns a Config with default values func NewConfig() Config { - return Config { - Contents: "./content", - Templates: "./templates", - Output: "./out", - } + return Config{ + Contents: "./content", + Templates: "./templates", + Output: "./out", + } } // NewConfigFromFile returns a Config with values read from the config file found at `fpath`. @@ -39,16 +39,16 @@ func NewConfig() Config { // suti.LoadDataFile() is called to load the file (see notabug.org/gearsix/suti). // Any relative filepaths in the returned Config are set relative to the parent directory of `fpath`. func NewConfigFromFile(fpath string) (cfg Config, err error) { - cfg = NewConfig() + cfg = NewConfig() - if _, err = os.Stat(fpath); err != nil { - return - } + if _, err = os.Stat(fpath); err != nil { + return + } - if err = suti.LoadDataFile(fpath, &cfg); err != nil { - return - } + if err = suti.LoadDataFile(fpath, &cfg); err != nil { + return + } - cfg.relPaths(filepath.Dir(fpath)) - return + cfg.relPaths(filepath.Dir(fpath)) + return } diff --git a/config_test.go b/config_test.go @@ -1,25 +1,25 @@ package main import ( - "fmt" - "os" - "testing" + "fmt" + "os" + "testing" ) func TestNewConfigFromFile(test *testing.T) { - test.Parallel() - tdir := test.TempDir() - cfgp := fmt.Sprintf("%s/%s.toml", tdir, Name) - if f, err := os.Create(cfgp); err != nil { - test.Skipf("failed to create config file: '%s'", cfgp) - } else { - f.WriteString(`Output = "./test"`) - f.Close() - } + test.Parallel() + tdir := test.TempDir() + cfgp := fmt.Sprintf("%s/%s.toml", tdir, Name) + if f, err := os.Create(cfgp); err != nil { + test.Skipf("failed to create config file: '%s'", cfgp) + } else { + f.WriteString(`Output = "./test"`) + f.Close() + } - if cfg, err := NewConfigFromFile(cfgp); err != nil { - test.Fatal(err) - } else if cfg.Output != tdir+"/test" { - test.Fatalf(".Output invalid: '%s'", cfg.Output) - } + if cfg, err := NewConfigFromFile(cfgp); err != nil { + test.Fatal(err) + } else if cfg.Output != tdir+"/test" { + test.Fatalf(".Output invalid: '%s'", cfg.Output) + } } diff --git a/content.go b/content.go @@ -1,158 +1,164 @@ package main import ( - "bytes" - "bufio" - "path/filepath" - "io" - "io/fs" - "strings" - "os" - "notabug.org/gearsix/suti" + "bufio" + "bytes" "github.com/yuin/goldmark" goldmarkext "github.com/yuin/goldmark/extension" goldmarkparse "github.com/yuin/goldmark/parser" goldmarkhtml "github.com/yuin/goldmark/renderer/html" + "io" + "io/fs" + "notabug.org/gearsix/suti" + "os" + "path/filepath" + "strings" ) var ContentBodyExts = [5]string{ - ".txt", // plain-text - ".html", // HTML - ".md", // commonmark with non-intrusive extensions: linkify, auto heading id, unsafe HTML - ".gfm", // github-flavoured markdown - ".cm", // commonmark + ".txt", // plain-text + ".html", // HTML + ".md", // commonmark with non-intrusive extensions: linkify, auto heading id, unsafe HTML + ".gfm", // github-flavoured markdown + ".cm", // commonmark } type Content []Page -func LoadContentDir(path string) (c Content, e error) { - pages := make(map[string]Page) - defaults := make(map[string]Meta) - e = filepath.Walk(path, func(fpath string, info fs.FileInfo, err error) error { - if err != nil { - return nil - } - - if info.IsDir() { - path := strings.TrimPrefix(fpath, path) - p := NewPage(path) - for _, dir := range strings.Split(fpath, "/") { - if _, ok := defaults[dir]; ok { - p.Meta.MergeMeta(defaults[dir], true) - } - } - pages[path] = p - return nil - } - - pdir := filepath.Dir(fpath) - page := pages[pdir] - if strings.Contains(fpath, ".page") || strings.Contains(fpath, ".defaults") { - var m Meta - if err = suti.LoadDataFile(fpath, &m); err != nil { - return err - } - if strings.Contains(fpath, ".page") { - page.Meta.MergeMeta(m, true) - } else if strings.Contains(fpath, ".defaults") { - defaults[pdir] = m - } - } else if isContentBodyExt(filepath.Ext(fpath)) > -1 { - page.NewBodyFromFile(fpath) - } else { - page.Assets = append(page.Assets, strings.TrimPrefix(fpath, path)) - } - - pages[pdir] = page - return nil - }) - - for _, page := range pages { - c = append(c, page) - } - - return c, e +func LoadContentDir(dir string) (c Content, e error) { + pages := make(map[string]Page) + defaults := make(map[string]Meta) + if dir[len(dir)-1] != '/' { + dir += "/" + } + e = filepath.Walk(dir, func(fpath string, info fs.FileInfo, err error) error { + if err != nil { + return nil + } + + var path string + if info.IsDir() { + path = "/" + strings.TrimPrefix(fpath, dir) + p := NewPage(path) + for _, d := range strings.Split(fpath, "/") { + if _, ok := defaults[d]; ok { + p.Meta.MergeMeta(defaults[d], true) + } + } + pages[path] = p + return nil + } + + path, _ = filepath.Split(fpath) + path = strings.TrimPrefix(path, dir) + path = "/" + strings.TrimSuffix(path, "/") + page := pages[path] + if strings.Contains(fpath, ".page") || strings.Contains(fpath, ".default") { + var m Meta + if err = suti.LoadDataFile(fpath, &m); err != nil { + return err + } + if strings.Contains(fpath, ".page") { + page.Meta.MergeMeta(m, true) + } else if strings.Contains(fpath, ".defaults") { + defaults[path] = m + } + } else if isContentBodyExt(filepath.Ext(fpath)) > -1 { + page.NewBodyFromFile(fpath) + } else { + page.Assets = append(page.Assets, strings.TrimPrefix(fpath, path)) + } + + pages[path] = page + return nil + }) + + for _, page := range pages { + c = append(c, page) + } + + return c, e } func isContentBodyExt(ext string) int { - for i, supported := range ContentBodyExts { - if ext == supported { - return i - } - } - return -1 + for i, supported := range ContentBodyExts { + if ext == supported { + return i + } + } + return -1 } type Meta map[string]interface{} func (m Meta) MergeMeta(meta Meta, overwrite bool) { - for k, v := range meta { - if _, ok := m[k]; ok && overwrite { - m[k] = v - } else if !ok { - m[k] = v - } - } + for k, v := range meta { + if _, ok := m[k]; ok && overwrite { + m[k] = v + } else if !ok { + m[k] = v + } + } } type Page struct { - Path string - Meta Meta - Body []string - Assets []string + Path string + Meta Meta + Body []string + Assets []string } func NewPage(path string) Page { - return Page { - Path: path, - Meta: make(Meta), - Body: make([]string, 0), - Assets: make([]string, 0), - } + return Page{ + Path: path, + Meta: make(Meta), + Body: make([]string, 0), + Assets: make([]string, 0), + } } func (p *Page) NewBodyFromFile(fpath string) (err error) { - var buf []byte - if f, err := os.Open(fpath); err == nil { - buf, err = io.ReadAll(f) - f.Close() - } - if err != nil { - return - } - - var body string - for _, lang := range ContentBodyExts { - if filepath.Ext(fpath) == lang { - switch (lang) { - case ".txt": - body = txt2html(bytes.NewReader(buf)) - case ".md": - fallthrough - case ".gfm": - fallthrough - case ".cm": - markdown := getMarkdown(lang) - var out bytes.Buffer - if err = markdown.Convert(buf, &out); err == nil { - body = out.String() - } else { - return err - } - case ".html": - body = string(buf) - default: - continue - } - } - } - - if len(body) == 0 { - panic("invalid filetype (" + filepath.Ext(fpath) + ") passed to NewBodyFromFile") - } - p.Body = append(p.Body, body) - - return err + var buf []byte + if f, err := os.Open(fpath); err == nil { + buf, err = io.ReadAll(f) + f.Close() + } + if err != nil { + return + } + + var body string + for _, lang := range ContentBodyExts { + if filepath.Ext(fpath) == lang { + switch lang { + case ".txt": + body = txt2html(bytes.NewReader(buf)) + case ".md": + fallthrough + case ".gfm": + fallthrough + case ".cm": + markdown := getMarkdown(lang) + var out bytes.Buffer + if err = markdown.Convert(buf, &out); err == nil { + body = out.String() + } else { + return err + } + case ".html": + body = string(buf) + default: + continue + } + } + } + + if len(body) == 0 { + panic("invalid filetype (" + filepath.Ext(fpath) + ") passed to NewBodyFromFile") + } + p.Body = append(p.Body, body) + + return err } // txt2html parses textual data from `in` and line-by-line converts @@ -163,7 +169,7 @@ func (p *Page) NewBodyFromFile(fpath string) (err error) { func txt2html(in io.Reader) (html string) { var tag int const p = 1 - const pre = 2 + const pre = 2 fscan := bufio.NewScanner(in) for fscan.Scan() { @@ -178,17 +184,17 @@ func txt2html(in io.Reader) (html string) { tag = 0 } else if tag == 0 && line[0] == '\t' { tag = pre - html += "<pre>" + line[1:] + "\n" + html += "<pre>" + line[1:] + "\n" } else if tag == 0 || (tag == pre && line[0] != '\t') { if tag == pre { - html += "</pre>\n" - } - tag = p - html += "<p>" + line + html += "</pre>\n" + } + tag = p + html += "<p>" + line } else if tag == p { html += " " + line } else if tag == pre { - html += line[1:] + "\n" + html += line[1:] + "\n" } } if tag == p { @@ -201,41 +207,40 @@ func txt2html(in io.Reader) (html string) { } func getMarkdown(lang string) (markdown goldmark.Markdown) { - switch lang { - case ".gfm": - markdown = goldmark.New( - goldmark.WithExtensions( - goldmarkext.GFM, - goldmarkext.Table, - goldmarkext.Strikethrough, - goldmarkext.Linkify, - goldmarkext.TaskList, - ), - goldmark.WithParserOptions( - goldmarkparse.WithAutoHeadingID(), - ), - goldmark.WithRendererOptions( - goldmarkhtml.WithUnsafe(), - goldmarkhtml.WithHardWraps(), - ), - ) - case ".cm": - markdown = goldmark.New() - case ".md": - fallthrough - default: - markdown = goldmark.New( - goldmark.WithExtensions( - goldmarkext.Linkify, - ), - goldmark.WithParserOptions( - goldmarkparse.WithAutoHeadingID(), - ), - goldmark.WithRendererOptions( - goldmarkhtml.WithUnsafe(), - ), - ) - } - return + switch lang { + case ".gfm": + markdown = goldmark.New( + goldmark.WithExtensions( + goldmarkext.GFM, + goldmarkext.Table, + goldmarkext.Strikethrough, + goldmarkext.Linkify, + goldmarkext.TaskList, + ), + goldmark.WithParserOptions( + goldmarkparse.WithAutoHeadingID(), + ), + goldmark.WithRendererOptions( + goldmarkhtml.WithUnsafe(), + goldmarkhtml.WithHardWraps(), + ), + ) + case ".cm": + markdown = goldmark.New() + case ".md": + fallthrough + default: + markdown = goldmark.New( + goldmark.WithExtensions( + goldmarkext.Linkify, + ), + goldmark.WithParserOptions( + goldmarkparse.WithAutoHeadingID(), + ), + goldmark.WithRendererOptions( + goldmarkhtml.WithUnsafe(), + ), + ) + } + return } - diff --git a/content_test.go b/content_test.go @@ -1,35 +1,41 @@ package main import ( - "fmt" - "testing" - "os" - "strings" + "fmt" + "os" + "testing" ) func TestLoadContentDir(t *testing.T) { - var err error + var err error tdir := t.TempDir() - cdir := tdir + "/contents" - if err = createProjectContents(cdir); err != nil { - t.Errorf("failed to create project: %s", err) - } - - var c Content - if c, err = LoadContentDir(tdir); err != nil { - t.Fatalf("LoadContentDir failed: %s", err) - } - for _, p := range c { - fmt.Println("--------------") - fmt.Println("Path:", p.Path) - if p.Path != strings.TrimPrefix(p.Path, cdir) { + if err = createProjectContents(tdir); err != nil { + t.Errorf("failed to create project: %s", err) + } + + var c Content + if c, err = LoadContentDir(tdir); err != nil { + t.Fatalf("LoadContentDir failed: %s", err) + } + + if len(c) != len(contentBody) { + t.Fatalf("invalid number of pages returned (%d should be %d)", + len(c), len(contentBody)) + } + for _, p := range c { + if len(p.Path) == 0 { + t.Fatalf("empty Path for page:\n%s\n", p) } - fmt.Println("Meta:", p.Meta) - for i, b := range p.Body { - fmt.Printf("Body%d: `%s`\n", i, b) - } - fmt.Println("Assets:", p.Assets) - } + if _, ok := p.Meta["test"]; !ok || len(p.Meta) == 0 { + t.Fatalf("empty Meta for page:\n%s\n", p) + } + if len(p.Body) == 0 { + t.Fatalf("empty Body for page:\n%s\n", p) + } + if len(p.Assets) == 0 { + t.Fatalf("empty Assets for page:\n%s\n", p) + } + } } var contentBody = map[string]string{ @@ -42,14 +48,14 @@ p3 p4 `, - ".html": `<p>p1<br> + ".html": `<p>p1<br> p2</p> <pre>pre1 pre2 </pre> <p>p3</p> <p>p4</p>`, - ".md": `p1 + ".md": `p1 p2 pre1 @@ -57,14 +63,14 @@ p2 p3 `, - ".gfm": `p1 + ".gfm": `p1 p2 pre1 pre2 p3`, - ".cm": `p1 + ".cm": `p1 p2 pre1 @@ -74,39 +80,53 @@ p3`, } var contentAsset = []byte{ // 5x5 black png - 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, - 0x0d, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, - 0x00, 0x05, 0x01, 0x03, 0x00, 0x00, 0x00, 0xb7, 0xa1, 0xb4, 0xa6, - 0x00, 0x00, 0x00, 0x06, 0x50, 0x4c, 0x54, 0x45, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0xa5, 0x67, 0xb9, 0xcf, 0x00, 0x00, 0x00, 0x02, - 0x74, 0x52, 0x4e, 0x53, 0xff, 0x00, 0xe5, 0xb7, 0x30, 0x4a, 0x00, - 0x00, 0x00, 0x09, 0x70, 0x48, 0x59, 0x73, 0x00, 0x00, 0x0e, 0xc4, - 0x00, 0x00, 0x0e, 0xc4, 0x01, 0x95, 0x2b, 0x0e, 0x1b, 0x00, 0x00, - 0x00, 0x10, 0x49, 0x44, 0x41, 0x54, 0x08, 0x99, 0x63, 0x60, 0x66, - 0x60, 0x66, 0x60, 0x00, 0x62, 0x76, 0x00, 0x00, 0x4a, 0x00, 0x11, - 0x3a, 0x34, 0x8c, 0xad, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, - 0x44, 0xae, 0x42, 0x60, 0x82, + 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, + 0x0d, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, + 0x00, 0x05, 0x01, 0x03, 0x00, 0x00, 0x00, 0xb7, 0xa1, 0xb4, 0xa6, + 0x00, 0x00, 0x00, 0x06, 0x50, 0x4c, 0x54, 0x45, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0xa5, 0x67, 0xb9, 0xcf, 0x00, 0x00, 0x00, 0x02, + 0x74, 0x52, 0x4e, 0x53, 0xff, 0x00, 0xe5, 0xb7, 0x30, 0x4a, 0x00, + 0x00, 0x00, 0x09, 0x70, 0x48, 0x59, 0x73, 0x00, 0x00, 0x0e, 0xc4, + 0x00, 0x00, 0x0e, 0xc4, 0x01, 0x95, 0x2b, 0x0e, 0x1b, 0x00, 0x00, + 0x00, 0x10, 0x49, 0x44, 0x41, 0x54, 0x08, 0x99, 0x63, 0x60, 0x66, + 0x60, 0x66, 0x60, 0x00, 0x62, 0x76, 0x00, 0x00, 0x4a, 0x00, 0x11, + 0x3a, 0x34, 0x8c, 0xad, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, + 0x44, 0xae, 0x42, 0x60, 0x82, } func createProjectContents(dir string) (err error) { - if err = os.Mkdir(dir, 0755); err != nil { - return - } + if _, err := os.Stat(dir); err != nil { + if err = os.Mkdir(dir, 0755); err != nil { + return err + } + } - var f *os.File + var f *os.File var path string - for l, lang := range ContentBodyExts { + for l, lang := range ContentBodyExts { if l > 0 { path, err = os.MkdirTemp(dir, "page") } else { path = dir + /* + if f, err = os.Create(fmt.Sprintf("%s/.defaults.json", path)); err == nil { + return + } + f.WriteString("{ test: \"data\" }") + f.Close() + if f, err = os.Create(fmt.Sprintf("%s/.page.toml", path)); err == nil { + return + } + f.WriteString("{ test = \"data\" }") + f.Close() + */ } - f, err = os.Create(fmt.Sprintf("%s/body%d%s", path, l, lang)) - if err != nil { - return - } - f.WriteString(contentBody[lang]) - f.Close() + f, err = os.Create(fmt.Sprintf("%s/body%d%s", path, l, lang)) + if err != nil { + return + } + f.WriteString(contentBody[lang]) + f.Close() if f, err = os.Create(fmt.Sprintf("%s/asset.png", path)); err != nil { return @@ -115,8 +135,7 @@ func createProjectContents(dir string) (err error) { return } f.Close() - } - + } + return } - diff --git a/pagr.go b/pagr.go @@ -1,38 +1,36 @@ package main import ( - "flag" - "log" + "flag" + "log" ) const Name = "pagr" const Version = "0.0.0" func main() { - cfg := flag.String("cfg", "", "path to pagr project configuration file") - verbose := flag.Bool("verbose", false, "print verbose logs") - flag.Parse() + cfg := flag.String("cfg", "", "path to pagr project configuration file") + //verbose := flag.Bool("verbose", false, "print verbose logs") + flag.Parse() - var err error + var err error - var config Config - if len(*cfg) > 0 { - config, err = NewConfigFromFile(*cfg) - check(err) - } else { - config = NewConfig() - } + var config Config + if len(*cfg) > 0 { + config, err = NewConfigFromFile(*cfg) + check(err) + } else { + config = NewConfig() + } - var content Content - content, err = LoadContentDir(config.Contents) - check(err) + _, err = LoadContentDir(config.Contents) + check(err) - return + return } func check(err error) { - if err != nil { - log.Fatalf("ERROR! %s\n", err) - } + if err != nil { + log.Fatalf("ERROR! %s\n", err) + } } -