fast and minimal static site generator
ssg
at master 3.3 kB view raw
1package markdown 2 3import ( 4 "bytes" 5 "fmt" 6 "os" 7 "path/filepath" 8 gotmpl "text/template" 9 "time" 10 11 "github.com/adrg/frontmatter" 12 "tangled.sh/icyphox.sh/vite/config" 13 "tangled.sh/icyphox.sh/vite/template" 14 "tangled.sh/icyphox.sh/vite/types" 15 16 bf "git.icyphox.sh/grayfriday" 17) 18 19var ( 20 bfFlags = bf.UseXHTML | bf.Smartypants | bf.SmartypantsFractions | 21 bf.SmartypantsDashes | bf.NofollowLinks | bf.FootnoteReturnLinks 22 bfExts = bf.NoIntraEmphasis | bf.Tables | bf.FencedCode | bf.Autolink | 23 bf.Strikethrough | bf.SpaceHeadings | bf.BackslashLineBreak | 24 bf.AutoHeadingIDs | bf.HeadingIDs | bf.Footnotes | bf.NoEmptyLineBeforeBlock 25) 26 27type Markdown struct { 28 body []byte 29 frontmatter map[string]any 30 Path string 31} 32 33func (*Markdown) Ext() string { return ".md" } 34 35func (md *Markdown) Basename() string { 36 return filepath.Base(md.Path) 37} 38 39// mdToHtml renders source markdown to html 40func mdToHtml(source []byte) []byte { 41 return bf.Run( 42 source, 43 bf.WithNoExtensions(), 44 bf.WithRenderer(bf.NewHTMLRenderer(bf.HTMLRendererParameters{Flags: bfFlags})), 45 bf.WithExtensions(bfExts), 46 ) 47} 48 49// template checks the frontmatter for a specified template or falls back 50// to the default template -- to which it, well, templates whatever is in 51// data and writes it to dest. 52func (md *Markdown) template(dest, tmplDir string, data any) error { 53 metaTemplate, ok := md.frontmatter["template"].(string) 54 if !ok || metaTemplate == "" { 55 metaTemplate = config.Config.DefaultTemplate 56 } 57 58 tmpl := template.NewTmpl() 59 tmpl.SetFuncs(gotmpl.FuncMap{ 60 "parsedate": func(s string) time.Time { 61 date, _ := time.Parse("2006-01-02", s) 62 return date 63 }, 64 }) 65 if err := tmpl.Load(tmplDir); err != nil { 66 return err 67 } 68 69 return tmpl.Write(dest, metaTemplate, data) 70} 71 72// extractFrontmatter takes the source markdown page, extracts the frontmatter 73// and body. The body is converted from markdown to html here. 74func (md *Markdown) extractFrontmatter(source []byte) error { 75 r := bytes.NewReader(source) 76 rest, err := frontmatter.Parse(r, &md.frontmatter) 77 if err != nil { 78 return err 79 } 80 md.body = mdToHtml(rest) 81 return nil 82} 83 84func (md *Markdown) Frontmatter() map[string]any { 85 return md.frontmatter 86} 87 88func (md *Markdown) Body() string { 89 return string(md.body) 90} 91 92type templateData struct { 93 Cfg config.ConfigYaml 94 Meta map[string]any 95 Body string 96 Extra any 97 Allowed bool 98} 99 100func (md *Markdown) Render(dest string, data any, drafts bool) error { 101 source, err := os.ReadFile(md.Path) 102 if err != nil { 103 return fmt.Errorf("markdown: error reading file: %w", err) 104 } 105 106 err = md.extractFrontmatter(source) 107 if err != nil { 108 return fmt.Errorf("markdown: error extracting frontmatter: %w", err) 109 } 110 111 isDraft, ok := md.frontmatter["draft"].(bool) 112 if ok && isDraft { 113 if !drafts { 114 fmt.Printf("vite: skipping draft %s\n", md.Path) 115 return nil 116 } 117 fmt.Printf("vite: rendering draft %s\n", md.Path) 118 } 119 120 // allow post if it's not a draft, or if it's a draft and drafts are enabled 121 allowed := !isDraft || drafts 122 123 err = md.template(dest, types.TemplatesDir, templateData{ 124 config.Config, 125 md.frontmatter, 126 string(md.body), 127 data, 128 allowed, 129 }) 130 if err != nil { 131 return fmt.Errorf("markdown: failed to render to destination %s: %w", dest, err) 132 } 133 return nil 134}