From 0be25e21501ccf8c0f27e2fa87aba00ad304c951 Mon Sep 17 00:00:00 2001
From: zeripath <art27@cantab.net>
Date: Thu, 9 Apr 2020 11:54:50 +0100
Subject: [PATCH] Handle yaml frontmatter (#11016)

Add goldmark-meta to render yaml frontmatter as a table

Fix #5377

Signed-off-by: Andrew Thornton <art27@cantab.net>
---
 go.mod                                        |   1 +
 go.sum                                        |   3 +
 modules/markup/markdown/markdown.go           |   2 +
 .../github.com/yuin/goldmark-meta/.gitignore  |  13 ++
 vendor/github.com/yuin/goldmark-meta/LICENSE  |  21 ++
 .../github.com/yuin/goldmark-meta/README.md   | 147 ++++++++++++
 vendor/github.com/yuin/goldmark-meta/go.mod   |   8 +
 vendor/github.com/yuin/goldmark-meta/go.sum   |   6 +
 vendor/github.com/yuin/goldmark-meta/meta.go  | 218 ++++++++++++++++++
 vendor/modules.txt                            |   3 +
 10 files changed, 422 insertions(+)
 create mode 100644 vendor/github.com/yuin/goldmark-meta/.gitignore
 create mode 100644 vendor/github.com/yuin/goldmark-meta/LICENSE
 create mode 100644 vendor/github.com/yuin/goldmark-meta/README.md
 create mode 100644 vendor/github.com/yuin/goldmark-meta/go.mod
 create mode 100644 vendor/github.com/yuin/goldmark-meta/go.sum
 create mode 100644 vendor/github.com/yuin/goldmark-meta/meta.go

diff --git a/go.mod b/go.mod
index e792b187a..0930b0d16 100644
--- a/go.mod
+++ b/go.mod
@@ -106,6 +106,7 @@ require (
 	github.com/urfave/cli v1.20.0
 	github.com/yohcop/openid-go v0.0.0-20160914080427-2c050d2dae53
 	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
 	golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073
 	golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e
diff --git a/go.sum b/go.sum
index 3cda494fd..5944cbb5e 100644
--- a/go.sum
+++ b/go.sum
@@ -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/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/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/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/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0=
 go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
diff --git a/modules/markup/markdown/markdown.go b/modules/markup/markdown/markdown.go
index 81b5635d3..c48bbab30 100644
--- a/modules/markup/markdown/markdown.go
+++ b/modules/markup/markdown/markdown.go
@@ -16,6 +16,7 @@ import (
 	giteautil "code.gitea.io/gitea/modules/util"
 
 	"github.com/yuin/goldmark"
+	meta "github.com/yuin/goldmark-meta"
 	"github.com/yuin/goldmark/extension"
 	"github.com/yuin/goldmark/parser"
 	"github.com/yuin/goldmark/renderer"
@@ -53,6 +54,7 @@ func RenderRaw(body []byte, urlPrefix string, wikiMarkdown bool) []byte {
 						extension.Ellipsis: nil,
 					}),
 				),
+				meta.New(meta.WithTable()),
 			),
 			goldmark.WithParserOptions(
 				parser.WithAttribute(),
diff --git a/vendor/github.com/yuin/goldmark-meta/.gitignore b/vendor/github.com/yuin/goldmark-meta/.gitignore
new file mode 100644
index 000000000..6e4db9226
--- /dev/null
+++ b/vendor/github.com/yuin/goldmark-meta/.gitignore
@@ -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
diff --git a/vendor/github.com/yuin/goldmark-meta/LICENSE b/vendor/github.com/yuin/goldmark-meta/LICENSE
new file mode 100644
index 000000000..dc5b2a690
--- /dev/null
+++ b/vendor/github.com/yuin/goldmark-meta/LICENSE
@@ -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.
diff --git a/vendor/github.com/yuin/goldmark-meta/README.md b/vendor/github.com/yuin/goldmark-meta/README.md
new file mode 100644
index 000000000..98f926b35
--- /dev/null
+++ b/vendor/github.com/yuin/goldmark-meta/README.md
@@ -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
diff --git a/vendor/github.com/yuin/goldmark-meta/go.mod b/vendor/github.com/yuin/goldmark-meta/go.mod
new file mode 100644
index 000000000..b5496c175
--- /dev/null
+++ b/vendor/github.com/yuin/goldmark-meta/go.mod
@@ -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
+)
diff --git a/vendor/github.com/yuin/goldmark-meta/go.sum b/vendor/github.com/yuin/goldmark-meta/go.sum
new file mode 100644
index 000000000..7cd925cd5
--- /dev/null
+++ b/vendor/github.com/yuin/goldmark-meta/go.sum
@@ -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=
diff --git a/vendor/github.com/yuin/goldmark-meta/meta.go b/vendor/github.com/yuin/goldmark-meta/meta.go
new file mode 100644
index 000000000..286043713
--- /dev/null
+++ b/vendor/github.com/yuin/goldmark-meta/meta.go
@@ -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),
+			),
+		)
+	}
+}
diff --git a/vendor/modules.txt b/vendor/modules.txt
index ea20dc305..8a7c6706e 100644
--- a/vendor/modules.txt
+++ b/vendor/modules.txt
@@ -654,6 +654,9 @@ github.com/yuin/goldmark/renderer
 github.com/yuin/goldmark/renderer/html
 github.com/yuin/goldmark/text
 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
 ## explicit
 # go.mongodb.org/mongo-driver v1.1.1