diff options
| author | Mitja Felicijan <mitja.felicijan@gmail.com> | 2024-10-25 00:47:47 +0200 |
|---|---|---|
| committer | Mitja Felicijan <mitja.felicijan@gmail.com> | 2024-10-25 00:47:47 +0200 |
| commit | c6cc0108ca7738023b45e0eeac0fa2390532dd93 (patch) | |
| tree | 36890e6cd3091bbab8efbe686cc56f467f645bfd /vendor/github.com/yuin/goldmark/extension | |
| parent | 0130404a1dc663d4aa68d780c9bcb23a4243e68d (diff) | |
| download | jbmafp-c6cc0108ca7738023b45e0eeac0fa2390532dd93.tar.gz | |
Diffstat (limited to 'vendor/github.com/yuin/goldmark/extension')
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 @@ | |||
| 1 | package ast | ||
| 2 | |||
| 3 | import ( | ||
| 4 | gast "github.com/yuin/goldmark/ast" | ||
| 5 | ) | ||
| 6 | |||
| 7 | // A DefinitionList struct represents a definition list of Markdown | ||
| 8 | // (PHPMarkdownExtra) text. | ||
| 9 | type DefinitionList struct { | ||
| 10 | gast.BaseBlock | ||
| 11 | Offset int | ||
| 12 | TemporaryParagraph *gast.Paragraph | ||
| 13 | } | ||
| 14 | |||
| 15 | // Dump implements Node.Dump. | ||
| 16 | func (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. | ||
| 21 | var KindDefinitionList = gast.NewNodeKind("DefinitionList") | ||
| 22 | |||
| 23 | // Kind implements Node.Kind. | ||
| 24 | func (n *DefinitionList) Kind() gast.NodeKind { | ||
| 25 | return KindDefinitionList | ||
| 26 | } | ||
| 27 | |||
| 28 | // NewDefinitionList returns a new DefinitionList node. | ||
| 29 | func 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. | ||
| 38 | type DefinitionTerm struct { | ||
| 39 | gast.BaseBlock | ||
| 40 | } | ||
| 41 | |||
| 42 | // Dump implements Node.Dump. | ||
| 43 | func (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. | ||
| 48 | var KindDefinitionTerm = gast.NewNodeKind("DefinitionTerm") | ||
| 49 | |||
| 50 | // Kind implements Node.Kind. | ||
| 51 | func (n *DefinitionTerm) Kind() gast.NodeKind { | ||
| 52 | return KindDefinitionTerm | ||
| 53 | } | ||
| 54 | |||
| 55 | // NewDefinitionTerm returns a new DefinitionTerm node. | ||
| 56 | func NewDefinitionTerm() *DefinitionTerm { | ||
| 57 | return &DefinitionTerm{} | ||
| 58 | } | ||
| 59 | |||
| 60 | // A DefinitionDescription struct represents a definition list description of Markdown | ||
| 61 | // (PHPMarkdownExtra) text. | ||
| 62 | type DefinitionDescription struct { | ||
| 63 | gast.BaseBlock | ||
| 64 | IsTight bool | ||
| 65 | } | ||
| 66 | |||
| 67 | // Dump implements Node.Dump. | ||
| 68 | func (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. | ||
| 73 | var KindDefinitionDescription = gast.NewNodeKind("DefinitionDescription") | ||
| 74 | |||
| 75 | // Kind implements Node.Kind. | ||
| 76 | func (n *DefinitionDescription) Kind() gast.NodeKind { | ||
| 77 | return KindDefinitionDescription | ||
| 78 | } | ||
| 79 | |||
| 80 | // NewDefinitionDescription returns a new DefinitionDescription node. | ||
| 81 | func 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 @@ | |||
| 1 | package ast | ||
| 2 | |||
| 3 | import ( | ||
| 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. | ||
| 11 | type FootnoteLink struct { | ||
| 12 | gast.BaseInline | ||
| 13 | Index int | ||
| 14 | RefCount int | ||
| 15 | RefIndex int | ||
| 16 | } | ||
| 17 | |||
| 18 | // Dump implements Node.Dump. | ||
| 19 | func (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. | ||
| 28 | var KindFootnoteLink = gast.NewNodeKind("FootnoteLink") | ||
| 29 | |||
| 30 | // Kind implements Node.Kind. | ||
| 31 | func (n *FootnoteLink) Kind() gast.NodeKind { | ||
| 32 | return KindFootnoteLink | ||
| 33 | } | ||
| 34 | |||
| 35 | // NewFootnoteLink returns a new FootnoteLink node. | ||
| 36 | func 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. | ||
| 46 | type FootnoteBacklink struct { | ||
| 47 | gast.BaseInline | ||
| 48 | Index int | ||
| 49 | RefCount int | ||
| 50 | RefIndex int | ||
| 51 | } | ||
| 52 | |||
| 53 | // Dump implements Node.Dump. | ||
| 54 | func (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. | ||
| 63 | var KindFootnoteBacklink = gast.NewNodeKind("FootnoteBacklink") | ||
| 64 | |||
| 65 | // Kind implements Node.Kind. | ||
| 66 | func (n *FootnoteBacklink) Kind() gast.NodeKind { | ||
| 67 | return KindFootnoteBacklink | ||
| 68 | } | ||
| 69 | |||
| 70 | // NewFootnoteBacklink returns a new FootnoteBacklink node. | ||
| 71 | func 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. | ||
| 81 | type Footnote struct { | ||
| 82 | gast.BaseBlock | ||
| 83 | Ref []byte | ||
| 84 | Index int | ||
| 85 | } | ||
| 86 | |||
| 87 | // Dump implements Node.Dump. | ||
| 88 | func (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. | ||
| 96 | var KindFootnote = gast.NewNodeKind("Footnote") | ||
| 97 | |||
| 98 | // Kind implements Node.Kind. | ||
| 99 | func (n *Footnote) Kind() gast.NodeKind { | ||
| 100 | return KindFootnote | ||
| 101 | } | ||
| 102 | |||
| 103 | // NewFootnote returns a new Footnote node. | ||
| 104 | func 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. | ||
| 113 | type FootnoteList struct { | ||
| 114 | gast.BaseBlock | ||
| 115 | Count int | ||
| 116 | } | ||
| 117 | |||
| 118 | // Dump implements Node.Dump. | ||
| 119 | func (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. | ||
| 126 | var KindFootnoteList = gast.NewNodeKind("FootnoteList") | ||
| 127 | |||
| 128 | // Kind implements Node.Kind. | ||
| 129 | func (n *FootnoteList) Kind() gast.NodeKind { | ||
| 130 | return KindFootnoteList | ||
| 131 | } | ||
| 132 | |||
| 133 | // NewFootnoteList returns a new FootnoteList node. | ||
| 134 | func 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 | ||
| 2 | package ast | ||
| 3 | |||
| 4 | import ( | ||
| 5 | gast "github.com/yuin/goldmark/ast" | ||
| 6 | ) | ||
| 7 | |||
| 8 | // A Strikethrough struct represents a strikethrough of GFM text. | ||
| 9 | type Strikethrough struct { | ||
| 10 | gast.BaseInline | ||
| 11 | } | ||
| 12 | |||
| 13 | // Dump implements Node.Dump. | ||
| 14 | func (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. | ||
| 19 | var KindStrikethrough = gast.NewNodeKind("Strikethrough") | ||
| 20 | |||
| 21 | // Kind implements Node.Kind. | ||
| 22 | func (n *Strikethrough) Kind() gast.NodeKind { | ||
| 23 | return KindStrikethrough | ||
| 24 | } | ||
| 25 | |||
| 26 | // NewStrikethrough returns a new Strikethrough node. | ||
| 27 | func 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 @@ | |||
| 1 | package ast | ||
| 2 | |||
| 3 | import ( | ||
| 4 | "fmt" | ||
| 5 | gast "github.com/yuin/goldmark/ast" | ||
| 6 | "strings" | ||
| 7 | ) | ||
| 8 | |||
| 9 | // Alignment is a text alignment of table cells. | ||
| 10 | type Alignment int | ||
| 11 | |||
| 12 | const ( | ||
| 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 | |||
| 26 | func (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. | ||
| 41 | type Table struct { | ||
| 42 | gast.BaseBlock | ||
| 43 | |||
| 44 | // Alignments returns alignments of the columns. | ||
| 45 | Alignments []Alignment | ||
| 46 | } | ||
| 47 | |||
| 48 | // Dump implements Node.Dump | ||
| 49 | func (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. | ||
| 65 | var KindTable = gast.NewNodeKind("Table") | ||
| 66 | |||
| 67 | // Kind implements Node.Kind. | ||
| 68 | func (n *Table) Kind() gast.NodeKind { | ||
| 69 | return KindTable | ||
| 70 | } | ||
| 71 | |||
| 72 | // NewTable returns a new Table node. | ||
| 73 | func NewTable() *Table { | ||
| 74 | return &Table{ | ||
| 75 | Alignments: []Alignment{}, | ||
| 76 | } | ||
| 77 | } | ||
| 78 | |||
| 79 | // A TableRow struct represents a table row of Markdown(GFM) text. | ||
| 80 | type TableRow struct { | ||
| 81 | gast.BaseBlock | ||
| 82 | Alignments []Alignment | ||
| 83 | } | ||
| 84 | |||
| 85 | // Dump implements Node.Dump. | ||
| 86 | func (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. | ||
| 91 | var KindTableRow = gast.NewNodeKind("TableRow") | ||
| 92 | |||
| 93 | // Kind implements Node.Kind. | ||
| 94 | func (n *TableRow) Kind() gast.NodeKind { | ||
| 95 | return KindTableRow | ||
| 96 | } | ||
| 97 | |||
| 98 | // NewTableRow returns a new TableRow node. | ||
| 99 | func NewTableRow(alignments []Alignment) *TableRow { | ||
| 100 | return &TableRow{Alignments: alignments} | ||
| 101 | } | ||
| 102 | |||
| 103 | // A TableHeader struct represents a table header of Markdown(GFM) text. | ||
| 104 | type TableHeader struct { | ||
| 105 | gast.BaseBlock | ||
| 106 | Alignments []Alignment | ||
| 107 | } | ||
| 108 | |||
| 109 | // KindTableHeader is a NodeKind of the TableHeader node. | ||
| 110 | var KindTableHeader = gast.NewNodeKind("TableHeader") | ||
| 111 | |||
| 112 | // Kind implements Node.Kind. | ||
| 113 | func (n *TableHeader) Kind() gast.NodeKind { | ||
| 114 | return KindTableHeader | ||
| 115 | } | ||
| 116 | |||
| 117 | // Dump implements Node.Dump. | ||
| 118 | func (n *TableHeader) Dump(source []byte, level int) { | ||
| 119 | gast.DumpHelper(n, source, level, nil, nil) | ||
| 120 | } | ||
| 121 | |||
| 122 | // NewTableHeader returns a new TableHeader node. | ||
| 123 | func 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. | ||
| 134 | type TableCell struct { | ||
| 135 | gast.BaseBlock | ||
| 136 | Alignment Alignment | ||
| 137 | } | ||
| 138 | |||
| 139 | // Dump implements Node.Dump. | ||
| 140 | func (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. | ||
| 145 | var KindTableCell = gast.NewNodeKind("TableCell") | ||
| 146 | |||
| 147 | // Kind implements Node.Kind. | ||
| 148 | func (n *TableCell) Kind() gast.NodeKind { | ||
| 149 | return KindTableCell | ||
| 150 | } | ||
| 151 | |||
| 152 | // NewTableCell returns a new TableCell node. | ||
| 153 | func 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 @@ | |||
| 1 | package ast | ||
| 2 | |||
| 3 | import ( | ||
| 4 | "fmt" | ||
| 5 | gast "github.com/yuin/goldmark/ast" | ||
| 6 | ) | ||
| 7 | |||
| 8 | // A TaskCheckBox struct represents a checkbox of a task list. | ||
| 9 | type TaskCheckBox struct { | ||
| 10 | gast.BaseInline | ||
| 11 | IsChecked bool | ||
| 12 | } | ||
| 13 | |||
| 14 | // Dump implements Node.Dump. | ||
| 15 | func (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. | ||
| 23 | var KindTaskCheckBox = gast.NewNodeKind("TaskCheckBox") | ||
| 24 | |||
| 25 | // Kind implements Node.Kind. | ||
| 26 | func (n *TaskCheckBox) Kind() gast.NodeKind { | ||
| 27 | return KindTaskCheckBox | ||
| 28 | } | ||
| 29 | |||
| 30 | // NewTaskCheckBox returns a new TaskCheckBox node. | ||
| 31 | func 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 @@ | |||
| 1 | package extension | ||
| 2 | |||
| 3 | import ( | ||
| 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. | ||
| 10 | type CJKOption func(*cjk) | ||
| 11 | |||
| 12 | // WithEastAsianLineBreaks is a functional option that indicates whether softline breaks | ||
| 13 | // between east asian wide characters should be ignored. | ||
| 14 | func 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. | ||
| 21 | func WithEscapedSpace() CJKOption { | ||
| 22 | return func(c *cjk) { | ||
| 23 | c.EscapedSpace = true | ||
| 24 | } | ||
| 25 | } | ||
| 26 | |||
| 27 | type cjk struct { | ||
| 28 | EastAsianLineBreaks bool | ||
| 29 | EscapedSpace bool | ||
| 30 | } | ||
| 31 | |||
| 32 | var CJK = NewCJK(WithEastAsianLineBreaks(), WithEscapedSpace()) | ||
| 33 | |||
| 34 | // NewCJK returns a new extension with given options. | ||
| 35 | func NewCJK(opts ...CJKOption) goldmark.Extender { | ||
| 36 | e := &cjk{} | ||
| 37 | for _, opt := range opts { | ||
| 38 | opt(e) | ||
| 39 | } | ||
| 40 | return e | ||
| 41 | } | ||
| 42 | |||
| 43 | func (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 @@ | |||
| 1 | package extension | ||
| 2 | |||
| 3 | import ( | ||
| 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 | |||
| 14 | type definitionListParser struct { | ||
| 15 | } | ||
| 16 | |||
| 17 | var defaultDefinitionListParser = &definitionListParser{} | ||
| 18 | |||
| 19 | // NewDefinitionListParser return a new parser.BlockParser that | ||
| 20 | // can parse PHP Markdown Extra Definition lists. | ||
| 21 | func NewDefinitionListParser() parser.BlockParser { | ||
| 22 | return defaultDefinitionListParser | ||
| 23 | } | ||
| 24 | |||
| 25 | func (b *definitionListParser) Trigger() []byte { | ||
| 26 | return []byte{':'} | ||
| 27 | } | ||
| 28 | |||
| 29 | func (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 | |||
| 74 | func (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 | |||
| 89 | func (b *definitionListParser) Close(node gast.Node, reader text.Reader, pc parser.Context) { | ||
| 90 | // nothing to do | ||
| 91 | } | ||
| 92 | |||
| 93 | func (b *definitionListParser) CanInterruptParagraph() bool { | ||
| 94 | return true | ||
| 95 | } | ||
| 96 | |||
| 97 | func (b *definitionListParser) CanAcceptIndentedLine() bool { | ||
| 98 | return false | ||
| 99 | } | ||
| 100 | |||
| 101 | type definitionDescriptionParser struct { | ||
| 102 | } | ||
| 103 | |||
| 104 | var defaultDefinitionDescriptionParser = &definitionDescriptionParser{} | ||
| 105 | |||
| 106 | // NewDefinitionDescriptionParser return a new parser.BlockParser that | ||
| 107 | // can parse definition description starts with ':'. | ||
| 108 | func NewDefinitionDescriptionParser() parser.BlockParser { | ||
| 109 | return defaultDefinitionDescriptionParser | ||
| 110 | } | ||
| 111 | |||
| 112 | func (b *definitionDescriptionParser) Trigger() []byte { | ||
| 113 | return []byte{':'} | ||
| 114 | } | ||
| 115 | |||
| 116 | func (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 | |||
| 146 | func (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 | |||
| 152 | func (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 | |||
| 167 | func (b *definitionDescriptionParser) CanInterruptParagraph() bool { | ||
| 168 | return true | ||
| 169 | } | ||
| 170 | |||
| 171 | func (b *definitionDescriptionParser) CanAcceptIndentedLine() bool { | ||
| 172 | return false | ||
| 173 | } | ||
| 174 | |||
| 175 | // DefinitionListHTMLRenderer is a renderer.NodeRenderer implementation that | ||
| 176 | // renders DefinitionList nodes. | ||
| 177 | type DefinitionListHTMLRenderer struct { | ||
| 178 | html.Config | ||
| 179 | } | ||
| 180 | |||
| 181 | // NewDefinitionListHTMLRenderer returns a new DefinitionListHTMLRenderer. | ||
| 182 | func 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. | ||
| 193 | func (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. | ||
| 200 | var DefinitionListAttributeFilter = html.GlobalAttributeFilter | ||
| 201 | |||
| 202 | func (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. | ||
| 218 | var DefinitionTermAttributeFilter = html.GlobalAttributeFilter | ||
| 219 | |||
| 220 | func (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. | ||
| 236 | var DefinitionDescriptionAttributeFilter = html.GlobalAttributeFilter | ||
| 237 | |||
| 238 | func (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 | |||
| 256 | type definitionList struct { | ||
| 257 | } | ||
| 258 | |||
| 259 | // DefinitionList is an extension that allow you to use PHP Markdown Extra Definition lists. | ||
| 260 | var DefinitionList = &definitionList{} | ||
| 261 | |||
| 262 | func (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 @@ | |||
| 1 | package extension | ||
| 2 | |||
| 3 | import ( | ||
| 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 | |||
| 18 | var footnoteListKey = parser.NewContextKey() | ||
| 19 | var footnoteLinkListKey = parser.NewContextKey() | ||
| 20 | |||
| 21 | type footnoteBlockParser struct { | ||
| 22 | } | ||
| 23 | |||
| 24 | var defaultFootnoteBlockParser = &footnoteBlockParser{} | ||
| 25 | |||
| 26 | // NewFootnoteBlockParser returns a new parser.BlockParser that can parse | ||
| 27 | // footnotes of the Markdown(PHP Markdown Extra) text. | ||
| 28 | func NewFootnoteBlockParser() parser.BlockParser { | ||
| 29 | return defaultFootnoteBlockParser | ||
| 30 | } | ||
| 31 | |||
| 32 | func (b *footnoteBlockParser) Trigger() []byte { | ||
| 33 | return []byte{'['} | ||
| 34 | } | ||
| 35 | |||
| 36 | func (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 | |||
| 74 | func (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 | |||
| 87 | func (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 | |||
| 100 | func (b *footnoteBlockParser) CanInterruptParagraph() bool { | ||
| 101 | return true | ||
| 102 | } | ||
| 103 | |||
| 104 | func (b *footnoteBlockParser) CanAcceptIndentedLine() bool { | ||
| 105 | return false | ||
| 106 | } | ||
| 107 | |||
| 108 | type footnoteParser struct { | ||
| 109 | } | ||
| 110 | |||
| 111 | var defaultFootnoteParser = &footnoteParser{} | ||
| 112 | |||
| 113 | // NewFootnoteParser returns a new parser.InlineParser that can parse | ||
| 114 | // footnote links of the Markdown(PHP Markdown Extra) text. | ||
| 115 | func NewFootnoteParser() parser.InlineParser { | ||
| 116 | return defaultFootnoteParser | ||
| 117 | } | ||
| 118 | |||
| 119 | func (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 | |||
| 125 | func (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 | |||
| 186 | type footnoteASTTransformer struct { | ||
| 187 | } | ||
| 188 | |||
| 189 | var defaultFootnoteASTTransformer = &footnoteASTTransformer{} | ||
| 190 | |||
| 191 | // NewFootnoteASTTransformer returns a new parser.ASTTransformer that | ||
| 192 | // insert a footnote list to the last of the document. | ||
| 193 | func NewFootnoteASTTransformer() parser.ASTTransformer { | ||
| 194 | return defaultFootnoteASTTransformer | ||
| 195 | } | ||
| 196 | |||
| 197 | func (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). | ||
| 279 | type 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. | ||
| 305 | type 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. | ||
| 312 | func 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("↩︎"), | ||
| 320 | } | ||
| 321 | } | ||
| 322 | |||
| 323 | // SetOption implements renderer.SetOptioner. | ||
| 324 | func (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 | |||
| 345 | type withFootnoteHTMLOptions struct { | ||
| 346 | value []html.Option | ||
| 347 | } | ||
| 348 | |||
| 349 | func (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 | |||
| 357 | func (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. | ||
| 366 | func WithFootnoteHTMLOptions(opts ...html.Option) FootnoteOption { | ||
| 367 | return &withFootnoteHTMLOptions{opts} | ||
| 368 | } | ||
| 369 | |||
| 370 | const optFootnoteIDPrefix renderer.OptionName = "FootnoteIDPrefix" | ||
| 371 | |||
| 372 | type withFootnoteIDPrefix struct { | ||
| 373 | value []byte | ||
| 374 | } | ||
| 375 | |||
| 376 | func (o *withFootnoteIDPrefix) SetConfig(c *renderer.Config) { | ||
| 377 | c.Options[optFootnoteIDPrefix] = o.value | ||
| 378 | } | ||
| 379 | |||
| 380 | func (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. | ||
| 385 | func WithFootnoteIDPrefix(a []byte) FootnoteOption { | ||
| 386 | return &withFootnoteIDPrefix{a} | ||
| 387 | } | ||
| 388 | |||
| 389 | const optFootnoteIDPrefixFunction renderer.OptionName = "FootnoteIDPrefixFunction" | ||
| 390 | |||
| 391 | type withFootnoteIDPrefixFunction struct { | ||
| 392 | value func(gast.Node) []byte | ||
| 393 | } | ||
| 394 | |||
| 395 | func (o *withFootnoteIDPrefixFunction) SetConfig(c *renderer.Config) { | ||
| 396 | c.Options[optFootnoteIDPrefixFunction] = o.value | ||
| 397 | } | ||
| 398 | |||
| 399 | func (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. | ||
| 404 | func WithFootnoteIDPrefixFunction(a func(gast.Node) []byte) FootnoteOption { | ||
| 405 | return &withFootnoteIDPrefixFunction{a} | ||
| 406 | } | ||
| 407 | |||
| 408 | const optFootnoteLinkTitle renderer.OptionName = "FootnoteLinkTitle" | ||
| 409 | |||
| 410 | type withFootnoteLinkTitle struct { | ||
| 411 | value []byte | ||
| 412 | } | ||
| 413 | |||
| 414 | func (o *withFootnoteLinkTitle) SetConfig(c *renderer.Config) { | ||
| 415 | c.Options[optFootnoteLinkTitle] = o.value | ||
| 416 | } | ||
| 417 | |||
| 418 | func (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. | ||
| 423 | func WithFootnoteLinkTitle(a []byte) FootnoteOption { | ||
| 424 | return &withFootnoteLinkTitle{a} | ||
| 425 | } | ||
| 426 | |||
| 427 | const optFootnoteBacklinkTitle renderer.OptionName = "FootnoteBacklinkTitle" | ||
| 428 | |||
| 429 | type withFootnoteBacklinkTitle struct { | ||
| 430 | value []byte | ||
| 431 | } | ||
| 432 | |||
| 433 | func (o *withFootnoteBacklinkTitle) SetConfig(c *renderer.Config) { | ||
| 434 | c.Options[optFootnoteBacklinkTitle] = o.value | ||
| 435 | } | ||
| 436 | |||
| 437 | func (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. | ||
| 442 | func WithFootnoteBacklinkTitle(a []byte) FootnoteOption { | ||
| 443 | return &withFootnoteBacklinkTitle{a} | ||
| 444 | } | ||
| 445 | |||
| 446 | const optFootnoteLinkClass renderer.OptionName = "FootnoteLinkClass" | ||
| 447 | |||
| 448 | type withFootnoteLinkClass struct { | ||
| 449 | value []byte | ||
| 450 | } | ||
| 451 | |||
| 452 | func (o *withFootnoteLinkClass) SetConfig(c *renderer.Config) { | ||
| 453 | c.Options[optFootnoteLinkClass] = o.value | ||
| 454 | } | ||
| 455 | |||
| 456 | func (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. | ||
| 461 | func WithFootnoteLinkClass(a []byte) FootnoteOption { | ||
| 462 | return &withFootnoteLinkClass{a} | ||
| 463 | } | ||
| 464 | |||
| 465 | const optFootnoteBacklinkClass renderer.OptionName = "FootnoteBacklinkClass" | ||
| 466 | |||
| 467 | type withFootnoteBacklinkClass struct { | ||
| 468 | value []byte | ||
| 469 | } | ||
| 470 | |||
| 471 | func (o *withFootnoteBacklinkClass) SetConfig(c *renderer.Config) { | ||
| 472 | c.Options[optFootnoteBacklinkClass] = o.value | ||
| 473 | } | ||
| 474 | |||
| 475 | func (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. | ||
| 480 | func WithFootnoteBacklinkClass(a []byte) FootnoteOption { | ||
| 481 | return &withFootnoteBacklinkClass{a} | ||
| 482 | } | ||
| 483 | |||
| 484 | const optFootnoteBacklinkHTML renderer.OptionName = "FootnoteBacklinkHTML" | ||
| 485 | |||
| 486 | type withFootnoteBacklinkHTML struct { | ||
| 487 | value []byte | ||
| 488 | } | ||
| 489 | |||
| 490 | func (o *withFootnoteBacklinkHTML) SetConfig(c *renderer.Config) { | ||
| 491 | c.Options[optFootnoteBacklinkHTML] = o.value | ||
| 492 | } | ||
| 493 | |||
| 494 | func (o *withFootnoteBacklinkHTML) SetFootnoteOption(c *FootnoteConfig) { | ||
| 495 | c.BacklinkHTML = o.value | ||
| 496 | } | ||
| 497 | |||
| 498 | // WithFootnoteBacklinkHTML is an HTML content for footnote backlinks. | ||
| 499 | func WithFootnoteBacklinkHTML(a []byte) FootnoteOption { | ||
| 500 | return &withFootnoteBacklinkHTML{a} | ||
| 501 | } | ||
| 502 | |||
| 503 | // FootnoteHTMLRenderer is a renderer.NodeRenderer implementation that | ||
| 504 | // renders FootnoteLink nodes. | ||
| 505 | type FootnoteHTMLRenderer struct { | ||
| 506 | FootnoteConfig | ||
| 507 | } | ||
| 508 | |||
| 509 | // NewFootnoteHTMLRenderer returns a new FootnoteHTMLRenderer. | ||
| 510 | func 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. | ||
| 521 | func (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 | |||
| 528 | func (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 | |||
| 559 | func (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(` <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 | |||
| 584 | func (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 | |||
| 603 | func (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 | |||
| 623 | func (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 | |||
| 633 | func 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 | |||
| 656 | type footnote struct { | ||
| 657 | options []FootnoteOption | ||
| 658 | } | ||
| 659 | |||
| 660 | // Footnote is an extension that allow you to use PHP Markdown Extra Footnotes. | ||
| 661 | var Footnote = &footnote{ | ||
| 662 | options: []FootnoteOption{}, | ||
| 663 | } | ||
| 664 | |||
| 665 | // NewFootnote returns a new extension with given options. | ||
| 666 | func NewFootnote(opts ...FootnoteOption) goldmark.Extender { | ||
| 667 | return &footnote{ | ||
| 668 | options: opts, | ||
| 669 | } | ||
| 670 | } | ||
| 671 | |||
| 672 | func (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 @@ | |||
| 1 | package extension | ||
| 2 | |||
| 3 | import ( | ||
| 4 | "github.com/yuin/goldmark" | ||
| 5 | ) | ||
| 6 | |||
| 7 | type gfm struct { | ||
| 8 | } | ||
| 9 | |||
| 10 | // GFM is an extension that provides Github Flavored markdown functionalities. | ||
| 11 | var GFM = &gfm{} | ||
| 12 | |||
| 13 | func (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 @@ | |||
| 1 | package extension | ||
| 2 | |||
| 3 | import ( | ||
| 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 | |||
| 14 | var wwwURLRegxp = regexp.MustCompile(`^www\.[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-z]+(?:[/#?][-a-zA-Z0-9@:%_\+.~#!?&/=\(\);,'">\^{}\[\]` + "`" + `]*)?`) | ||
| 15 | |||
| 16 | var 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. | ||
| 20 | type LinkifyConfig struct { | ||
| 21 | AllowedProtocols [][]byte | ||
| 22 | URLRegexp *regexp.Regexp | ||
| 23 | WWWRegexp *regexp.Regexp | ||
| 24 | EmailRegexp *regexp.Regexp | ||
| 25 | } | ||
| 26 | |||
| 27 | const ( | ||
| 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. | ||
| 35 | func (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. | ||
| 49 | type LinkifyOption interface { | ||
| 50 | parser.Option | ||
| 51 | SetLinkifyOption(*LinkifyConfig) | ||
| 52 | } | ||
| 53 | |||
| 54 | type withLinkifyAllowedProtocols struct { | ||
| 55 | value [][]byte | ||
| 56 | } | ||
| 57 | |||
| 58 | func (o *withLinkifyAllowedProtocols) SetParserOption(c *parser.Config) { | ||
| 59 | c.Options[optLinkifyAllowedProtocols] = o.value | ||
| 60 | } | ||
| 61 | |||
| 62 | func (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:' . | ||
| 69 | func WithLinkifyAllowedProtocols(value [][]byte) LinkifyOption { | ||
| 70 | return &withLinkifyAllowedProtocols{ | ||
| 71 | value: value, | ||
| 72 | } | ||
| 73 | } | ||
| 74 | |||
| 75 | type withLinkifyURLRegexp struct { | ||
| 76 | value *regexp.Regexp | ||
| 77 | } | ||
| 78 | |||
| 79 | func (o *withLinkifyURLRegexp) SetParserOption(c *parser.Config) { | ||
| 80 | c.Options[optLinkifyURLRegexp] = o.value | ||
| 81 | } | ||
| 82 | |||
| 83 | func (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. | ||
| 89 | func 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.' . | ||
| 98 | type withLinkifyWWWRegexp struct { | ||
| 99 | value *regexp.Regexp | ||
| 100 | } | ||
| 101 | |||
| 102 | func (o *withLinkifyWWWRegexp) SetParserOption(c *parser.Config) { | ||
| 103 | c.Options[optLinkifyWWWRegexp] = o.value | ||
| 104 | } | ||
| 105 | |||
| 106 | func (o *withLinkifyWWWRegexp) SetLinkifyOption(p *LinkifyConfig) { | ||
| 107 | p.WWWRegexp = o.value | ||
| 108 | } | ||
| 109 | |||
| 110 | func 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. | ||
| 118 | type withLinkifyEmailRegexp struct { | ||
| 119 | value *regexp.Regexp | ||
| 120 | } | ||
| 121 | |||
| 122 | func (o *withLinkifyEmailRegexp) SetParserOption(c *parser.Config) { | ||
| 123 | c.Options[optLinkifyEmailRegexp] = o.value | ||
| 124 | } | ||
| 125 | |||
| 126 | func (o *withLinkifyEmailRegexp) SetLinkifyOption(p *LinkifyConfig) { | ||
| 127 | p.EmailRegexp = o.value | ||
| 128 | } | ||
| 129 | |||
| 130 | func WithLinkifyEmailRegexp(value *regexp.Regexp) LinkifyOption { | ||
| 131 | return &withLinkifyEmailRegexp{ | ||
| 132 | value: value, | ||
| 133 | } | ||
| 134 | } | ||
| 135 | |||
| 136 | type linkifyParser struct { | ||
| 137 | LinkifyConfig | ||
| 138 | } | ||
| 139 | |||
| 140 | // NewLinkifyParser return a new InlineParser can parse | ||
| 141 | // text that seems like a URL. | ||
| 142 | func 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 | |||
| 156 | func (s *linkifyParser) Trigger() []byte { | ||
| 157 | // ' ' indicates any white spaces and a line head | ||
| 158 | return []byte{' ', '*', '_', '~', '('} | ||
| 159 | } | ||
| 160 | |||
| 161 | var ( | ||
| 162 | protoHTTP = []byte("http:") | ||
| 163 | protoHTTPS = []byte("https:") | ||
| 164 | protoFTP = []byte("ftp:") | ||
| 165 | domainWWW = []byte("www.") | ||
| 166 | ) | ||
| 167 | |||
| 168 | func (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 | } | ||
| 285 | endfor: | ||
| 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 | |||
| 295 | func (s *linkifyParser) CloseBlock(parent ast.Node, pc parser.Context) { | ||
| 296 | // nothing to do | ||
| 297 | } | ||
| 298 | |||
| 299 | type linkify struct { | ||
| 300 | options []LinkifyOption | ||
| 301 | } | ||
| 302 | |||
| 303 | // Linkify is an extension that allow you to parse text that seems like a URL. | ||
| 304 | var Linkify = &linkify{} | ||
| 305 | |||
| 306 | func NewLinkify(opts ...LinkifyOption) goldmark.Extender { | ||
| 307 | return &linkify{ | ||
| 308 | options: opts, | ||
| 309 | } | ||
| 310 | } | ||
| 311 | |||
| 312 | func (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 @@ | |||
| 1 | package extension | ||
| 2 | |||
| 3 | import ( | ||
| 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 | |||
| 14 | type strikethroughDelimiterProcessor struct { | ||
| 15 | } | ||
| 16 | |||
| 17 | func (p *strikethroughDelimiterProcessor) IsDelimiter(b byte) bool { | ||
| 18 | return b == '~' | ||
| 19 | } | ||
| 20 | |||
| 21 | func (p *strikethroughDelimiterProcessor) CanOpenCloser(opener, closer *parser.Delimiter) bool { | ||
| 22 | return opener.Char == closer.Char | ||
| 23 | } | ||
| 24 | |||
| 25 | func (p *strikethroughDelimiterProcessor) OnMatch(consumes int) gast.Node { | ||
| 26 | return ast.NewStrikethrough() | ||
| 27 | } | ||
| 28 | |||
| 29 | var defaultStrikethroughDelimiterProcessor = &strikethroughDelimiterProcessor{} | ||
| 30 | |||
| 31 | type strikethroughParser struct { | ||
| 32 | } | ||
| 33 | |||
| 34 | var defaultStrikethroughParser = &strikethroughParser{} | ||
| 35 | |||
| 36 | // NewStrikethroughParser return a new InlineParser that parses | ||
| 37 | // strikethrough expressions. | ||
| 38 | func NewStrikethroughParser() parser.InlineParser { | ||
| 39 | return defaultStrikethroughParser | ||
| 40 | } | ||
| 41 | |||
| 42 | func (s *strikethroughParser) Trigger() []byte { | ||
| 43 | return []byte{'~'} | ||
| 44 | } | ||
| 45 | |||
| 46 | func (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 | |||
| 59 | func (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. | ||
| 65 | type StrikethroughHTMLRenderer struct { | ||
| 66 | html.Config | ||
| 67 | } | ||
| 68 | |||
| 69 | // NewStrikethroughHTMLRenderer returns a new StrikethroughHTMLRenderer. | ||
| 70 | func 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. | ||
| 81 | func (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. | ||
| 86 | var StrikethroughAttributeFilter = html.GlobalAttributeFilter | ||
| 87 | |||
| 88 | func (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 | |||
| 103 | type strikethrough struct { | ||
| 104 | } | ||
| 105 | |||
| 106 | // Strikethrough is an extension that allow you to use strikethrough expression like '~~text~~' . | ||
| 107 | var Strikethrough = &strikethrough{} | ||
| 108 | |||
| 109 | func (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 @@ | |||
| 1 | package extension | ||
| 2 | |||
| 3 | import ( | ||
| 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 | |||
| 18 | var escapedPipeCellListKey = parser.NewContextKey() | ||
| 19 | |||
| 20 | type 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. | ||
| 27 | type TableCellAlignMethod int | ||
| 28 | |||
| 29 | const ( | ||
| 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. | ||
| 48 | type 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. | ||
| 56 | type 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. | ||
| 63 | func NewTableConfig() TableConfig { | ||
| 64 | return TableConfig{ | ||
| 65 | Config: html.NewConfig(), | ||
| 66 | TableCellAlignMethod: TableCellAlignDefault, | ||
| 67 | } | ||
| 68 | } | ||
| 69 | |||
| 70 | // SetOption implements renderer.SetOptioner. | ||
| 71 | func (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 | |||
| 80 | type withTableHTMLOptions struct { | ||
| 81 | value []html.Option | ||
| 82 | } | ||
| 83 | |||
| 84 | func (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 | |||
| 92 | func (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. | ||
| 101 | func WithTableHTMLOptions(opts ...html.Option) TableOption { | ||
| 102 | return &withTableHTMLOptions{opts} | ||
| 103 | } | ||
| 104 | |||
| 105 | const optTableCellAlignMethod renderer.OptionName = "TableTableCellAlignMethod" | ||
| 106 | |||
| 107 | type withTableCellAlignMethod struct { | ||
| 108 | value TableCellAlignMethod | ||
| 109 | } | ||
| 110 | |||
| 111 | func (o *withTableCellAlignMethod) SetConfig(c *renderer.Config) { | ||
| 112 | c.Options[optTableCellAlignMethod] = o.value | ||
| 113 | } | ||
| 114 | |||
| 115 | func (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. | ||
| 120 | func WithTableCellAlignMethod(a TableCellAlignMethod) TableOption { | ||
| 121 | return &withTableCellAlignMethod{a} | ||
| 122 | } | ||
| 123 | |||
| 124 | func 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 | |||
| 136 | var tableDelimLeft = regexp.MustCompile(`^\s*\:\-+\s*$`) | ||
| 137 | var tableDelimRight = regexp.MustCompile(`^\s*\-+\:\s*$`) | ||
| 138 | var tableDelimCenter = regexp.MustCompile(`^\s*\:\-+\:\s*$`) | ||
| 139 | var tableDelimNone = regexp.MustCompile(`^\s*\-+\s*$`) | ||
| 140 | |||
| 141 | type tableParagraphTransformer struct { | ||
| 142 | } | ||
| 143 | |||
| 144 | var defaultTableParagraphTransformer = &tableParagraphTransformer{} | ||
| 145 | |||
| 146 | // NewTableParagraphTransformer returns a new ParagraphTransformer | ||
| 147 | // that can transform paragraphs into tables. | ||
| 148 | func NewTableParagraphTransformer() parser.ParagraphTransformer { | ||
| 149 | return defaultTableParagraphTransformer | ||
| 150 | } | ||
| 151 | |||
| 152 | func (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 | |||
| 184 | func (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 | |||
| 248 | func (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 | |||
| 279 | type tableASTTransformer struct { | ||
| 280 | } | ||
| 281 | |||
| 282 | var defaultTableASTTransformer = &tableASTTransformer{} | ||
| 283 | |||
| 284 | // NewTableASTTransformer returns a parser.ASTTransformer for tables. | ||
| 285 | func NewTableASTTransformer() parser.ASTTransformer { | ||
| 286 | return defaultTableASTTransformer | ||
| 287 | } | ||
| 288 | |||
| 289 | func (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. | ||
| 336 | type TableHTMLRenderer struct { | ||
| 337 | TableConfig | ||
| 338 | } | ||
| 339 | |||
| 340 | // NewTableHTMLRenderer returns a new TableHTMLRenderer. | ||
| 341 | func 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. | ||
| 352 | func (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. | ||
| 360 | var 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 | |||
| 372 | func (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. | ||
| 386 | var 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 | |||
| 394 | func (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. | ||
| 413 | var 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 | |||
| 421 | func (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. | ||
| 438 | var 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. | ||
| 460 | var 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 | |||
| 480 | func (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 | |||
| 528 | type table struct { | ||
| 529 | options []TableOption | ||
| 530 | } | ||
| 531 | |||
| 532 | // Table is an extension that allow you to use GFM tables . | ||
| 533 | var Table = &table{ | ||
| 534 | options: []TableOption{}, | ||
| 535 | } | ||
| 536 | |||
| 537 | // NewTable returns a new extension with given options. | ||
| 538 | func NewTable(opts ...TableOption) goldmark.Extender { | ||
| 539 | return &table{ | ||
| 540 | options: opts, | ||
| 541 | } | ||
| 542 | } | ||
| 543 | |||
| 544 | func (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 @@ | |||
| 1 | package extension | ||
| 2 | |||
| 3 | import ( | ||
| 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 | |||
| 15 | var taskListRegexp = regexp.MustCompile(`^\[([\sxX])\]\s*`) | ||
| 16 | |||
| 17 | type taskCheckBoxParser struct { | ||
| 18 | } | ||
| 19 | |||
| 20 | var 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. | ||
| 25 | func NewTaskCheckBoxParser() parser.InlineParser { | ||
| 26 | return defaultTaskCheckBoxParser | ||
| 27 | } | ||
| 28 | |||
| 29 | func (s *taskCheckBoxParser) Trigger() []byte { | ||
| 30 | return []byte{'['} | ||
| 31 | } | ||
| 32 | |||
| 33 | func (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 | |||
| 57 | func (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. | ||
| 63 | type TaskCheckBoxHTMLRenderer struct { | ||
| 64 | html.Config | ||
| 65 | } | ||
| 66 | |||
| 67 | // NewTaskCheckBoxHTMLRenderer returns a new TaskCheckBoxHTMLRenderer. | ||
| 68 | func 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. | ||
| 79 | func (r *TaskCheckBoxHTMLRenderer) RegisterFuncs(reg renderer.NodeRendererFuncRegisterer) { | ||
| 80 | reg.Register(ast.KindTaskCheckBox, r.renderTaskCheckBox) | ||
| 81 | } | ||
| 82 | |||
| 83 | func (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 | |||
| 102 | type taskList struct { | ||
| 103 | } | ||
| 104 | |||
| 105 | // TaskList is an extension that allow you to use GFM task lists. | ||
| 106 | var TaskList = &taskList{} | ||
| 107 | |||
| 108 | func (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 @@ | |||
| 1 | package extension | ||
| 2 | |||
| 3 | import ( | ||
| 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 | |||
| 13 | var uncloseCounterKey = parser.NewContextKey() | ||
| 14 | |||
| 15 | type unclosedCounter struct { | ||
| 16 | Single int | ||
| 17 | Double int | ||
| 18 | } | ||
| 19 | |||
| 20 | func (u *unclosedCounter) Reset() { | ||
| 21 | u.Single = 0 | ||
| 22 | u.Double = 0 | ||
| 23 | } | ||
| 24 | |||
| 25 | func 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. | ||
| 36 | type TypographicPunctuation int | ||
| 37 | |||
| 38 | const ( | ||
| 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. | ||
| 65 | type TypographerConfig struct { | ||
| 66 | Substitutions [][]byte | ||
| 67 | } | ||
| 68 | |||
| 69 | func newDefaultSubstitutions() [][]byte { | ||
| 70 | replacements := make([][]byte, typographicPunctuationMax) | ||
| 71 | replacements[LeftSingleQuote] = []byte("‘") | ||
| 72 | replacements[RightSingleQuote] = []byte("’") | ||
| 73 | replacements[LeftDoubleQuote] = []byte("“") | ||
| 74 | replacements[RightDoubleQuote] = []byte("”") | ||
| 75 | replacements[EnDash] = []byte("–") | ||
| 76 | replacements[EmDash] = []byte("—") | ||
| 77 | replacements[Ellipsis] = []byte("…") | ||
| 78 | replacements[LeftAngleQuote] = []byte("«") | ||
| 79 | replacements[RightAngleQuote] = []byte("»") | ||
| 80 | replacements[Apostrophe] = []byte("’") | ||
| 81 | |||
| 82 | return replacements | ||
| 83 | } | ||
| 84 | |||
| 85 | // SetOption implements SetOptioner. | ||
| 86 | func (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. | ||
| 94 | type TypographerOption interface { | ||
| 95 | parser.Option | ||
| 96 | SetTypographerOption(*TypographerConfig) | ||
| 97 | } | ||
| 98 | |||
| 99 | const optTypographicSubstitutions parser.OptionName = "TypographicSubstitutions" | ||
| 100 | |||
| 101 | // TypographicSubstitutions is a list of the substitutions for the Typographer extension. | ||
| 102 | type TypographicSubstitutions map[TypographicPunctuation][]byte | ||
| 103 | |||
| 104 | type withTypographicSubstitutions struct { | ||
| 105 | value [][]byte | ||
| 106 | } | ||
| 107 | |||
| 108 | func (o *withTypographicSubstitutions) SetParserOption(c *parser.Config) { | ||
| 109 | c.Options[optTypographicSubstitutions] = o.value | ||
| 110 | } | ||
| 111 | |||
| 112 | func (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. | ||
| 118 | func 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 | |||
| 127 | type typographerDelimiterProcessor struct { | ||
| 128 | } | ||
| 129 | |||
| 130 | func (p *typographerDelimiterProcessor) IsDelimiter(b byte) bool { | ||
| 131 | return b == '\'' || b == '"' | ||
| 132 | } | ||
| 133 | |||
| 134 | func (p *typographerDelimiterProcessor) CanOpenCloser(opener, closer *parser.Delimiter) bool { | ||
| 135 | return opener.Char == closer.Char | ||
| 136 | } | ||
| 137 | |||
| 138 | func (p *typographerDelimiterProcessor) OnMatch(consumes int) gast.Node { | ||
| 139 | return nil | ||
| 140 | } | ||
| 141 | |||
| 142 | var defaultTypographerDelimiterProcessor = &typographerDelimiterProcessor{} | ||
| 143 | |||
| 144 | type typographerParser struct { | ||
| 145 | TypographerConfig | ||
| 146 | } | ||
| 147 | |||
| 148 | // NewTypographerParser return a new InlineParser that parses | ||
| 149 | // typographer expressions. | ||
| 150 | func 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 | |||
| 162 | func (s *typographerParser) Trigger() []byte { | ||
| 163 | return []byte{'\'', '"', '-', '.', ',', '<', '>', '*', '['} | ||
| 164 | } | ||
| 165 | |||
| 166 | func (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 | |||
| 317 | func (s *typographerParser) CloseBlock(parent gast.Node, pc parser.Context) { | ||
| 318 | getUnclosedCounter(pc).Reset() | ||
| 319 | } | ||
| 320 | |||
| 321 | type typographer struct { | ||
| 322 | options []TypographerOption | ||
| 323 | } | ||
| 324 | |||
| 325 | // Typographer is an extension that replaces punctuations with typographic entities. | ||
| 326 | var Typographer = &typographer{} | ||
| 327 | |||
| 328 | // NewTypographer returns a new Extender that replaces punctuations with typographic entities. | ||
| 329 | func NewTypographer(opts ...TypographerOption) goldmark.Extender { | ||
| 330 | return &typographer{ | ||
| 331 | options: opts, | ||
| 332 | } | ||
| 333 | } | ||
| 334 | |||
| 335 | func (e *typographer) Extend(m goldmark.Markdown) { | ||
| 336 | m.Parser().AddOptions(parser.WithInlineParsers( | ||
| 337 | util.Prioritized(NewTypographerParser(e.options...), 9999), | ||
| 338 | )) | ||
| 339 | } | ||
