all repos — vite @ 3fb201f38e7e127f8d2000217cd50c1e723666a7

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

formats/markdown/markdown.go (view raw)

 1
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
 100
 101
 102
 103
 104
 105
 106
 107
 108
 109
 110
 111
 112
 113
 114
 115
 116
 117
 118
 119
 120
 121
 122
 123
 124
 125
 126
 127
 128
package markdown

import (
	"bytes"
	"fmt"
	"os"
	"path/filepath"
	gotmpl "text/template"
	"time"

	"git.icyphox.sh/vite/config"
	"git.icyphox.sh/vite/template"
	"git.icyphox.sh/vite/types"
	"github.com/adrg/frontmatter"

	bf "git.icyphox.sh/grayfriday"
)

var (
	bfFlags = bf.UseXHTML | bf.Smartypants | bf.SmartypantsFractions |
		bf.SmartypantsDashes | bf.NofollowLinks | bf.FootnoteReturnLinks
	bfExts = bf.NoIntraEmphasis | bf.Tables | bf.FencedCode | bf.Autolink |
		bf.Strikethrough | bf.SpaceHeadings | bf.BackslashLineBreak |
		bf.AutoHeadingIDs | bf.HeadingIDs | bf.Footnotes | bf.NoEmptyLineBeforeBlock
)

type Markdown struct {
	body        []byte
	frontmatter map[string]string
	Path        string
}

func (*Markdown) Ext() string { return ".md" }

func (md *Markdown) Basename() string {
	return filepath.Base(md.Path)
}

// mdToHtml renders source markdown to html
func mdToHtml(source []byte) []byte {
	return bf.Run(
		source,
		bf.WithNoExtensions(),
		bf.WithRenderer(bf.NewHTMLRenderer(bf.HTMLRendererParameters{Flags: bfFlags})),
		bf.WithExtensions(bfExts),
	)
}

// template checks the frontmatter for a specified template or falls back
// to the default template -- to which it, well, templates whatever is in
// data and writes it to dest.
func (md *Markdown) template(dest, tmplDir string, data interface{}) error {
	metaTemplate := md.frontmatter["template"]
	if metaTemplate == "" {
		metaTemplate = config.Config.DefaultTemplate
	}

	tmpl := template.NewTmpl()
	tmpl.SetFuncs(gotmpl.FuncMap{
		"parsedate": func(s string) time.Time {
			date, _ := time.Parse("2006-01-02", s)
			return date
		},
	})
	if err := tmpl.Load(tmplDir); err != nil {
		return err
	}

	w, err := os.Create(dest)
	if err != nil {
		return err
	}

	if err = tmpl.ExecuteTemplate(w, metaTemplate, data); err != nil {
		return err
	}
	return nil
}

// extract takes the source markdown page, extracts the frontmatter
// and body. The body is converted from markdown to html here.
func (md *Markdown) extractFrontmatter(source []byte) error {
	r := bytes.NewReader(source)
	rest, err := frontmatter.Parse(r, &md.frontmatter)
	if err != nil {
		return err
	}
	md.body = mdToHtml(rest)
	return nil
}

func (md *Markdown) Frontmatter() map[string]string {
	return md.frontmatter
}

func (md *Markdown) Body() string {
	return string(md.body)
}

type templateData struct {
	Cfg   config.ConfigYaml
	Meta  map[string]string
	Body  string
	Extra interface{}
}

func (md *Markdown) Render(dest string, data interface{}) error {
	source, err := os.ReadFile(md.Path)
	if err != nil {
		return fmt.Errorf("markdown: error reading file: %w", err)
	}

	err = md.extractFrontmatter(source)
	if err != nil {
		return fmt.Errorf("markdown: error extracting frontmatter: %w", err)
	}

	err = md.template(dest, types.TemplatesDir, templateData{
		config.Config,
		md.frontmatter,
		string(md.body),
		data,
	})
	if err != nil {
		return fmt.Errorf("markdown: failed to render to destination %s: %w", dest, err)
	}
	return nil
}