diff --git a/.vscode/settings.json b/.vscode/settings.json index 638570e..94d2725 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -3,6 +3,7 @@ "abhg", "alecthomas", "chromahtml", + "definitionlist", "errbuf", "Furqan", "goldmark", @@ -15,6 +16,7 @@ "sirupsen", "stefanfritsch", "Strikethrough", + "tasklist", "wikilink", "yuin", "ZSCONFIG", diff --git a/main.go b/main.go index 332efb9..836ca4e 100644 --- a/main.go +++ b/main.go @@ -64,8 +64,12 @@ README.md` // Ignore holds a set of patterns to ignore from parsing a .zsignore file var Ignore *ignore.GitIgnore +// Parser holds a configured global instance of the goldmark markdown parser +var Parser goldmark.Markdown + var ( - configFile string + configFile string + enabledExtensions []string // Title site title Title string @@ -80,9 +84,43 @@ var ( Production = false ) +// Extensions is a mapping of name to extension and the default set of extensions enabled +// which can be overridden with -e/--extension or the extensions key +// in ia config file such as .zs/config.yml +var Extensions = map[string]goldmark.Extender{ + "table": extension.Table, + "strikethrough": extension.Strikethrough, + "linkify": extension.Linkify, + "tasklist": extension.TaskList, + "definitionlist": extension.DefinitionList, + "footnote": extension.Footnote, + "typography": extension.Typographer, + "cjk": extension.CJK, + "highlighting": highlighting.NewHighlighting( + highlighting.WithStyle("github"), + highlighting.WithFormatOptions( + chromahtml.WithLineNumbers(true), + ), + ), + "anchor": &anchor.Extender{}, + "d2": &d2.Extender{}, + "embed": embed.New(), + "fences": &fences.Extender{}, + "wikilink": &wikilink.Extender{}, +} + // Vars holds a map of global variables type Vars map[string]string +// MapKeys returns a slice of keys from a map +func MapKeys[K comparable, V any](m map[K]V) []K { + r := make([]K, 0, len(m)) + for k := range m { + r = append(r, k) + } + return r +} + // NewTicker is a function that wraps a time.Ticker and ticks immediately instead of waiting for the first interval func NewTicker(d time.Duration) *time.Ticker { ticker := time.NewTicker(d) @@ -124,6 +162,28 @@ var RootCmd = &cobra.Command{ log.SetLevel(log.InfoLevel) } + var extensions []goldmark.Extender + for _, name := range enabledExtensions { + if extender, valid := Extensions[name]; valid { + extensions = append(extensions, extender) + } else { + log.Warnf("invalid extension: %s", name) + } + } + + Parser = goldmark.New( + goldmark.WithExtensions(extensions...), + goldmark.WithParserOptions( + parser.WithAttribute(), + parser.WithAutoHeadingID(), + ), + goldmark.WithRendererOptions( + html.WithHardWraps(), + html.WithXHTML(), + html.WithUnsafe(), + ), + ) + return nil }, } @@ -425,39 +485,7 @@ func buildMarkdown(path string, w io.Writer, vars Vars) error { } buf := &bytes.Buffer{} - gm := goldmark.New( - goldmark.WithExtensions( - extension.Table, - extension.Strikethrough, - extension.Linkify, - extension.TaskList, - extension.DefinitionList, - extension.Footnote, - extension.Typographer, - extension.CJK, - highlighting.NewHighlighting( - highlighting.WithStyle("github"), - highlighting.WithFormatOptions( - chromahtml.WithLineNumbers(true), - ), - ), - &anchor.Extender{}, - &d2.Extender{}, - embed.New(), - &fences.Extender{}, - &wikilink.Extender{}, - ), - goldmark.WithParserOptions( - parser.WithAttribute(), - parser.WithAutoHeadingID(), - ), - goldmark.WithRendererOptions( - html.WithHardWraps(), - html.WithXHTML(), - html.WithUnsafe(), - ), - ) - if err := gm.Convert([]byte(content), buf); err != nil { + if err := Parser.Convert([]byte(content), buf); err != nil { return err } v["content"] = buf.String() @@ -638,6 +666,7 @@ func init() { RootCmd.PersistentFlags().BoolP("debug", "D", false, "enable debug logging $($ZS_DEBUG)") RootCmd.PersistentFlags().StringVarP(&configFile, "config", "c", "", "config file (default: .zs/config.yml)") + RootCmd.PersistentFlags().StringSliceVarP(&enabledExtensions, "extensions", "e", MapKeys(Extensions), "override and enable specific extensions") RootCmd.PersistentFlags().BoolVarP(&Production, "production", "p", false, "enable production mode ($ZS_PRODUCTION)") RootCmd.PersistentFlags().StringVarP(&Title, "title", "t", "", "site title ($ZS_TITLE)") RootCmd.PersistentFlags().StringVarP(&Description, "description", "d", "", "site description ($ZS_DESCRIPTION)")