formats/markdown/markdown.go (view raw)
1package markdown
2
3import (
4 "bytes"
5 "fmt"
6 "os"
7 "path/filepath"
8 gotmpl "text/template"
9 "time"
10
11 "git.icyphox.sh/vite/config"
12 "git.icyphox.sh/vite/template"
13 "git.icyphox.sh/vite/types"
14 "github.com/adrg/frontmatter"
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]string
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 interface{}) error {
53 metaTemplate := md.frontmatter["template"]
54 if 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// extract 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]string {
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]string
95 Body string
96 Extra interface{}
97}
98
99func (md *Markdown) Render(dest string, data interface{}, drafts bool) error {
100 source, err := os.ReadFile(md.Path)
101 if err != nil {
102 return fmt.Errorf("markdown: error reading file: %w", err)
103 }
104
105 err = md.extractFrontmatter(source)
106 if err != nil {
107 return fmt.Errorf("markdown: error extracting frontmatter: %w", err)
108 }
109
110 if md.frontmatter["draft"] == "true" {
111 if !drafts {
112 fmt.Printf("vite: skipping draft %s\n", md.Path)
113 return nil
114 }
115 fmt.Printf("vite: rendering draft %s\n", md.Path)
116 }
117
118 err = md.template(dest, types.TemplatesDir, templateData{
119 config.Config,
120 md.frontmatter,
121 string(md.body),
122 data,
123 })
124 if err != nil {
125 return fmt.Errorf("markdown: failed to render to destination %s: %w", dest, err)
126 }
127 return nil
128}