goldmark-mmd

A different way of handling Markdown Metadata
git clone git://src.gearsix.net/goldmark-mmd
Log | Files | Refs | Atom | README | LICENSE

commit e03b0d49f716e556026620fc9e5035e3b2770d4e
parent 395e3be2c1fa6dd5b85c4f91f3d4c645ea6724ad
Author: yuin <yuin@inforno.net>
Date:   Sat, 12 Feb 2022 19:42:33 +0900

Add WithStoresInDocument option

Diffstat:
MREADME.md | 35+++++++++++++++++++++++++++++++++++
Mgo.mod | 4++--
Mgo.sum | 8++------
Mmeta.go | 162+++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------------------
Mmeta_test.go | 43+++++++++++++++++++++++++++++++++++++++++++
5 files changed, 199 insertions(+), 53 deletions(-)

diff --git a/README.md b/README.md @@ -88,6 +88,41 @@ Tags: } ``` +Or `WithStoresInDocument` option: + +```go +import ( + "bytes" + "fmt" + "github.com/yuin/goldmark" + "github.com/yuin/goldmark/extension" + "github.com/yuin/goldmark/parser" + "github.com/yuin/goldmark-meta" +) + +func main() { + markdown := goldmark.New( + goldmark.WithExtensions( + meta.New( + meta.WithStoresInDocument(), + ), + ), + ) + source := `--- +Title: goldmark-meta +Summary: Add YAML metadata to the document +Tags: + - markdown + - goldmark +--- +` + + document := markdown.Parser().Parse(text.NewReader([]byte(source))) + metaData := document.OwnerDocument().Meta() + title := metaData["Title"] + fmt.Print(title) +``` + ### Render the metadata as a table You need to add `extension.TableHTMLRenderer` or the `Table` extension to diff --git a/go.mod b/go.mod @@ -1,8 +1,8 @@ module github.com/yuin/goldmark-meta -go 1.15 +go 1.17 require ( - github.com/yuin/goldmark v1.2.1 + github.com/yuin/goldmark v1.4.6 gopkg.in/yaml.v2 v2.3.0 ) diff --git a/go.sum b/go.sum @@ -1,10 +1,6 @@ -github.com/yuin/goldmark v1.1.7 h1:XiwWADvxJeIM1JbXqthrEhDc19hTMui+o+QaY1hGXlk= -github.com/yuin/goldmark v1.1.7/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.2.1 h1:ruQGxdhGHe7FWOJPT0mKs5+pD2Xs1Bm/kdGlHO04FmM= -github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.4.6 h1:EQ1OkiNq/eMbQxs/2O/A8VDIHERXGH14s19ednd4XIw= +github.com/yuin/goldmark v1.4.6/go.mod h1:rmuwmfZ0+bvzB24eSC//bk1R1Zp3hM0OXYv/G2LIilg= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/meta.go b/meta.go @@ -27,6 +27,11 @@ type data struct { var contextKey = parser.NewContextKey() +// Option interface sets options for this extension. +type Option interface { + metaOption() +} + // Get returns a YAML metadata. func Get(pc parser.Context) map[string]interface{} { v := pc.Get(contextKey) @@ -80,11 +85,11 @@ func TryGetItems(pc parser.Context) (yaml.MapSlice, error) { type metaParser struct { } -var defaultMetaParser = &metaParser{} +var defaultParser = &metaParser{} // NewParser returns a BlockParser that can parse YAML metadata blocks. func NewParser() parser.BlockParser { - return defaultMetaParser + return defaultParser } func isSeparator(line []byte) bool { @@ -162,9 +167,74 @@ func (b *metaParser) CanAcceptIndentedLine() bool { } type astTransformer struct { + transformerConfig +} + +type transformerConfig struct { + // Renders metadata as an html table. + Table bool + + // Stores metadata in ast.Document.Meta(). + StoresInDocument bool +} + +type transformerOption interface { + Option + + // SetMetaOption sets options for the metadata parser. + SetMetaOption(*transformerConfig) +} + +var _ transformerOption = &withTable{} + +type withTable struct { + value bool +} + +func (o *withTable) metaOption() {} + +func (o *withTable) SetMetaOption(m *transformerConfig) { + m.Table = o.value +} + +// WithTable is a functional option that renders a YAML metadata as a table. +func WithTable() Option { + return &withTable{ + value: true, + } +} + +var _ transformerOption = &withStoresInDocument{} + +type withStoresInDocument struct { + value bool +} + +func (o *withStoresInDocument) metaOption() {} + +func (o *withStoresInDocument) SetMetaOption(c *transformerConfig) { + c.StoresInDocument = o.value } -var defaultASTTransformer = &astTransformer{} +// WithStoresInDocument is a functional option that parser will store YAML meta in ast.Document.Meta(). +func WithStoresInDocument() Option { + return &withStoresInDocument{ + value: true, + } +} + +func newTransformer(opts ...transformerOption) parser.ASTTransformer { + p := &astTransformer{ + transformerConfig: transformerConfig{ + Table: false, + StoresInDocument: false, + }, + } + for _, o := range opts { + o.SetMetaOption(&p.transformerConfig) + } + return p +} func (a *astTransformer) Transform(node *gast.Document, reader text.Reader, pc parser.Context) { dtmp := pc.Get(contextKey) @@ -179,45 +249,43 @@ func (a *astTransformer) Transform(node *gast.Document, reader text.Reader, pc p return } - meta := GetItems(pc) - if meta == nil { - return - } - table := east.NewTable() - alignments := []east.Alignment{} - for range meta { - alignments = append(alignments, east.AlignNone) - } - row := east.NewTableRow(alignments) - for _, item := range meta { - cell := east.NewTableCell() - cell.AppendChild(cell, gast.NewString([]byte(fmt.Sprintf("%v", item.Key)))) - row.AppendChild(row, cell) - } - table.AppendChild(table, east.NewTableHeader(row)) + if a.Table { + meta := GetItems(pc) + if meta == nil { + return + } + table := east.NewTable() + alignments := []east.Alignment{} + for range meta { + alignments = append(alignments, east.AlignNone) + } + row := east.NewTableRow(alignments) + for _, item := range meta { + cell := east.NewTableCell() + cell.AppendChild(cell, gast.NewString([]byte(fmt.Sprintf("%v", item.Key)))) + row.AppendChild(row, cell) + } + table.AppendChild(table, east.NewTableHeader(row)) - row = east.NewTableRow(alignments) - for _, item := range meta { - cell := east.NewTableCell() - cell.AppendChild(cell, gast.NewString([]byte(fmt.Sprintf("%v", item.Value)))) - row.AppendChild(row, cell) + row = east.NewTableRow(alignments) + for _, item := range meta { + cell := east.NewTableCell() + cell.AppendChild(cell, gast.NewString([]byte(fmt.Sprintf("%v", item.Value)))) + row.AppendChild(row, cell) + } + table.AppendChild(table, row) + node.InsertBefore(node, node.FirstChild(), table) } - table.AppendChild(table, row) - node.InsertBefore(node, node.FirstChild(), table) -} -// Option is a functional option type for this extension. -type Option func(*meta) - -// WithTable is a functional option that renders a YAML metadata as a table. -func WithTable() Option { - return func(m *meta) { - m.Table = true + if a.StoresInDocument { + for k, v := range d.Map { + node.AddMeta(k, v) + } } } type meta struct { - Table bool + options []Option } // Meta is a extension for the goldmark. @@ -225,24 +293,28 @@ var Meta = &meta{} // New returns a new Meta extension. func New(opts ...Option) goldmark.Extender { - e := &meta{} - for _, opt := range opts { - opt(e) + e := &meta{ + options: opts, } return e } +// Extend implements goldmark.Extender. func (e *meta) Extend(m goldmark.Markdown) { + topts := []transformerOption{} + for _, opt := range e.options { + if topt, ok := opt.(transformerOption); ok { + topts = append(topts, topt) + } + } m.Parser().AddOptions( parser.WithBlockParsers( util.Prioritized(NewParser(), 0), ), ) - if e.Table { - m.Parser().AddOptions( - parser.WithASTTransformers( - util.Prioritized(defaultASTTransformer, 0), - ), - ) - } + m.Parser().AddOptions( + parser.WithASTTransformers( + util.Prioritized(newTransformer(topts...), 0), + ), + ) } diff --git a/meta_test.go b/meta_test.go @@ -8,6 +8,7 @@ import ( "github.com/yuin/goldmark/extension" "github.com/yuin/goldmark/parser" "github.com/yuin/goldmark/renderer" + "github.com/yuin/goldmark/text" "github.com/yuin/goldmark/util" ) @@ -203,3 +204,45 @@ Tags: t.Error("invalid table output") } } + +func TestMetaStoreInDocument(t *testing.T) { + markdown := goldmark.New( + goldmark.WithExtensions( + New( + WithStoresInDocument(), + ), + ), + ) + source := `--- +Title: goldmark-meta +Summary: Add YAML metadata to the document +Tags: + - markdown + - goldmark +--- +` + + document := markdown.Parser().Parse(text.NewReader([]byte(source))) + metaData := document.OwnerDocument().Meta() + title := metaData["Title"] + s, ok := title.(string) + if !ok { + t.Error("Title not found in meta data or is not a string") + } + if s != "goldmark-meta" { + t.Errorf("Title must be %s, but got %v", "goldmark-meta", s) + } + tags, ok := metaData["Tags"].([]interface{}) + if !ok { + t.Error("Tags not found in meta data or is not a slice") + } + if len(tags) != 2 { + t.Error("Tags must be a slice that has 2 elements") + } + if tags[0] != "markdown" { + t.Errorf("Tag#1 must be 'markdown', but got %s", tags[0]) + } + if tags[1] != "goldmark" { + t.Errorf("Tag#2 must be 'goldmark', but got %s", tags[1]) + } +}