all repos — vite @ cf6acc3b2905bd905bd44805a159e4bb3b6072e6

a fast (this time, actually) and minimal static site generator

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{}) 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		fmt.Printf("vite: skipping draft %s\n", md.Path)
112		return nil
113	}
114
115	err = md.template(dest, types.TemplatesDir, templateData{
116		config.Config,
117		md.frontmatter,
118		string(md.body),
119		data,
120	})
121	if err != nil {
122		return fmt.Errorf("markdown: failed to render to destination %s: %w", dest, err)
123	}
124	return nil
125}