summaryrefslogtreecommitdiff
path: root/vendor/github.com/yuin/goldmark/extension
diff options
context:
space:
mode:
authorLibravatar Autumn! <86073772+autumnull@users.noreply.github.com>2022-12-16 11:20:22 +0000
committerLibravatar GitHub <noreply@github.com>2022-12-16 12:20:22 +0100
commiteb08529f35ce33ed98c34fb48013f0f4a5fc9635 (patch)
tree394fd774a943f5c33ce793c67b5865f2570b46c5 /vendor/github.com/yuin/goldmark/extension
parent[bugfix] use match-sorter for filtering domain blocks (#1270) (diff)
downloadgotosocial-eb08529f35ce33ed98c34fb48013f0f4a5fc9635.tar.xz
[chore/bugfix] Switch markdown from blackfriday to goldmark (#1267)
Co-authored-by: Autumn! <autumnull@posteo.net>
Diffstat (limited to 'vendor/github.com/yuin/goldmark/extension')
-rw-r--r--vendor/github.com/yuin/goldmark/extension/ast/definition_list.go83
-rw-r--r--vendor/github.com/yuin/goldmark/extension/ast/footnote.go138
-rw-r--r--vendor/github.com/yuin/goldmark/extension/ast/strikethrough.go29
-rw-r--r--vendor/github.com/yuin/goldmark/extension/ast/table.go157
-rw-r--r--vendor/github.com/yuin/goldmark/extension/ast/tasklist.go35
-rw-r--r--vendor/github.com/yuin/goldmark/extension/cjk.go51
-rw-r--r--vendor/github.com/yuin/goldmark/extension/definition_list.go270
-rw-r--r--vendor/github.com/yuin/goldmark/extension/footnote.go687
-rw-r--r--vendor/github.com/yuin/goldmark/extension/gfm.go18
-rw-r--r--vendor/github.com/yuin/goldmark/extension/linkify.go318
-rw-r--r--vendor/github.com/yuin/goldmark/extension/strikethrough.go116
-rw-r--r--vendor/github.com/yuin/goldmark/extension/table.go556
-rw-r--r--vendor/github.com/yuin/goldmark/extension/tasklist.go115
-rw-r--r--vendor/github.com/yuin/goldmark/extension/typographer.go339
14 files changed, 2912 insertions, 0 deletions
diff --git a/vendor/github.com/yuin/goldmark/extension/ast/definition_list.go b/vendor/github.com/yuin/goldmark/extension/ast/definition_list.go
new file mode 100644
index 000000000..1beffb3aa
--- /dev/null
+++ b/vendor/github.com/yuin/goldmark/extension/ast/definition_list.go
@@ -0,0 +1,83 @@
+package ast
+
+import (
+ gast "github.com/yuin/goldmark/ast"
+)
+
+// A DefinitionList struct represents a definition list of Markdown
+// (PHPMarkdownExtra) text.
+type DefinitionList struct {
+ gast.BaseBlock
+ Offset int
+ TemporaryParagraph *gast.Paragraph
+}
+
+// Dump implements Node.Dump.
+func (n *DefinitionList) Dump(source []byte, level int) {
+ gast.DumpHelper(n, source, level, nil, nil)
+}
+
+// KindDefinitionList is a NodeKind of the DefinitionList node.
+var KindDefinitionList = gast.NewNodeKind("DefinitionList")
+
+// Kind implements Node.Kind.
+func (n *DefinitionList) Kind() gast.NodeKind {
+ return KindDefinitionList
+}
+
+// NewDefinitionList returns a new DefinitionList node.
+func NewDefinitionList(offset int, para *gast.Paragraph) *DefinitionList {
+ return &DefinitionList{
+ Offset: offset,
+ TemporaryParagraph: para,
+ }
+}
+
+// A DefinitionTerm struct represents a definition list term of Markdown
+// (PHPMarkdownExtra) text.
+type DefinitionTerm struct {
+ gast.BaseBlock
+}
+
+// Dump implements Node.Dump.
+func (n *DefinitionTerm) Dump(source []byte, level int) {
+ gast.DumpHelper(n, source, level, nil, nil)
+}
+
+// KindDefinitionTerm is a NodeKind of the DefinitionTerm node.
+var KindDefinitionTerm = gast.NewNodeKind("DefinitionTerm")
+
+// Kind implements Node.Kind.
+func (n *DefinitionTerm) Kind() gast.NodeKind {
+ return KindDefinitionTerm
+}
+
+// NewDefinitionTerm returns a new DefinitionTerm node.
+func NewDefinitionTerm() *DefinitionTerm {
+ return &DefinitionTerm{}
+}
+
+// A DefinitionDescription struct represents a definition list description of Markdown
+// (PHPMarkdownExtra) text.
+type DefinitionDescription struct {
+ gast.BaseBlock
+ IsTight bool
+}
+
+// Dump implements Node.Dump.
+func (n *DefinitionDescription) Dump(source []byte, level int) {
+ gast.DumpHelper(n, source, level, nil, nil)
+}
+
+// KindDefinitionDescription is a NodeKind of the DefinitionDescription node.
+var KindDefinitionDescription = gast.NewNodeKind("DefinitionDescription")
+
+// Kind implements Node.Kind.
+func (n *DefinitionDescription) Kind() gast.NodeKind {
+ return KindDefinitionDescription
+}
+
+// NewDefinitionDescription returns a new DefinitionDescription node.
+func NewDefinitionDescription() *DefinitionDescription {
+ return &DefinitionDescription{}
+}
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 000000000..97fea4403
--- /dev/null
+++ b/vendor/github.com/yuin/goldmark/extension/ast/footnote.go
@@ -0,0 +1,138 @@
+package ast
+
+import (
+ "fmt"
+
+ gast "github.com/yuin/goldmark/ast"
+)
+
+// A FootnoteLink struct represents a link to a footnote of Markdown
+// (PHP Markdown Extra) text.
+type FootnoteLink struct {
+ gast.BaseInline
+ Index int
+ RefCount int
+ RefIndex int
+}
+
+// Dump implements Node.Dump.
+func (n *FootnoteLink) Dump(source []byte, level int) {
+ m := map[string]string{}
+ m["Index"] = fmt.Sprintf("%v", n.Index)
+ m["RefCount"] = fmt.Sprintf("%v", n.RefCount)
+ m["RefIndex"] = fmt.Sprintf("%v", n.RefIndex)
+ gast.DumpHelper(n, source, level, m, nil)
+}
+
+// KindFootnoteLink is a NodeKind of the FootnoteLink node.
+var KindFootnoteLink = gast.NewNodeKind("FootnoteLink")
+
+// Kind implements Node.Kind.
+func (n *FootnoteLink) Kind() gast.NodeKind {
+ return KindFootnoteLink
+}
+
+// NewFootnoteLink returns a new FootnoteLink node.
+func NewFootnoteLink(index int) *FootnoteLink {
+ return &FootnoteLink{
+ Index: index,
+ RefCount: 0,
+ RefIndex: 0,
+ }
+}
+
+// A FootnoteBacklink struct represents a link to a footnote of Markdown
+// (PHP Markdown Extra) text.
+type FootnoteBacklink struct {
+ gast.BaseInline
+ Index int
+ RefCount int
+ RefIndex int
+}
+
+// Dump implements Node.Dump.
+func (n *FootnoteBacklink) Dump(source []byte, level int) {
+ m := map[string]string{}
+ m["Index"] = fmt.Sprintf("%v", n.Index)
+ m["RefCount"] = fmt.Sprintf("%v", n.RefCount)
+ m["RefIndex"] = fmt.Sprintf("%v", n.RefIndex)
+ gast.DumpHelper(n, source, level, m, nil)
+}
+
+// KindFootnoteBacklink is a NodeKind of the FootnoteBacklink node.
+var KindFootnoteBacklink = gast.NewNodeKind("FootnoteBacklink")
+
+// Kind implements Node.Kind.
+func (n *FootnoteBacklink) Kind() gast.NodeKind {
+ return KindFootnoteBacklink
+}
+
+// NewFootnoteBacklink returns a new FootnoteBacklink node.
+func NewFootnoteBacklink(index int) *FootnoteBacklink {
+ return &FootnoteBacklink{
+ Index: index,
+ RefCount: 0,
+ RefIndex: 0,
+ }
+}
+
+// A Footnote struct represents a footnote of Markdown
+// (PHP Markdown Extra) text.
+type Footnote struct {
+ gast.BaseBlock
+ Ref []byte
+ Index int
+}
+
+// Dump implements Node.Dump.
+func (n *Footnote) Dump(source []byte, level int) {
+ m := map[string]string{}
+ m["Index"] = fmt.Sprintf("%v", n.Index)
+ m["Ref"] = fmt.Sprintf("%s", n.Ref)
+ gast.DumpHelper(n, source, level, m, nil)
+}
+
+// KindFootnote is a NodeKind of the Footnote node.
+var KindFootnote = gast.NewNodeKind("Footnote")
+
+// Kind implements Node.Kind.
+func (n *Footnote) Kind() gast.NodeKind {
+ return KindFootnote
+}
+
+// NewFootnote returns a new Footnote node.
+func NewFootnote(ref []byte) *Footnote {
+ return &Footnote{
+ Ref: ref,
+ Index: -1,
+ }
+}
+
+// A FootnoteList struct represents footnotes of Markdown
+// (PHP Markdown Extra) text.
+type FootnoteList struct {
+ gast.BaseBlock
+ Count int
+}
+
+// Dump implements Node.Dump.
+func (n *FootnoteList) Dump(source []byte, level int) {
+ m := map[string]string{}
+ m["Count"] = fmt.Sprintf("%v", n.Count)
+ gast.DumpHelper(n, source, level, m, nil)
+}
+
+// KindFootnoteList is a NodeKind of the FootnoteList node.
+var KindFootnoteList = gast.NewNodeKind("FootnoteList")
+
+// Kind implements Node.Kind.
+func (n *FootnoteList) Kind() gast.NodeKind {
+ return KindFootnoteList
+}
+
+// NewFootnoteList returns a new FootnoteList node.
+func NewFootnoteList() *FootnoteList {
+ return &FootnoteList{
+ Count: 0,
+ }
+}
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 000000000..a9216b72e
--- /dev/null
+++ b/vendor/github.com/yuin/goldmark/extension/ast/strikethrough.go
@@ -0,0 +1,29 @@
+// Package ast defines AST nodes that represents extension's elements
+package ast
+
+import (
+ gast "github.com/yuin/goldmark/ast"
+)
+
+// A Strikethrough struct represents a strikethrough of GFM text.
+type Strikethrough struct {
+ gast.BaseInline
+}
+
+// Dump implements Node.Dump.
+func (n *Strikethrough) Dump(source []byte, level int) {
+ gast.DumpHelper(n, source, level, nil, nil)
+}
+
+// KindStrikethrough is a NodeKind of the Strikethrough node.
+var KindStrikethrough = gast.NewNodeKind("Strikethrough")
+
+// Kind implements Node.Kind.
+func (n *Strikethrough) Kind() gast.NodeKind {
+ return KindStrikethrough
+}
+
+// NewStrikethrough returns a new Strikethrough node.
+func NewStrikethrough() *Strikethrough {
+ return &Strikethrough{}
+}
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 000000000..e9eff3ceb
--- /dev/null
+++ b/vendor/github.com/yuin/goldmark/extension/ast/table.go
@@ -0,0 +1,157 @@
+package ast
+
+import (
+ "fmt"
+ gast "github.com/yuin/goldmark/ast"
+ "strings"
+)
+
+// Alignment is a text alignment of table cells.
+type Alignment int
+
+const (
+ // AlignLeft indicates text should be left justified.
+ AlignLeft Alignment = iota + 1
+
+ // AlignRight indicates text should be right justified.
+ AlignRight
+
+ // AlignCenter indicates text should be centered.
+ AlignCenter
+
+ // AlignNone indicates text should be aligned by default manner.
+ AlignNone
+)
+
+func (a Alignment) String() string {
+ switch a {
+ case AlignLeft:
+ return "left"
+ case AlignRight:
+ return "right"
+ case AlignCenter:
+ return "center"
+ case AlignNone:
+ return "none"
+ }
+ return ""
+}
+
+// A Table struct represents a table of Markdown(GFM) text.
+type Table struct {
+ gast.BaseBlock
+
+ // Alignments returns alignments of the columns.
+ Alignments []Alignment
+}
+
+// Dump implements Node.Dump
+func (n *Table) Dump(source []byte, level int) {
+ gast.DumpHelper(n, source, level, nil, func(level int) {
+ indent := strings.Repeat(" ", level)
+ fmt.Printf("%sAlignments {\n", indent)
+ for i, alignment := range n.Alignments {
+ indent2 := strings.Repeat(" ", level+1)
+ fmt.Printf("%s%s", indent2, alignment.String())
+ if i != len(n.Alignments)-1 {
+ fmt.Println("")
+ }
+ }
+ fmt.Printf("\n%s}\n", indent)
+ })
+}
+
+// KindTable is a NodeKind of the Table node.
+var KindTable = gast.NewNodeKind("Table")
+
+// Kind implements Node.Kind.
+func (n *Table) Kind() gast.NodeKind {
+ return KindTable
+}
+
+// NewTable returns a new Table node.
+func NewTable() *Table {
+ return &Table{
+ Alignments: []Alignment{},
+ }
+}
+
+// A TableRow struct represents a table row of Markdown(GFM) text.
+type TableRow struct {
+ gast.BaseBlock
+ Alignments []Alignment
+}
+
+// Dump implements Node.Dump.
+func (n *TableRow) Dump(source []byte, level int) {
+ gast.DumpHelper(n, source, level, nil, nil)
+}
+
+// KindTableRow is a NodeKind of the TableRow node.
+var KindTableRow = gast.NewNodeKind("TableRow")
+
+// Kind implements Node.Kind.
+func (n *TableRow) Kind() gast.NodeKind {
+ return KindTableRow
+}
+
+// NewTableRow returns a new TableRow node.
+func NewTableRow(alignments []Alignment) *TableRow {
+ return &TableRow{Alignments: alignments}
+}
+
+// A TableHeader struct represents a table header of Markdown(GFM) text.
+type TableHeader struct {
+ gast.BaseBlock
+ Alignments []Alignment
+}
+
+// KindTableHeader is a NodeKind of the TableHeader node.
+var KindTableHeader = gast.NewNodeKind("TableHeader")
+
+// Kind implements Node.Kind.
+func (n *TableHeader) Kind() gast.NodeKind {
+ return KindTableHeader
+}
+
+// Dump implements Node.Dump.
+func (n *TableHeader) Dump(source []byte, level int) {
+ gast.DumpHelper(n, source, level, nil, nil)
+}
+
+// NewTableHeader returns a new TableHeader node.
+func NewTableHeader(row *TableRow) *TableHeader {
+ n := &TableHeader{}
+ for c := row.FirstChild(); c != nil; {
+ next := c.NextSibling()
+ n.AppendChild(n, c)
+ c = next
+ }
+ return n
+}
+
+// A TableCell struct represents a table cell of a Markdown(GFM) text.
+type TableCell struct {
+ gast.BaseBlock
+ Alignment Alignment
+}
+
+// Dump implements Node.Dump.
+func (n *TableCell) Dump(source []byte, level int) {
+ gast.DumpHelper(n, source, level, nil, nil)
+}
+
+// KindTableCell is a NodeKind of the TableCell node.
+var KindTableCell = gast.NewNodeKind("TableCell")
+
+// Kind implements Node.Kind.
+func (n *TableCell) Kind() gast.NodeKind {
+ return KindTableCell
+}
+
+// NewTableCell returns a new TableCell node.
+func NewTableCell() *TableCell {
+ return &TableCell{
+ Alignment: AlignNone,
+ }
+}
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 000000000..670cc1495
--- /dev/null
+++ b/vendor/github.com/yuin/goldmark/extension/ast/tasklist.go
@@ -0,0 +1,35 @@
+package ast
+
+import (
+ "fmt"
+ gast "github.com/yuin/goldmark/ast"
+)
+
+// A TaskCheckBox struct represents a checkbox of a task list.
+type TaskCheckBox struct {
+ gast.BaseInline
+ IsChecked bool
+}
+
+// Dump implements Node.Dump.
+func (n *TaskCheckBox) Dump(source []byte, level int) {
+ m := map[string]string{
+ "Checked": fmt.Sprintf("%v", n.IsChecked),
+ }
+ gast.DumpHelper(n, source, level, m, nil)
+}
+
+// KindTaskCheckBox is a NodeKind of the TaskCheckBox node.
+var KindTaskCheckBox = gast.NewNodeKind("TaskCheckBox")
+
+// Kind implements Node.Kind.
+func (n *TaskCheckBox) Kind() gast.NodeKind {
+ return KindTaskCheckBox
+}
+
+// NewTaskCheckBox returns a new TaskCheckBox node.
+func NewTaskCheckBox(checked bool) *TaskCheckBox {
+ return &TaskCheckBox{
+ IsChecked: checked,
+ }
+}
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 000000000..cb6f9553d
--- /dev/null
+++ b/vendor/github.com/yuin/goldmark/extension/cjk.go
@@ -0,0 +1,51 @@
+package extension
+
+import (
+ "github.com/yuin/goldmark"
+ "github.com/yuin/goldmark/parser"
+ "github.com/yuin/goldmark/renderer/html"
+)
+
+// A CJKOption sets options for CJK support mostly for HTML based renderers.
+type CJKOption func(*cjk)
+
+// WithEastAsianLineBreaks is a functional option that indicates whether softline breaks
+// between east asian wide characters should be ignored.
+func WithEastAsianLineBreaks() CJKOption {
+ return func(c *cjk) {
+ c.EastAsianLineBreaks = true
+ }
+}
+
+// WithEscapedSpace is a functional option that indicates that a '\' escaped half-space(0x20) should not be rendered.
+func WithEscapedSpace() CJKOption {
+ return func(c *cjk) {
+ c.EscapedSpace = true
+ }
+}
+
+type cjk struct {
+ EastAsianLineBreaks bool
+ EscapedSpace bool
+}
+
+var CJK = NewCJK(WithEastAsianLineBreaks(), WithEscapedSpace())
+
+// NewCJK returns a new extension with given options.
+func NewCJK(opts ...CJKOption) goldmark.Extender {
+ e := &cjk{}
+ for _, opt := range opts {
+ opt(e)
+ }
+ return e
+}
+
+func (e *cjk) Extend(m goldmark.Markdown) {
+ if e.EastAsianLineBreaks {
+ m.Renderer().AddOptions(html.WithEastAsianLineBreaks())
+ }
+ if e.EscapedSpace {
+ m.Renderer().AddOptions(html.WithWriter(html.NewWriter(html.WithEscapedSpace())))
+ m.Parser().AddOptions(parser.WithEscapedSpace())
+ }
+}
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 000000000..d2f5fecbe
--- /dev/null
+++ b/vendor/github.com/yuin/goldmark/extension/definition_list.go
@@ -0,0 +1,270 @@
+package extension
+
+import (
+ "github.com/yuin/goldmark"
+ gast "github.com/yuin/goldmark/ast"
+ "github.com/yuin/goldmark/extension/ast"
+ "github.com/yuin/goldmark/parser"
+ "github.com/yuin/goldmark/renderer"
+ "github.com/yuin/goldmark/renderer/html"
+ "github.com/yuin/goldmark/text"
+ "github.com/yuin/goldmark/util"
+)
+
+type definitionListParser struct {
+}
+
+var defaultDefinitionListParser = &definitionListParser{}
+
+// NewDefinitionListParser return a new parser.BlockParser that
+// can parse PHP Markdown Extra Definition lists.
+func NewDefinitionListParser() parser.BlockParser {
+ return defaultDefinitionListParser
+}
+
+func (b *definitionListParser) Trigger() []byte {
+ return []byte{':'}
+}
+
+func (b *definitionListParser) Open(parent gast.Node, reader text.Reader, pc parser.Context) (gast.Node, parser.State) {
+ if _, ok := parent.(*ast.DefinitionList); ok {
+ return nil, parser.NoChildren
+ }
+ line, _ := reader.PeekLine()
+ pos := pc.BlockOffset()
+ indent := pc.BlockIndent()
+ if pos < 0 || line[pos] != ':' || indent != 0 {
+ return nil, parser.NoChildren
+ }
+
+ last := parent.LastChild()
+ // need 1 or more spaces after ':'
+ w, _ := util.IndentWidth(line[pos+1:], pos+1)
+ if w < 1 {
+ return nil, parser.NoChildren
+ }
+ if w >= 8 { // starts with indented code
+ w = 5
+ }
+ w += pos + 1 /* 1 = ':' */
+
+ para, lastIsParagraph := last.(*gast.Paragraph)
+ var list *ast.DefinitionList
+ status := parser.HasChildren
+ var ok bool
+ if lastIsParagraph {
+ list, ok = last.PreviousSibling().(*ast.DefinitionList)
+ if ok { // is not first item
+ list.Offset = w
+ list.TemporaryParagraph = para
+ } else { // is first item
+ list = ast.NewDefinitionList(w, para)
+ status |= parser.RequireParagraph
+ }
+ } else if list, ok = last.(*ast.DefinitionList); ok { // multiple description
+ list.Offset = w
+ list.TemporaryParagraph = nil
+ } else {
+ return nil, parser.NoChildren
+ }
+
+ return list, status
+}
+
+func (b *definitionListParser) Continue(node gast.Node, reader text.Reader, pc parser.Context) parser.State {
+ line, _ := reader.PeekLine()
+ if util.IsBlank(line) {
+ return parser.Continue | parser.HasChildren
+ }
+ list, _ := node.(*ast.DefinitionList)
+ w, _ := util.IndentWidth(line, reader.LineOffset())
+ if w < list.Offset {
+ return parser.Close
+ }
+ pos, padding := util.IndentPosition(line, reader.LineOffset(), list.Offset)
+ reader.AdvanceAndSetPadding(pos, padding)
+ return parser.Continue | parser.HasChildren
+}
+
+func (b *definitionListParser) Close(node gast.Node, reader text.Reader, pc parser.Context) {
+ // nothing to do
+}
+
+func (b *definitionListParser) CanInterruptParagraph() bool {
+ return true
+}
+
+func (b *definitionListParser) CanAcceptIndentedLine() bool {
+ return false
+}
+
+type definitionDescriptionParser struct {
+}
+
+var defaultDefinitionDescriptionParser = &definitionDescriptionParser{}
+
+// NewDefinitionDescriptionParser return a new parser.BlockParser that
+// can parse definition description starts with ':'.
+func NewDefinitionDescriptionParser() parser.BlockParser {
+ return defaultDefinitionDescriptionParser
+}
+
+func (b *definitionDescriptionParser) Trigger() []byte {
+ return []byte{':'}
+}
+
+func (b *definitionDescriptionParser) Open(parent gast.Node, reader text.Reader, pc parser.Context) (gast.Node, parser.State) {
+ line, _ := reader.PeekLine()
+ pos := pc.BlockOffset()
+ indent := pc.BlockIndent()
+ if pos < 0 || line[pos] != ':' || indent != 0 {
+ return nil, parser.NoChildren
+ }
+ list, _ := parent.(*ast.DefinitionList)
+ if list == nil {
+ return nil, parser.NoChildren
+ }
+ para := list.TemporaryParagraph
+ list.TemporaryParagraph = nil
+ if para != nil {
+ lines := para.Lines()
+ l := lines.Len()
+ for i := 0; i < l; i++ {
+ term := ast.NewDefinitionTerm()
+ segment := lines.At(i)
+ term.Lines().Append(segment.TrimRightSpace(reader.Source()))
+ list.AppendChild(list, term)
+ }
+ para.Parent().RemoveChild(para.Parent(), para)
+ }
+ cpos, padding := util.IndentPosition(line[pos+1:], pos+1, list.Offset-pos-1)
+ reader.AdvanceAndSetPadding(cpos+1, padding)
+
+ return ast.NewDefinitionDescription(), parser.HasChildren
+}
+
+func (b *definitionDescriptionParser) Continue(node gast.Node, reader text.Reader, pc parser.Context) parser.State {
+ // definitionListParser detects end of the description.
+ // so this method will never be called.
+ return parser.Continue | parser.HasChildren
+}
+
+func (b *definitionDescriptionParser) Close(node gast.Node, reader text.Reader, pc parser.Context) {
+ desc := node.(*ast.DefinitionDescription)
+ desc.IsTight = !desc.HasBlankPreviousLines()
+ if desc.IsTight {
+ for gc := desc.FirstChild(); gc != nil; gc = gc.NextSibling() {
+ paragraph, ok := gc.(*gast.Paragraph)
+ if ok {
+ textBlock := gast.NewTextBlock()
+ textBlock.SetLines(paragraph.Lines())
+ desc.ReplaceChild(desc, paragraph, textBlock)
+ }
+ }
+ }
+}
+
+func (b *definitionDescriptionParser) CanInterruptParagraph() bool {
+ return true
+}
+
+func (b *definitionDescriptionParser) CanAcceptIndentedLine() bool {
+ return false
+}
+
+// DefinitionListHTMLRenderer is a renderer.NodeRenderer implementation that
+// renders DefinitionList nodes.
+type DefinitionListHTMLRenderer struct {
+ html.Config
+}
+
+// NewDefinitionListHTMLRenderer returns a new DefinitionListHTMLRenderer.
+func NewDefinitionListHTMLRenderer(opts ...html.Option) renderer.NodeRenderer {
+ r := &DefinitionListHTMLRenderer{
+ Config: html.NewConfig(),
+ }
+ for _, opt := range opts {
+ opt.SetHTMLOption(&r.Config)
+ }
+ return r
+}
+
+// RegisterFuncs implements renderer.NodeRenderer.RegisterFuncs.
+func (r *DefinitionListHTMLRenderer) RegisterFuncs(reg renderer.NodeRendererFuncRegisterer) {
+ reg.Register(ast.KindDefinitionList, r.renderDefinitionList)
+ reg.Register(ast.KindDefinitionTerm, r.renderDefinitionTerm)
+ reg.Register(ast.KindDefinitionDescription, r.renderDefinitionDescription)
+}
+
+// DefinitionListAttributeFilter defines attribute names which dl elements can have.
+var DefinitionListAttributeFilter = html.GlobalAttributeFilter
+
+func (r *DefinitionListHTMLRenderer) renderDefinitionList(w util.BufWriter, source []byte, n gast.Node, entering bool) (gast.WalkStatus, error) {
+ if entering {
+ if n.Attributes() != nil {
+ _, _ = w.WriteString("<dl")
+ html.RenderAttributes(w, n, DefinitionListAttributeFilter)
+ _, _ = w.WriteString(">\n")
+ } else {
+ _, _ = w.WriteString("<dl>\n")
+ }
+ } else {
+ _, _ = w.WriteString("</dl>\n")
+ }
+ return gast.WalkContinue, nil
+}
+
+// DefinitionTermAttributeFilter defines attribute names which dd elements can have.
+var DefinitionTermAttributeFilter = html.GlobalAttributeFilter
+
+func (r *DefinitionListHTMLRenderer) renderDefinitionTerm(w util.BufWriter, source []byte, n gast.Node, entering bool) (gast.WalkStatus, error) {
+ if entering {
+ if n.Attributes() != nil {
+ _, _ = w.WriteString("<dt")
+ html.RenderAttributes(w, n, DefinitionTermAttributeFilter)
+ _ = w.WriteByte('>')
+ } else {
+ _, _ = w.WriteString("<dt>")
+ }
+ } else {
+ _, _ = w.WriteString("</dt>\n")
+ }
+ return gast.WalkContinue, nil
+}
+
+// DefinitionDescriptionAttributeFilter defines attribute names which dd elements can have.
+var DefinitionDescriptionAttributeFilter = html.GlobalAttributeFilter
+
+func (r *DefinitionListHTMLRenderer) renderDefinitionDescription(w util.BufWriter, source []byte, node gast.Node, entering bool) (gast.WalkStatus, error) {
+ if entering {
+ n := node.(*ast.DefinitionDescription)
+ _, _ = w.WriteString("<dd")
+ if n.Attributes() != nil {
+ html.RenderAttributes(w, n, DefinitionDescriptionAttributeFilter)
+ }
+ if n.IsTight {
+ _, _ = w.WriteString(">")
+ } else {
+ _, _ = w.WriteString(">\n")
+ }
+ } else {
+ _, _ = w.WriteString("</dd>\n")
+ }
+ return gast.WalkContinue, nil
+}
+
+type definitionList struct {
+}
+
+// DefinitionList is an extension that allow you to use PHP Markdown Extra Definition lists.
+var DefinitionList = &definitionList{}
+
+func (e *definitionList) Extend(m goldmark.Markdown) {
+ m.Parser().AddOptions(parser.WithBlockParsers(
+ util.Prioritized(NewDefinitionListParser(), 101),
+ util.Prioritized(NewDefinitionDescriptionParser(), 102),
+ ))
+ m.Renderer().AddOptions(renderer.WithNodeRenderers(
+ util.Prioritized(NewDefinitionListHTMLRenderer(), 500),
+ ))
+}
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 000000000..09b6fa8b0
--- /dev/null
+++ b/vendor/github.com/yuin/goldmark/extension/footnote.go
@@ -0,0 +1,687 @@
+package extension
+
+import (
+ "bytes"
+ "fmt"
+ "strconv"
+
+ "github.com/yuin/goldmark"
+ gast "github.com/yuin/goldmark/ast"
+ "github.com/yuin/goldmark/extension/ast"
+ "github.com/yuin/goldmark/parser"
+ "github.com/yuin/goldmark/renderer"
+ "github.com/yuin/goldmark/renderer/html"
+ "github.com/yuin/goldmark/text"
+ "github.com/yuin/goldmark/util"
+)
+
+var footnoteListKey = parser.NewContextKey()
+var footnoteLinkListKey = parser.NewContextKey()
+
+type footnoteBlockParser struct {
+}
+
+var defaultFootnoteBlockParser = &footnoteBlockParser{}
+
+// NewFootnoteBlockParser returns a new parser.BlockParser that can parse
+// footnotes of the Markdown(PHP Markdown Extra) text.
+func NewFootnoteBlockParser() parser.BlockParser {
+ return defaultFootnoteBlockParser
+}
+
+func (b *footnoteBlockParser) Trigger() []byte {
+ return []byte{'['}
+}
+
+func (b *footnoteBlockParser) Open(parent gast.Node, reader text.Reader, pc parser.Context) (gast.Node, parser.State) {
+ line, segment := reader.PeekLine()
+ pos := pc.BlockOffset()
+ if pos < 0 || line[pos] != '[' {
+ return nil, parser.NoChildren
+ }
+ pos++
+ if pos > len(line)-1 || line[pos] != '^' {
+ return nil, parser.NoChildren
+ }
+ open := pos + 1
+ closes := 0
+ closure := util.FindClosure(line[pos+1:], '[', ']', false, false)
+ closes = pos + 1 + closure
+ next := closes + 1
+ if closure > -1 {
+ if next >= len(line) || line[next] != ':' {
+ return nil, parser.NoChildren
+ }
+ } else {
+ return nil, parser.NoChildren
+ }
+ padding := segment.Padding
+ label := reader.Value(text.NewSegment(segment.Start+open-padding, segment.Start+closes-padding))
+ if util.IsBlank(label) {
+ return nil, parser.NoChildren
+ }
+ item := ast.NewFootnote(label)
+
+ pos = next + 1 - padding
+ if pos >= len(line) {
+ reader.Advance(pos)
+ return item, parser.NoChildren
+ }
+ reader.AdvanceAndSetPadding(pos, padding)
+ return item, parser.HasChildren
+}
+
+func (b *footnoteBlockParser) Continue(node gast.Node, reader text.Reader, pc parser.Context) parser.State {
+ line, _ := reader.PeekLine()
+ if util.IsBlank(line) {
+ return parser.Continue | parser.HasChildren
+ }
+ childpos, padding := util.IndentPosition(line, reader.LineOffset(), 4)
+ if childpos < 0 {
+ return parser.Close
+ }
+ reader.AdvanceAndSetPadding(childpos, padding)
+ return parser.Continue | parser.HasChildren
+}
+
+func (b *footnoteBlockParser) Close(node gast.Node, reader text.Reader, pc parser.Context) {
+ var list *ast.FootnoteList
+ if tlist := pc.Get(footnoteListKey); tlist != nil {
+ list = tlist.(*ast.FootnoteList)
+ } else {
+ list = ast.NewFootnoteList()
+ pc.Set(footnoteListKey, list)
+ node.Parent().InsertBefore(node.Parent(), node, list)
+ }
+ node.Parent().RemoveChild(node.Parent(), node)
+ list.AppendChild(list, node)
+}
+
+func (b *footnoteBlockParser) CanInterruptParagraph() bool {
+ return true
+}
+
+func (b *footnoteBlockParser) CanAcceptIndentedLine() bool {
+ return false
+}
+
+type footnoteParser struct {
+}
+
+var defaultFootnoteParser = &footnoteParser{}
+
+// NewFootnoteParser returns a new parser.InlineParser that can parse
+// footnote links of the Markdown(PHP Markdown Extra) text.
+func NewFootnoteParser() parser.InlineParser {
+ return defaultFootnoteParser
+}
+
+func (s *footnoteParser) Trigger() []byte {
+ // footnote syntax probably conflict with the image syntax.
+ // So we need trigger this parser with '!'.
+ return []byte{'!', '['}
+}
+
+func (s *footnoteParser) Parse(parent gast.Node, block text.Reader, pc parser.Context) gast.Node {
+ line, segment := block.PeekLine()
+ pos := 1
+ if len(line) > 0 && line[0] == '!' {
+ pos++
+ }
+ if pos >= len(line) || line[pos] != '^' {
+ return nil
+ }
+ pos++
+ if pos >= len(line) {
+ return nil
+ }
+ open := pos
+ closure := util.FindClosure(line[pos:], '[', ']', false, false)
+ if closure < 0 {
+ return nil
+ }
+ closes := pos + closure
+ value := block.Value(text.NewSegment(segment.Start+open, segment.Start+closes))
+ block.Advance(closes + 1)
+
+ var list *ast.FootnoteList
+ if tlist := pc.Get(footnoteListKey); tlist != nil {
+ list = tlist.(*ast.FootnoteList)
+ }
+ if list == nil {
+ return nil
+ }
+ index := 0
+ for def := list.FirstChild(); def != nil; def = def.NextSibling() {
+ d := def.(*ast.Footnote)
+ if bytes.Equal(d.Ref, value) {
+ if d.Index < 0 {
+ list.Count += 1
+ d.Index = list.Count
+ }
+ index = d.Index
+ break
+ }
+ }
+ if index == 0 {
+ return nil
+ }
+
+ fnlink := ast.NewFootnoteLink(index)
+ var fnlist []*ast.FootnoteLink
+ if tmp := pc.Get(footnoteLinkListKey); tmp != nil {
+ fnlist = tmp.([]*ast.FootnoteLink)
+ } else {
+ fnlist = []*ast.FootnoteLink{}
+ pc.Set(footnoteLinkListKey, fnlist)
+ }
+ pc.Set(footnoteLinkListKey, append(fnlist, fnlink))
+ if line[0] == '!' {
+ parent.AppendChild(parent, gast.NewTextSegment(text.NewSegment(segment.Start, segment.Start+1)))
+ }
+
+ return fnlink
+}
+
+type footnoteASTTransformer struct {
+}
+
+var defaultFootnoteASTTransformer = &footnoteASTTransformer{}
+
+// NewFootnoteASTTransformer returns a new parser.ASTTransformer that
+// insert a footnote list to the last of the document.
+func NewFootnoteASTTransformer() parser.ASTTransformer {
+ return defaultFootnoteASTTransformer
+}
+
+func (a *footnoteASTTransformer) Transform(node *gast.Document, reader text.Reader, pc parser.Context) {
+ var list *ast.FootnoteList
+ var fnlist []*ast.FootnoteLink
+ if tmp := pc.Get(footnoteListKey); tmp != nil {
+ list = tmp.(*ast.FootnoteList)
+ }
+ if tmp := pc.Get(footnoteLinkListKey); tmp != nil {
+ fnlist = tmp.([]*ast.FootnoteLink)
+ }
+
+ pc.Set(footnoteListKey, nil)
+ pc.Set(footnoteLinkListKey, nil)
+
+ if list == nil {
+ return
+ }
+
+ counter := map[int]int{}
+ if fnlist != nil {
+ for _, fnlink := range fnlist {
+ if fnlink.Index >= 0 {
+ counter[fnlink.Index]++
+ }
+ }
+ refCounter := map[int]int{}
+ for _, fnlink := range fnlist {
+ fnlink.RefCount = counter[fnlink.Index]
+ if _, ok := refCounter[fnlink.Index]; !ok {
+ refCounter[fnlink.Index] = 0
+ }
+ fnlink.RefIndex = refCounter[fnlink.Index]
+ refCounter[fnlink.Index]++
+ }
+ }
+ for footnote := list.FirstChild(); footnote != nil; {
+ var container gast.Node = footnote
+ next := footnote.NextSibling()
+ if fc := container.LastChild(); fc != nil && gast.IsParagraph(fc) {
+ container = fc
+ }
+ fn := footnote.(*ast.Footnote)
+ index := fn.Index
+ if index < 0 {
+ list.RemoveChild(list, footnote)
+ } else {
+ refCount := counter[index]
+ backLink := ast.NewFootnoteBacklink(index)
+ backLink.RefCount = refCount
+ backLink.RefIndex = 0
+ container.AppendChild(container, backLink)
+ if refCount > 1 {
+ for i := 1; i < refCount; i++ {
+ backLink := ast.NewFootnoteBacklink(index)
+ backLink.RefCount = refCount
+ backLink.RefIndex = i
+ container.AppendChild(container, backLink)
+ }
+ }
+ }
+ footnote = next
+ }
+ list.SortChildren(func(n1, n2 gast.Node) int {
+ if n1.(*ast.Footnote).Index < n2.(*ast.Footnote).Index {
+ return -1
+ }
+ return 1
+ })
+ if list.Count <= 0 {
+ list.Parent().RemoveChild(list.Parent(), list)
+ return
+ }
+
+ node.AppendChild(node, list)
+}
+
+// FootnoteConfig holds configuration values for the footnote extension.
+//
+// Link* and Backlink* configurations have some variables:
+// Occurrances of “^^” in the string will be replaced by the
+// corresponding footnote number in the HTML output.
+// Occurrances of “%%” will be replaced by a number for the
+// reference (footnotes can have multiple references).
+type FootnoteConfig struct {
+ html.Config
+
+ // IDPrefix is a prefix for the id attributes generated by footnotes.
+ IDPrefix []byte
+
+ // IDPrefix is a function that determines the id attribute for given Node.
+ IDPrefixFunction func(gast.Node) []byte
+
+ // LinkTitle is an optional title attribute for footnote links.
+ LinkTitle []byte
+
+ // BacklinkTitle is an optional title attribute for footnote backlinks.
+ BacklinkTitle []byte
+
+ // LinkClass is a class for footnote links.
+ LinkClass []byte
+
+ // BacklinkClass is a class for footnote backlinks.
+ BacklinkClass []byte
+
+ // BacklinkHTML is an HTML content for footnote backlinks.
+ BacklinkHTML []byte
+}
+
+// FootnoteOption interface is a functional option interface for the extension.
+type FootnoteOption interface {
+ renderer.Option
+ // SetFootnoteOption sets given option to the extension.
+ SetFootnoteOption(*FootnoteConfig)
+}
+
+// NewFootnoteConfig returns a new Config with defaults.
+func NewFootnoteConfig() FootnoteConfig {
+ return FootnoteConfig{
+ Config: html.NewConfig(),
+ LinkTitle: []byte(""),
+ BacklinkTitle: []byte(""),
+ LinkClass: []byte("footnote-ref"),
+ BacklinkClass: []byte("footnote-backref"),
+ BacklinkHTML: []byte("&#x21a9;&#xfe0e;"),
+ }
+}
+
+// SetOption implements renderer.SetOptioner.
+func (c *FootnoteConfig) SetOption(name renderer.OptionName, value interface{}) {
+ switch name {
+ case optFootnoteIDPrefixFunction:
+ c.IDPrefixFunction = value.(func(gast.Node) []byte)
+ case optFootnoteIDPrefix:
+ c.IDPrefix = value.([]byte)
+ case optFootnoteLinkTitle:
+ c.LinkTitle = value.([]byte)
+ case optFootnoteBacklinkTitle:
+ c.BacklinkTitle = value.([]byte)
+ case optFootnoteLinkClass:
+ c.LinkClass = value.([]byte)
+ case optFootnoteBacklinkClass:
+ c.BacklinkClass = value.([]byte)
+ case optFootnoteBacklinkHTML:
+ c.BacklinkHTML = value.([]byte)
+ default:
+ c.Config.SetOption(name, value)
+ }
+}
+
+type withFootnoteHTMLOptions struct {
+ value []html.Option
+}
+
+func (o *withFootnoteHTMLOptions) SetConfig(c *renderer.Config) {
+ if o.value != nil {
+ for _, v := range o.value {
+ v.(renderer.Option).SetConfig(c)
+ }
+ }
+}
+
+func (o *withFootnoteHTMLOptions) SetFootnoteOption(c *FootnoteConfig) {
+ if o.value != nil {
+ for _, v := range o.value {
+ v.SetHTMLOption(&c.Config)
+ }
+ }
+}
+
+// WithFootnoteHTMLOptions is functional option that wraps goldmark HTMLRenderer options.
+func WithFootnoteHTMLOptions(opts ...html.Option) FootnoteOption {
+ return &withFootnoteHTMLOptions{opts}
+}
+
+const optFootnoteIDPrefix renderer.OptionName = "FootnoteIDPrefix"
+
+type withFootnoteIDPrefix struct {
+ value []byte
+}
+
+func (o *withFootnoteIDPrefix) SetConfig(c *renderer.Config) {
+ c.Options[optFootnoteIDPrefix] = o.value
+}
+
+func (o *withFootnoteIDPrefix) SetFootnoteOption(c *FootnoteConfig) {
+ c.IDPrefix = o.value
+}
+
+// WithFootnoteIDPrefix is a functional option that is a prefix for the id attributes generated by footnotes.
+func WithFootnoteIDPrefix(a []byte) FootnoteOption {
+ return &withFootnoteIDPrefix{a}
+}
+
+const optFootnoteIDPrefixFunction renderer.OptionName = "FootnoteIDPrefixFunction"
+
+type withFootnoteIDPrefixFunction struct {
+ value func(gast.Node) []byte
+}
+
+func (o *withFootnoteIDPrefixFunction) SetConfig(c *renderer.Config) {
+ c.Options[optFootnoteIDPrefixFunction] = o.value
+}
+
+func (o *withFootnoteIDPrefixFunction) SetFootnoteOption(c *FootnoteConfig) {
+ c.IDPrefixFunction = o.value
+}
+
+// WithFootnoteIDPrefixFunction is a functional option that is a prefix for the id attributes generated by footnotes.
+func WithFootnoteIDPrefixFunction(a func(gast.Node) []byte) FootnoteOption {
+ return &withFootnoteIDPrefixFunction{a}
+}
+
+const optFootnoteLinkTitle renderer.OptionName = "FootnoteLinkTitle"
+
+type withFootnoteLinkTitle struct {
+ value []byte
+}
+
+func (o *withFootnoteLinkTitle) SetConfig(c *renderer.Config) {
+ c.Options[optFootnoteLinkTitle] = o.value
+}
+
+func (o *withFootnoteLinkTitle) SetFootnoteOption(c *FootnoteConfig) {
+ c.LinkTitle = o.value
+}
+
+// WithFootnoteLinkTitle is a functional option that is an optional title attribute for footnote links.
+func WithFootnoteLinkTitle(a []byte) FootnoteOption {
+ return &withFootnoteLinkTitle{a}
+}
+
+const optFootnoteBacklinkTitle renderer.OptionName = "FootnoteBacklinkTitle"
+
+type withFootnoteBacklinkTitle struct {
+ value []byte
+}
+
+func (o *withFootnoteBacklinkTitle) SetConfig(c *renderer.Config) {
+ c.Options[optFootnoteBacklinkTitle] = o.value
+}
+
+func (o *withFootnoteBacklinkTitle) SetFootnoteOption(c *FootnoteConfig) {
+ c.BacklinkTitle = o.value
+}
+
+// WithFootnoteBacklinkTitle is a functional option that is an optional title attribute for footnote backlinks.
+func WithFootnoteBacklinkTitle(a []byte) FootnoteOption {
+ return &withFootnoteBacklinkTitle{a}
+}
+
+const optFootnoteLinkClass renderer.OptionName = "FootnoteLinkClass"
+
+type withFootnoteLinkClass struct {
+ value []byte
+}
+
+func (o *withFootnoteLinkClass) SetConfig(c *renderer.Config) {
+ c.Options[optFootnoteLinkClass] = o.value
+}
+
+func (o *withFootnoteLinkClass) SetFootnoteOption(c *FootnoteConfig) {
+ c.LinkClass = o.value
+}
+
+// WithFootnoteLinkClass is a functional option that is a class for footnote links.
+func WithFootnoteLinkClass(a []byte) FootnoteOption {
+ return &withFootnoteLinkClass{a}
+}
+
+const optFootnoteBacklinkClass renderer.OptionName = "FootnoteBacklinkClass"
+
+type withFootnoteBacklinkClass struct {
+ value []byte
+}
+
+func (o *withFootnoteBacklinkClass) SetConfig(c *renderer.Config) {
+ c.Options[optFootnoteBacklinkClass] = o.value
+}
+
+func (o *withFootnoteBacklinkClass) SetFootnoteOption(c *FootnoteConfig) {
+ c.BacklinkClass = o.value
+}
+
+// WithFootnoteBacklinkClass is a functional option that is a class for footnote backlinks.
+func WithFootnoteBacklinkClass(a []byte) FootnoteOption {
+ return &withFootnoteBacklinkClass{a}
+}
+
+const optFootnoteBacklinkHTML renderer.OptionName = "FootnoteBacklinkHTML"
+
+type withFootnoteBacklinkHTML struct {
+ value []byte
+}
+
+func (o *withFootnoteBacklinkHTML) SetConfig(c *renderer.Config) {
+ c.Options[optFootnoteBacklinkHTML] = o.value
+}
+
+func (o *withFootnoteBacklinkHTML) SetFootnoteOption(c *FootnoteConfig) {
+ c.BacklinkHTML = o.value
+}
+
+// WithFootnoteBacklinkHTML is an HTML content for footnote backlinks.
+func WithFootnoteBacklinkHTML(a []byte) FootnoteOption {
+ return &withFootnoteBacklinkHTML{a}
+}
+
+// FootnoteHTMLRenderer is a renderer.NodeRenderer implementation that
+// renders FootnoteLink nodes.
+type FootnoteHTMLRenderer struct {
+ FootnoteConfig
+}
+
+// NewFootnoteHTMLRenderer returns a new FootnoteHTMLRenderer.
+func NewFootnoteHTMLRenderer(opts ...FootnoteOption) renderer.NodeRenderer {
+ r := &FootnoteHTMLRenderer{
+ FootnoteConfig: NewFootnoteConfig(),
+ }
+ for _, opt := range opts {
+ opt.SetFootnoteOption(&r.FootnoteConfig)
+ }
+ return r
+}
+
+// RegisterFuncs implements renderer.NodeRenderer.RegisterFuncs.
+func (r *FootnoteHTMLRenderer) RegisterFuncs(reg renderer.NodeRendererFuncRegisterer) {
+ reg.Register(ast.KindFootnoteLink, r.renderFootnoteLink)
+ reg.Register(ast.KindFootnoteBacklink, r.renderFootnoteBacklink)
+ reg.Register(ast.KindFootnote, r.renderFootnote)
+ reg.Register(ast.KindFootnoteList, r.renderFootnoteList)
+}
+
+func (r *FootnoteHTMLRenderer) renderFootnoteLink(w util.BufWriter, source []byte, node gast.Node, entering bool) (gast.WalkStatus, error) {
+ if entering {
+ n := node.(*ast.FootnoteLink)
+ is := strconv.Itoa(n.Index)
+ _, _ = w.WriteString(`<sup id="`)
+ _, _ = w.Write(r.idPrefix(node))
+ _, _ = w.WriteString(`fnref`)
+ if n.RefIndex > 0 {
+ _, _ = w.WriteString(fmt.Sprintf("%v", n.RefIndex))
+ }
+ _ = w.WriteByte(':')
+ _, _ = w.WriteString(is)
+ _, _ = w.WriteString(`"><a href="#`)
+ _, _ = w.Write(r.idPrefix(node))
+ _, _ = w.WriteString(`fn:`)
+ _, _ = w.WriteString(is)
+ _, _ = w.WriteString(`" class="`)
+ _, _ = w.Write(applyFootnoteTemplate(r.FootnoteConfig.LinkClass,
+ n.Index, n.RefCount))
+ if len(r.FootnoteConfig.LinkTitle) > 0 {
+ _, _ = w.WriteString(`" title="`)
+ _, _ = w.Write(util.EscapeHTML(applyFootnoteTemplate(r.FootnoteConfig.LinkTitle, n.Index, n.RefCount)))
+ }
+ _, _ = w.WriteString(`" role="doc-noteref">`)
+
+ _, _ = w.WriteString(is)
+ _, _ = w.WriteString(`</a></sup>`)
+ }
+ return gast.WalkContinue, nil
+}
+
+func (r *FootnoteHTMLRenderer) renderFootnoteBacklink(w util.BufWriter, source []byte, node gast.Node, entering bool) (gast.WalkStatus, error) {
+ if entering {
+ n := node.(*ast.FootnoteBacklink)
+ is := strconv.Itoa(n.Index)
+ _, _ = w.WriteString(`&#160;<a href="#`)
+ _, _ = w.Write(r.idPrefix(node))
+ _, _ = w.WriteString(`fnref`)
+ if n.RefIndex > 0 {
+ _, _ = w.WriteString(fmt.Sprintf("%v", n.RefIndex))
+ }
+ _ = w.WriteByte(':')
+ _, _ = w.WriteString(is)
+ _, _ = w.WriteString(`" class="`)
+ _, _ = w.Write(applyFootnoteTemplate(r.FootnoteConfig.BacklinkClass, n.Index, n.RefCount))
+ if len(r.FootnoteConfig.BacklinkTitle) > 0 {
+ _, _ = w.WriteString(`" title="`)
+ _, _ = w.Write(util.EscapeHTML(applyFootnoteTemplate(r.FootnoteConfig.BacklinkTitle, n.Index, n.RefCount)))
+ }
+ _, _ = w.WriteString(`" role="doc-backlink">`)
+ _, _ = w.Write(applyFootnoteTemplate(r.FootnoteConfig.BacklinkHTML, n.Index, n.RefCount))
+ _, _ = w.WriteString(`</a>`)
+ }
+ return gast.WalkContinue, nil
+}
+
+func (r *FootnoteHTMLRenderer) renderFootnote(w util.BufWriter, source []byte, node gast.Node, entering bool) (gast.WalkStatus, error) {
+ n := node.(*ast.Footnote)
+ is := strconv.Itoa(n.Index)
+ if entering {
+ _, _ = w.WriteString(`<li id="`)
+ _, _ = w.Write(r.idPrefix(node))
+ _, _ = w.WriteString(`fn:`)
+ _, _ = w.WriteString(is)
+ _, _ = w.WriteString(`"`)
+ if node.Attributes() != nil {
+ html.RenderAttributes(w, node, html.ListItemAttributeFilter)
+ }
+ _, _ = w.WriteString(">\n")
+ } else {
+ _, _ = w.WriteString("</li>\n")
+ }
+ return gast.WalkContinue, nil
+}
+
+func (r *FootnoteHTMLRenderer) renderFootnoteList(w util.BufWriter, source []byte, node gast.Node, entering bool) (gast.WalkStatus, error) {
+ if entering {
+ _, _ = w.WriteString(`<div class="footnotes" role="doc-endnotes"`)
+ if node.Attributes() != nil {
+ html.RenderAttributes(w, node, html.GlobalAttributeFilter)
+ }
+ _ = w.WriteByte('>')
+ if r.Config.XHTML {
+ _, _ = w.WriteString("\n<hr />\n")
+ } else {
+ _, _ = w.WriteString("\n<hr>\n")
+ }
+ _, _ = w.WriteString("<ol>\n")
+ } else {
+ _, _ = w.WriteString("</ol>\n")
+ _, _ = w.WriteString("</div>\n")
+ }
+ return gast.WalkContinue, nil
+}
+
+func (r *FootnoteHTMLRenderer) idPrefix(node gast.Node) []byte {
+ if r.FootnoteConfig.IDPrefix != nil {
+ return r.FootnoteConfig.IDPrefix
+ }
+ if r.FootnoteConfig.IDPrefixFunction != nil {
+ return r.FootnoteConfig.IDPrefixFunction(node)
+ }
+ return []byte("")
+}
+
+func applyFootnoteTemplate(b []byte, index, refCount int) []byte {
+ fast := true
+ for i, c := range b {
+ if i != 0 {
+ if b[i-1] == '^' && c == '^' {
+ fast = false
+ break
+ }
+ if b[i-1] == '%' && c == '%' {
+ fast = false
+ break
+ }
+ }
+ }
+ if fast {
+ return b
+ }
+ is := []byte(strconv.Itoa(index))
+ rs := []byte(strconv.Itoa(refCount))
+ ret := bytes.Replace(b, []byte("^^"), is, -1)
+ return bytes.Replace(ret, []byte("%%"), rs, -1)
+}
+
+type footnote struct {
+ options []FootnoteOption
+}
+
+// Footnote is an extension that allow you to use PHP Markdown Extra Footnotes.
+var Footnote = &footnote{
+ options: []FootnoteOption{},
+}
+
+// NewFootnote returns a new extension with given options.
+func NewFootnote(opts ...FootnoteOption) goldmark.Extender {
+ return &footnote{
+ options: opts,
+ }
+}
+
+func (e *footnote) Extend(m goldmark.Markdown) {
+ m.Parser().AddOptions(
+ parser.WithBlockParsers(
+ util.Prioritized(NewFootnoteBlockParser(), 999),
+ ),
+ parser.WithInlineParsers(
+ util.Prioritized(NewFootnoteParser(), 101),
+ ),
+ parser.WithASTTransformers(
+ util.Prioritized(NewFootnoteASTTransformer(), 999),
+ ),
+ )
+ m.Renderer().AddOptions(renderer.WithNodeRenderers(
+ util.Prioritized(NewFootnoteHTMLRenderer(e.options...), 500),
+ ))
+}
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 000000000..a570fbdb3
--- /dev/null
+++ b/vendor/github.com/yuin/goldmark/extension/gfm.go
@@ -0,0 +1,18 @@
+package extension
+
+import (
+ "github.com/yuin/goldmark"
+)
+
+type gfm struct {
+}
+
+// GFM is an extension that provides Github Flavored markdown functionalities.
+var GFM = &gfm{}
+
+func (e *gfm) Extend(m goldmark.Markdown) {
+ Linkify.Extend(m)
+ Table.Extend(m)
+ Strikethrough.Extend(m)
+ TaskList.Extend(m)
+}
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 000000000..2f046eb54
--- /dev/null
+++ b/vendor/github.com/yuin/goldmark/extension/linkify.go
@@ -0,0 +1,318 @@
+package extension
+
+import (
+ "bytes"
+ "regexp"
+
+ "github.com/yuin/goldmark"
+ "github.com/yuin/goldmark/ast"
+ "github.com/yuin/goldmark/parser"
+ "github.com/yuin/goldmark/text"
+ "github.com/yuin/goldmark/util"
+)
+
+var wwwURLRegxp = regexp.MustCompile(`^www\.[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-z]+(?:[/#?][-a-zA-Z0-9@:%_\+.~#!?&/=\(\);,'">\^{}\[\]` + "`" + `]*)?`)
+
+var urlRegexp = regexp.MustCompile(`^(?:http|https|ftp)://[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-z]+(?::\d+)?(?:[/#?][-a-zA-Z0-9@:%_+.~#$!?&/=\(\);,'">\^{}\[\]` + "`" + `]*)?`)
+
+// An LinkifyConfig struct is a data structure that holds configuration of the
+// Linkify extension.
+type LinkifyConfig struct {
+ AllowedProtocols [][]byte
+ URLRegexp *regexp.Regexp
+ WWWRegexp *regexp.Regexp
+ EmailRegexp *regexp.Regexp
+}
+
+const (
+ optLinkifyAllowedProtocols parser.OptionName = "LinkifyAllowedProtocols"
+ optLinkifyURLRegexp parser.OptionName = "LinkifyURLRegexp"
+ optLinkifyWWWRegexp parser.OptionName = "LinkifyWWWRegexp"
+ optLinkifyEmailRegexp parser.OptionName = "LinkifyEmailRegexp"
+)
+
+// SetOption implements SetOptioner.
+func (c *LinkifyConfig) SetOption(name parser.OptionName, value interface{}) {
+ switch name {
+ case optLinkifyAllowedProtocols:
+ c.AllowedProtocols = value.([][]byte)
+ case optLinkifyURLRegexp:
+ c.URLRegexp = value.(*regexp.Regexp)
+ case optLinkifyWWWRegexp:
+ c.WWWRegexp = value.(*regexp.Regexp)
+ case optLinkifyEmailRegexp:
+ c.EmailRegexp = value.(*regexp.Regexp)
+ }
+}
+
+// A LinkifyOption interface sets options for the LinkifyOption.
+type LinkifyOption interface {
+ parser.Option
+ SetLinkifyOption(*LinkifyConfig)
+}
+
+type withLinkifyAllowedProtocols struct {
+ value [][]byte
+}
+
+func (o *withLinkifyAllowedProtocols) SetParserOption(c *parser.Config) {
+ c.Options[optLinkifyAllowedProtocols] = o.value
+}
+
+func (o *withLinkifyAllowedProtocols) SetLinkifyOption(p *LinkifyConfig) {
+ p.AllowedProtocols = o.value
+}
+
+// WithLinkifyAllowedProtocols is a functional option that specify allowed
+// protocols in autolinks. Each protocol must end with ':' like
+// 'http:' .
+func WithLinkifyAllowedProtocols(value [][]byte) LinkifyOption {
+ return &withLinkifyAllowedProtocols{
+ value: value,
+ }
+}
+
+type withLinkifyURLRegexp struct {
+ value *regexp.Regexp
+}
+
+func (o *withLinkifyURLRegexp) SetParserOption(c *parser.Config) {
+ c.Options[optLinkifyURLRegexp] = o.value
+}
+
+func (o *withLinkifyURLRegexp) SetLinkifyOption(p *LinkifyConfig) {
+ p.URLRegexp = o.value
+}
+
+// WithLinkifyURLRegexp is a functional option that specify
+// a pattern of the URL including a protocol.
+func WithLinkifyURLRegexp(value *regexp.Regexp) LinkifyOption {
+ return &withLinkifyURLRegexp{
+ value: value,
+ }
+}
+
+// WithLinkifyWWWRegexp is a functional option that specify
+// a pattern of the URL without a protocol.
+// This pattern must start with 'www.' .
+type withLinkifyWWWRegexp struct {
+ value *regexp.Regexp
+}
+
+func (o *withLinkifyWWWRegexp) SetParserOption(c *parser.Config) {
+ c.Options[optLinkifyWWWRegexp] = o.value
+}
+
+func (o *withLinkifyWWWRegexp) SetLinkifyOption(p *LinkifyConfig) {
+ p.WWWRegexp = o.value
+}
+
+func WithLinkifyWWWRegexp(value *regexp.Regexp) LinkifyOption {
+ return &withLinkifyWWWRegexp{
+ value: value,
+ }
+}
+
+// WithLinkifyWWWRegexp is a functional otpion that specify
+// a pattern of the email address.
+type withLinkifyEmailRegexp struct {
+ value *regexp.Regexp
+}
+
+func (o *withLinkifyEmailRegexp) SetParserOption(c *parser.Config) {
+ c.Options[optLinkifyEmailRegexp] = o.value
+}
+
+func (o *withLinkifyEmailRegexp) SetLinkifyOption(p *LinkifyConfig) {
+ p.EmailRegexp = o.value
+}
+
+func WithLinkifyEmailRegexp(value *regexp.Regexp) LinkifyOption {
+ return &withLinkifyEmailRegexp{
+ value: value,
+ }
+}
+
+type linkifyParser struct {
+ LinkifyConfig
+}
+
+// NewLinkifyParser return a new InlineParser can parse
+// text that seems like a URL.
+func NewLinkifyParser(opts ...LinkifyOption) parser.InlineParser {
+ p := &linkifyParser{
+ LinkifyConfig: LinkifyConfig{
+ AllowedProtocols: nil,
+ URLRegexp: urlRegexp,
+ WWWRegexp: wwwURLRegxp,
+ },
+ }
+ for _, o := range opts {
+ o.SetLinkifyOption(&p.LinkifyConfig)
+ }
+ return p
+}
+
+func (s *linkifyParser) Trigger() []byte {
+ // ' ' indicates any white spaces and a line head
+ return []byte{' ', '*', '_', '~', '('}
+}
+
+var (
+ protoHTTP = []byte("http:")
+ protoHTTPS = []byte("https:")
+ protoFTP = []byte("ftp:")
+ domainWWW = []byte("www.")
+)
+
+func (s *linkifyParser) Parse(parent ast.Node, block text.Reader, pc parser.Context) ast.Node {
+ if pc.IsInLinkLabel() {
+ return nil
+ }
+ line, segment := block.PeekLine()
+ consumes := 0
+ start := segment.Start
+ c := line[0]
+ // advance if current position is not a line head.
+ if c == ' ' || c == '*' || c == '_' || c == '~' || c == '(' {
+ consumes++
+ start++
+ line = line[1:]
+ }
+
+ var m []int
+ var protocol []byte
+ var typ ast.AutoLinkType = ast.AutoLinkURL
+ if s.LinkifyConfig.AllowedProtocols == nil {
+ if bytes.HasPrefix(line, protoHTTP) || bytes.HasPrefix(line, protoHTTPS) || bytes.HasPrefix(line, protoFTP) {
+ m = s.LinkifyConfig.URLRegexp.FindSubmatchIndex(line)
+ }
+ } else {
+ for _, prefix := range s.LinkifyConfig.AllowedProtocols {
+ if bytes.HasPrefix(line, prefix) {
+ m = s.LinkifyConfig.URLRegexp.FindSubmatchIndex(line)
+ break
+ }
+ }
+ }
+ if m == nil && bytes.HasPrefix(line, domainWWW) {
+ m = s.LinkifyConfig.WWWRegexp.FindSubmatchIndex(line)
+ protocol = []byte("http")
+ }
+ if m != nil && m[0] != 0 {
+ m = nil
+ }
+ if m != nil && m[0] == 0 {
+ lastChar := line[m[1]-1]
+ if lastChar == '.' {
+ m[1]--
+ } else if lastChar == ')' {
+ closing := 0
+ for i := m[1] - 1; i >= m[0]; i-- {
+ if line[i] == ')' {
+ closing++
+ } else if line[i] == '(' {
+ closing--
+ }
+ }
+ if closing > 0 {
+ m[1] -= closing
+ }
+ } else if lastChar == ';' {
+ i := m[1] - 2
+ for ; i >= m[0]; i-- {
+ if util.IsAlphaNumeric(line[i]) {
+ continue
+ }
+ break
+ }
+ if i != m[1]-2 {
+ if line[i] == '&' {
+ m[1] -= m[1] - i
+ }
+ }
+ }
+ }
+ if m == nil {
+ if len(line) > 0 && util.IsPunct(line[0]) {
+ return nil
+ }
+ typ = ast.AutoLinkEmail
+ stop := -1
+ if s.LinkifyConfig.EmailRegexp == nil {
+ stop = util.FindEmailIndex(line)
+ } else {
+ m := s.LinkifyConfig.EmailRegexp.FindSubmatchIndex(line)
+ if m != nil && m[0] == 0 {
+ stop = m[1]
+ }
+ }
+ if stop < 0 {
+ return nil
+ }
+ at := bytes.IndexByte(line, '@')
+ m = []int{0, stop, at, stop - 1}
+ if m == nil || bytes.IndexByte(line[m[2]:m[3]], '.') < 0 {
+ return nil
+ }
+ lastChar := line[m[1]-1]
+ if lastChar == '.' {
+ m[1]--
+ }
+ if m[1] < len(line) {
+ nextChar := line[m[1]]
+ if nextChar == '-' || nextChar == '_' {
+ return nil
+ }
+ }
+ }
+ if m == nil {
+ return nil
+ }
+ if consumes != 0 {
+ s := segment.WithStop(segment.Start + 1)
+ ast.MergeOrAppendTextSegment(parent, s)
+ }
+ i := m[1] - 1
+ for ; i > 0; i-- {
+ c := line[i]
+ switch c {
+ case '?', '!', '.', ',', ':', '*', '_', '~':
+ default:
+ goto endfor
+ }
+ }
+endfor:
+ i++
+ consumes += i
+ block.Advance(consumes)
+ n := ast.NewTextSegment(text.NewSegment(start, start+i))
+ link := ast.NewAutoLink(typ, n)
+ link.Protocol = protocol
+ return link
+}
+
+func (s *linkifyParser) CloseBlock(parent ast.Node, pc parser.Context) {
+ // nothing to do
+}
+
+type linkify struct {
+ options []LinkifyOption
+}
+
+// Linkify is an extension that allow you to parse text that seems like a URL.
+var Linkify = &linkify{}
+
+func NewLinkify(opts ...LinkifyOption) goldmark.Extender {
+ return &linkify{
+ options: opts,
+ }
+}
+
+func (e *linkify) Extend(m goldmark.Markdown) {
+ m.Parser().AddOptions(
+ parser.WithInlineParsers(
+ util.Prioritized(NewLinkifyParser(e.options...), 999),
+ ),
+ )
+}
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 000000000..1b629ad8f
--- /dev/null
+++ b/vendor/github.com/yuin/goldmark/extension/strikethrough.go
@@ -0,0 +1,116 @@
+package extension
+
+import (
+ "github.com/yuin/goldmark"
+ gast "github.com/yuin/goldmark/ast"
+ "github.com/yuin/goldmark/extension/ast"
+ "github.com/yuin/goldmark/parser"
+ "github.com/yuin/goldmark/renderer"
+ "github.com/yuin/goldmark/renderer/html"
+ "github.com/yuin/goldmark/text"
+ "github.com/yuin/goldmark/util"
+)
+
+type strikethroughDelimiterProcessor struct {
+}
+
+func (p *strikethroughDelimiterProcessor) IsDelimiter(b byte) bool {
+ return b == '~'
+}
+
+func (p *strikethroughDelimiterProcessor) CanOpenCloser(opener, closer *parser.Delimiter) bool {
+ return opener.Char == closer.Char
+}
+
+func (p *strikethroughDelimiterProcessor) OnMatch(consumes int) gast.Node {
+ return ast.NewStrikethrough()
+}
+
+var defaultStrikethroughDelimiterProcessor = &strikethroughDelimiterProcessor{}
+
+type strikethroughParser struct {
+}
+
+var defaultStrikethroughParser = &strikethroughParser{}
+
+// NewStrikethroughParser return a new InlineParser that parses
+// strikethrough expressions.
+func NewStrikethroughParser() parser.InlineParser {
+ return defaultStrikethroughParser
+}
+
+func (s *strikethroughParser) Trigger() []byte {
+ return []byte{'~'}
+}
+
+func (s *strikethroughParser) Parse(parent gast.Node, block text.Reader, pc parser.Context) gast.Node {
+ before := block.PrecendingCharacter()
+ line, segment := block.PeekLine()
+ node := parser.ScanDelimiter(line, before, 2, defaultStrikethroughDelimiterProcessor)
+ if node == nil {
+ return nil
+ }
+ node.Segment = segment.WithStop(segment.Start + node.OriginalLength)
+ block.Advance(node.OriginalLength)
+ pc.PushDelimiter(node)
+ return node
+}
+
+func (s *strikethroughParser) CloseBlock(parent gast.Node, pc parser.Context) {
+ // nothing to do
+}
+
+// StrikethroughHTMLRenderer is a renderer.NodeRenderer implementation that
+// renders Strikethrough nodes.
+type StrikethroughHTMLRenderer struct {
+ html.Config
+}
+
+// NewStrikethroughHTMLRenderer returns a new StrikethroughHTMLRenderer.
+func NewStrikethroughHTMLRenderer(opts ...html.Option) renderer.NodeRenderer {
+ r := &StrikethroughHTMLRenderer{
+ Config: html.NewConfig(),
+ }
+ for _, opt := range opts {
+ opt.SetHTMLOption(&r.Config)
+ }
+ return r
+}
+
+// RegisterFuncs implements renderer.NodeRenderer.RegisterFuncs.
+func (r *StrikethroughHTMLRenderer) RegisterFuncs(reg renderer.NodeRendererFuncRegisterer) {
+ reg.Register(ast.KindStrikethrough, r.renderStrikethrough)
+}
+
+// StrikethroughAttributeFilter defines attribute names which dd elements can have.
+var StrikethroughAttributeFilter = html.GlobalAttributeFilter
+
+func (r *StrikethroughHTMLRenderer) renderStrikethrough(w util.BufWriter, source []byte, n gast.Node, entering bool) (gast.WalkStatus, error) {
+ if entering {
+ if n.Attributes() != nil {
+ _, _ = w.WriteString("<del")
+ html.RenderAttributes(w, n, StrikethroughAttributeFilter)
+ _ = w.WriteByte('>')
+ } else {
+ _, _ = w.WriteString("<del>")
+ }
+ } else {
+ _, _ = w.WriteString("</del>")
+ }
+ return gast.WalkContinue, nil
+}
+
+type strikethrough struct {
+}
+
+// Strikethrough is an extension that allow you to use strikethrough expression like '~~text~~' .
+var Strikethrough = &strikethrough{}
+
+func (e *strikethrough) Extend(m goldmark.Markdown) {
+ m.Parser().AddOptions(parser.WithInlineParsers(
+ util.Prioritized(NewStrikethroughParser(), 500),
+ ))
+ m.Renderer().AddOptions(renderer.WithNodeRenderers(
+ util.Prioritized(NewStrikethroughHTMLRenderer(), 500),
+ ))
+}
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 000000000..48d0d6827
--- /dev/null
+++ b/vendor/github.com/yuin/goldmark/extension/table.go
@@ -0,0 +1,556 @@
+package extension
+
+import (
+ "bytes"
+ "fmt"
+ "regexp"
+
+ "github.com/yuin/goldmark"
+ gast "github.com/yuin/goldmark/ast"
+ "github.com/yuin/goldmark/extension/ast"
+ "github.com/yuin/goldmark/parser"
+ "github.com/yuin/goldmark/renderer"
+ "github.com/yuin/goldmark/renderer/html"
+ "github.com/yuin/goldmark/text"
+ "github.com/yuin/goldmark/util"
+)
+
+var escapedPipeCellListKey = parser.NewContextKey()
+
+type escapedPipeCell struct {
+ Cell *ast.TableCell
+ Pos []int
+ Transformed bool
+}
+
+// TableCellAlignMethod indicates how are table cells aligned in HTML format.indicates how are table cells aligned in HTML format.
+type TableCellAlignMethod int
+
+const (
+ // TableCellAlignDefault renders alignments by default method.
+ // With XHTML, alignments are rendered as an align attribute.
+ // With HTML5, alignments are rendered as a style attribute.
+ TableCellAlignDefault TableCellAlignMethod = iota
+
+ // TableCellAlignAttribute renders alignments as an align attribute.
+ TableCellAlignAttribute
+
+ // TableCellAlignStyle renders alignments as a style attribute.
+ TableCellAlignStyle
+
+ // TableCellAlignNone does not care about alignments.
+ // If you using classes or other styles, you can add these attributes
+ // in an ASTTransformer.
+ TableCellAlignNone
+)
+
+// TableConfig struct holds options for the extension.
+type TableConfig struct {
+ html.Config
+
+ // TableCellAlignMethod indicates how are table celss aligned.
+ TableCellAlignMethod TableCellAlignMethod
+}
+
+// TableOption interface is a functional option interface for the extension.
+type TableOption interface {
+ renderer.Option
+ // SetTableOption sets given option to the extension.
+ SetTableOption(*TableConfig)
+}
+
+// NewTableConfig returns a new Config with defaults.
+func NewTableConfig() TableConfig {
+ return TableConfig{
+ Config: html.NewConfig(),
+ TableCellAlignMethod: TableCellAlignDefault,
+ }
+}
+
+// SetOption implements renderer.SetOptioner.
+func (c *TableConfig) SetOption(name renderer.OptionName, value interface{}) {
+ switch name {
+ case optTableCellAlignMethod:
+ c.TableCellAlignMethod = value.(TableCellAlignMethod)
+ default:
+ c.Config.SetOption(name, value)
+ }
+}
+
+type withTableHTMLOptions struct {
+ value []html.Option
+}
+
+func (o *withTableHTMLOptions) SetConfig(c *renderer.Config) {
+ if o.value != nil {
+ for _, v := range o.value {
+ v.(renderer.Option).SetConfig(c)
+ }
+ }
+}
+
+func (o *withTableHTMLOptions) SetTableOption(c *TableConfig) {
+ if o.value != nil {
+ for _, v := range o.value {
+ v.SetHTMLOption(&c.Config)
+ }
+ }
+}
+
+// WithTableHTMLOptions is functional option that wraps goldmark HTMLRenderer options.
+func WithTableHTMLOptions(opts ...html.Option) TableOption {
+ return &withTableHTMLOptions{opts}
+}
+
+const optTableCellAlignMethod renderer.OptionName = "TableTableCellAlignMethod"
+
+type withTableCellAlignMethod struct {
+ value TableCellAlignMethod
+}
+
+func (o *withTableCellAlignMethod) SetConfig(c *renderer.Config) {
+ c.Options[optTableCellAlignMethod] = o.value
+}
+
+func (o *withTableCellAlignMethod) SetTableOption(c *TableConfig) {
+ c.TableCellAlignMethod = o.value
+}
+
+// WithTableCellAlignMethod is a functional option that indicates how are table cells aligned in HTML format.
+func WithTableCellAlignMethod(a TableCellAlignMethod) TableOption {
+ return &withTableCellAlignMethod{a}
+}
+
+func isTableDelim(bs []byte) bool {
+ if w, _ := util.IndentWidth(bs, 0); w > 3 {
+ return false
+ }
+ for _, b := range bs {
+ if !(util.IsSpace(b) || b == '-' || b == '|' || b == ':') {
+ return false
+ }
+ }
+ return true
+}
+
+var tableDelimLeft = regexp.MustCompile(`^\s*\:\-+\s*$`)
+var tableDelimRight = regexp.MustCompile(`^\s*\-+\:\s*$`)
+var tableDelimCenter = regexp.MustCompile(`^\s*\:\-+\:\s*$`)
+var tableDelimNone = regexp.MustCompile(`^\s*\-+\s*$`)
+
+type tableParagraphTransformer struct {
+}
+
+var defaultTableParagraphTransformer = &tableParagraphTransformer{}
+
+// NewTableParagraphTransformer returns a new ParagraphTransformer
+// that can transform paragraphs into tables.
+func NewTableParagraphTransformer() parser.ParagraphTransformer {
+ return defaultTableParagraphTransformer
+}
+
+func (b *tableParagraphTransformer) Transform(node *gast.Paragraph, reader text.Reader, pc parser.Context) {
+ lines := node.Lines()
+ if lines.Len() < 2 {
+ return
+ }
+ for i := 1; i < lines.Len(); i++ {
+ alignments := b.parseDelimiter(lines.At(i), reader)
+ if alignments == nil {
+ continue
+ }
+ header := b.parseRow(lines.At(i-1), alignments, true, reader, pc)
+ if header == nil || len(alignments) != header.ChildCount() {
+ return
+ }
+ table := ast.NewTable()
+ table.Alignments = alignments
+ table.AppendChild(table, ast.NewTableHeader(header))
+ for j := i + 1; j < lines.Len(); j++ {
+ table.AppendChild(table, b.parseRow(lines.At(j), alignments, false, reader, pc))
+ }
+ node.Lines().SetSliced(0, i-1)
+ node.Parent().InsertAfter(node.Parent(), node, table)
+ if node.Lines().Len() == 0 {
+ node.Parent().RemoveChild(node.Parent(), node)
+ } else {
+ last := node.Lines().At(i - 2)
+ last.Stop = last.Stop - 1 // trim last newline(\n)
+ node.Lines().Set(i-2, last)
+ }
+ }
+}
+
+func (b *tableParagraphTransformer) parseRow(segment text.Segment, alignments []ast.Alignment, isHeader bool, reader text.Reader, pc parser.Context) *ast.TableRow {
+ source := reader.Source()
+ line := segment.Value(source)
+ pos := 0
+ pos += util.TrimLeftSpaceLength(line)
+ limit := len(line)
+ limit -= util.TrimRightSpaceLength(line)
+ row := ast.NewTableRow(alignments)
+ if len(line) > 0 && line[pos] == '|' {
+ pos++
+ }
+ if len(line) > 0 && line[limit-1] == '|' {
+ limit--
+ }
+ i := 0
+ for ; pos < limit; i++ {
+ alignment := ast.AlignNone
+ if i >= len(alignments) {
+ if !isHeader {
+ return row
+ }
+ } else {
+ alignment = alignments[i]
+ }
+
+ var escapedCell *escapedPipeCell
+ node := ast.NewTableCell()
+ node.Alignment = alignment
+ hasBacktick := false
+ closure := pos
+ for ; closure < limit; closure++ {
+ if line[closure] == '`' {
+ hasBacktick = true
+ }
+ if line[closure] == '|' {
+ if closure == 0 || line[closure-1] != '\\' {
+ break
+ } else if hasBacktick {
+ if escapedCell == nil {
+ escapedCell = &escapedPipeCell{node, []int{}, false}
+ escapedList := pc.ComputeIfAbsent(escapedPipeCellListKey,
+ func() interface{} {
+ return []*escapedPipeCell{}
+ }).([]*escapedPipeCell)
+ escapedList = append(escapedList, escapedCell)
+ pc.Set(escapedPipeCellListKey, escapedList)
+ }
+ escapedCell.Pos = append(escapedCell.Pos, segment.Start+closure-1)
+ }
+ }
+ }
+ seg := text.NewSegment(segment.Start+pos, segment.Start+closure)
+ seg = seg.TrimLeftSpace(source)
+ seg = seg.TrimRightSpace(source)
+ node.Lines().Append(seg)
+ row.AppendChild(row, node)
+ pos = closure + 1
+ }
+ for ; i < len(alignments); i++ {
+ row.AppendChild(row, ast.NewTableCell())
+ }
+ return row
+}
+
+func (b *tableParagraphTransformer) parseDelimiter(segment text.Segment, reader text.Reader) []ast.Alignment {
+
+ line := segment.Value(reader.Source())
+ if !isTableDelim(line) {
+ return nil
+ }
+ cols := bytes.Split(line, []byte{'|'})
+ if util.IsBlank(cols[0]) {
+ cols = cols[1:]
+ }
+ if len(cols) > 0 && util.IsBlank(cols[len(cols)-1]) {
+ cols = cols[:len(cols)-1]
+ }
+
+ var alignments []ast.Alignment
+ for _, col := range cols {
+ if tableDelimLeft.Match(col) {
+ alignments = append(alignments, ast.AlignLeft)
+ } else if tableDelimRight.Match(col) {
+ alignments = append(alignments, ast.AlignRight)
+ } else if tableDelimCenter.Match(col) {
+ alignments = append(alignments, ast.AlignCenter)
+ } else if tableDelimNone.Match(col) {
+ alignments = append(alignments, ast.AlignNone)
+ } else {
+ return nil
+ }
+ }
+ return alignments
+}
+
+type tableASTTransformer struct {
+}
+
+var defaultTableASTTransformer = &tableASTTransformer{}
+
+// NewTableASTTransformer returns a parser.ASTTransformer for tables.
+func NewTableASTTransformer() parser.ASTTransformer {
+ return defaultTableASTTransformer
+}
+
+func (a *tableASTTransformer) Transform(node *gast.Document, reader text.Reader, pc parser.Context) {
+ lst := pc.Get(escapedPipeCellListKey)
+ if lst == nil {
+ return
+ }
+ pc.Set(escapedPipeCellListKey, nil)
+ for _, v := range lst.([]*escapedPipeCell) {
+ if v.Transformed {
+ continue
+ }
+ _ = gast.Walk(v.Cell, func(n gast.Node, entering bool) (gast.WalkStatus, error) {
+ if !entering || n.Kind() != gast.KindCodeSpan {
+ return gast.WalkContinue, nil
+ }
+
+ for c := n.FirstChild(); c != nil; {
+ next := c.NextSibling()
+ if c.Kind() != gast.KindText {
+ c = next
+ continue
+ }
+ parent := c.Parent()
+ ts := &c.(*gast.Text).Segment
+ n := c
+ for _, v := range lst.([]*escapedPipeCell) {
+ for _, pos := range v.Pos {
+ if ts.Start <= pos && pos < ts.Stop {
+ segment := n.(*gast.Text).Segment
+ n1 := gast.NewRawTextSegment(segment.WithStop(pos))
+ n2 := gast.NewRawTextSegment(segment.WithStart(pos + 1))
+ parent.InsertAfter(parent, n, n1)
+ parent.InsertAfter(parent, n1, n2)
+ parent.RemoveChild(parent, n)
+ n = n2
+ v.Transformed = true
+ }
+ }
+ }
+ c = next
+ }
+ return gast.WalkContinue, nil
+ })
+ }
+}
+
+// TableHTMLRenderer is a renderer.NodeRenderer implementation that
+// renders Table nodes.
+type TableHTMLRenderer struct {
+ TableConfig
+}
+
+// NewTableHTMLRenderer returns a new TableHTMLRenderer.
+func NewTableHTMLRenderer(opts ...TableOption) renderer.NodeRenderer {
+ r := &TableHTMLRenderer{
+ TableConfig: NewTableConfig(),
+ }
+ for _, opt := range opts {
+ opt.SetTableOption(&r.TableConfig)
+ }
+ return r
+}
+
+// RegisterFuncs implements renderer.NodeRenderer.RegisterFuncs.
+func (r *TableHTMLRenderer) RegisterFuncs(reg renderer.NodeRendererFuncRegisterer) {
+ reg.Register(ast.KindTable, r.renderTable)
+ reg.Register(ast.KindTableHeader, r.renderTableHeader)
+ reg.Register(ast.KindTableRow, r.renderTableRow)
+ reg.Register(ast.KindTableCell, r.renderTableCell)
+}
+
+// TableAttributeFilter defines attribute names which table elements can have.
+var TableAttributeFilter = html.GlobalAttributeFilter.Extend(
+ []byte("align"), // [Deprecated]
+ []byte("bgcolor"), // [Deprecated]
+ []byte("border"), // [Deprecated]
+ []byte("cellpadding"), // [Deprecated]
+ []byte("cellspacing"), // [Deprecated]
+ []byte("frame"), // [Deprecated]
+ []byte("rules"), // [Deprecated]
+ []byte("summary"), // [Deprecated]
+ []byte("width"), // [Deprecated]
+)
+
+func (r *TableHTMLRenderer) renderTable(w util.BufWriter, source []byte, n gast.Node, entering bool) (gast.WalkStatus, error) {
+ if entering {
+ _, _ = w.WriteString("<table")
+ if n.Attributes() != nil {
+ html.RenderAttributes(w, n, TableAttributeFilter)
+ }
+ _, _ = w.WriteString(">\n")
+ } else {
+ _, _ = w.WriteString("</table>\n")
+ }
+ return gast.WalkContinue, nil
+}
+
+// TableHeaderAttributeFilter defines attribute names which <thead> elements can have.
+var TableHeaderAttributeFilter = html.GlobalAttributeFilter.Extend(
+ []byte("align"), // [Deprecated since HTML4] [Obsolete since HTML5]
+ []byte("bgcolor"), // [Not Standardized]
+ []byte("char"), // [Deprecated since HTML4] [Obsolete since HTML5]
+ []byte("charoff"), // [Deprecated since HTML4] [Obsolete since HTML5]
+ []byte("valign"), // [Deprecated since HTML4] [Obsolete since HTML5]
+)
+
+func (r *TableHTMLRenderer) renderTableHeader(w util.BufWriter, source []byte, n gast.Node, entering bool) (gast.WalkStatus, error) {
+ if entering {
+ _, _ = w.WriteString("<thead")
+ if n.Attributes() != nil {
+ html.RenderAttributes(w, n, TableHeaderAttributeFilter)
+ }
+ _, _ = w.WriteString(">\n")
+ _, _ = w.WriteString("<tr>\n") // Header <tr> has no separate handle
+ } else {
+ _, _ = w.WriteString("</tr>\n")
+ _, _ = w.WriteString("</thead>\n")
+ if n.NextSibling() != nil {
+ _, _ = w.WriteString("<tbody>\n")
+ }
+ }
+ return gast.WalkContinue, nil
+}
+
+// TableRowAttributeFilter defines attribute names which <tr> elements can have.
+var TableRowAttributeFilter = html.GlobalAttributeFilter.Extend(
+ []byte("align"), // [Obsolete since HTML5]
+ []byte("bgcolor"), // [Obsolete since HTML5]
+ []byte("char"), // [Obsolete since HTML5]
+ []byte("charoff"), // [Obsolete since HTML5]
+ []byte("valign"), // [Obsolete since HTML5]
+)
+
+func (r *TableHTMLRenderer) renderTableRow(w util.BufWriter, source []byte, n gast.Node, entering bool) (gast.WalkStatus, error) {
+ if entering {
+ _, _ = w.WriteString("<tr")
+ if n.Attributes() != nil {
+ html.RenderAttributes(w, n, TableRowAttributeFilter)
+ }
+ _, _ = w.WriteString(">\n")
+ } else {
+ _, _ = w.WriteString("</tr>\n")
+ if n.Parent().LastChild() == n {
+ _, _ = w.WriteString("</tbody>\n")
+ }
+ }
+ return gast.WalkContinue, nil
+}
+
+// TableThCellAttributeFilter defines attribute names which table <th> cells can have.
+var TableThCellAttributeFilter = html.GlobalAttributeFilter.Extend(
+ []byte("abbr"), // [OK] Contains a short abbreviated description of the cell's content [NOT OK in <td>]
+
+ []byte("align"), // [Obsolete since HTML5]
+ []byte("axis"), // [Obsolete since HTML5]
+ []byte("bgcolor"), // [Not Standardized]
+ []byte("char"), // [Obsolete since HTML5]
+ []byte("charoff"), // [Obsolete since HTML5]
+
+ []byte("colspan"), // [OK] Number of columns that the cell is to span
+ []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
+
+ []byte("height"), // [Deprecated since HTML4] [Obsolete since HTML5]
+
+ []byte("rowspan"), // [OK] Number of rows that the cell is to span
+ []byte("scope"), // [OK] This enumerated attribute defines the cells that the header (defined in the <th>) element relates to [NOT OK in <td>]
+
+ []byte("valign"), // [Obsolete since HTML5]
+ []byte("width"), // [Deprecated since HTML4] [Obsolete since HTML5]
+)
+
+// TableTdCellAttributeFilter defines attribute names which table <td> cells can have.
+var TableTdCellAttributeFilter = html.GlobalAttributeFilter.Extend(
+ []byte("abbr"), // [Obsolete since HTML5] [OK in <th>]
+ []byte("align"), // [Obsolete since HTML5]
+ []byte("axis"), // [Obsolete since HTML5]
+ []byte("bgcolor"), // [Not Standardized]
+ []byte("char"), // [Obsolete since HTML5]
+ []byte("charoff"), // [Obsolete since HTML5]
+
+ []byte("colspan"), // [OK] Number of columns that the cell is to span
+ []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
+
+ []byte("height"), // [Deprecated since HTML4] [Obsolete since HTML5]
+
+ []byte("rowspan"), // [OK] Number of rows that the cell is to span
+
+ []byte("scope"), // [Obsolete since HTML5] [OK in <th>]
+ []byte("valign"), // [Obsolete since HTML5]
+ []byte("width"), // [Deprecated since HTML4] [Obsolete since HTML5]
+)
+
+func (r *TableHTMLRenderer) renderTableCell(w util.BufWriter, source []byte, node gast.Node, entering bool) (gast.WalkStatus, error) {
+ n := node.(*ast.TableCell)
+ tag := "td"
+ if n.Parent().Kind() == ast.KindTableHeader {
+ tag = "th"
+ }
+ if entering {
+ fmt.Fprintf(w, "<%s", tag)
+ if n.Alignment != ast.AlignNone {
+ amethod := r.TableConfig.TableCellAlignMethod
+ if amethod == TableCellAlignDefault {
+ if r.Config.XHTML {
+ amethod = TableCellAlignAttribute
+ } else {
+ amethod = TableCellAlignStyle
+ }
+ }
+ switch amethod {
+ case TableCellAlignAttribute:
+ if _, ok := n.AttributeString("align"); !ok { // Skip align render if overridden
+ fmt.Fprintf(w, ` align="%s"`, n.Alignment.String())
+ }
+ case TableCellAlignStyle:
+ v, ok := n.AttributeString("style")
+ var cob util.CopyOnWriteBuffer
+ if ok {
+ cob = util.NewCopyOnWriteBuffer(v.([]byte))
+ cob.AppendByte(';')
+ }
+ style := fmt.Sprintf("text-align:%s", n.Alignment.String())
+ cob.AppendString(style)
+ n.SetAttributeString("style", cob.Bytes())
+ }
+ }
+ if n.Attributes() != nil {
+ if tag == "td" {
+ html.RenderAttributes(w, n, TableTdCellAttributeFilter) // <td>
+ } else {
+ html.RenderAttributes(w, n, TableThCellAttributeFilter) // <th>
+ }
+ }
+ _ = w.WriteByte('>')
+ } else {
+ fmt.Fprintf(w, "</%s>\n", tag)
+ }
+ return gast.WalkContinue, nil
+}
+
+type table struct {
+ options []TableOption
+}
+
+// Table is an extension that allow you to use GFM tables .
+var Table = &table{
+ options: []TableOption{},
+}
+
+// NewTable returns a new extension with given options.
+func NewTable(opts ...TableOption) goldmark.Extender {
+ return &table{
+ options: opts,
+ }
+}
+
+func (e *table) Extend(m goldmark.Markdown) {
+ m.Parser().AddOptions(
+ parser.WithParagraphTransformers(
+ util.Prioritized(NewTableParagraphTransformer(), 200),
+ ),
+ parser.WithASTTransformers(
+ util.Prioritized(defaultTableASTTransformer, 0),
+ ),
+ )
+ m.Renderer().AddOptions(renderer.WithNodeRenderers(
+ util.Prioritized(NewTableHTMLRenderer(e.options...), 500),
+ ))
+}
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 000000000..1f3e52c20
--- /dev/null
+++ b/vendor/github.com/yuin/goldmark/extension/tasklist.go
@@ -0,0 +1,115 @@
+package extension
+
+import (
+ "github.com/yuin/goldmark"
+ gast "github.com/yuin/goldmark/ast"
+ "github.com/yuin/goldmark/extension/ast"
+ "github.com/yuin/goldmark/parser"
+ "github.com/yuin/goldmark/renderer"
+ "github.com/yuin/goldmark/renderer/html"
+ "github.com/yuin/goldmark/text"
+ "github.com/yuin/goldmark/util"
+ "regexp"
+)
+
+var taskListRegexp = regexp.MustCompile(`^\[([\sxX])\]\s*`)
+
+type taskCheckBoxParser struct {
+}
+
+var defaultTaskCheckBoxParser = &taskCheckBoxParser{}
+
+// NewTaskCheckBoxParser returns a new InlineParser that can parse
+// checkboxes in list items.
+// This parser must take precedence over the parser.LinkParser.
+func NewTaskCheckBoxParser() parser.InlineParser {
+ return defaultTaskCheckBoxParser
+}
+
+func (s *taskCheckBoxParser) Trigger() []byte {
+ return []byte{'['}
+}
+
+func (s *taskCheckBoxParser) Parse(parent gast.Node, block text.Reader, pc parser.Context) gast.Node {
+ // Given AST structure must be like
+ // - List
+ // - ListItem : parent.Parent
+ // - TextBlock : parent
+ // (current line)
+ if parent.Parent() == nil || parent.Parent().FirstChild() != parent {
+ return nil
+ }
+
+ if _, ok := parent.Parent().(*gast.ListItem); !ok {
+ return nil
+ }
+ line, _ := block.PeekLine()
+ m := taskListRegexp.FindSubmatchIndex(line)
+ if m == nil {
+ return nil
+ }
+ value := line[m[2]:m[3]][0]
+ block.Advance(m[1])
+ checked := value == 'x' || value == 'X'
+ return ast.NewTaskCheckBox(checked)
+}
+
+func (s *taskCheckBoxParser) CloseBlock(parent gast.Node, pc parser.Context) {
+ // nothing to do
+}
+
+// TaskCheckBoxHTMLRenderer is a renderer.NodeRenderer implementation that
+// renders checkboxes in list items.
+type TaskCheckBoxHTMLRenderer struct {
+ html.Config
+}
+
+// NewTaskCheckBoxHTMLRenderer returns a new TaskCheckBoxHTMLRenderer.
+func NewTaskCheckBoxHTMLRenderer(opts ...html.Option) renderer.NodeRenderer {
+ r := &TaskCheckBoxHTMLRenderer{
+ Config: html.NewConfig(),
+ }
+ for _, opt := range opts {
+ opt.SetHTMLOption(&r.Config)
+ }
+ return r
+}
+
+// RegisterFuncs implements renderer.NodeRenderer.RegisterFuncs.
+func (r *TaskCheckBoxHTMLRenderer) RegisterFuncs(reg renderer.NodeRendererFuncRegisterer) {
+ reg.Register(ast.KindTaskCheckBox, r.renderTaskCheckBox)
+}
+
+func (r *TaskCheckBoxHTMLRenderer) renderTaskCheckBox(w util.BufWriter, source []byte, node gast.Node, entering bool) (gast.WalkStatus, error) {
+ if !entering {
+ return gast.WalkContinue, nil
+ }
+ n := node.(*ast.TaskCheckBox)
+
+ if n.IsChecked {
+ w.WriteString(`<input checked="" disabled="" type="checkbox"`)
+ } else {
+ w.WriteString(`<input disabled="" type="checkbox"`)
+ }
+ if r.XHTML {
+ w.WriteString(" /> ")
+ } else {
+ w.WriteString("> ")
+ }
+ return gast.WalkContinue, nil
+}
+
+type taskList struct {
+}
+
+// TaskList is an extension that allow you to use GFM task lists.
+var TaskList = &taskList{}
+
+func (e *taskList) Extend(m goldmark.Markdown) {
+ m.Parser().AddOptions(parser.WithInlineParsers(
+ util.Prioritized(NewTaskCheckBoxParser(), 0),
+ ))
+ m.Renderer().AddOptions(renderer.WithNodeRenderers(
+ util.Prioritized(NewTaskCheckBoxHTMLRenderer(), 500),
+ ))
+}
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 000000000..f56c06f74
--- /dev/null
+++ b/vendor/github.com/yuin/goldmark/extension/typographer.go
@@ -0,0 +1,339 @@
+package extension
+
+import (
+ "unicode"
+
+ "github.com/yuin/goldmark"
+ gast "github.com/yuin/goldmark/ast"
+ "github.com/yuin/goldmark/parser"
+ "github.com/yuin/goldmark/text"
+ "github.com/yuin/goldmark/util"
+)
+
+var uncloseCounterKey = parser.NewContextKey()
+
+type unclosedCounter struct {
+ Single int
+ Double int
+}
+
+func (u *unclosedCounter) Reset() {
+ u.Single = 0
+ u.Double = 0
+}
+
+func getUnclosedCounter(pc parser.Context) *unclosedCounter {
+ v := pc.Get(uncloseCounterKey)
+ if v == nil {
+ v = &unclosedCounter{}
+ pc.Set(uncloseCounterKey, v)
+ }
+ return v.(*unclosedCounter)
+}
+
+// TypographicPunctuation is a key of the punctuations that can be replaced with
+// typographic entities.
+type TypographicPunctuation int
+
+const (
+ // LeftSingleQuote is '
+ LeftSingleQuote TypographicPunctuation = iota + 1
+ // RightSingleQuote is '
+ RightSingleQuote
+ // LeftDoubleQuote is "
+ LeftDoubleQuote
+ // RightDoubleQuote is "
+ RightDoubleQuote
+ // EnDash is --
+ EnDash
+ // EmDash is ---
+ EmDash
+ // Ellipsis is ...
+ Ellipsis
+ // LeftAngleQuote is <<
+ LeftAngleQuote
+ // RightAngleQuote is >>
+ RightAngleQuote
+ // Apostrophe is '
+ Apostrophe
+
+ typographicPunctuationMax
+)
+
+// An TypographerConfig struct is a data structure that holds configuration of the
+// Typographer extension.
+type TypographerConfig struct {
+ Substitutions [][]byte
+}
+
+func newDefaultSubstitutions() [][]byte {
+ replacements := make([][]byte, typographicPunctuationMax)
+ replacements[LeftSingleQuote] = []byte("&lsquo;")
+ replacements[RightSingleQuote] = []byte("&rsquo;")
+ replacements[LeftDoubleQuote] = []byte("&ldquo;")
+ replacements[RightDoubleQuote] = []byte("&rdquo;")
+ replacements[EnDash] = []byte("&ndash;")
+ replacements[EmDash] = []byte("&mdash;")
+ replacements[Ellipsis] = []byte("&hellip;")
+ replacements[LeftAngleQuote] = []byte("&laquo;")
+ replacements[RightAngleQuote] = []byte("&raquo;")
+ replacements[Apostrophe] = []byte("&rsquo;")
+
+ return replacements
+}
+
+// SetOption implements SetOptioner.
+func (b *TypographerConfig) SetOption(name parser.OptionName, value interface{}) {
+ switch name {
+ case optTypographicSubstitutions:
+ b.Substitutions = value.([][]byte)
+ }
+}
+
+// A TypographerOption interface sets options for the TypographerParser.
+type TypographerOption interface {
+ parser.Option
+ SetTypographerOption(*TypographerConfig)
+}
+
+const optTypographicSubstitutions parser.OptionName = "TypographicSubstitutions"
+
+// TypographicSubstitutions is a list of the substitutions for the Typographer extension.
+type TypographicSubstitutions map[TypographicPunctuation][]byte
+
+type withTypographicSubstitutions struct {
+ value [][]byte
+}
+
+func (o *withTypographicSubstitutions) SetParserOption(c *parser.Config) {
+ c.Options[optTypographicSubstitutions] = o.value
+}
+
+func (o *withTypographicSubstitutions) SetTypographerOption(p *TypographerConfig) {
+ p.Substitutions = o.value
+}
+
+// WithTypographicSubstitutions is a functional otpion that specify replacement text
+// for punctuations.
+func WithTypographicSubstitutions(values map[TypographicPunctuation][]byte) TypographerOption {
+ replacements := newDefaultSubstitutions()
+ for k, v := range values {
+ replacements[k] = v
+ }
+
+ return &withTypographicSubstitutions{replacements}
+}
+
+type typographerDelimiterProcessor struct {
+}
+
+func (p *typographerDelimiterProcessor) IsDelimiter(b byte) bool {
+ return b == '\'' || b == '"'
+}
+
+func (p *typographerDelimiterProcessor) CanOpenCloser(opener, closer *parser.Delimiter) bool {
+ return opener.Char == closer.Char
+}
+
+func (p *typographerDelimiterProcessor) OnMatch(consumes int) gast.Node {
+ return nil
+}
+
+var defaultTypographerDelimiterProcessor = &typographerDelimiterProcessor{}
+
+type typographerParser struct {
+ TypographerConfig
+}
+
+// NewTypographerParser return a new InlineParser that parses
+// typographer expressions.
+func NewTypographerParser(opts ...TypographerOption) parser.InlineParser {
+ p := &typographerParser{
+ TypographerConfig: TypographerConfig{
+ Substitutions: newDefaultSubstitutions(),
+ },
+ }
+ for _, o := range opts {
+ o.SetTypographerOption(&p.TypographerConfig)
+ }
+ return p
+}
+
+func (s *typographerParser) Trigger() []byte {
+ return []byte{'\'', '"', '-', '.', ',', '<', '>', '*', '['}
+}
+
+func (s *typographerParser) Parse(parent gast.Node, block text.Reader, pc parser.Context) gast.Node {
+ line, _ := block.PeekLine()
+ c := line[0]
+ if len(line) > 2 {
+ if c == '-' {
+ if s.Substitutions[EmDash] != nil && line[1] == '-' && line[2] == '-' { // ---
+ node := gast.NewString(s.Substitutions[EmDash])
+ node.SetCode(true)
+ block.Advance(3)
+ return node
+ }
+ } else if c == '.' {
+ if s.Substitutions[Ellipsis] != nil && line[1] == '.' && line[2] == '.' { // ...
+ node := gast.NewString(s.Substitutions[Ellipsis])
+ node.SetCode(true)
+ block.Advance(3)
+ return node
+ }
+ return nil
+ }
+ }
+ if len(line) > 1 {
+ if c == '<' {
+ if s.Substitutions[LeftAngleQuote] != nil && line[1] == '<' { // <<
+ node := gast.NewString(s.Substitutions[LeftAngleQuote])
+ node.SetCode(true)
+ block.Advance(2)
+ return node
+ }
+ return nil
+ } else if c == '>' {
+ if s.Substitutions[RightAngleQuote] != nil && line[1] == '>' { // >>
+ node := gast.NewString(s.Substitutions[RightAngleQuote])
+ node.SetCode(true)
+ block.Advance(2)
+ return node
+ }
+ return nil
+ } else if s.Substitutions[EnDash] != nil && c == '-' && line[1] == '-' { // --
+ node := gast.NewString(s.Substitutions[EnDash])
+ node.SetCode(true)
+ block.Advance(2)
+ return node
+ }
+ }
+ if c == '\'' || c == '"' {
+ before := block.PrecendingCharacter()
+ d := parser.ScanDelimiter(line, before, 1, defaultTypographerDelimiterProcessor)
+ if d == nil {
+ return nil
+ }
+ counter := getUnclosedCounter(pc)
+ if c == '\'' {
+ if s.Substitutions[Apostrophe] != nil {
+ // Handle decade abbrevations such as '90s
+ if d.CanOpen && !d.CanClose && len(line) > 3 && util.IsNumeric(line[1]) && util.IsNumeric(line[2]) && line[3] == 's' {
+ after := rune(' ')
+ if len(line) > 4 {
+ after = util.ToRune(line, 4)
+ }
+ if len(line) == 3 || util.IsSpaceRune(after) || util.IsPunctRune(after) {
+ node := gast.NewString(s.Substitutions[Apostrophe])
+ node.SetCode(true)
+ block.Advance(1)
+ return node
+ }
+ }
+ // special cases: 'twas, 'em, 'net
+ if len(line) > 1 && (unicode.IsPunct(before) || unicode.IsSpace(before)) && (line[1] == 't' || line[1] == 'e' || line[1] == 'n' || line[1] == 'l') {
+ node := gast.NewString(s.Substitutions[Apostrophe])
+ node.SetCode(true)
+ block.Advance(1)
+ return node
+ }
+ // Convert normal apostrophes. This is probably more flexible than necessary but
+ // converts any apostrophe in between two alphanumerics.
+ if len(line) > 1 && (unicode.IsDigit(before) || unicode.IsLetter(before)) && (unicode.IsLetter(util.ToRune(line, 1))) {
+ node := gast.NewString(s.Substitutions[Apostrophe])
+ node.SetCode(true)
+ block.Advance(1)
+ return node
+ }
+ }
+ if s.Substitutions[LeftSingleQuote] != nil && d.CanOpen && !d.CanClose {
+ nt := LeftSingleQuote
+ // special cases: Alice's, I'm, Don't, You'd
+ 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])) {
+ nt = RightSingleQuote
+ }
+ // special cases: I've, I'll, You're
+ 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])) {
+ nt = RightSingleQuote
+ }
+ if nt == LeftSingleQuote {
+ counter.Single++
+ }
+
+ node := gast.NewString(s.Substitutions[nt])
+ node.SetCode(true)
+ block.Advance(1)
+ return node
+ }
+ if s.Substitutions[RightSingleQuote] != nil {
+ // plural possesives and abbreviations: Smiths', doin'
+ 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))) {
+ node := gast.NewString(s.Substitutions[RightSingleQuote])
+ node.SetCode(true)
+ block.Advance(1)
+ return node
+ }
+ }
+ if s.Substitutions[RightSingleQuote] != nil && counter.Single > 0 {
+ isClose := d.CanClose && !d.CanOpen
+ 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])))
+ if isClose || maybeClose {
+ node := gast.NewString(s.Substitutions[RightSingleQuote])
+ node.SetCode(true)
+ block.Advance(1)
+ counter.Single--
+ return node
+ }
+ }
+ }
+ if c == '"' {
+ if s.Substitutions[LeftDoubleQuote] != nil && d.CanOpen && !d.CanClose {
+ node := gast.NewString(s.Substitutions[LeftDoubleQuote])
+ node.SetCode(true)
+ block.Advance(1)
+ counter.Double++
+ return node
+ }
+ if s.Substitutions[RightDoubleQuote] != nil && counter.Double > 0 {
+ isClose := d.CanClose && !d.CanOpen
+ 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])))
+ if isClose || maybeClose {
+ // special case: "Monitor 21""
+ if len(line) > 1 && line[1] == '"' && unicode.IsDigit(before) {
+ return nil
+ }
+ node := gast.NewString(s.Substitutions[RightDoubleQuote])
+ node.SetCode(true)
+ block.Advance(1)
+ counter.Double--
+ return node
+ }
+ }
+ }
+ }
+ return nil
+}
+
+func (s *typographerParser) CloseBlock(parent gast.Node, pc parser.Context) {
+ getUnclosedCounter(pc).Reset()
+}
+
+type typographer struct {
+ options []TypographerOption
+}
+
+// Typographer is an extension that replaces punctuations with typographic entities.
+var Typographer = &typographer{}
+
+// NewTypographer returns a new Extender that replaces punctuations with typographic entities.
+func NewTypographer(opts ...TypographerOption) goldmark.Extender {
+ return &typographer{
+ options: opts,
+ }
+}
+
+func (e *typographer) Extend(m goldmark.Markdown) {
+ m.Parser().AddOptions(parser.WithInlineParsers(
+ util.Prioritized(NewTypographerParser(e.options...), 9999),
+ ))
+}