Skip to content

Commit 1f81006

Browse files
committed
remove goldmark meta and handle yaml frontmatter ourselves
Signed-off-by: Andrew Thornton <[email protected]>
1 parent 3ac5fb5 commit 1f81006

File tree

8 files changed

+457
-167
lines changed

8 files changed

+457
-167
lines changed

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ require (
101101
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df
102102
gopkg.in/ini.v1 v1.66.4
103103
gopkg.in/yaml.v2 v2.4.0
104+
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b
104105
mvdan.cc/xurls/v2 v2.4.0
105106
strk.kbt.io/projects/go/libravatar v0.0.0-20191008002943-06d1c002b251
106107
xorm.io/builder v0.3.11
@@ -286,7 +287,6 @@ require (
286287
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
287288
gopkg.in/cheggaaa/pb.v1 v1.0.28 // indirect
288289
gopkg.in/warnings.v0 v0.1.2 // indirect
289-
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
290290
sigs.k8s.io/yaml v1.2.0 // indirect
291291
)
292292

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
// Copyright 2022 The Gitea Authors. All rights reserved.
2+
// Use of this source code is governed by a MIT-style
3+
// license that can be found in the LICENSE file.
4+
5+
package markdown
6+
7+
import (
8+
"github.com/yuin/goldmark/ast"
9+
east "github.com/yuin/goldmark/extension/ast"
10+
"gopkg.in/yaml.v3"
11+
)
12+
13+
func nodeToTable(meta *yaml.Node) ast.Node {
14+
for {
15+
if meta == nil {
16+
return nil
17+
}
18+
switch meta.Kind {
19+
case yaml.DocumentNode:
20+
meta = meta.Content[0]
21+
continue
22+
default:
23+
}
24+
break
25+
}
26+
switch meta.Kind {
27+
case yaml.MappingNode:
28+
return mappingNodeToTable(meta)
29+
case yaml.SequenceNode:
30+
return sequenceNodeToTable(meta)
31+
default:
32+
return ast.NewString([]byte(meta.Value))
33+
}
34+
}
35+
36+
func mappingNodeToTable(meta *yaml.Node) ast.Node {
37+
table := east.NewTable()
38+
alignments := []east.Alignment{}
39+
for i := 0; i < len(meta.Content); i += 2 {
40+
alignments = append(alignments, east.AlignNone)
41+
}
42+
43+
headerRow := east.NewTableRow(alignments)
44+
valueRow := east.NewTableRow(alignments)
45+
for i := 0; i < len(meta.Content); i += 2 {
46+
cell := east.NewTableCell()
47+
48+
cell.AppendChild(cell, nodeToTable(meta.Content[i]))
49+
headerRow.AppendChild(headerRow, cell)
50+
51+
if i+1 < len(meta.Content) {
52+
cell = east.NewTableCell()
53+
cell.AppendChild(cell, nodeToTable(meta.Content[i+1]))
54+
valueRow.AppendChild(valueRow, cell)
55+
}
56+
}
57+
58+
table.AppendChild(table, east.NewTableHeader(headerRow))
59+
table.AppendChild(table, valueRow)
60+
return table
61+
}
62+
63+
func sequenceNodeToTable(meta *yaml.Node) ast.Node {
64+
table := east.NewTable()
65+
alignments := []east.Alignment{east.AlignNone}
66+
for _, item := range meta.Content {
67+
row := east.NewTableRow(alignments)
68+
cell := east.NewTableCell()
69+
cell.AppendChild(cell, nodeToTable(item))
70+
row.AppendChild(row, cell)
71+
table.AppendChild(table, row)
72+
}
73+
return table
74+
}
75+
76+
func nodeToDetails(meta *yaml.Node, icon string) ast.Node {
77+
details := NewDetails()
78+
summary := NewSummary()
79+
summary.AppendChild(summary, NewIcon(icon))
80+
details.AppendChild(details, summary)
81+
details.AppendChild(details, nodeToTable(meta))
82+
83+
return details
84+
}

modules/markup/markdown/goldmark.go

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ import (
1515
"code.gitea.io/gitea/modules/setting"
1616
giteautil "code.gitea.io/gitea/modules/util"
1717

18-
meta "github.com/yuin/goldmark-meta"
1918
"github.com/yuin/goldmark/ast"
2019
east "github.com/yuin/goldmark/extension/ast"
2120
"github.com/yuin/goldmark/parser"
@@ -32,20 +31,12 @@ type ASTTransformer struct{}
3231

3332
// Transform transforms the given AST tree.
3433
func (g *ASTTransformer) Transform(node *ast.Document, reader text.Reader, pc parser.Context) {
35-
metaData := meta.GetItems(pc)
3634
firstChild := node.FirstChild()
3735
createTOC := false
3836
ctx := pc.Get(renderContextKey).(*markup.RenderContext)
39-
rc := &RenderConfig{
40-
Meta: "table",
41-
Icon: "table",
42-
Lang: "",
43-
}
44-
45-
if metaData != nil {
46-
rc.ToRenderConfig(metaData)
47-
48-
metaNode := rc.toMetaNode(metaData)
37+
rc := pc.Get(renderConfigKey).(*RenderConfig)
38+
if rc.yamlNode != nil {
39+
metaNode := rc.toMetaNode()
4940
if metaNode != nil {
5041
node.InsertBefore(node, firstChild, metaNode)
5142
}

modules/markup/markdown/markdown.go

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ var (
3939
isWikiKey = parser.NewContextKey()
4040
renderMetasKey = parser.NewContextKey()
4141
renderContextKey = parser.NewContextKey()
42+
renderConfigKey = parser.NewContextKey()
4243
)
4344

4445
type limitWriter struct {
@@ -172,7 +173,18 @@ func actualRender(ctx *markup.RenderContext, input io.Reader, output io.Writer)
172173
log.Error("Unable to ReadAll: %v", err)
173174
return err
174175
}
175-
if err := converter.Convert(giteautil.NormalizeEOL(buf), lw, parser.WithContext(pc)); err != nil {
176+
buf = giteautil.NormalizeEOL(buf)
177+
178+
rc := &RenderConfig{
179+
Meta: "table",
180+
Icon: "table",
181+
Lang: "",
182+
}
183+
buf, _ = ExtractMetadataBytes(buf, rc)
184+
185+
pc.Set(renderConfigKey, rc)
186+
187+
if err := converter.Convert(buf, lw, parser.WithContext(pc)); err != nil {
176188
log.Error("Unable to render: %v", err)
177189
return err
178190
}

modules/markup/markdown/meta.go

Lines changed: 75 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -5,47 +5,101 @@
55
package markdown
66

77
import (
8+
"bytes"
89
"errors"
9-
"strings"
10+
"unicode"
11+
"unicode/utf8"
1012

11-
"gopkg.in/yaml.v2"
13+
"code.gitea.io/gitea/modules/log"
14+
"gopkg.in/yaml.v3"
1215
)
1316

14-
func isYAMLSeparator(line string) bool {
15-
line = strings.TrimSpace(line)
16-
for i := 0; i < len(line); i++ {
17-
if line[i] != '-' {
17+
func isYAMLSeparator(line []byte) bool {
18+
idx := 0
19+
for ; idx < len(line); idx++ {
20+
if line[idx] >= utf8.RuneSelf {
21+
r, sz := utf8.DecodeRune(line[idx:])
22+
if !unicode.IsSpace(r) {
23+
return false
24+
}
25+
idx += sz
26+
continue
27+
}
28+
if line[idx] != ' ' {
29+
break
30+
}
31+
}
32+
dashCount := 0
33+
for ; idx < len(line); idx++ {
34+
if line[idx] != '-' {
35+
break
36+
}
37+
dashCount++
38+
}
39+
if dashCount < 3 {
40+
return false
41+
}
42+
for ; idx < len(line); idx++ {
43+
if line[idx] >= utf8.RuneSelf {
44+
r, sz := utf8.DecodeRune(line[idx:])
45+
if !unicode.IsSpace(r) {
46+
return false
47+
}
48+
idx += sz
49+
continue
50+
}
51+
if line[idx] != ' ' {
1852
return false
1953
}
2054
}
21-
return len(line) > 2
55+
return true
2256
}
2357

2458
// ExtractMetadata consumes a markdown file, parses YAML frontmatter,
2559
// and returns the frontmatter metadata separated from the markdown content
2660
func ExtractMetadata(contents string, out interface{}) (string, error) {
27-
var front, body []string
28-
lines := strings.Split(contents, "\n")
29-
for idx, line := range lines {
30-
if idx == 0 {
31-
// First line has to be a separator
32-
if !isYAMLSeparator(line) {
33-
return "", errors.New("frontmatter must start with a separator line")
34-
}
35-
continue
61+
body, err := ExtractMetadataBytes([]byte(contents), out)
62+
return string(body), err
63+
}
64+
65+
// ExtractMetadata consumes a markdown file, parses YAML frontmatter,
66+
// and returns the frontmatter metadata separated from the markdown content
67+
func ExtractMetadataBytes(contents []byte, out interface{}) ([]byte, error) {
68+
var front, body []byte
69+
70+
start, end := 0, len(contents)
71+
idx := bytes.IndexByte(contents[start:], '\n')
72+
if idx >= 0 {
73+
end = start + idx
74+
}
75+
line := contents[start:end]
76+
77+
if !isYAMLSeparator(line) {
78+
return contents, errors.New("frontmatter must start with a separator line")
79+
}
80+
frontMatterStart := end + 1
81+
for start = frontMatterStart; start < len(contents); start = end + 1 {
82+
end = len(contents)
83+
idx := bytes.IndexByte(contents[start:], '\n')
84+
if idx >= 0 {
85+
end = start + idx
3686
}
87+
line := contents[start:end]
3788
if isYAMLSeparator(line) {
38-
front, body = lines[1:idx], lines[idx+1:]
89+
front = contents[frontMatterStart:start]
90+
body = contents[end+1:]
3991
break
4092
}
4193
}
4294

4395
if len(front) == 0 {
44-
return "", errors.New("could not determine metadata")
96+
return contents, errors.New("could not determine metadata")
4597
}
4698

47-
if err := yaml.Unmarshal([]byte(strings.Join(front, "\n")), out); err != nil {
48-
return "", err
99+
log.Info("%s", string(front))
100+
101+
if err := yaml.Unmarshal(front, out); err != nil {
102+
return contents, err
49103
}
50-
return strings.Join(body, "\n"), nil
104+
return body, nil
51105
}

modules/markup/markdown/meta_test.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,38 @@ func TestExtractMetadata(t *testing.T) {
4545
})
4646
}
4747

48+
func TestExtractMetadataBytes(t *testing.T) {
49+
t.Run("ValidFrontAndBody", func(t *testing.T) {
50+
var meta structs.IssueTemplate
51+
body, err := ExtractMetadataBytes([]byte(fmt.Sprintf("%s\n%s\n%s\n%s", sepTest, frontTest, sepTest, bodyTest)), &meta)
52+
assert.NoError(t, err)
53+
assert.Equal(t, bodyTest, body)
54+
assert.Equal(t, metaTest, meta)
55+
assert.True(t, meta.Valid())
56+
})
57+
58+
t.Run("NoFirstSeparator", func(t *testing.T) {
59+
var meta structs.IssueTemplate
60+
_, err := ExtractMetadataBytes([]byte(fmt.Sprintf("%s\n%s\n%s", frontTest, sepTest, bodyTest)), &meta)
61+
assert.Error(t, err)
62+
})
63+
64+
t.Run("NoLastSeparator", func(t *testing.T) {
65+
var meta structs.IssueTemplate
66+
_, err := ExtractMetadataBytes([]byte(fmt.Sprintf("%s\n%s\n%s", sepTest, frontTest, bodyTest)), &meta)
67+
assert.Error(t, err)
68+
})
69+
70+
t.Run("NoBody", func(t *testing.T) {
71+
var meta structs.IssueTemplate
72+
body, err := ExtractMetadataBytes([]byte(fmt.Sprintf("%s\n%s\n%s", sepTest, frontTest, sepTest)), &meta)
73+
assert.NoError(t, err)
74+
assert.Equal(t, "", body)
75+
assert.Equal(t, metaTest, meta)
76+
assert.True(t, meta.Valid())
77+
})
78+
}
79+
4880
var (
4981
sepTest = "-----"
5082
frontTest = `name: Test

0 commit comments

Comments
 (0)