diff options
| author | 2022-12-16 11:20:22 +0000 | |
|---|---|---|
| committer | 2022-12-16 12:20:22 +0100 | |
| commit | eb08529f35ce33ed98c34fb48013f0f4a5fc9635 (patch) | |
| tree | 394fd774a943f5c33ce793c67b5865f2570b46c5 /vendor/github.com/yuin/goldmark/extension | |
| parent | [bugfix] use match-sorter for filtering domain blocks (#1270) (diff) | |
| download | gotosocial-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')
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("↩︎"), + } +} + +// 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(` <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("‘") + replacements[RightSingleQuote] = []byte("’") + replacements[LeftDoubleQuote] = []byte("“") + replacements[RightDoubleQuote] = []byte("”") + replacements[EnDash] = []byte("–") + replacements[EmDash] = []byte("—") + replacements[Ellipsis] = []byte("…") + replacements[LeftAngleQuote] = []byte("«") + replacements[RightAngleQuote] = []byte("»") + replacements[Apostrophe] = []byte("’") + + 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), + )) +} |
