Add goldmark-meta to render yaml frontmatter as a table Fix #5377 Signed-off-by: Andrew Thornton <art27@cantab.net>tags/v1.21.12.1
| @@ -106,6 +106,7 @@ require ( | |||||
| github.com/urfave/cli v1.20.0 | github.com/urfave/cli v1.20.0 | ||||
| github.com/yohcop/openid-go v0.0.0-20160914080427-2c050d2dae53 | github.com/yohcop/openid-go v0.0.0-20160914080427-2c050d2dae53 | ||||
| github.com/yuin/goldmark v1.1.25 | github.com/yuin/goldmark v1.1.25 | ||||
| github.com/yuin/goldmark-meta v0.0.0-20191126180153-f0638e958b60 | |||||
| go.etcd.io/bbolt v1.3.3 // indirect | go.etcd.io/bbolt v1.3.3 // indirect | ||||
| golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073 | golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073 | ||||
| golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e | golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e | ||||
| @@ -614,8 +614,11 @@ github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q | |||||
| github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= | github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= | ||||
| github.com/yohcop/openid-go v0.0.0-20160914080427-2c050d2dae53 h1:HsIQ6yAjfjQ3IxPGrTusxp6Qxn92gNVq2x5CbvQvx3w= | github.com/yohcop/openid-go v0.0.0-20160914080427-2c050d2dae53 h1:HsIQ6yAjfjQ3IxPGrTusxp6Qxn92gNVq2x5CbvQvx3w= | ||||
| github.com/yohcop/openid-go v0.0.0-20160914080427-2c050d2dae53/go.mod h1:f6elajwZV+xceiaqgRL090YzLEDGSbqr3poGL3ZgXYo= | github.com/yohcop/openid-go v0.0.0-20160914080427-2c050d2dae53/go.mod h1:f6elajwZV+xceiaqgRL090YzLEDGSbqr3poGL3ZgXYo= | ||||
| github.com/yuin/goldmark v1.1.7/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= | |||||
| github.com/yuin/goldmark v1.1.25 h1:isv+Q6HQAmmL2Ofcmg8QauBmDPlUUnSoNhEcC940Rds= | github.com/yuin/goldmark v1.1.25 h1:isv+Q6HQAmmL2Ofcmg8QauBmDPlUUnSoNhEcC940Rds= | ||||
| github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= | github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= | ||||
| github.com/yuin/goldmark-meta v0.0.0-20191126180153-f0638e958b60 h1:gZucqLjL1eDzVWrXj4uiWeMbAopJlBR2mKQAsTGdPwo= | |||||
| github.com/yuin/goldmark-meta v0.0.0-20191126180153-f0638e958b60/go.mod h1:i9VhcIHN2PxXMbQrKqXNueok6QNONoPjNMoj9MygVL0= | |||||
| github.com/ziutek/mymysql v1.5.4 h1:GB0qdRGsTwQSBVYuVShFBKaXSnSnYYC2d9knnE1LHFs= | github.com/ziutek/mymysql v1.5.4 h1:GB0qdRGsTwQSBVYuVShFBKaXSnSnYYC2d9knnE1LHFs= | ||||
| github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0= | github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0= | ||||
| go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= | go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= | ||||
| @@ -16,6 +16,7 @@ import ( | |||||
| giteautil "code.gitea.io/gitea/modules/util" | giteautil "code.gitea.io/gitea/modules/util" | ||||
| "github.com/yuin/goldmark" | "github.com/yuin/goldmark" | ||||
| meta "github.com/yuin/goldmark-meta" | |||||
| "github.com/yuin/goldmark/extension" | "github.com/yuin/goldmark/extension" | ||||
| "github.com/yuin/goldmark/parser" | "github.com/yuin/goldmark/parser" | ||||
| "github.com/yuin/goldmark/renderer" | "github.com/yuin/goldmark/renderer" | ||||
| @@ -53,6 +54,7 @@ func RenderRaw(body []byte, urlPrefix string, wikiMarkdown bool) []byte { | |||||
| extension.Ellipsis: nil, | extension.Ellipsis: nil, | ||||
| }), | }), | ||||
| ), | ), | ||||
| meta.New(meta.WithTable()), | |||||
| ), | ), | ||||
| goldmark.WithParserOptions( | goldmark.WithParserOptions( | ||||
| parser.WithAttribute(), | parser.WithAttribute(), | ||||
| @@ -0,0 +1,13 @@ | |||||
| # Binaries for programs and plugins | |||||
| *.exe | |||||
| *.exe~ | |||||
| *.dll | |||||
| *.so | |||||
| *.dylib | |||||
| # Test binary, build with `go test -c` | |||||
| *.test | |||||
| *.pprof | |||||
| # Output of the go coverage tool, specifically when used with LiteIDE | |||||
| *.out | |||||
| @@ -0,0 +1,21 @@ | |||||
| MIT License | |||||
| Copyright (c) 2019 Yusuke Inuzuka | |||||
| Permission is hereby granted, free of charge, to any person obtaining a copy | |||||
| of this software and associated documentation files (the "Software"), to deal | |||||
| in the Software without restriction, including without limitation the rights | |||||
| to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||||
| copies of the Software, and to permit persons to whom the Software is | |||||
| furnished to do so, subject to the following conditions: | |||||
| The above copyright notice and this permission notice shall be included in all | |||||
| copies or substantial portions of the Software. | |||||
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||||
| IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||||
| FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||||
| AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||||
| LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||||
| OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |||||
| SOFTWARE. | |||||
| @@ -0,0 +1,147 @@ | |||||
| goldmark-meta | |||||
| ========================= | |||||
| goldmark-meta is an extension for the [goldmark](http://github.com/yuin/goldmark) | |||||
| that allows you to define document metadata in YAML format. | |||||
| Usage | |||||
| -------------------- | |||||
| ### Installation | |||||
| ``` | |||||
| go get github.com/yuin/goldmark-meta | |||||
| ``` | |||||
| ### Markdown syntax | |||||
| YAML metadata block is a leaf block that can not have any markdown element | |||||
| as a child. | |||||
| YAML metadata must start with a **YAML metadata separator**. | |||||
| This separator must be at first line of the document. | |||||
| A **YAML metadata separator** is a line that only `-` is repeated. | |||||
| YAML metadata must end with a **YAML metadata separator**. | |||||
| You can define objects as a 1st level item. At deeper level, you can define | |||||
| any kind of YAML element. | |||||
| Example: | |||||
| ``` | |||||
| --- | |||||
| Title: goldmark-meta | |||||
| Summary: Add YAML metadata to the document | |||||
| Tags: | |||||
| - markdown | |||||
| - goldmark | |||||
| --- | |||||
| # Heading 1 | |||||
| ``` | |||||
| ### Access the metadata | |||||
| ```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.Meta, | |||||
| ), | |||||
| ) | |||||
| source := `--- | |||||
| Title: goldmark-meta | |||||
| Summary: Add YAML metadata to the document | |||||
| Tags: | |||||
| - markdown | |||||
| - goldmark | |||||
| --- | |||||
| # Hello goldmark-meta | |||||
| ` | |||||
| var buf bytes.Buffer | |||||
| context := parser.NewContext() | |||||
| if err := markdown.Convert([]byte(source), &buf, parser.WithContext(context)); err != nil { | |||||
| panic(err) | |||||
| } | |||||
| metaData := meta.Get(context) | |||||
| title := metaData["Title"] | |||||
| fmt.Print(title) | |||||
| } | |||||
| ``` | |||||
| ### Render the metadata as a table | |||||
| You need to add `extension.TableHTMLRenderer` or the `Table` extension to | |||||
| render metadata as a table. | |||||
| ```go | |||||
| import ( | |||||
| "bytes" | |||||
| "fmt" | |||||
| "github.com/yuin/goldmark" | |||||
| "github.com/yuin/goldmark/extension" | |||||
| "github.com/yuin/goldmark/parser" | |||||
| "github.com/yuin/goldmark/renderer" | |||||
| "github.com/yuin/goldmark/util" | |||||
| "github.com/yuin/goldmark-meta" | |||||
| ) | |||||
| func main() { | |||||
| markdown := goldmark.New( | |||||
| goldmark.WithExtensions( | |||||
| meta.New(meta.WithTable()), | |||||
| ), | |||||
| goldmark.WithRendererOptions( | |||||
| renderer.WithNodeRenderers( | |||||
| util.Prioritized(extension.NewTableHTMLRenderer(), 500), | |||||
| ), | |||||
| ), | |||||
| ) | |||||
| // OR | |||||
| // markdown := goldmark.New( | |||||
| // goldmark.WithExtensions( | |||||
| // meta.New(meta.WithTable()), | |||||
| // extension.Table, | |||||
| // ), | |||||
| // ) | |||||
| source := `--- | |||||
| Title: goldmark-meta | |||||
| Summary: Add YAML metadata to the document | |||||
| Tags: | |||||
| - markdown | |||||
| - goldmark | |||||
| --- | |||||
| # Hello goldmark-meta | |||||
| ` | |||||
| var buf bytes.Buffer | |||||
| if err := markdown.Convert([]byte(source), &buf); err != nil { | |||||
| panic(err) | |||||
| } | |||||
| fmt.Print(buf.String()) | |||||
| } | |||||
| ``` | |||||
| License | |||||
| -------------------- | |||||
| MIT | |||||
| Author | |||||
| -------------------- | |||||
| Yusuke Inuzuka | |||||
| @@ -0,0 +1,8 @@ | |||||
| module github.com/yuin/goldmark-meta | |||||
| go 1.13 | |||||
| require ( | |||||
| github.com/yuin/goldmark v1.1.7 | |||||
| gopkg.in/yaml.v2 v2.2.2 | |||||
| ) | |||||
| @@ -0,0 +1,6 @@ | |||||
| github.com/yuin/goldmark v1.1.7 h1:XiwWADvxJeIM1JbXqthrEhDc19hTMui+o+QaY1hGXlk= | |||||
| github.com/yuin/goldmark v1.1.7/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= | |||||
| 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= | |||||
| @@ -0,0 +1,218 @@ | |||||
| // package meta is a extension for the goldmark(http://github.com/yuin/goldmark). | |||||
| // | |||||
| // This extension parses YAML metadata blocks and store metadata to a | |||||
| // parser.Context. | |||||
| package meta | |||||
| import ( | |||||
| "bytes" | |||||
| "fmt" | |||||
| "github.com/yuin/goldmark" | |||||
| gast "github.com/yuin/goldmark/ast" | |||||
| east "github.com/yuin/goldmark/extension/ast" | |||||
| "github.com/yuin/goldmark/parser" | |||||
| "github.com/yuin/goldmark/text" | |||||
| "github.com/yuin/goldmark/util" | |||||
| "gopkg.in/yaml.v2" | |||||
| ) | |||||
| type data struct { | |||||
| Map map[string]interface{} | |||||
| Items yaml.MapSlice | |||||
| Error error | |||||
| Node gast.Node | |||||
| } | |||||
| var contextKey = parser.NewContextKey() | |||||
| // Get returns a YAML metadata. | |||||
| func Get(pc parser.Context) map[string]interface{} { | |||||
| v := pc.Get(contextKey) | |||||
| if v == nil { | |||||
| return nil | |||||
| } | |||||
| d := v.(*data) | |||||
| return d.Map | |||||
| } | |||||
| // GetItems returns a YAML metadata. | |||||
| // GetItems preserves defined key order. | |||||
| func GetItems(pc parser.Context) yaml.MapSlice { | |||||
| v := pc.Get(contextKey) | |||||
| if v == nil { | |||||
| return nil | |||||
| } | |||||
| d := v.(*data) | |||||
| return d.Items | |||||
| } | |||||
| type metaParser struct { | |||||
| } | |||||
| var defaultMetaParser = &metaParser{} | |||||
| // NewParser returns a BlockParser that can parse YAML metadata blocks. | |||||
| func NewParser() parser.BlockParser { | |||||
| return defaultMetaParser | |||||
| } | |||||
| func isSeparator(line []byte) bool { | |||||
| line = util.TrimRightSpace(util.TrimLeftSpace(line)) | |||||
| for i := 0; i < len(line); i++ { | |||||
| if line[i] != '-' { | |||||
| return false | |||||
| } | |||||
| } | |||||
| return true | |||||
| } | |||||
| func (b *metaParser) Trigger() []byte { | |||||
| return []byte{'-'} | |||||
| } | |||||
| func (b *metaParser) Open(parent gast.Node, reader text.Reader, pc parser.Context) (gast.Node, parser.State) { | |||||
| linenum, _ := reader.Position() | |||||
| if linenum != 0 { | |||||
| return nil, parser.NoChildren | |||||
| } | |||||
| line, _ := reader.PeekLine() | |||||
| if isSeparator(line) { | |||||
| return gast.NewTextBlock(), parser.NoChildren | |||||
| } | |||||
| return nil, parser.NoChildren | |||||
| } | |||||
| func (b *metaParser) Continue(node gast.Node, reader text.Reader, pc parser.Context) parser.State { | |||||
| line, segment := reader.PeekLine() | |||||
| if isSeparator(line) { | |||||
| reader.Advance(segment.Len()) | |||||
| return parser.Close | |||||
| } | |||||
| node.Lines().Append(segment) | |||||
| return parser.Continue | parser.NoChildren | |||||
| } | |||||
| func (b *metaParser) Close(node gast.Node, reader text.Reader, pc parser.Context) { | |||||
| lines := node.Lines() | |||||
| var buf bytes.Buffer | |||||
| for i := 0; i < lines.Len(); i++ { | |||||
| segment := lines.At(i) | |||||
| buf.Write(segment.Value(reader.Source())) | |||||
| } | |||||
| d := &data{} | |||||
| d.Node = node | |||||
| meta := map[string]interface{}{} | |||||
| if err := yaml.Unmarshal(buf.Bytes(), &meta); err != nil { | |||||
| d.Error = err | |||||
| } else { | |||||
| d.Map = meta | |||||
| } | |||||
| metaMapSlice := yaml.MapSlice{} | |||||
| if err := yaml.Unmarshal(buf.Bytes(), &metaMapSlice); err != nil { | |||||
| d.Error = err | |||||
| } else { | |||||
| d.Items = metaMapSlice | |||||
| } | |||||
| pc.Set(contextKey, d) | |||||
| if d.Error == nil { | |||||
| node.Parent().RemoveChild(node.Parent(), node) | |||||
| } | |||||
| } | |||||
| func (b *metaParser) CanInterruptParagraph() bool { | |||||
| return false | |||||
| } | |||||
| func (b *metaParser) CanAcceptIndentedLine() bool { | |||||
| return false | |||||
| } | |||||
| type astTransformer struct { | |||||
| } | |||||
| var defaultASTTransformer = &astTransformer{} | |||||
| func (a *astTransformer) Transform(node *gast.Document, reader text.Reader, pc parser.Context) { | |||||
| dtmp := pc.Get(contextKey) | |||||
| if dtmp == nil { | |||||
| return | |||||
| } | |||||
| d := dtmp.(*data) | |||||
| if d.Error != nil { | |||||
| msg := gast.NewString([]byte(fmt.Sprintf("<!-- %s -->", d.Error))) | |||||
| msg.SetCode(true) | |||||
| d.Node.AppendChild(d.Node, msg) | |||||
| 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)) | |||||
| 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) | |||||
| } | |||||
| // 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 | |||||
| } | |||||
| } | |||||
| type meta struct { | |||||
| Table bool | |||||
| } | |||||
| // Meta is a extension for the goldmark. | |||||
| var Meta = &meta{} | |||||
| // New returns a new Meta extension. | |||||
| func New(opts ...Option) goldmark.Extender { | |||||
| e := &meta{} | |||||
| for _, opt := range opts { | |||||
| opt(e) | |||||
| } | |||||
| return e | |||||
| } | |||||
| func (e *meta) Extend(m goldmark.Markdown) { | |||||
| m.Parser().AddOptions( | |||||
| parser.WithBlockParsers( | |||||
| util.Prioritized(NewParser(), 0), | |||||
| ), | |||||
| ) | |||||
| if e.Table { | |||||
| m.Parser().AddOptions( | |||||
| parser.WithASTTransformers( | |||||
| util.Prioritized(defaultASTTransformer, 0), | |||||
| ), | |||||
| ) | |||||
| } | |||||
| } | |||||
| @@ -654,6 +654,9 @@ github.com/yuin/goldmark/renderer | |||||
| github.com/yuin/goldmark/renderer/html | github.com/yuin/goldmark/renderer/html | ||||
| github.com/yuin/goldmark/text | github.com/yuin/goldmark/text | ||||
| github.com/yuin/goldmark/util | github.com/yuin/goldmark/util | ||||
| # github.com/yuin/goldmark-meta v0.0.0-20191126180153-f0638e958b60 | |||||
| ## explicit | |||||
| github.com/yuin/goldmark-meta | |||||
| # go.etcd.io/bbolt v1.3.3 | # go.etcd.io/bbolt v1.3.3 | ||||
| ## explicit | ## explicit | ||||
| # go.mongodb.org/mongo-driver v1.1.1 | # go.mongodb.org/mongo-driver v1.1.1 | ||||