aboutsummaryrefslogtreecommitdiff
path: root/vendor/github.com/yuin/goldmark/extension
diff options
context:
space:
mode:
authorMitja Felicijan <mitja.felicijan@gmail.com>2024-10-25 00:47:47 +0200
committerMitja Felicijan <mitja.felicijan@gmail.com>2024-10-25 00:47:47 +0200
commitc6cc0108ca7738023b45e0eeac0fa2390532dd93 (patch)
tree36890e6cd3091bbab8efbe686cc56f467f645bfd /vendor/github.com/yuin/goldmark/extension
parent0130404a1dc663d4aa68d780c9bcb23a4243e68d (diff)
downloadjbmafp-c6cc0108ca7738023b45e0eeac0fa2390532dd93.tar.gz
Added vendor lock on depsHEADmaster
Diffstat (limited to 'vendor/github.com/yuin/goldmark/extension')
-rw-r--r--vendor/github.com/yuin/goldmark/extension/ast/definition_list.go83
-rw-r--r--vendor/github.com/yuin/goldmark/extension/ast/footnote.go138
-rw-r--r--vendor/github.com/yuin/goldmark/extension/ast/strikethrough.go29
-rw-r--r--vendor/github.com/yuin/goldmark/extension/ast/table.go157
-rw-r--r--vendor/github.com/yuin/goldmark/extension/ast/tasklist.go35
-rw-r--r--vendor/github.com/yuin/goldmark/extension/cjk.go51
-rw-r--r--vendor/github.com/yuin/goldmark/extension/definition_list.go270
-rw-r--r--vendor/github.com/yuin/goldmark/extension/footnote.go687
-rw-r--r--vendor/github.com/yuin/goldmark/extension/gfm.go18
-rw-r--r--vendor/github.com/yuin/goldmark/extension/linkify.go318
-rw-r--r--vendor/github.com/yuin/goldmark/extension/strikethrough.go116
-rw-r--r--vendor/github.com/yuin/goldmark/extension/table.go556
-rw-r--r--vendor/github.com/yuin/goldmark/extension/tasklist.go115
-rw-r--r--vendor/github.com/yuin/goldmark/extension/typographer.go339
14 files changed, 2912 insertions, 0 deletions
diff --git a/vendor/github.com/yuin/goldmark/extension/ast/definition_list.go b/vendor/github.com/yuin/goldmark/extension/ast/definition_list.go
new file mode 100644
index 0000000..1beffb3
--- /dev/null
+++ b/vendor/github.com/yuin/goldmark/extension/ast/definition_list.go
@@ -0,0 +1,83 @@
1package ast
2
3import (
4 gast "github.com/yuin/goldmark/ast"
5)
6
7// A DefinitionList struct represents a definition list of Markdown
8// (PHPMarkdownExtra) text.
9type DefinitionList struct {
10 gast.BaseBlock
11 Offset int
12 TemporaryParagraph *gast.Paragraph
13}
14
15// Dump implements Node.Dump.
16func (n *DefinitionList) Dump(source []byte, level int) {
17 gast.DumpHelper(n, source, level, nil, nil)
18}
19
20// KindDefinitionList is a NodeKind of the DefinitionList node.
21var KindDefinitionList = gast.NewNodeKind("DefinitionList")
22
23// Kind implements Node.Kind.
24func (n *DefinitionList) Kind() gast.NodeKind {
25 return KindDefinitionList
26}
27
28// NewDefinitionList returns a new DefinitionList node.
29func NewDefinitionList(offset int, para *gast.Paragraph) *DefinitionList {
30 return &DefinitionList{
31 Offset: offset,
32 TemporaryParagraph: para,
33 }
34}
35
36// A DefinitionTerm struct represents a definition list term of Markdown
37// (PHPMarkdownExtra) text.
38type DefinitionTerm struct {
39 gast.BaseBlock
40}
41
42// Dump implements Node.Dump.
43func (n *DefinitionTerm) Dump(source []byte, level int) {
44 gast.DumpHelper(n, source, level, nil, nil)
45}
46
47// KindDefinitionTerm is a NodeKind of the DefinitionTerm node.
48var KindDefinitionTerm = gast.NewNodeKind("DefinitionTerm")
49
50// Kind implements Node.Kind.
51func (n *DefinitionTerm) Kind() gast.NodeKind {
52 return KindDefinitionTerm
53}
54
55// NewDefinitionTerm returns a new DefinitionTerm node.
56func NewDefinitionTerm() *DefinitionTerm {
57 return &DefinitionTerm{}
58}
59
60// A DefinitionDescription struct represents a definition list description of Markdown
61// (PHPMarkdownExtra) text.
62type DefinitionDescription struct {
63 gast.BaseBlock
64 IsTight bool
65}
66
67// Dump implements Node.Dump.
68func (n *DefinitionDescription) Dump(source []byte, level int) {
69 gast.DumpHelper(n, source, level, nil, nil)
70}
71
72// KindDefinitionDescription is a NodeKind of the DefinitionDescription node.
73var KindDefinitionDescription = gast.NewNodeKind("DefinitionDescription")
74
75// Kind implements Node.Kind.
76func (n *DefinitionDescription) Kind() gast.NodeKind {
77 return KindDefinitionDescription
78}
79
80// NewDefinitionDescription returns a new DefinitionDescription node.
81func NewDefinitionDescription() *DefinitionDescription {
82 return &DefinitionDescription{}
83}
diff --git a/vendor/github.com/yuin/goldmark/extension/ast/footnote.go b/vendor/github.com/yuin/goldmark/extension/ast/footnote.go
new file mode 100644
index 0000000..97fea44
--- /dev/null
+++ b/vendor/github.com/yuin/goldmark/extension/ast/footnote.go
@@ -0,0 +1,138 @@
1package ast
2
3import (
4 "fmt"
5
6 gast "github.com/yuin/goldmark/ast"
7)
8
9// A FootnoteLink struct represents a link to a footnote of Markdown
10// (PHP Markdown Extra) text.
11type FootnoteLink struct {
12 gast.BaseInline
13 Index int
14 RefCount int
15 RefIndex int
16}
17
18// Dump implements Node.Dump.
19func (n *FootnoteLink) Dump(source []byte, level int) {
20 m := map[string]string{}
21 m["Index"] = fmt.Sprintf("%v", n.Index)
22 m["RefCount"] = fmt.Sprintf("%v", n.RefCount)
23 m["RefIndex"] = fmt.Sprintf("%v", n.RefIndex)
24 gast.DumpHelper(n, source, level, m, nil)
25}
26
27// KindFootnoteLink is a NodeKind of the FootnoteLink node.
28var KindFootnoteLink = gast.NewNodeKind("FootnoteLink")
29
30// Kind implements Node.Kind.
31func (n *FootnoteLink) Kind() gast.NodeKind {
32 return KindFootnoteLink
33}
34
35// NewFootnoteLink returns a new FootnoteLink node.
36func NewFootnoteLink(index int) *FootnoteLink {
37 return &FootnoteLink{
38 Index: index,
39 RefCount: 0,
40 RefIndex: 0,
41 }
42}
43
44// A FootnoteBacklink struct represents a link to a footnote of Markdown
45// (PHP Markdown Extra) text.
46type FootnoteBacklink struct {
47 gast.BaseInline
48 Index int
49 RefCount int
50 RefIndex int
51}
52
53// Dump implements Node.Dump.
54func (n *FootnoteBacklink) Dump(source []byte, level int) {
55 m := map[string]string{}
56 m["Index"] = fmt.Sprintf("%v", n.Index)
57 m["RefCount"] = fmt.Sprintf("%v", n.RefCount)
58 m["RefIndex"] = fmt.Sprintf("%v", n.RefIndex)
59 gast.DumpHelper(n, source, level, m, nil)
60}
61
62// KindFootnoteBacklink is a NodeKind of the FootnoteBacklink node.
63var KindFootnoteBacklink = gast.NewNodeKind("FootnoteBacklink")
64
65// Kind implements Node.Kind.
66func (n *FootnoteBacklink) Kind() gast.NodeKind {
67 return KindFootnoteBacklink
68}
69
70// NewFootnoteBacklink returns a new FootnoteBacklink node.
71func NewFootnoteBacklink(index int) *FootnoteBacklink {
72 return &FootnoteBacklink{
73 Index: index,
74 RefCount: 0,
75 RefIndex: 0,
76 }
77}
78
79// A Footnote struct represents a footnote of Markdown
80// (PHP Markdown Extra) text.
81type Footnote struct {
82 gast.BaseBlock
83 Ref []byte
84 Index int
85}
86
87// Dump implements Node.Dump.
88func (n *Footnote) Dump(source []byte, level int) {
89 m := map[string]string{}
90 m["Index"] = fmt.Sprintf("%v", n.Index)
91 m["Ref"] = fmt.Sprintf("%s", n.Ref)
92 gast.DumpHelper(n, source, level, m, nil)
93}
94
95// KindFootnote is a NodeKind of the Footnote node.
96var KindFootnote = gast.NewNodeKind("Footnote")
97
98// Kind implements Node.Kind.
99func (n *Footnote) Kind() gast.NodeKind {
100 return KindFootnote
101}
102
103// NewFootnote returns a new Footnote node.
104func NewFootnote(ref []byte) *Footnote {
105 return &Footnote{
106 Ref: ref,
107 Index: -1,
108 }
109}
110
111// A FootnoteList struct represents footnotes of Markdown
112// (PHP Markdown Extra) text.
113type FootnoteList struct {
114 gast.BaseBlock
115 Count int
116}
117
118// Dump implements Node.Dump.
119func (n *FootnoteList) Dump(source []byte, level int) {
120 m := map[string]string{}
121 m["Count"] = fmt.Sprintf("%v", n.Count)
122 gast.DumpHelper(n, source, level, m, nil)
123}
124
125// KindFootnoteList is a NodeKind of the FootnoteList node.
126var KindFootnoteList = gast.NewNodeKind("FootnoteList")
127
128// Kind implements Node.Kind.
129func (n *FootnoteList) Kind() gast.NodeKind {
130 return KindFootnoteList
131}
132
133// NewFootnoteList returns a new FootnoteList node.
134func NewFootnoteList() *FootnoteList {
135 return &FootnoteList{
136 Count: 0,
137 }
138}
diff --git a/vendor/github.com/yuin/goldmark/extension/ast/strikethrough.go b/vendor/github.com/yuin/goldmark/extension/ast/strikethrough.go
new file mode 100644
index 0000000..a9216b7
--- /dev/null
+++ b/vendor/github.com/yuin/goldmark/extension/ast/strikethrough.go
@@ -0,0 +1,29 @@
1// Package ast defines AST nodes that represents extension's elements
2package ast
3
4import (
5 gast "github.com/yuin/goldmark/ast"
6)
7
8// A Strikethrough struct represents a strikethrough of GFM text.
9type Strikethrough struct {
10 gast.BaseInline
11}
12
13// Dump implements Node.Dump.
14func (n *Strikethrough) Dump(source []byte, level int) {
15 gast.DumpHelper(n, source, level, nil, nil)
16}
17
18// KindStrikethrough is a NodeKind of the Strikethrough node.
19var KindStrikethrough = gast.NewNodeKind("Strikethrough")
20
21// Kind implements Node.Kind.
22func (n *Strikethrough) Kind() gast.NodeKind {
23 return KindStrikethrough
24}
25
26// NewStrikethrough returns a new Strikethrough node.
27func NewStrikethrough() *Strikethrough {
28 return &Strikethrough{}
29}
diff --git a/vendor/github.com/yuin/goldmark/extension/ast/table.go b/vendor/github.com/yuin/goldmark/extension/ast/table.go
new file mode 100644
index 0000000..e9eff3c
--- /dev/null
+++ b/vendor/github.com/yuin/goldmark/extension/ast/table.go
@@ -0,0 +1,157 @@
1package ast
2
3import (
4 "fmt"
5 gast "github.com/yuin/goldmark/ast"
6 "strings"
7)
8
9// Alignment is a text alignment of table cells.
10type Alignment int
11
12const (
13 // AlignLeft indicates text should be left justified.
14 AlignLeft Alignment = iota + 1
15
16 // AlignRight indicates text should be right justified.
17 AlignRight
18
19 // AlignCenter indicates text should be centered.
20 AlignCenter
21
22 // AlignNone indicates text should be aligned by default manner.
23 AlignNone
24)
25
26func (a Alignment) String() string {
27 switch a {
28 case AlignLeft:
29 return "left"
30 case AlignRight:
31 return "right"
32 case AlignCenter:
33 return "center"
34 case AlignNone:
35 return "none"
36 }
37 return ""
38}
39
40// A Table struct represents a table of Markdown(GFM) text.
41type Table struct {
42 gast.BaseBlock
43
44 // Alignments returns alignments of the columns.
45 Alignments []Alignment
46}
47
48// Dump implements Node.Dump
49func (n *Table) Dump(source []byte, level int) {
50 gast.DumpHelper(n, source, level, nil, func(level int) {
51 indent := strings.Repeat(" ", level)
52 fmt.Printf("%sAlignments {\n", indent)
53 for i, alignment := range n.Alignments {
54 indent2 := strings.Repeat(" ", level+1)
55 fmt.Printf("%s%s", indent2, alignment.String())
56 if i != len(n.Alignments)-1 {
57 fmt.Println("")
58 }
59 }
60 fmt.Printf("\n%s}\n", indent)
61 })
62}
63
64// KindTable is a NodeKind of the Table node.
65var KindTable = gast.NewNodeKind("Table")
66
67// Kind implements Node.Kind.
68func (n *Table) Kind() gast.NodeKind {
69 return KindTable
70}
71
72// NewTable returns a new Table node.
73func NewTable() *Table {
74 return &Table{
75 Alignments: []Alignment{},
76 }
77}
78
79// A TableRow struct represents a table row of Markdown(GFM) text.
80type TableRow struct {
81 gast.BaseBlock
82 Alignments []Alignment
83}
84
85// Dump implements Node.Dump.
86func (n *TableRow) Dump(source []byte, level int) {
87 gast.DumpHelper(n, source, level, nil, nil)
88}
89
90// KindTableRow is a NodeKind of the TableRow node.
91var KindTableRow = gast.NewNodeKind("TableRow")
92
93// Kind implements Node.Kind.
94func (n *TableRow) Kind() gast.NodeKind {
95 return KindTableRow
96}
97
98// NewTableRow returns a new TableRow node.
99func NewTableRow(alignments []Alignment) *TableRow {
100 return &TableRow{Alignments: alignments}
101}
102
103// A TableHeader struct represents a table header of Markdown(GFM) text.
104type TableHeader struct {
105 gast.BaseBlock
106 Alignments []Alignment
107}
108
109// KindTableHeader is a NodeKind of the TableHeader node.
110var KindTableHeader = gast.NewNodeKind("TableHeader")
111
112// Kind implements Node.Kind.
113func (n *TableHeader) Kind() gast.NodeKind {
114 return KindTableHeader
115}
116
117// Dump implements Node.Dump.
118func (n *TableHeader) Dump(source []byte, level int) {
119 gast.DumpHelper(n, source, level, nil, nil)
120}
121
122// NewTableHeader returns a new TableHeader node.
123func NewTableHeader(row *TableRow) *TableHeader {
124 n := &TableHeader{}
125 for c := row.FirstChild(); c != nil; {
126 next := c.NextSibling()
127 n.AppendChild(n, c)
128 c = next
129 }
130 return n
131}
132
133// A TableCell struct represents a table cell of a Markdown(GFM) text.
134type TableCell struct {
135 gast.BaseBlock
136 Alignment Alignment
137}
138
139// Dump implements Node.Dump.
140func (n *TableCell) Dump(source []byte, level int) {
141 gast.DumpHelper(n, source, level, nil, nil)
142}
143
144// KindTableCell is a NodeKind of the TableCell node.
145var KindTableCell = gast.NewNodeKind("TableCell")
146
147// Kind implements Node.Kind.
148func (n *TableCell) Kind() gast.NodeKind {
149 return KindTableCell
150}
151
152// NewTableCell returns a new TableCell node.
153func NewTableCell() *TableCell {
154 return &TableCell{
155 Alignment: AlignNone,
156 }
157}
diff --git a/vendor/github.com/yuin/goldmark/extension/ast/tasklist.go b/vendor/github.com/yuin/goldmark/extension/ast/tasklist.go
new file mode 100644
index 0000000..670cc14
--- /dev/null
+++ b/vendor/github.com/yuin/goldmark/extension/ast/tasklist.go
@@ -0,0 +1,35 @@
1package ast
2
3import (
4 "fmt"
5 gast "github.com/yuin/goldmark/ast"
6)
7
8// A TaskCheckBox struct represents a checkbox of a task list.
9type TaskCheckBox struct {
10 gast.BaseInline
11 IsChecked bool
12}
13
14// Dump implements Node.Dump.
15func (n *TaskCheckBox) Dump(source []byte, level int) {
16 m := map[string]string{
17 "Checked": fmt.Sprintf("%v", n.IsChecked),
18 }
19 gast.DumpHelper(n, source, level, m, nil)
20}
21
22// KindTaskCheckBox is a NodeKind of the TaskCheckBox node.
23var KindTaskCheckBox = gast.NewNodeKind("TaskCheckBox")
24
25// Kind implements Node.Kind.
26func (n *TaskCheckBox) Kind() gast.NodeKind {
27 return KindTaskCheckBox
28}
29
30// NewTaskCheckBox returns a new TaskCheckBox node.
31func NewTaskCheckBox(checked bool) *TaskCheckBox {
32 return &TaskCheckBox{
33 IsChecked: checked,
34 }
35}
diff --git a/vendor/github.com/yuin/goldmark/extension/cjk.go b/vendor/github.com/yuin/goldmark/extension/cjk.go
new file mode 100644
index 0000000..cb6f955
--- /dev/null
+++ b/vendor/github.com/yuin/goldmark/extension/cjk.go
@@ -0,0 +1,51 @@
1package extension
2
3import (
4 "github.com/yuin/goldmark"
5 "github.com/yuin/goldmark/parser"
6 "github.com/yuin/goldmark/renderer/html"
7)
8
9// A CJKOption sets options for CJK support mostly for HTML based renderers.
10type CJKOption func(*cjk)
11
12// WithEastAsianLineBreaks is a functional option that indicates whether softline breaks
13// between east asian wide characters should be ignored.
14func WithEastAsianLineBreaks() CJKOption {
15 return func(c *cjk) {
16 c.EastAsianLineBreaks = true
17 }
18}
19
20// WithEscapedSpace is a functional option that indicates that a '\' escaped half-space(0x20) should not be rendered.
21func WithEscapedSpace() CJKOption {
22 return func(c *cjk) {
23 c.EscapedSpace = true
24 }
25}
26
27type cjk struct {
28 EastAsianLineBreaks bool
29 EscapedSpace bool
30}
31
32var CJK = NewCJK(WithEastAsianLineBreaks(), WithEscapedSpace())
33
34// NewCJK returns a new extension with given options.
35func NewCJK(opts ...CJKOption) goldmark.Extender {
36 e := &cjk{}
37 for _, opt := range opts {
38 opt(e)
39 }
40 return e
41}
42
43func (e *cjk) Extend(m goldmark.Markdown) {
44 if e.EastAsianLineBreaks {
45 m.Renderer().AddOptions(html.WithEastAsianLineBreaks())
46 }
47 if e.EscapedSpace {
48 m.Renderer().AddOptions(html.WithWriter(html.NewWriter(html.WithEscapedSpace())))
49 m.Parser().AddOptions(parser.WithEscapedSpace())
50 }
51}
diff --git a/vendor/github.com/yuin/goldmark/extension/definition_list.go b/vendor/github.com/yuin/goldmark/extension/definition_list.go
new file mode 100644
index 0000000..d2f5fec
--- /dev/null
+++ b/vendor/github.com/yuin/goldmark/extension/definition_list.go
@@ -0,0 +1,270 @@
1package extension
2
3import (
4 "github.com/yuin/goldmark"
5 gast "github.com/yuin/goldmark/ast"
6 "github.com/yuin/goldmark/extension/ast"
7 "github.com/yuin/goldmark/parser"
8 "github.com/yuin/goldmark/renderer"
9 "github.com/yuin/goldmark/renderer/html"
10 "github.com/yuin/goldmark/text"
11 "github.com/yuin/goldmark/util"
12)
13
14type definitionListParser struct {
15}
16
17var defaultDefinitionListParser = &definitionListParser{}
18
19// NewDefinitionListParser return a new parser.BlockParser that
20// can parse PHP Markdown Extra Definition lists.
21func NewDefinitionListParser() parser.BlockParser {
22 return defaultDefinitionListParser
23}
24
25func (b *definitionListParser) Trigger() []byte {
26 return []byte{':'}
27}
28
29func (b *definitionListParser) Open(parent gast.Node, reader text.Reader, pc parser.Context) (gast.Node, parser.State) {
30 if _, ok := parent.(*ast.DefinitionList); ok {
31 return nil, parser.NoChildren
32 }
33 line, _ := reader.PeekLine()
34 pos := pc.BlockOffset()
35 indent := pc.BlockIndent()
36 if pos < 0 || line[pos] != ':' || indent != 0 {
37 return nil, parser.NoChildren
38 }
39
40 last := parent.LastChild()
41 // need 1 or more spaces after ':'
42 w, _ := util.IndentWidth(line[pos+1:], pos+1)
43 if w < 1 {
44 return nil, parser.NoChildren
45 }
46 if w >= 8 { // starts with indented code
47 w = 5
48 }
49 w += pos + 1 /* 1 = ':' */
50
51 para, lastIsParagraph := last.(*gast.Paragraph)
52 var list *ast.DefinitionList
53 status := parser.HasChildren
54 var ok bool
55 if lastIsParagraph {
56 list, ok = last.PreviousSibling().(*ast.DefinitionList)
57 if ok { // is not first item
58 list.Offset = w
59 list.TemporaryParagraph = para
60 } else { // is first item
61 list = ast.NewDefinitionList(w, para)
62 status |= parser.RequireParagraph
63 }
64 } else if list, ok = last.(*ast.DefinitionList); ok { // multiple description
65 list.Offset = w
66 list.TemporaryParagraph = nil
67 } else {
68 return nil, parser.NoChildren
69 }
70
71 return list, status
72}
73
74func (b *definitionListParser) Continue(node gast.Node, reader text.Reader, pc parser.Context) parser.State {
75 line, _ := reader.PeekLine()
76 if util.IsBlank(line) {
77 return parser.Continue | parser.HasChildren
78 }
79 list, _ := node.(*ast.DefinitionList)
80 w, _ := util.IndentWidth(line, reader.LineOffset())
81 if w < list.Offset {
82 return parser.Close
83 }
84 pos, padding := util.IndentPosition(line, reader.LineOffset(), list.Offset)
85 reader.AdvanceAndSetPadding(pos, padding)
86 return parser.Continue | parser.HasChildren
87}
88
89func (b *definitionListParser) Close(node gast.Node, reader text.Reader, pc parser.Context) {
90 // nothing to do
91}
92
93func (b *definitionListParser) CanInterruptParagraph() bool {
94 return true
95}
96
97func (b *definitionListParser) CanAcceptIndentedLine() bool {
98 return false
99}
100
101type definitionDescriptionParser struct {
102}
103
104var defaultDefinitionDescriptionParser = &definitionDescriptionParser{}
105
106// NewDefinitionDescriptionParser return a new parser.BlockParser that
107// can parse definition description starts with ':'.
108func NewDefinitionDescriptionParser() parser.BlockParser {
109 return defaultDefinitionDescriptionParser
110}
111
112func (b *definitionDescriptionParser) Trigger() []byte {
113 return []byte{':'}
114}
115
116func (b *definitionDescriptionParser) Open(parent gast.Node, reader text.Reader, pc parser.Context) (gast.Node, parser.State) {
117 line, _ := reader.PeekLine()
118 pos := pc.BlockOffset()
119 indent := pc.BlockIndent()
120 if pos < 0 || line[pos] != ':' || indent != 0 {
121 return nil, parser.NoChildren
122 }
123 list, _ := parent.(*ast.DefinitionList)
124 if list == nil {
125 return nil, parser.NoChildren
126 }
127 para := list.TemporaryParagraph
128 list.TemporaryParagraph = nil
129 if para != nil {
130 lines := para.Lines()
131 l := lines.Len()
132 for i := 0; i < l; i++ {
133 term := ast.NewDefinitionTerm()
134 segment := lines.At(i)
135 term.Lines().Append(segment.TrimRightSpace(reader.Source()))
136 list.AppendChild(list, term)
137 }
138 para.Parent().RemoveChild(para.Parent(), para)
139 }
140 cpos, padding := util.IndentPosition(line[pos+1:], pos+1, list.Offset-pos-1)
141 reader.AdvanceAndSetPadding(cpos+1, padding)
142
143 return ast.NewDefinitionDescription(), parser.HasChildren
144}
145
146func (b *definitionDescriptionParser) Continue(node gast.Node, reader text.Reader, pc parser.Context) parser.State {
147 // definitionListParser detects end of the description.
148 // so this method will never be called.
149 return parser.Continue | parser.HasChildren
150}
151
152func (b *definitionDescriptionParser) Close(node gast.Node, reader text.Reader, pc parser.Context) {
153 desc := node.(*ast.DefinitionDescription)
154 desc.IsTight = !desc.HasBlankPreviousLines()
155 if desc.IsTight {
156 for gc := desc.FirstChild(); gc != nil; gc = gc.NextSibling() {
157 paragraph, ok := gc.(*gast.Paragraph)
158 if ok {
159 textBlock := gast.NewTextBlock()
160 textBlock.SetLines(paragraph.Lines())
161 desc.ReplaceChild(desc, paragraph, textBlock)
162 }
163 }
164 }
165}
166
167func (b *definitionDescriptionParser) CanInterruptParagraph() bool {
168 return true
169}
170
171func (b *definitionDescriptionParser) CanAcceptIndentedLine() bool {
172 return false
173}
174
175// DefinitionListHTMLRenderer is a renderer.NodeRenderer implementation that
176// renders DefinitionList nodes.
177type DefinitionListHTMLRenderer struct {
178 html.Config
179}
180
181// NewDefinitionListHTMLRenderer returns a new DefinitionListHTMLRenderer.
182func NewDefinitionListHTMLRenderer(opts ...html.Option) renderer.NodeRenderer {
183 r := &DefinitionListHTMLRenderer{
184 Config: html.NewConfig(),
185 }
186 for _, opt := range opts {
187 opt.SetHTMLOption(&r.Config)
188 }
189 return r
190}
191
192// RegisterFuncs implements renderer.NodeRenderer.RegisterFuncs.
193func (r *DefinitionListHTMLRenderer) RegisterFuncs(reg renderer.NodeRendererFuncRegisterer) {
194 reg.Register(ast.KindDefinitionList, r.renderDefinitionList)
195 reg.Register(ast.KindDefinitionTerm, r.renderDefinitionTerm)
196 reg.Register(ast.KindDefinitionDescription, r.renderDefinitionDescription)
197}
198
199// DefinitionListAttributeFilter defines attribute names which dl elements can have.
200var DefinitionListAttributeFilter = html.GlobalAttributeFilter
201
202func (r *DefinitionListHTMLRenderer) renderDefinitionList(w util.BufWriter, source []byte, n gast.Node, entering bool) (gast.WalkStatus, error) {
203 if entering {
204 if n.Attributes() != nil {
205 _, _ = w.WriteString("<dl")
206 html.RenderAttributes(w, n, DefinitionListAttributeFilter)
207 _, _ = w.WriteString(">\n")
208 } else {
209 _, _ = w.WriteString("<dl>\n")
210 }
211 } else {
212 _, _ = w.WriteString("</dl>\n")
213 }
214 return gast.WalkContinue, nil
215}
216
217// DefinitionTermAttributeFilter defines attribute names which dd elements can have.
218var DefinitionTermAttributeFilter = html.GlobalAttributeFilter
219
220func (r *DefinitionListHTMLRenderer) renderDefinitionTerm(w util.BufWriter, source []byte, n gast.Node, entering bool) (gast.WalkStatus, error) {
221 if entering {
222 if n.Attributes() != nil {
223 _, _ = w.WriteString("<dt")
224 html.RenderAttributes(w, n, DefinitionTermAttributeFilter)
225 _ = w.WriteByte('>')
226 } else {
227 _, _ = w.WriteString("<dt>")
228 }
229 } else {
230 _, _ = w.WriteString("</dt>\n")
231 }
232 return gast.WalkContinue, nil
233}
234
235// DefinitionDescriptionAttributeFilter defines attribute names which dd elements can have.
236var DefinitionDescriptionAttributeFilter = html.GlobalAttributeFilter
237
238func (r *DefinitionListHTMLRenderer) renderDefinitionDescription(w util.BufWriter, source []byte, node gast.Node, entering bool) (gast.WalkStatus, error) {
239 if entering {
240 n := node.(*ast.DefinitionDescription)
241 _, _ = w.WriteString("<dd")
242 if n.Attributes() != nil {
243 html.RenderAttributes(w, n, DefinitionDescriptionAttributeFilter)
244 }
245 if n.IsTight {
246 _, _ = w.WriteString(">")
247 } else {
248 _, _ = w.WriteString(">\n")
249 }
250 } else {
251 _, _ = w.WriteString("</dd>\n")
252 }
253 return gast.WalkContinue, nil
254}
255
256type definitionList struct {
257}
258
259// DefinitionList is an extension that allow you to use PHP Markdown Extra Definition lists.
260var DefinitionList = &definitionList{}
261
262func (e *definitionList) Extend(m goldmark.Markdown) {
263 m.Parser().AddOptions(parser.WithBlockParsers(
264 util.Prioritized(NewDefinitionListParser(), 101),
265 util.Prioritized(NewDefinitionDescriptionParser(), 102),
266 ))
267 m.Renderer().AddOptions(renderer.WithNodeRenderers(
268 util.Prioritized(NewDefinitionListHTMLRenderer(), 500),
269 ))
270}
diff --git a/vendor/github.com/yuin/goldmark/extension/footnote.go b/vendor/github.com/yuin/goldmark/extension/footnote.go
new file mode 100644
index 0000000..09b6fa8
--- /dev/null
+++ b/vendor/github.com/yuin/goldmark/extension/footnote.go
@@ -0,0 +1,687 @@
1package extension
2
3import (
4 "bytes"
5 "fmt"
6 "strconv"
7
8 "github.com/yuin/goldmark"
9 gast "github.com/yuin/goldmark/ast"
10 "github.com/yuin/goldmark/extension/ast"
11 "github.com/yuin/goldmark/parser"
12 "github.com/yuin/goldmark/renderer"
13 "github.com/yuin/goldmark/renderer/html"
14 "github.com/yuin/goldmark/text"
15 "github.com/yuin/goldmark/util"
16)
17
18var footnoteListKey = parser.NewContextKey()
19var footnoteLinkListKey = parser.NewContextKey()
20
21type footnoteBlockParser struct {
22}
23
24var defaultFootnoteBlockParser = &footnoteBlockParser{}
25
26// NewFootnoteBlockParser returns a new parser.BlockParser that can parse
27// footnotes of the Markdown(PHP Markdown Extra) text.
28func NewFootnoteBlockParser() parser.BlockParser {
29 return defaultFootnoteBlockParser
30}
31
32func (b *footnoteBlockParser) Trigger() []byte {
33 return []byte{'['}
34}
35
36func (b *footnoteBlockParser) Open(parent gast.Node, reader text.Reader, pc parser.Context) (gast.Node, parser.State) {
37 line, segment := reader.PeekLine()
38 pos := pc.BlockOffset()
39 if pos < 0 || line[pos] != '[' {
40 return nil, parser.NoChildren
41 }
42 pos++
43 if pos > len(line)-1 || line[pos] != '^' {
44 return nil, parser.NoChildren
45 }
46 open := pos + 1
47 closes := 0
48 closure := util.FindClosure(line[pos+1:], '[', ']', false, false)
49 closes = pos + 1 + closure
50 next := closes + 1
51 if closure > -1 {
52 if next >= len(line) || line[next] != ':' {
53 return nil, parser.NoChildren
54 }
55 } else {
56 return nil, parser.NoChildren
57 }
58 padding := segment.Padding
59 label := reader.Value(text.NewSegment(segment.Start+open-padding, segment.Start+closes-padding))
60 if util.IsBlank(label) {
61 return nil, parser.NoChildren
62 }
63 item := ast.NewFootnote(label)
64
65 pos = next + 1 - padding
66 if pos >= len(line) {
67 reader.Advance(pos)
68 return item, parser.NoChildren
69 }
70 reader.AdvanceAndSetPadding(pos, padding)
71 return item, parser.HasChildren
72}
73
74func (b *footnoteBlockParser) Continue(node gast.Node, reader text.Reader, pc parser.Context) parser.State {
75 line, _ := reader.PeekLine()
76 if util.IsBlank(line) {
77 return parser.Continue | parser.HasChildren
78 }
79 childpos, padding := util.IndentPosition(line, reader.LineOffset(), 4)
80 if childpos < 0 {
81 return parser.Close
82 }
83 reader.AdvanceAndSetPadding(childpos, padding)
84 return parser.Continue | parser.HasChildren
85}
86
87func (b *footnoteBlockParser) Close(node gast.Node, reader text.Reader, pc parser.Context) {
88 var list *ast.FootnoteList
89 if tlist := pc.Get(footnoteListKey); tlist != nil {
90 list = tlist.(*ast.FootnoteList)
91 } else {
92 list = ast.NewFootnoteList()
93 pc.Set(footnoteListKey, list)
94 node.Parent().InsertBefore(node.Parent(), node, list)
95 }
96 node.Parent().RemoveChild(node.Parent(), node)
97 list.AppendChild(list, node)
98}
99
100func (b *footnoteBlockParser) CanInterruptParagraph() bool {
101 return true
102}
103
104func (b *footnoteBlockParser) CanAcceptIndentedLine() bool {
105 return false
106}
107
108type footnoteParser struct {
109}
110
111var defaultFootnoteParser = &footnoteParser{}
112
113// NewFootnoteParser returns a new parser.InlineParser that can parse
114// footnote links of the Markdown(PHP Markdown Extra) text.
115func NewFootnoteParser() parser.InlineParser {
116 return defaultFootnoteParser
117}
118
119func (s *footnoteParser) Trigger() []byte {
120 // footnote syntax probably conflict with the image syntax.
121 // So we need trigger this parser with '!'.
122 return []byte{'!', '['}
123}
124
125func (s *footnoteParser) Parse(parent gast.Node, block text.Reader, pc parser.Context) gast.Node {
126 line, segment := block.PeekLine()
127 pos := 1
128 if len(line) > 0 && line[0] == '!' {
129 pos++
130 }
131 if pos >= len(line) || line[pos] != '^' {
132 return nil
133 }
134 pos++
135 if pos >= len(line) {
136 return nil
137 }
138 open := pos
139 closure := util.FindClosure(line[pos:], '[', ']', false, false)
140 if closure < 0 {
141 return nil
142 }
143 closes := pos + closure
144 value := block.Value(text.NewSegment(segment.Start+open, segment.Start+closes))
145 block.Advance(closes + 1)
146
147 var list *ast.FootnoteList
148 if tlist := pc.Get(footnoteListKey); tlist != nil {
149 list = tlist.(*ast.FootnoteList)
150 }
151 if list == nil {
152 return nil
153 }
154 index := 0
155 for def := list.FirstChild(); def != nil; def = def.NextSibling() {
156 d := def.(*ast.Footnote)
157 if bytes.Equal(d.Ref, value) {
158 if d.Index < 0 {
159 list.Count += 1
160 d.Index = list.Count
161 }
162 index = d.Index
163 break
164 }
165 }
166 if index == 0 {
167 return nil
168 }
169
170 fnlink := ast.NewFootnoteLink(index)
171 var fnlist []*ast.FootnoteLink
172 if tmp := pc.Get(footnoteLinkListKey); tmp != nil {
173 fnlist = tmp.([]*ast.FootnoteLink)
174 } else {
175 fnlist = []*ast.FootnoteLink{}
176 pc.Set(footnoteLinkListKey, fnlist)
177 }
178 pc.Set(footnoteLinkListKey, append(fnlist, fnlink))
179 if line[0] == '!' {
180 parent.AppendChild(parent, gast.NewTextSegment(text.NewSegment(segment.Start, segment.Start+1)))
181 }
182
183 return fnlink
184}
185
186type footnoteASTTransformer struct {
187}
188
189var defaultFootnoteASTTransformer = &footnoteASTTransformer{}
190
191// NewFootnoteASTTransformer returns a new parser.ASTTransformer that
192// insert a footnote list to the last of the document.
193func NewFootnoteASTTransformer() parser.ASTTransformer {
194 return defaultFootnoteASTTransformer
195}
196
197func (a *footnoteASTTransformer) Transform(node *gast.Document, reader text.Reader, pc parser.Context) {
198 var list *ast.FootnoteList
199 var fnlist []*ast.FootnoteLink
200 if tmp := pc.Get(footnoteListKey); tmp != nil {
201 list = tmp.(*ast.FootnoteList)
202 }
203 if tmp := pc.Get(footnoteLinkListKey); tmp != nil {
204 fnlist = tmp.([]*ast.FootnoteLink)
205 }
206
207 pc.Set(footnoteListKey, nil)
208 pc.Set(footnoteLinkListKey, nil)
209
210 if list == nil {
211 return
212 }
213
214 counter := map[int]int{}
215 if fnlist != nil {
216 for _, fnlink := range fnlist {
217 if fnlink.Index >= 0 {
218 counter[fnlink.Index]++
219 }
220 }
221 refCounter := map[int]int{}
222 for _, fnlink := range fnlist {
223 fnlink.RefCount = counter[fnlink.Index]
224 if _, ok := refCounter[fnlink.Index]; !ok {
225 refCounter[fnlink.Index] = 0
226 }
227 fnlink.RefIndex = refCounter[fnlink.Index]
228 refCounter[fnlink.Index]++
229 }
230 }
231 for footnote := list.FirstChild(); footnote != nil; {
232 var container gast.Node = footnote
233 next := footnote.NextSibling()
234 if fc := container.LastChild(); fc != nil && gast.IsParagraph(fc) {
235 container = fc
236 }
237 fn := footnote.(*ast.Footnote)
238 index := fn.Index
239 if index < 0 {
240 list.RemoveChild(list, footnote)
241 } else {
242 refCount := counter[index]
243 backLink := ast.NewFootnoteBacklink(index)
244 backLink.RefCount = refCount
245 backLink.RefIndex = 0
246 container.AppendChild(container, backLink)
247 if refCount > 1 {
248 for i := 1; i < refCount; i++ {
249 backLink := ast.NewFootnoteBacklink(index)
250 backLink.RefCount = refCount
251 backLink.RefIndex = i
252 container.AppendChild(container, backLink)
253 }
254 }
255 }
256 footnote = next
257 }
258 list.SortChildren(func(n1, n2 gast.Node) int {
259 if n1.(*ast.Footnote).Index < n2.(*ast.Footnote).Index {
260 return -1
261 }
262 return 1
263 })
264 if list.Count <= 0 {
265 list.Parent().RemoveChild(list.Parent(), list)
266 return
267 }
268
269 node.AppendChild(node, list)
270}
271
272// FootnoteConfig holds configuration values for the footnote extension.
273//
274// Link* and Backlink* configurations have some variables:
275// Occurrances of “^^” in the string will be replaced by the
276// corresponding footnote number in the HTML output.
277// Occurrances of “%%” will be replaced by a number for the
278// reference (footnotes can have multiple references).
279type FootnoteConfig struct {
280 html.Config
281
282 // IDPrefix is a prefix for the id attributes generated by footnotes.
283 IDPrefix []byte
284
285 // IDPrefix is a function that determines the id attribute for given Node.
286 IDPrefixFunction func(gast.Node) []byte
287
288 // LinkTitle is an optional title attribute for footnote links.
289 LinkTitle []byte
290
291 // BacklinkTitle is an optional title attribute for footnote backlinks.
292 BacklinkTitle []byte
293
294 // LinkClass is a class for footnote links.
295 LinkClass []byte
296
297 // BacklinkClass is a class for footnote backlinks.
298 BacklinkClass []byte
299
300 // BacklinkHTML is an HTML content for footnote backlinks.
301 BacklinkHTML []byte
302}
303
304// FootnoteOption interface is a functional option interface for the extension.
305type FootnoteOption interface {
306 renderer.Option
307 // SetFootnoteOption sets given option to the extension.
308 SetFootnoteOption(*FootnoteConfig)
309}
310
311// NewFootnoteConfig returns a new Config with defaults.
312func NewFootnoteConfig() FootnoteConfig {
313 return FootnoteConfig{
314 Config: html.NewConfig(),
315 LinkTitle: []byte(""),
316 BacklinkTitle: []byte(""),
317 LinkClass: []byte("footnote-ref"),
318 BacklinkClass: []byte("footnote-backref"),
319 BacklinkHTML: []byte("&#x21a9;&#xfe0e;"),
320 }
321}
322
323// SetOption implements renderer.SetOptioner.
324func (c *FootnoteConfig) SetOption(name renderer.OptionName, value interface{}) {
325 switch name {
326 case optFootnoteIDPrefixFunction:
327 c.IDPrefixFunction = value.(func(gast.Node) []byte)
328 case optFootnoteIDPrefix:
329 c.IDPrefix = value.([]byte)
330 case optFootnoteLinkTitle:
331 c.LinkTitle = value.([]byte)
332 case optFootnoteBacklinkTitle:
333 c.BacklinkTitle = value.([]byte)
334 case optFootnoteLinkClass:
335 c.LinkClass = value.([]byte)
336 case optFootnoteBacklinkClass:
337 c.BacklinkClass = value.([]byte)
338 case optFootnoteBacklinkHTML:
339 c.BacklinkHTML = value.([]byte)
340 default:
341 c.Config.SetOption(name, value)
342 }
343}
344
345type withFootnoteHTMLOptions struct {
346 value []html.Option
347}
348
349func (o *withFootnoteHTMLOptions) SetConfig(c *renderer.Config) {
350 if o.value != nil {
351 for _, v := range o.value {
352 v.(renderer.Option).SetConfig(c)
353 }
354 }
355}
356
357func (o *withFootnoteHTMLOptions) SetFootnoteOption(c *FootnoteConfig) {
358 if o.value != nil {
359 for _, v := range o.value {
360 v.SetHTMLOption(&c.Config)
361 }
362 }
363}
364
365// WithFootnoteHTMLOptions is functional option that wraps goldmark HTMLRenderer options.
366func WithFootnoteHTMLOptions(opts ...html.Option) FootnoteOption {
367 return &withFootnoteHTMLOptions{opts}
368}
369
370const optFootnoteIDPrefix renderer.OptionName = "FootnoteIDPrefix"
371
372type withFootnoteIDPrefix struct {
373 value []byte
374}
375
376func (o *withFootnoteIDPrefix) SetConfig(c *renderer.Config) {
377 c.Options[optFootnoteIDPrefix] = o.value
378}
379
380func (o *withFootnoteIDPrefix) SetFootnoteOption(c *FootnoteConfig) {
381 c.IDPrefix = o.value
382}
383
384// WithFootnoteIDPrefix is a functional option that is a prefix for the id attributes generated by footnotes.
385func WithFootnoteIDPrefix(a []byte) FootnoteOption {
386 return &withFootnoteIDPrefix{a}
387}
388
389const optFootnoteIDPrefixFunction renderer.OptionName = "FootnoteIDPrefixFunction"
390
391type withFootnoteIDPrefixFunction struct {
392 value func(gast.Node) []byte
393}
394
395func (o *withFootnoteIDPrefixFunction) SetConfig(c *renderer.Config) {
396 c.Options[optFootnoteIDPrefixFunction] = o.value
397}
398
399func (o *withFootnoteIDPrefixFunction) SetFootnoteOption(c *FootnoteConfig) {
400 c.IDPrefixFunction = o.value
401}
402
403// WithFootnoteIDPrefixFunction is a functional option that is a prefix for the id attributes generated by footnotes.
404func WithFootnoteIDPrefixFunction(a func(gast.Node) []byte) FootnoteOption {
405 return &withFootnoteIDPrefixFunction{a}
406}
407
408const optFootnoteLinkTitle renderer.OptionName = "FootnoteLinkTitle"
409
410type withFootnoteLinkTitle struct {
411 value []byte
412}
413
414func (o *withFootnoteLinkTitle) SetConfig(c *renderer.Config) {
415 c.Options[optFootnoteLinkTitle] = o.value
416}
417
418func (o *withFootnoteLinkTitle) SetFootnoteOption(c *FootnoteConfig) {
419 c.LinkTitle = o.value
420}
421
422// WithFootnoteLinkTitle is a functional option that is an optional title attribute for footnote links.
423func WithFootnoteLinkTitle(a []byte) FootnoteOption {
424 return &withFootnoteLinkTitle{a}
425}
426
427const optFootnoteBacklinkTitle renderer.OptionName = "FootnoteBacklinkTitle"
428
429type withFootnoteBacklinkTitle struct {
430 value []byte
431}
432
433func (o *withFootnoteBacklinkTitle) SetConfig(c *renderer.Config) {
434 c.Options[optFootnoteBacklinkTitle] = o.value
435}
436
437func (o *withFootnoteBacklinkTitle) SetFootnoteOption(c *FootnoteConfig) {
438 c.BacklinkTitle = o.value
439}
440
441// WithFootnoteBacklinkTitle is a functional option that is an optional title attribute for footnote backlinks.
442func WithFootnoteBacklinkTitle(a []byte) FootnoteOption {
443 return &withFootnoteBacklinkTitle{a}
444}
445
446const optFootnoteLinkClass renderer.OptionName = "FootnoteLinkClass"
447
448type withFootnoteLinkClass struct {
449 value []byte
450}
451
452func (o *withFootnoteLinkClass) SetConfig(c *renderer.Config) {
453 c.Options[optFootnoteLinkClass] = o.value
454}
455
456func (o *withFootnoteLinkClass) SetFootnoteOption(c *FootnoteConfig) {
457 c.LinkClass = o.value
458}
459
460// WithFootnoteLinkClass is a functional option that is a class for footnote links.
461func WithFootnoteLinkClass(a []byte) FootnoteOption {
462 return &withFootnoteLinkClass{a}
463}
464
465const optFootnoteBacklinkClass renderer.OptionName = "FootnoteBacklinkClass"
466
467type withFootnoteBacklinkClass struct {
468 value []byte
469}
470
471func (o *withFootnoteBacklinkClass) SetConfig(c *renderer.Config) {
472 c.Options[optFootnoteBacklinkClass] = o.value
473}
474
475func (o *withFootnoteBacklinkClass) SetFootnoteOption(c *FootnoteConfig) {
476 c.BacklinkClass = o.value
477}
478
479// WithFootnoteBacklinkClass is a functional option that is a class for footnote backlinks.
480func WithFootnoteBacklinkClass(a []byte) FootnoteOption {
481 return &withFootnoteBacklinkClass{a}
482}
483
484const optFootnoteBacklinkHTML renderer.OptionName = "FootnoteBacklinkHTML"
485
486type withFootnoteBacklinkHTML struct {
487 value []byte
488}
489
490func (o *withFootnoteBacklinkHTML) SetConfig(c *renderer.Config) {
491 c.Options[optFootnoteBacklinkHTML] = o.value
492}
493
494func (o *withFootnoteBacklinkHTML) SetFootnoteOption(c *FootnoteConfig) {
495 c.BacklinkHTML = o.value
496}
497
498// WithFootnoteBacklinkHTML is an HTML content for footnote backlinks.
499func WithFootnoteBacklinkHTML(a []byte) FootnoteOption {
500 return &withFootnoteBacklinkHTML{a}
501}
502
503// FootnoteHTMLRenderer is a renderer.NodeRenderer implementation that
504// renders FootnoteLink nodes.
505type FootnoteHTMLRenderer struct {
506 FootnoteConfig
507}
508
509// NewFootnoteHTMLRenderer returns a new FootnoteHTMLRenderer.
510func NewFootnoteHTMLRenderer(opts ...FootnoteOption) renderer.NodeRenderer {
511 r := &FootnoteHTMLRenderer{
512 FootnoteConfig: NewFootnoteConfig(),
513 }
514 for _, opt := range opts {
515 opt.SetFootnoteOption(&r.FootnoteConfig)
516 }
517 return r
518}
519
520// RegisterFuncs implements renderer.NodeRenderer.RegisterFuncs.
521func (r *FootnoteHTMLRenderer) RegisterFuncs(reg renderer.NodeRendererFuncRegisterer) {
522 reg.Register(ast.KindFootnoteLink, r.renderFootnoteLink)
523 reg.Register(ast.KindFootnoteBacklink, r.renderFootnoteBacklink)
524 reg.Register(ast.KindFootnote, r.renderFootnote)
525 reg.Register(ast.KindFootnoteList, r.renderFootnoteList)
526}
527
528func (r *FootnoteHTMLRenderer) renderFootnoteLink(w util.BufWriter, source []byte, node gast.Node, entering bool) (gast.WalkStatus, error) {
529 if entering {
530 n := node.(*ast.FootnoteLink)
531 is := strconv.Itoa(n.Index)
532 _, _ = w.WriteString(`<sup id="`)
533 _, _ = w.Write(r.idPrefix(node))
534 _, _ = w.WriteString(`fnref`)
535 if n.RefIndex > 0 {
536 _, _ = w.WriteString(fmt.Sprintf("%v", n.RefIndex))
537 }
538 _ = w.WriteByte(':')
539 _, _ = w.WriteString(is)
540 _, _ = w.WriteString(`"><a href="#`)
541 _, _ = w.Write(r.idPrefix(node))
542 _, _ = w.WriteString(`fn:`)
543 _, _ = w.WriteString(is)
544 _, _ = w.WriteString(`" class="`)
545 _, _ = w.Write(applyFootnoteTemplate(r.FootnoteConfig.LinkClass,
546 n.Index, n.RefCount))
547 if len(r.FootnoteConfig.LinkTitle) > 0 {
548 _, _ = w.WriteString(`" title="`)
549 _, _ = w.Write(util.EscapeHTML(applyFootnoteTemplate(r.FootnoteConfig.LinkTitle, n.Index, n.RefCount)))
550 }
551 _, _ = w.WriteString(`" role="doc-noteref">`)
552
553 _, _ = w.WriteString(is)
554 _, _ = w.WriteString(`</a></sup>`)
555 }
556 return gast.WalkContinue, nil
557}
558
559func (r *FootnoteHTMLRenderer) renderFootnoteBacklink(w util.BufWriter, source []byte, node gast.Node, entering bool) (gast.WalkStatus, error) {
560 if entering {
561 n := node.(*ast.FootnoteBacklink)
562 is := strconv.Itoa(n.Index)
563 _, _ = w.WriteString(`&#160;<a href="#`)
564 _, _ = w.Write(r.idPrefix(node))
565 _, _ = w.WriteString(`fnref`)
566 if n.RefIndex > 0 {
567 _, _ = w.WriteString(fmt.Sprintf("%v", n.RefIndex))
568 }
569 _ = w.WriteByte(':')
570 _, _ = w.WriteString(is)
571 _, _ = w.WriteString(`" class="`)
572 _, _ = w.Write(applyFootnoteTemplate(r.FootnoteConfig.BacklinkClass, n.Index, n.RefCount))
573 if len(r.FootnoteConfig.BacklinkTitle) > 0 {
574 _, _ = w.WriteString(`" title="`)
575 _, _ = w.Write(util.EscapeHTML(applyFootnoteTemplate(r.FootnoteConfig.BacklinkTitle, n.Index, n.RefCount)))
576 }
577 _, _ = w.WriteString(`" role="doc-backlink">`)
578 _, _ = w.Write(applyFootnoteTemplate(r.FootnoteConfig.BacklinkHTML, n.Index, n.RefCount))
579 _, _ = w.WriteString(`</a>`)
580 }
581 return gast.WalkContinue, nil
582}
583
584func (r *FootnoteHTMLRenderer) renderFootnote(w util.BufWriter, source []byte, node gast.Node, entering bool) (gast.WalkStatus, error) {
585 n := node.(*ast.Footnote)
586 is := strconv.Itoa(n.Index)
587 if entering {
588 _, _ = w.WriteString(`<li id="`)
589 _, _ = w.Write(r.idPrefix(node))
590 _, _ = w.WriteString(`fn:`)
591 _, _ = w.WriteString(is)
592 _, _ = w.WriteString(`"`)
593 if node.Attributes() != nil {
594 html.RenderAttributes(w, node, html.ListItemAttributeFilter)
595 }
596 _, _ = w.WriteString(">\n")
597 } else {
598 _, _ = w.WriteString("</li>\n")
599 }
600 return gast.WalkContinue, nil
601}
602
603func (r *FootnoteHTMLRenderer) renderFootnoteList(w util.BufWriter, source []byte, node gast.Node, entering bool) (gast.WalkStatus, error) {
604 if entering {
605 _, _ = w.WriteString(`<div class="footnotes" role="doc-endnotes"`)
606 if node.Attributes() != nil {
607 html.RenderAttributes(w, node, html.GlobalAttributeFilter)
608 }
609 _ = w.WriteByte('>')
610 if r.Config.XHTML {
611 _, _ = w.WriteString("\n<hr />\n")
612 } else {
613 _, _ = w.WriteString("\n<hr>\n")
614 }
615 _, _ = w.WriteString("<ol>\n")
616 } else {
617 _, _ = w.WriteString("</ol>\n")
618 _, _ = w.WriteString("</div>\n")
619 }
620 return gast.WalkContinue, nil
621}
622
623func (r *FootnoteHTMLRenderer) idPrefix(node gast.Node) []byte {
624 if r.FootnoteConfig.IDPrefix != nil {
625 return r.FootnoteConfig.IDPrefix
626 }
627 if r.FootnoteConfig.IDPrefixFunction != nil {
628 return r.FootnoteConfig.IDPrefixFunction(node)
629 }
630 return []byte("")
631}
632
633func applyFootnoteTemplate(b []byte, index, refCount int) []byte {
634 fast := true
635 for i, c := range b {
636 if i != 0 {
637 if b[i-1] == '^' && c == '^' {
638 fast = false
639 break
640 }
641 if b[i-1] == '%' && c == '%' {
642 fast = false
643 break
644 }
645 }
646 }
647 if fast {
648 return b
649 }
650 is := []byte(strconv.Itoa(index))
651 rs := []byte(strconv.Itoa(refCount))
652 ret := bytes.Replace(b, []byte("^^"), is, -1)
653 return bytes.Replace(ret, []byte("%%"), rs, -1)
654}
655
656type footnote struct {
657 options []FootnoteOption
658}
659
660// Footnote is an extension that allow you to use PHP Markdown Extra Footnotes.
661var Footnote = &footnote{
662 options: []FootnoteOption{},
663}
664
665// NewFootnote returns a new extension with given options.
666func NewFootnote(opts ...FootnoteOption) goldmark.Extender {
667 return &footnote{
668 options: opts,
669 }
670}
671
672func (e *footnote) Extend(m goldmark.Markdown) {
673 m.Parser().AddOptions(
674 parser.WithBlockParsers(
675 util.Prioritized(NewFootnoteBlockParser(), 999),
676 ),
677 parser.WithInlineParsers(
678 util.Prioritized(NewFootnoteParser(), 101),
679 ),
680 parser.WithASTTransformers(
681 util.Prioritized(NewFootnoteASTTransformer(), 999),
682 ),
683 )
684 m.Renderer().AddOptions(renderer.WithNodeRenderers(
685 util.Prioritized(NewFootnoteHTMLRenderer(e.options...), 500),
686 ))
687}
diff --git a/vendor/github.com/yuin/goldmark/extension/gfm.go b/vendor/github.com/yuin/goldmark/extension/gfm.go
new file mode 100644
index 0000000..a570fbd
--- /dev/null
+++ b/vendor/github.com/yuin/goldmark/extension/gfm.go
@@ -0,0 +1,18 @@
1package extension
2
3import (
4 "github.com/yuin/goldmark"
5)
6
7type gfm struct {
8}
9
10// GFM is an extension that provides Github Flavored markdown functionalities.
11var GFM = &gfm{}
12
13func (e *gfm) Extend(m goldmark.Markdown) {
14 Linkify.Extend(m)
15 Table.Extend(m)
16 Strikethrough.Extend(m)
17 TaskList.Extend(m)
18}
diff --git a/vendor/github.com/yuin/goldmark/extension/linkify.go b/vendor/github.com/yuin/goldmark/extension/linkify.go
new file mode 100644
index 0000000..2f046eb
--- /dev/null
+++ b/vendor/github.com/yuin/goldmark/extension/linkify.go
@@ -0,0 +1,318 @@
1package extension
2
3import (
4 "bytes"
5 "regexp"
6
7 "github.com/yuin/goldmark"
8 "github.com/yuin/goldmark/ast"
9 "github.com/yuin/goldmark/parser"
10 "github.com/yuin/goldmark/text"
11 "github.com/yuin/goldmark/util"
12)
13
14var wwwURLRegxp = regexp.MustCompile(`^www\.[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-z]+(?:[/#?][-a-zA-Z0-9@:%_\+.~#!?&/=\(\);,'">\^{}\[\]` + "`" + `]*)?`)
15
16var urlRegexp = regexp.MustCompile(`^(?:http|https|ftp)://[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-z]+(?::\d+)?(?:[/#?][-a-zA-Z0-9@:%_+.~#$!?&/=\(\);,'">\^{}\[\]` + "`" + `]*)?`)
17
18// An LinkifyConfig struct is a data structure that holds configuration of the
19// Linkify extension.
20type LinkifyConfig struct {
21 AllowedProtocols [][]byte
22 URLRegexp *regexp.Regexp
23 WWWRegexp *regexp.Regexp
24 EmailRegexp *regexp.Regexp
25}
26
27const (
28 optLinkifyAllowedProtocols parser.OptionName = "LinkifyAllowedProtocols"
29 optLinkifyURLRegexp parser.OptionName = "LinkifyURLRegexp"
30 optLinkifyWWWRegexp parser.OptionName = "LinkifyWWWRegexp"
31 optLinkifyEmailRegexp parser.OptionName = "LinkifyEmailRegexp"
32)
33
34// SetOption implements SetOptioner.
35func (c *LinkifyConfig) SetOption(name parser.OptionName, value interface{}) {
36 switch name {
37 case optLinkifyAllowedProtocols:
38 c.AllowedProtocols = value.([][]byte)
39 case optLinkifyURLRegexp:
40 c.URLRegexp = value.(*regexp.Regexp)
41 case optLinkifyWWWRegexp:
42 c.WWWRegexp = value.(*regexp.Regexp)
43 case optLinkifyEmailRegexp:
44 c.EmailRegexp = value.(*regexp.Regexp)
45 }
46}
47
48// A LinkifyOption interface sets options for the LinkifyOption.
49type LinkifyOption interface {
50 parser.Option
51 SetLinkifyOption(*LinkifyConfig)
52}
53
54type withLinkifyAllowedProtocols struct {
55 value [][]byte
56}
57
58func (o *withLinkifyAllowedProtocols) SetParserOption(c *parser.Config) {
59 c.Options[optLinkifyAllowedProtocols] = o.value
60}
61
62func (o *withLinkifyAllowedProtocols) SetLinkifyOption(p *LinkifyConfig) {
63 p.AllowedProtocols = o.value
64}
65
66// WithLinkifyAllowedProtocols is a functional option that specify allowed
67// protocols in autolinks. Each protocol must end with ':' like
68// 'http:' .
69func WithLinkifyAllowedProtocols(value [][]byte) LinkifyOption {
70 return &withLinkifyAllowedProtocols{
71 value: value,
72 }
73}
74
75type withLinkifyURLRegexp struct {
76 value *regexp.Regexp
77}
78
79func (o *withLinkifyURLRegexp) SetParserOption(c *parser.Config) {
80 c.Options[optLinkifyURLRegexp] = o.value
81}
82
83func (o *withLinkifyURLRegexp) SetLinkifyOption(p *LinkifyConfig) {
84 p.URLRegexp = o.value
85}
86
87// WithLinkifyURLRegexp is a functional option that specify
88// a pattern of the URL including a protocol.
89func WithLinkifyURLRegexp(value *regexp.Regexp) LinkifyOption {
90 return &withLinkifyURLRegexp{
91 value: value,
92 }
93}
94
95// WithLinkifyWWWRegexp is a functional option that specify
96// a pattern of the URL without a protocol.
97// This pattern must start with 'www.' .
98type withLinkifyWWWRegexp struct {
99 value *regexp.Regexp
100}
101
102func (o *withLinkifyWWWRegexp) SetParserOption(c *parser.Config) {
103 c.Options[optLinkifyWWWRegexp] = o.value
104}
105
106func (o *withLinkifyWWWRegexp) SetLinkifyOption(p *LinkifyConfig) {
107 p.WWWRegexp = o.value
108}
109
110func WithLinkifyWWWRegexp(value *regexp.Regexp) LinkifyOption {
111 return &withLinkifyWWWRegexp{
112 value: value,
113 }
114}
115
116// WithLinkifyWWWRegexp is a functional otpion that specify
117// a pattern of the email address.
118type withLinkifyEmailRegexp struct {
119 value *regexp.Regexp
120}
121
122func (o *withLinkifyEmailRegexp) SetParserOption(c *parser.Config) {
123 c.Options[optLinkifyEmailRegexp] = o.value
124}
125
126func (o *withLinkifyEmailRegexp) SetLinkifyOption(p *LinkifyConfig) {
127 p.EmailRegexp = o.value
128}
129
130func WithLinkifyEmailRegexp(value *regexp.Regexp) LinkifyOption {
131 return &withLinkifyEmailRegexp{
132 value: value,
133 }
134}
135
136type linkifyParser struct {
137 LinkifyConfig
138}
139
140// NewLinkifyParser return a new InlineParser can parse
141// text that seems like a URL.
142func NewLinkifyParser(opts ...LinkifyOption) parser.InlineParser {
143 p := &linkifyParser{
144 LinkifyConfig: LinkifyConfig{
145 AllowedProtocols: nil,
146 URLRegexp: urlRegexp,
147 WWWRegexp: wwwURLRegxp,
148 },
149 }
150 for _, o := range opts {
151 o.SetLinkifyOption(&p.LinkifyConfig)
152 }
153 return p
154}
155
156func (s *linkifyParser) Trigger() []byte {
157 // ' ' indicates any white spaces and a line head
158 return []byte{' ', '*', '_', '~', '('}
159}
160
161var (
162 protoHTTP = []byte("http:")
163 protoHTTPS = []byte("https:")
164 protoFTP = []byte("ftp:")
165 domainWWW = []byte("www.")
166)
167
168func (s *linkifyParser) Parse(parent ast.Node, block text.Reader, pc parser.Context) ast.Node {
169 if pc.IsInLinkLabel() {
170 return nil
171 }
172 line, segment := block.PeekLine()
173 consumes := 0
174 start := segment.Start
175 c := line[0]
176 // advance if current position is not a line head.
177 if c == ' ' || c == '*' || c == '_' || c == '~' || c == '(' {
178 consumes++
179 start++
180 line = line[1:]
181 }
182
183 var m []int
184 var protocol []byte
185 var typ ast.AutoLinkType = ast.AutoLinkURL
186 if s.LinkifyConfig.AllowedProtocols == nil {
187 if bytes.HasPrefix(line, protoHTTP) || bytes.HasPrefix(line, protoHTTPS) || bytes.HasPrefix(line, protoFTP) {
188 m = s.LinkifyConfig.URLRegexp.FindSubmatchIndex(line)
189 }
190 } else {
191 for _, prefix := range s.LinkifyConfig.AllowedProtocols {
192 if bytes.HasPrefix(line, prefix) {
193 m = s.LinkifyConfig.URLRegexp.FindSubmatchIndex(line)
194 break
195 }
196 }
197 }
198 if m == nil && bytes.HasPrefix(line, domainWWW) {
199 m = s.LinkifyConfig.WWWRegexp.FindSubmatchIndex(line)
200 protocol = []byte("http")
201 }
202 if m != nil && m[0] != 0 {
203 m = nil
204 }
205 if m != nil && m[0] == 0 {
206 lastChar := line[m[1]-1]
207 if lastChar == '.' {
208 m[1]--
209 } else if lastChar == ')' {
210 closing := 0
211 for i := m[1] - 1; i >= m[0]; i-- {
212 if line[i] == ')' {
213 closing++
214 } else if line[i] == '(' {
215 closing--
216 }
217 }
218 if closing > 0 {
219 m[1] -= closing
220 }
221 } else if lastChar == ';' {
222 i := m[1] - 2
223 for ; i >= m[0]; i-- {
224 if util.IsAlphaNumeric(line[i]) {
225 continue
226 }
227 break
228 }
229 if i != m[1]-2 {
230 if line[i] == '&' {
231 m[1] -= m[1] - i
232 }
233 }
234 }
235 }
236 if m == nil {
237 if len(line) > 0 && util.IsPunct(line[0]) {
238 return nil
239 }
240 typ = ast.AutoLinkEmail
241 stop := -1
242 if s.LinkifyConfig.EmailRegexp == nil {
243 stop = util.FindEmailIndex(line)
244 } else {
245 m := s.LinkifyConfig.EmailRegexp.FindSubmatchIndex(line)
246 if m != nil && m[0] == 0 {
247 stop = m[1]
248 }
249 }
250 if stop < 0 {
251 return nil
252 }
253 at := bytes.IndexByte(line, '@')
254 m = []int{0, stop, at, stop - 1}
255 if m == nil || bytes.IndexByte(line[m[2]:m[3]], '.') < 0 {
256 return nil
257 }
258 lastChar := line[m[1]-1]
259 if lastChar == '.' {
260 m[1]--
261 }
262 if m[1] < len(line) {
263 nextChar := line[m[1]]
264 if nextChar == '-' || nextChar == '_' {
265 return nil
266 }
267 }
268 }
269 if m == nil {
270 return nil
271 }
272 if consumes != 0 {
273 s := segment.WithStop(segment.Start + 1)
274 ast.MergeOrAppendTextSegment(parent, s)
275 }
276 i := m[1] - 1
277 for ; i > 0; i-- {
278 c := line[i]
279 switch c {
280 case '?', '!', '.', ',', ':', '*', '_', '~':
281 default:
282 goto endfor
283 }
284 }
285endfor:
286 i++
287 consumes += i
288 block.Advance(consumes)
289 n := ast.NewTextSegment(text.NewSegment(start, start+i))
290 link := ast.NewAutoLink(typ, n)
291 link.Protocol = protocol
292 return link
293}
294
295func (s *linkifyParser) CloseBlock(parent ast.Node, pc parser.Context) {
296 // nothing to do
297}
298
299type linkify struct {
300 options []LinkifyOption
301}
302
303// Linkify is an extension that allow you to parse text that seems like a URL.
304var Linkify = &linkify{}
305
306func NewLinkify(opts ...LinkifyOption) goldmark.Extender {
307 return &linkify{
308 options: opts,
309 }
310}
311
312func (e *linkify) Extend(m goldmark.Markdown) {
313 m.Parser().AddOptions(
314 parser.WithInlineParsers(
315 util.Prioritized(NewLinkifyParser(e.options...), 999),
316 ),
317 )
318}
diff --git a/vendor/github.com/yuin/goldmark/extension/strikethrough.go b/vendor/github.com/yuin/goldmark/extension/strikethrough.go
new file mode 100644
index 0000000..1b629ad
--- /dev/null
+++ b/vendor/github.com/yuin/goldmark/extension/strikethrough.go
@@ -0,0 +1,116 @@
1package extension
2
3import (
4 "github.com/yuin/goldmark"
5 gast "github.com/yuin/goldmark/ast"
6 "github.com/yuin/goldmark/extension/ast"
7 "github.com/yuin/goldmark/parser"
8 "github.com/yuin/goldmark/renderer"
9 "github.com/yuin/goldmark/renderer/html"
10 "github.com/yuin/goldmark/text"
11 "github.com/yuin/goldmark/util"
12)
13
14type strikethroughDelimiterProcessor struct {
15}
16
17func (p *strikethroughDelimiterProcessor) IsDelimiter(b byte) bool {
18 return b == '~'
19}
20
21func (p *strikethroughDelimiterProcessor) CanOpenCloser(opener, closer *parser.Delimiter) bool {
22 return opener.Char == closer.Char
23}
24
25func (p *strikethroughDelimiterProcessor) OnMatch(consumes int) gast.Node {
26 return ast.NewStrikethrough()
27}
28
29var defaultStrikethroughDelimiterProcessor = &strikethroughDelimiterProcessor{}
30
31type strikethroughParser struct {
32}
33
34var defaultStrikethroughParser = &strikethroughParser{}
35
36// NewStrikethroughParser return a new InlineParser that parses
37// strikethrough expressions.
38func NewStrikethroughParser() parser.InlineParser {
39 return defaultStrikethroughParser
40}
41
42func (s *strikethroughParser) Trigger() []byte {
43 return []byte{'~'}
44}
45
46func (s *strikethroughParser) Parse(parent gast.Node, block text.Reader, pc parser.Context) gast.Node {
47 before := block.PrecendingCharacter()
48 line, segment := block.PeekLine()
49 node := parser.ScanDelimiter(line, before, 2, defaultStrikethroughDelimiterProcessor)
50 if node == nil {
51 return nil
52 }
53 node.Segment = segment.WithStop(segment.Start + node.OriginalLength)
54 block.Advance(node.OriginalLength)
55 pc.PushDelimiter(node)
56 return node
57}
58
59func (s *strikethroughParser) CloseBlock(parent gast.Node, pc parser.Context) {
60 // nothing to do
61}
62
63// StrikethroughHTMLRenderer is a renderer.NodeRenderer implementation that
64// renders Strikethrough nodes.
65type StrikethroughHTMLRenderer struct {
66 html.Config
67}
68
69// NewStrikethroughHTMLRenderer returns a new StrikethroughHTMLRenderer.
70func NewStrikethroughHTMLRenderer(opts ...html.Option) renderer.NodeRenderer {
71 r := &StrikethroughHTMLRenderer{
72 Config: html.NewConfig(),
73 }
74 for _, opt := range opts {
75 opt.SetHTMLOption(&r.Config)
76 }
77 return r
78}
79
80// RegisterFuncs implements renderer.NodeRenderer.RegisterFuncs.
81func (r *StrikethroughHTMLRenderer) RegisterFuncs(reg renderer.NodeRendererFuncRegisterer) {
82 reg.Register(ast.KindStrikethrough, r.renderStrikethrough)
83}
84
85// StrikethroughAttributeFilter defines attribute names which dd elements can have.
86var StrikethroughAttributeFilter = html.GlobalAttributeFilter
87
88func (r *StrikethroughHTMLRenderer) renderStrikethrough(w util.BufWriter, source []byte, n gast.Node, entering bool) (gast.WalkStatus, error) {
89 if entering {
90 if n.Attributes() != nil {
91 _, _ = w.WriteString("<del")
92 html.RenderAttributes(w, n, StrikethroughAttributeFilter)
93 _ = w.WriteByte('>')
94 } else {
95 _, _ = w.WriteString("<del>")
96 }
97 } else {
98 _, _ = w.WriteString("</del>")
99 }
100 return gast.WalkContinue, nil
101}
102
103type strikethrough struct {
104}
105
106// Strikethrough is an extension that allow you to use strikethrough expression like '~~text~~' .
107var Strikethrough = &strikethrough{}
108
109func (e *strikethrough) Extend(m goldmark.Markdown) {
110 m.Parser().AddOptions(parser.WithInlineParsers(
111 util.Prioritized(NewStrikethroughParser(), 500),
112 ))
113 m.Renderer().AddOptions(renderer.WithNodeRenderers(
114 util.Prioritized(NewStrikethroughHTMLRenderer(), 500),
115 ))
116}
diff --git a/vendor/github.com/yuin/goldmark/extension/table.go b/vendor/github.com/yuin/goldmark/extension/table.go
new file mode 100644
index 0000000..48d0d68
--- /dev/null
+++ b/vendor/github.com/yuin/goldmark/extension/table.go
@@ -0,0 +1,556 @@
1package extension
2
3import (
4 "bytes"
5 "fmt"
6 "regexp"
7
8 "github.com/yuin/goldmark"
9 gast "github.com/yuin/goldmark/ast"
10 "github.com/yuin/goldmark/extension/ast"
11 "github.com/yuin/goldmark/parser"
12 "github.com/yuin/goldmark/renderer"
13 "github.com/yuin/goldmark/renderer/html"
14 "github.com/yuin/goldmark/text"
15 "github.com/yuin/goldmark/util"
16)
17
18var escapedPipeCellListKey = parser.NewContextKey()
19
20type escapedPipeCell struct {
21 Cell *ast.TableCell
22 Pos []int
23 Transformed bool
24}
25
26// TableCellAlignMethod indicates how are table cells aligned in HTML format.indicates how are table cells aligned in HTML format.
27type TableCellAlignMethod int
28
29const (
30 // TableCellAlignDefault renders alignments by default method.
31 // With XHTML, alignments are rendered as an align attribute.
32 // With HTML5, alignments are rendered as a style attribute.
33 TableCellAlignDefault TableCellAlignMethod = iota
34
35 // TableCellAlignAttribute renders alignments as an align attribute.
36 TableCellAlignAttribute
37
38 // TableCellAlignStyle renders alignments as a style attribute.
39 TableCellAlignStyle
40
41 // TableCellAlignNone does not care about alignments.
42 // If you using classes or other styles, you can add these attributes
43 // in an ASTTransformer.
44 TableCellAlignNone
45)
46
47// TableConfig struct holds options for the extension.
48type TableConfig struct {
49 html.Config
50
51 // TableCellAlignMethod indicates how are table celss aligned.
52 TableCellAlignMethod TableCellAlignMethod
53}
54
55// TableOption interface is a functional option interface for the extension.
56type TableOption interface {
57 renderer.Option
58 // SetTableOption sets given option to the extension.
59 SetTableOption(*TableConfig)
60}
61
62// NewTableConfig returns a new Config with defaults.
63func NewTableConfig() TableConfig {
64 return TableConfig{
65 Config: html.NewConfig(),
66 TableCellAlignMethod: TableCellAlignDefault,
67 }
68}
69
70// SetOption implements renderer.SetOptioner.
71func (c *TableConfig) SetOption(name renderer.OptionName, value interface{}) {
72 switch name {
73 case optTableCellAlignMethod:
74 c.TableCellAlignMethod = value.(TableCellAlignMethod)
75 default:
76 c.Config.SetOption(name, value)
77 }
78}
79
80type withTableHTMLOptions struct {
81 value []html.Option
82}
83
84func (o *withTableHTMLOptions) SetConfig(c *renderer.Config) {
85 if o.value != nil {
86 for _, v := range o.value {
87 v.(renderer.Option).SetConfig(c)
88 }
89 }
90}
91
92func (o *withTableHTMLOptions) SetTableOption(c *TableConfig) {
93 if o.value != nil {
94 for _, v := range o.value {
95 v.SetHTMLOption(&c.Config)
96 }
97 }
98}
99
100// WithTableHTMLOptions is functional option that wraps goldmark HTMLRenderer options.
101func WithTableHTMLOptions(opts ...html.Option) TableOption {
102 return &withTableHTMLOptions{opts}
103}
104
105const optTableCellAlignMethod renderer.OptionName = "TableTableCellAlignMethod"
106
107type withTableCellAlignMethod struct {
108 value TableCellAlignMethod
109}
110
111func (o *withTableCellAlignMethod) SetConfig(c *renderer.Config) {
112 c.Options[optTableCellAlignMethod] = o.value
113}
114
115func (o *withTableCellAlignMethod) SetTableOption(c *TableConfig) {
116 c.TableCellAlignMethod = o.value
117}
118
119// WithTableCellAlignMethod is a functional option that indicates how are table cells aligned in HTML format.
120func WithTableCellAlignMethod(a TableCellAlignMethod) TableOption {
121 return &withTableCellAlignMethod{a}
122}
123
124func isTableDelim(bs []byte) bool {
125 if w, _ := util.IndentWidth(bs, 0); w > 3 {
126 return false
127 }
128 for _, b := range bs {
129 if !(util.IsSpace(b) || b == '-' || b == '|' || b == ':') {
130 return false
131 }
132 }
133 return true
134}
135
136var tableDelimLeft = regexp.MustCompile(`^\s*\:\-+\s*$`)
137var tableDelimRight = regexp.MustCompile(`^\s*\-+\:\s*$`)
138var tableDelimCenter = regexp.MustCompile(`^\s*\:\-+\:\s*$`)
139var tableDelimNone = regexp.MustCompile(`^\s*\-+\s*$`)
140
141type tableParagraphTransformer struct {
142}
143
144var defaultTableParagraphTransformer = &tableParagraphTransformer{}
145
146// NewTableParagraphTransformer returns a new ParagraphTransformer
147// that can transform paragraphs into tables.
148func NewTableParagraphTransformer() parser.ParagraphTransformer {
149 return defaultTableParagraphTransformer
150}
151
152func (b *tableParagraphTransformer) Transform(node *gast.Paragraph, reader text.Reader, pc parser.Context) {
153 lines := node.Lines()
154 if lines.Len() < 2 {
155 return
156 }
157 for i := 1; i < lines.Len(); i++ {
158 alignments := b.parseDelimiter(lines.At(i), reader)
159 if alignments == nil {
160 continue
161 }
162 header := b.parseRow(lines.At(i-1), alignments, true, reader, pc)
163 if header == nil || len(alignments) != header.ChildCount() {
164 return
165 }
166 table := ast.NewTable()
167 table.Alignments = alignments
168 table.AppendChild(table, ast.NewTableHeader(header))
169 for j := i + 1; j < lines.Len(); j++ {
170 table.AppendChild(table, b.parseRow(lines.At(j), alignments, false, reader, pc))
171 }
172 node.Lines().SetSliced(0, i-1)
173 node.Parent().InsertAfter(node.Parent(), node, table)
174 if node.Lines().Len() == 0 {
175 node.Parent().RemoveChild(node.Parent(), node)
176 } else {
177 last := node.Lines().At(i - 2)
178 last.Stop = last.Stop - 1 // trim last newline(\n)
179 node.Lines().Set(i-2, last)
180 }
181 }
182}
183
184func (b *tableParagraphTransformer) parseRow(segment text.Segment, alignments []ast.Alignment, isHeader bool, reader text.Reader, pc parser.Context) *ast.TableRow {
185 source := reader.Source()
186 line := segment.Value(source)
187 pos := 0
188 pos += util.TrimLeftSpaceLength(line)
189 limit := len(line)
190 limit -= util.TrimRightSpaceLength(line)
191 row := ast.NewTableRow(alignments)
192 if len(line) > 0 && line[pos] == '|' {
193 pos++
194 }
195 if len(line) > 0 && line[limit-1] == '|' {
196 limit--
197 }
198 i := 0
199 for ; pos < limit; i++ {
200 alignment := ast.AlignNone
201 if i >= len(alignments) {
202 if !isHeader {
203 return row
204 }
205 } else {
206 alignment = alignments[i]
207 }
208
209 var escapedCell *escapedPipeCell
210 node := ast.NewTableCell()
211 node.Alignment = alignment
212 hasBacktick := false
213 closure := pos
214 for ; closure < limit; closure++ {
215 if line[closure] == '`' {
216 hasBacktick = true
217 }
218 if line[closure] == '|' {
219 if closure == 0 || line[closure-1] != '\\' {
220 break
221 } else if hasBacktick {
222 if escapedCell == nil {
223 escapedCell = &escapedPipeCell{node, []int{}, false}
224 escapedList := pc.ComputeIfAbsent(escapedPipeCellListKey,
225 func() interface{} {
226 return []*escapedPipeCell{}
227 }).([]*escapedPipeCell)
228 escapedList = append(escapedList, escapedCell)
229 pc.Set(escapedPipeCellListKey, escapedList)
230 }
231 escapedCell.Pos = append(escapedCell.Pos, segment.Start+closure-1)
232 }
233 }
234 }
235 seg := text.NewSegment(segment.Start+pos, segment.Start+closure)
236 seg = seg.TrimLeftSpace(source)
237 seg = seg.TrimRightSpace(source)
238 node.Lines().Append(seg)
239 row.AppendChild(row, node)
240 pos = closure + 1
241 }
242 for ; i < len(alignments); i++ {
243 row.AppendChild(row, ast.NewTableCell())
244 }
245 return row
246}
247
248func (b *tableParagraphTransformer) parseDelimiter(segment text.Segment, reader text.Reader) []ast.Alignment {
249
250 line := segment.Value(reader.Source())
251 if !isTableDelim(line) {
252 return nil
253 }
254 cols := bytes.Split(line, []byte{'|'})
255 if util.IsBlank(cols[0]) {
256 cols = cols[1:]
257 }
258 if len(cols) > 0 && util.IsBlank(cols[len(cols)-1]) {
259 cols = cols[:len(cols)-1]
260 }
261
262 var alignments []ast.Alignment
263 for _, col := range cols {
264 if tableDelimLeft.Match(col) {
265 alignments = append(alignments, ast.AlignLeft)
266 } else if tableDelimRight.Match(col) {
267 alignments = append(alignments, ast.AlignRight)
268 } else if tableDelimCenter.Match(col) {
269 alignments = append(alignments, ast.AlignCenter)
270 } else if tableDelimNone.Match(col) {
271 alignments = append(alignments, ast.AlignNone)
272 } else {
273 return nil
274 }
275 }
276 return alignments
277}
278
279type tableASTTransformer struct {
280}
281
282var defaultTableASTTransformer = &tableASTTransformer{}
283
284// NewTableASTTransformer returns a parser.ASTTransformer for tables.
285func NewTableASTTransformer() parser.ASTTransformer {
286 return defaultTableASTTransformer
287}
288
289func (a *tableASTTransformer) Transform(node *gast.Document, reader text.Reader, pc parser.Context) {
290 lst := pc.Get(escapedPipeCellListKey)
291 if lst == nil {
292 return
293 }
294 pc.Set(escapedPipeCellListKey, nil)
295 for _, v := range lst.([]*escapedPipeCell) {
296 if v.Transformed {
297 continue
298 }
299 _ = gast.Walk(v.Cell, func(n gast.Node, entering bool) (gast.WalkStatus, error) {
300 if !entering || n.Kind() != gast.KindCodeSpan {
301 return gast.WalkContinue, nil
302 }
303
304 for c := n.FirstChild(); c != nil; {
305 next := c.NextSibling()
306 if c.Kind() != gast.KindText {
307 c = next
308 continue
309 }
310 parent := c.Parent()
311 ts := &c.(*gast.Text).Segment
312 n := c
313 for _, v := range lst.([]*escapedPipeCell) {
314 for _, pos := range v.Pos {
315 if ts.Start <= pos && pos < ts.Stop {
316 segment := n.(*gast.Text).Segment
317 n1 := gast.NewRawTextSegment(segment.WithStop(pos))
318 n2 := gast.NewRawTextSegment(segment.WithStart(pos + 1))
319 parent.InsertAfter(parent, n, n1)
320 parent.InsertAfter(parent, n1, n2)
321 parent.RemoveChild(parent, n)
322 n = n2
323 v.Transformed = true
324 }
325 }
326 }
327 c = next
328 }
329 return gast.WalkContinue, nil
330 })
331 }
332}
333
334// TableHTMLRenderer is a renderer.NodeRenderer implementation that
335// renders Table nodes.
336type TableHTMLRenderer struct {
337 TableConfig
338}
339
340// NewTableHTMLRenderer returns a new TableHTMLRenderer.
341func NewTableHTMLRenderer(opts ...TableOption) renderer.NodeRenderer {
342 r := &TableHTMLRenderer{
343 TableConfig: NewTableConfig(),
344 }
345 for _, opt := range opts {
346 opt.SetTableOption(&r.TableConfig)
347 }
348 return r
349}
350
351// RegisterFuncs implements renderer.NodeRenderer.RegisterFuncs.
352func (r *TableHTMLRenderer) RegisterFuncs(reg renderer.NodeRendererFuncRegisterer) {
353 reg.Register(ast.KindTable, r.renderTable)
354 reg.Register(ast.KindTableHeader, r.renderTableHeader)
355 reg.Register(ast.KindTableRow, r.renderTableRow)
356 reg.Register(ast.KindTableCell, r.renderTableCell)
357}
358
359// TableAttributeFilter defines attribute names which table elements can have.
360var TableAttributeFilter = html.GlobalAttributeFilter.Extend(
361 []byte("align"), // [Deprecated]
362 []byte("bgcolor"), // [Deprecated]
363 []byte("border"), // [Deprecated]
364 []byte("cellpadding"), // [Deprecated]
365 []byte("cellspacing"), // [Deprecated]
366 []byte("frame"), // [Deprecated]
367 []byte("rules"), // [Deprecated]
368 []byte("summary"), // [Deprecated]
369 []byte("width"), // [Deprecated]
370)
371
372func (r *TableHTMLRenderer) renderTable(w util.BufWriter, source []byte, n gast.Node, entering bool) (gast.WalkStatus, error) {
373 if entering {
374 _, _ = w.WriteString("<table")
375 if n.Attributes() != nil {
376 html.RenderAttributes(w, n, TableAttributeFilter)
377 }
378 _, _ = w.WriteString(">\n")
379 } else {
380 _, _ = w.WriteString("</table>\n")
381 }
382 return gast.WalkContinue, nil
383}
384
385// TableHeaderAttributeFilter defines attribute names which <thead> elements can have.
386var TableHeaderAttributeFilter = html.GlobalAttributeFilter.Extend(
387 []byte("align"), // [Deprecated since HTML4] [Obsolete since HTML5]
388 []byte("bgcolor"), // [Not Standardized]
389 []byte("char"), // [Deprecated since HTML4] [Obsolete since HTML5]
390 []byte("charoff"), // [Deprecated since HTML4] [Obsolete since HTML5]
391 []byte("valign"), // [Deprecated since HTML4] [Obsolete since HTML5]
392)
393
394func (r *TableHTMLRenderer) renderTableHeader(w util.BufWriter, source []byte, n gast.Node, entering bool) (gast.WalkStatus, error) {
395 if entering {
396 _, _ = w.WriteString("<thead")
397 if n.Attributes() != nil {
398 html.RenderAttributes(w, n, TableHeaderAttributeFilter)
399 }
400 _, _ = w.WriteString(">\n")
401 _, _ = w.WriteString("<tr>\n") // Header <tr> has no separate handle
402 } else {
403 _, _ = w.WriteString("</tr>\n")
404 _, _ = w.WriteString("</thead>\n")
405 if n.NextSibling() != nil {
406 _, _ = w.WriteString("<tbody>\n")
407 }
408 }
409 return gast.WalkContinue, nil
410}
411
412// TableRowAttributeFilter defines attribute names which <tr> elements can have.
413var TableRowAttributeFilter = html.GlobalAttributeFilter.Extend(
414 []byte("align"), // [Obsolete since HTML5]
415 []byte("bgcolor"), // [Obsolete since HTML5]
416 []byte("char"), // [Obsolete since HTML5]
417 []byte("charoff"), // [Obsolete since HTML5]
418 []byte("valign"), // [Obsolete since HTML5]
419)
420
421func (r *TableHTMLRenderer) renderTableRow(w util.BufWriter, source []byte, n gast.Node, entering bool) (gast.WalkStatus, error) {
422 if entering {
423 _, _ = w.WriteString("<tr")
424 if n.Attributes() != nil {
425 html.RenderAttributes(w, n, TableRowAttributeFilter)
426 }
427 _, _ = w.WriteString(">\n")
428 } else {
429 _, _ = w.WriteString("</tr>\n")
430 if n.Parent().LastChild() == n {
431 _, _ = w.WriteString("</tbody>\n")
432 }
433 }
434 return gast.WalkContinue, nil
435}
436
437// TableThCellAttributeFilter defines attribute names which table <th> cells can have.
438var TableThCellAttributeFilter = html.GlobalAttributeFilter.Extend(
439 []byte("abbr"), // [OK] Contains a short abbreviated description of the cell's content [NOT OK in <td>]
440
441 []byte("align"), // [Obsolete since HTML5]
442 []byte("axis"), // [Obsolete since HTML5]
443 []byte("bgcolor"), // [Not Standardized]
444 []byte("char"), // [Obsolete since HTML5]
445 []byte("charoff"), // [Obsolete since HTML5]
446
447 []byte("colspan"), // [OK] Number of columns that the cell is to span
448 []byte("headers"), // [OK] This attribute contains a list of space-separated strings, each corresponding to the id attribute of the <th> elements that apply to this element
449
450 []byte("height"), // [Deprecated since HTML4] [Obsolete since HTML5]
451
452 []byte("rowspan"), // [OK] Number of rows that the cell is to span
453 []byte("scope"), // [OK] This enumerated attribute defines the cells that the header (defined in the <th>) element relates to [NOT OK in <td>]
454
455 []byte("valign"), // [Obsolete since HTML5]
456 []byte("width"), // [Deprecated since HTML4] [Obsolete since HTML5]
457)
458
459// TableTdCellAttributeFilter defines attribute names which table <td> cells can have.
460var TableTdCellAttributeFilter = html.GlobalAttributeFilter.Extend(
461 []byte("abbr"), // [Obsolete since HTML5] [OK in <th>]
462 []byte("align"), // [Obsolete since HTML5]
463 []byte("axis"), // [Obsolete since HTML5]
464 []byte("bgcolor"), // [Not Standardized]
465 []byte("char"), // [Obsolete since HTML5]
466 []byte("charoff"), // [Obsolete since HTML5]
467
468 []byte("colspan"), // [OK] Number of columns that the cell is to span
469 []byte("headers"), // [OK] This attribute contains a list of space-separated strings, each corresponding to the id attribute of the <th> elements that apply to this element
470
471 []byte("height"), // [Deprecated since HTML4] [Obsolete since HTML5]
472
473 []byte("rowspan"), // [OK] Number of rows that the cell is to span
474
475 []byte("scope"), // [Obsolete since HTML5] [OK in <th>]
476 []byte("valign"), // [Obsolete since HTML5]
477 []byte("width"), // [Deprecated since HTML4] [Obsolete since HTML5]
478)
479
480func (r *TableHTMLRenderer) renderTableCell(w util.BufWriter, source []byte, node gast.Node, entering bool) (gast.WalkStatus, error) {
481 n := node.(*ast.TableCell)
482 tag := "td"
483 if n.Parent().Kind() == ast.KindTableHeader {
484 tag = "th"
485 }
486 if entering {
487 fmt.Fprintf(w, "<%s", tag)
488 if n.Alignment != ast.AlignNone {
489 amethod := r.TableConfig.TableCellAlignMethod
490 if amethod == TableCellAlignDefault {
491 if r.Config.XHTML {
492 amethod = TableCellAlignAttribute
493 } else {
494 amethod = TableCellAlignStyle
495 }
496 }
497 switch amethod {
498 case TableCellAlignAttribute:
499 if _, ok := n.AttributeString("align"); !ok { // Skip align render if overridden
500 fmt.Fprintf(w, ` align="%s"`, n.Alignment.String())
501 }
502 case TableCellAlignStyle:
503 v, ok := n.AttributeString("style")
504 var cob util.CopyOnWriteBuffer
505 if ok {
506 cob = util.NewCopyOnWriteBuffer(v.([]byte))
507 cob.AppendByte(';')
508 }
509 style := fmt.Sprintf("text-align:%s", n.Alignment.String())
510 cob.AppendString(style)
511 n.SetAttributeString("style", cob.Bytes())
512 }
513 }
514 if n.Attributes() != nil {
515 if tag == "td" {
516 html.RenderAttributes(w, n, TableTdCellAttributeFilter) // <td>
517 } else {
518 html.RenderAttributes(w, n, TableThCellAttributeFilter) // <th>
519 }
520 }
521 _ = w.WriteByte('>')
522 } else {
523 fmt.Fprintf(w, "</%s>\n", tag)
524 }
525 return gast.WalkContinue, nil
526}
527
528type table struct {
529 options []TableOption
530}
531
532// Table is an extension that allow you to use GFM tables .
533var Table = &table{
534 options: []TableOption{},
535}
536
537// NewTable returns a new extension with given options.
538func NewTable(opts ...TableOption) goldmark.Extender {
539 return &table{
540 options: opts,
541 }
542}
543
544func (e *table) Extend(m goldmark.Markdown) {
545 m.Parser().AddOptions(
546 parser.WithParagraphTransformers(
547 util.Prioritized(NewTableParagraphTransformer(), 200),
548 ),
549 parser.WithASTTransformers(
550 util.Prioritized(defaultTableASTTransformer, 0),
551 ),
552 )
553 m.Renderer().AddOptions(renderer.WithNodeRenderers(
554 util.Prioritized(NewTableHTMLRenderer(e.options...), 500),
555 ))
556}
diff --git a/vendor/github.com/yuin/goldmark/extension/tasklist.go b/vendor/github.com/yuin/goldmark/extension/tasklist.go
new file mode 100644
index 0000000..1f3e52c
--- /dev/null
+++ b/vendor/github.com/yuin/goldmark/extension/tasklist.go
@@ -0,0 +1,115 @@
1package extension
2
3import (
4 "github.com/yuin/goldmark"
5 gast "github.com/yuin/goldmark/ast"
6 "github.com/yuin/goldmark/extension/ast"
7 "github.com/yuin/goldmark/parser"
8 "github.com/yuin/goldmark/renderer"
9 "github.com/yuin/goldmark/renderer/html"
10 "github.com/yuin/goldmark/text"
11 "github.com/yuin/goldmark/util"
12 "regexp"
13)
14
15var taskListRegexp = regexp.MustCompile(`^\[([\sxX])\]\s*`)
16
17type taskCheckBoxParser struct {
18}
19
20var defaultTaskCheckBoxParser = &taskCheckBoxParser{}
21
22// NewTaskCheckBoxParser returns a new InlineParser that can parse
23// checkboxes in list items.
24// This parser must take precedence over the parser.LinkParser.
25func NewTaskCheckBoxParser() parser.InlineParser {
26 return defaultTaskCheckBoxParser
27}
28
29func (s *taskCheckBoxParser) Trigger() []byte {
30 return []byte{'['}
31}
32
33func (s *taskCheckBoxParser) Parse(parent gast.Node, block text.Reader, pc parser.Context) gast.Node {
34 // Given AST structure must be like
35 // - List
36 // - ListItem : parent.Parent
37 // - TextBlock : parent
38 // (current line)
39 if parent.Parent() == nil || parent.Parent().FirstChild() != parent {
40 return nil
41 }
42
43 if _, ok := parent.Parent().(*gast.ListItem); !ok {
44 return nil
45 }
46 line, _ := block.PeekLine()
47 m := taskListRegexp.FindSubmatchIndex(line)
48 if m == nil {
49 return nil
50 }
51 value := line[m[2]:m[3]][0]
52 block.Advance(m[1])
53 checked := value == 'x' || value == 'X'
54 return ast.NewTaskCheckBox(checked)
55}
56
57func (s *taskCheckBoxParser) CloseBlock(parent gast.Node, pc parser.Context) {
58 // nothing to do
59}
60
61// TaskCheckBoxHTMLRenderer is a renderer.NodeRenderer implementation that
62// renders checkboxes in list items.
63type TaskCheckBoxHTMLRenderer struct {
64 html.Config
65}
66
67// NewTaskCheckBoxHTMLRenderer returns a new TaskCheckBoxHTMLRenderer.
68func NewTaskCheckBoxHTMLRenderer(opts ...html.Option) renderer.NodeRenderer {
69 r := &TaskCheckBoxHTMLRenderer{
70 Config: html.NewConfig(),
71 }
72 for _, opt := range opts {
73 opt.SetHTMLOption(&r.Config)
74 }
75 return r
76}
77
78// RegisterFuncs implements renderer.NodeRenderer.RegisterFuncs.
79func (r *TaskCheckBoxHTMLRenderer) RegisterFuncs(reg renderer.NodeRendererFuncRegisterer) {
80 reg.Register(ast.KindTaskCheckBox, r.renderTaskCheckBox)
81}
82
83func (r *TaskCheckBoxHTMLRenderer) renderTaskCheckBox(w util.BufWriter, source []byte, node gast.Node, entering bool) (gast.WalkStatus, error) {
84 if !entering {
85 return gast.WalkContinue, nil
86 }
87 n := node.(*ast.TaskCheckBox)
88
89 if n.IsChecked {
90 w.WriteString(`<input checked="" disabled="" type="checkbox"`)
91 } else {
92 w.WriteString(`<input disabled="" type="checkbox"`)
93 }
94 if r.XHTML {
95 w.WriteString(" /> ")
96 } else {
97 w.WriteString("> ")
98 }
99 return gast.WalkContinue, nil
100}
101
102type taskList struct {
103}
104
105// TaskList is an extension that allow you to use GFM task lists.
106var TaskList = &taskList{}
107
108func (e *taskList) Extend(m goldmark.Markdown) {
109 m.Parser().AddOptions(parser.WithInlineParsers(
110 util.Prioritized(NewTaskCheckBoxParser(), 0),
111 ))
112 m.Renderer().AddOptions(renderer.WithNodeRenderers(
113 util.Prioritized(NewTaskCheckBoxHTMLRenderer(), 500),
114 ))
115}
diff --git a/vendor/github.com/yuin/goldmark/extension/typographer.go b/vendor/github.com/yuin/goldmark/extension/typographer.go
new file mode 100644
index 0000000..f56c06f
--- /dev/null
+++ b/vendor/github.com/yuin/goldmark/extension/typographer.go
@@ -0,0 +1,339 @@
1package extension
2
3import (
4 "unicode"
5
6 "github.com/yuin/goldmark"
7 gast "github.com/yuin/goldmark/ast"
8 "github.com/yuin/goldmark/parser"
9 "github.com/yuin/goldmark/text"
10 "github.com/yuin/goldmark/util"
11)
12
13var uncloseCounterKey = parser.NewContextKey()
14
15type unclosedCounter struct {
16 Single int
17 Double int
18}
19
20func (u *unclosedCounter) Reset() {
21 u.Single = 0
22 u.Double = 0
23}
24
25func getUnclosedCounter(pc parser.Context) *unclosedCounter {
26 v := pc.Get(uncloseCounterKey)
27 if v == nil {
28 v = &unclosedCounter{}
29 pc.Set(uncloseCounterKey, v)
30 }
31 return v.(*unclosedCounter)
32}
33
34// TypographicPunctuation is a key of the punctuations that can be replaced with
35// typographic entities.
36type TypographicPunctuation int
37
38const (
39 // LeftSingleQuote is '
40 LeftSingleQuote TypographicPunctuation = iota + 1
41 // RightSingleQuote is '
42 RightSingleQuote
43 // LeftDoubleQuote is "
44 LeftDoubleQuote
45 // RightDoubleQuote is "
46 RightDoubleQuote
47 // EnDash is --
48 EnDash
49 // EmDash is ---
50 EmDash
51 // Ellipsis is ...
52 Ellipsis
53 // LeftAngleQuote is <<
54 LeftAngleQuote
55 // RightAngleQuote is >>
56 RightAngleQuote
57 // Apostrophe is '
58 Apostrophe
59
60 typographicPunctuationMax
61)
62
63// An TypographerConfig struct is a data structure that holds configuration of the
64// Typographer extension.
65type TypographerConfig struct {
66 Substitutions [][]byte
67}
68
69func newDefaultSubstitutions() [][]byte {
70 replacements := make([][]byte, typographicPunctuationMax)
71 replacements[LeftSingleQuote] = []byte("&lsquo;")
72 replacements[RightSingleQuote] = []byte("&rsquo;")
73 replacements[LeftDoubleQuote] = []byte("&ldquo;")
74 replacements[RightDoubleQuote] = []byte("&rdquo;")
75 replacements[EnDash] = []byte("&ndash;")
76 replacements[EmDash] = []byte("&mdash;")
77 replacements[Ellipsis] = []byte("&hellip;")
78 replacements[LeftAngleQuote] = []byte("&laquo;")
79 replacements[RightAngleQuote] = []byte("&raquo;")
80 replacements[Apostrophe] = []byte("&rsquo;")
81
82 return replacements
83}
84
85// SetOption implements SetOptioner.
86func (b *TypographerConfig) SetOption(name parser.OptionName, value interface{}) {
87 switch name {
88 case optTypographicSubstitutions:
89 b.Substitutions = value.([][]byte)
90 }
91}
92
93// A TypographerOption interface sets options for the TypographerParser.
94type TypographerOption interface {
95 parser.Option
96 SetTypographerOption(*TypographerConfig)
97}
98
99const optTypographicSubstitutions parser.OptionName = "TypographicSubstitutions"
100
101// TypographicSubstitutions is a list of the substitutions for the Typographer extension.
102type TypographicSubstitutions map[TypographicPunctuation][]byte
103
104type withTypographicSubstitutions struct {
105 value [][]byte
106}
107
108func (o *withTypographicSubstitutions) SetParserOption(c *parser.Config) {
109 c.Options[optTypographicSubstitutions] = o.value
110}
111
112func (o *withTypographicSubstitutions) SetTypographerOption(p *TypographerConfig) {
113 p.Substitutions = o.value
114}
115
116// WithTypographicSubstitutions is a functional otpion that specify replacement text
117// for punctuations.
118func WithTypographicSubstitutions(values map[TypographicPunctuation][]byte) TypographerOption {
119 replacements := newDefaultSubstitutions()
120 for k, v := range values {
121 replacements[k] = v
122 }
123
124 return &withTypographicSubstitutions{replacements}
125}
126
127type typographerDelimiterProcessor struct {
128}
129
130func (p *typographerDelimiterProcessor) IsDelimiter(b byte) bool {
131 return b == '\'' || b == '"'
132}
133
134func (p *typographerDelimiterProcessor) CanOpenCloser(opener, closer *parser.Delimiter) bool {
135 return opener.Char == closer.Char
136}
137
138func (p *typographerDelimiterProcessor) OnMatch(consumes int) gast.Node {
139 return nil
140}
141
142var defaultTypographerDelimiterProcessor = &typographerDelimiterProcessor{}
143
144type typographerParser struct {
145 TypographerConfig
146}
147
148// NewTypographerParser return a new InlineParser that parses
149// typographer expressions.
150func NewTypographerParser(opts ...TypographerOption) parser.InlineParser {
151 p := &typographerParser{
152 TypographerConfig: TypographerConfig{
153 Substitutions: newDefaultSubstitutions(),
154 },
155 }
156 for _, o := range opts {
157 o.SetTypographerOption(&p.TypographerConfig)
158 }
159 return p
160}
161
162func (s *typographerParser) Trigger() []byte {
163 return []byte{'\'', '"', '-', '.', ',', '<', '>', '*', '['}
164}
165
166func (s *typographerParser) Parse(parent gast.Node, block text.Reader, pc parser.Context) gast.Node {
167 line, _ := block.PeekLine()
168 c := line[0]
169 if len(line) > 2 {
170 if c == '-' {
171 if s.Substitutions[EmDash] != nil && line[1] == '-' && line[2] == '-' { // ---
172 node := gast.NewString(s.Substitutions[EmDash])
173 node.SetCode(true)
174 block.Advance(3)
175 return node
176 }
177 } else if c == '.' {
178 if s.Substitutions[Ellipsis] != nil && line[1] == '.' && line[2] == '.' { // ...
179 node := gast.NewString(s.Substitutions[Ellipsis])
180 node.SetCode(true)
181 block.Advance(3)
182 return node
183 }
184 return nil
185 }
186 }
187 if len(line) > 1 {
188 if c == '<' {
189 if s.Substitutions[LeftAngleQuote] != nil && line[1] == '<' { // <<
190 node := gast.NewString(s.Substitutions[LeftAngleQuote])
191 node.SetCode(true)
192 block.Advance(2)
193 return node
194 }
195 return nil
196 } else if c == '>' {
197 if s.Substitutions[RightAngleQuote] != nil && line[1] == '>' { // >>
198 node := gast.NewString(s.Substitutions[RightAngleQuote])
199 node.SetCode(true)
200 block.Advance(2)
201 return node
202 }
203 return nil
204 } else if s.Substitutions[EnDash] != nil && c == '-' && line[1] == '-' { // --
205 node := gast.NewString(s.Substitutions[EnDash])
206 node.SetCode(true)
207 block.Advance(2)
208 return node
209 }
210 }
211 if c == '\'' || c == '"' {
212 before := block.PrecendingCharacter()
213 d := parser.ScanDelimiter(line, before, 1, defaultTypographerDelimiterProcessor)
214 if d == nil {
215 return nil
216 }
217 counter := getUnclosedCounter(pc)
218 if c == '\'' {
219 if s.Substitutions[Apostrophe] != nil {
220 // Handle decade abbrevations such as '90s
221 if d.CanOpen && !d.CanClose && len(line) > 3 && util.IsNumeric(line[1]) && util.IsNumeric(line[2]) && line[3] == 's' {
222 after := rune(' ')
223 if len(line) > 4 {
224 after = util.ToRune(line, 4)
225 }
226 if len(line) == 3 || util.IsSpaceRune(after) || util.IsPunctRune(after) {
227 node := gast.NewString(s.Substitutions[Apostrophe])
228 node.SetCode(true)
229 block.Advance(1)
230 return node
231 }
232 }
233 // special cases: 'twas, 'em, 'net
234 if len(line) > 1 && (unicode.IsPunct(before) || unicode.IsSpace(before)) && (line[1] == 't' || line[1] == 'e' || line[1] == 'n' || line[1] == 'l') {
235 node := gast.NewString(s.Substitutions[Apostrophe])
236 node.SetCode(true)
237 block.Advance(1)
238 return node
239 }
240 // Convert normal apostrophes. This is probably more flexible than necessary but
241 // converts any apostrophe in between two alphanumerics.
242 if len(line) > 1 && (unicode.IsDigit(before) || unicode.IsLetter(before)) && (unicode.IsLetter(util.ToRune(line, 1))) {
243 node := gast.NewString(s.Substitutions[Apostrophe])
244 node.SetCode(true)
245 block.Advance(1)
246 return node
247 }
248 }
249 if s.Substitutions[LeftSingleQuote] != nil && d.CanOpen && !d.CanClose {
250 nt := LeftSingleQuote
251 // special cases: Alice's, I'm, Don't, You'd
252 if len(line) > 1 && (line[1] == 's' || line[1] == 'm' || line[1] == 't' || line[1] == 'd') && (len(line) < 3 || util.IsPunct(line[2]) || util.IsSpace(line[2])) {
253 nt = RightSingleQuote
254 }
255 // special cases: I've, I'll, You're
256 if len(line) > 2 && ((line[1] == 'v' && line[2] == 'e') || (line[1] == 'l' && line[2] == 'l') || (line[1] == 'r' && line[2] == 'e')) && (len(line) < 4 || util.IsPunct(line[3]) || util.IsSpace(line[3])) {
257 nt = RightSingleQuote
258 }
259 if nt == LeftSingleQuote {
260 counter.Single++
261 }
262
263 node := gast.NewString(s.Substitutions[nt])
264 node.SetCode(true)
265 block.Advance(1)
266 return node
267 }
268 if s.Substitutions[RightSingleQuote] != nil {
269 // plural possesives and abbreviations: Smiths', doin'
270 if len(line) > 1 && unicode.IsSpace(util.ToRune(line, 0)) || unicode.IsPunct(util.ToRune(line, 0)) && (len(line) > 2 && !unicode.IsDigit(util.ToRune(line, 1))) {
271 node := gast.NewString(s.Substitutions[RightSingleQuote])
272 node.SetCode(true)
273 block.Advance(1)
274 return node
275 }
276 }
277 if s.Substitutions[RightSingleQuote] != nil && counter.Single > 0 {
278 isClose := d.CanClose && !d.CanOpen
279 maybeClose := d.CanClose && d.CanOpen && len(line) > 1 && unicode.IsPunct(util.ToRune(line, 1)) && (len(line) == 2 || (len(line) > 2 && util.IsPunct(line[2]) || util.IsSpace(line[2])))
280 if isClose || maybeClose {
281 node := gast.NewString(s.Substitutions[RightSingleQuote])
282 node.SetCode(true)
283 block.Advance(1)
284 counter.Single--
285 return node
286 }
287 }
288 }
289 if c == '"' {
290 if s.Substitutions[LeftDoubleQuote] != nil && d.CanOpen && !d.CanClose {
291 node := gast.NewString(s.Substitutions[LeftDoubleQuote])
292 node.SetCode(true)
293 block.Advance(1)
294 counter.Double++
295 return node
296 }
297 if s.Substitutions[RightDoubleQuote] != nil && counter.Double > 0 {
298 isClose := d.CanClose && !d.CanOpen
299 maybeClose := d.CanClose && d.CanOpen && len(line) > 1 && (unicode.IsPunct(util.ToRune(line, 1))) && (len(line) == 2 || (len(line) > 2 && util.IsPunct(line[2]) || util.IsSpace(line[2])))
300 if isClose || maybeClose {
301 // special case: "Monitor 21""
302 if len(line) > 1 && line[1] == '"' && unicode.IsDigit(before) {
303 return nil
304 }
305 node := gast.NewString(s.Substitutions[RightDoubleQuote])
306 node.SetCode(true)
307 block.Advance(1)
308 counter.Double--
309 return node
310 }
311 }
312 }
313 }
314 return nil
315}
316
317func (s *typographerParser) CloseBlock(parent gast.Node, pc parser.Context) {
318 getUnclosedCounter(pc).Reset()
319}
320
321type typographer struct {
322 options []TypographerOption
323}
324
325// Typographer is an extension that replaces punctuations with typographic entities.
326var Typographer = &typographer{}
327
328// NewTypographer returns a new Extender that replaces punctuations with typographic entities.
329func NewTypographer(opts ...TypographerOption) goldmark.Extender {
330 return &typographer{
331 options: opts,
332 }
333}
334
335func (e *typographer) Extend(m goldmark.Markdown) {
336 m.Parser().AddOptions(parser.WithInlineParsers(
337 util.Prioritized(NewTypographerParser(e.options...), 9999),
338 ))
339}