mirror of
https://codeberg.org/forgejo/forgejo.git
synced 2025-05-17 04:19:29 +02:00
Change markdown rendering from blackfriday to goldmark (#9533)
* Move to goldmark Markdown rendering moved from blackfriday to the goldmark. Multiple subtle changes required to the goldmark extensions to keep current rendering and defaults. Can go further with goldmark linkify and have this work within markdown rendering making the link processor unnecessary. Need to think about how to go about allowing extensions - at present it seems that these would be hard to do without recompilation. * linter fixes Co-authored-by: Lauris BH <lauris@nix.lv>
This commit is contained in:
parent
0c07f1de5b
commit
27757714d0
83 changed files with 13838 additions and 6297 deletions
83
vendor/github.com/yuin/goldmark/extension/ast/definition_list.go
generated
vendored
Normal file
83
vendor/github.com/yuin/goldmark/extension/ast/definition_list.go
generated
vendored
Normal file
|
@ -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{}
|
||||
}
|
125
vendor/github.com/yuin/goldmark/extension/ast/footnote.go
generated
vendored
Normal file
125
vendor/github.com/yuin/goldmark/extension/ast/footnote.go
generated
vendored
Normal file
|
@ -0,0 +1,125 @@
|
|||
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
|
||||
}
|
||||
|
||||
// Dump implements Node.Dump.
|
||||
func (n *FootnoteLink) Dump(source []byte, level int) {
|
||||
m := map[string]string{}
|
||||
m["Index"] = fmt.Sprintf("%v", n.Index)
|
||||
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,
|
||||
}
|
||||
}
|
||||
|
||||
// A FootnoteBackLink struct represents a link to a footnote of Markdown
|
||||
// (PHP Markdown Extra) text.
|
||||
type FootnoteBackLink struct {
|
||||
gast.BaseInline
|
||||
Index int
|
||||
}
|
||||
|
||||
// Dump implements Node.Dump.
|
||||
func (n *FootnoteBackLink) Dump(source []byte, level int) {
|
||||
m := map[string]string{}
|
||||
m["Index"] = fmt.Sprintf("%v", n.Index)
|
||||
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,
|
||||
}
|
||||
}
|
||||
|
||||
// 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,
|
||||
}
|
||||
}
|
29
vendor/github.com/yuin/goldmark/extension/ast/strikethrough.go
generated
vendored
Normal file
29
vendor/github.com/yuin/goldmark/extension/ast/strikethrough.go
generated
vendored
Normal file
|
@ -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{}
|
||||
}
|
157
vendor/github.com/yuin/goldmark/extension/ast/table.go
generated
vendored
Normal file
157
vendor/github.com/yuin/goldmark/extension/ast/table.go
generated
vendored
Normal file
|
@ -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{}
|
||||
}
|
||||
|
||||
// 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,
|
||||
}
|
||||
}
|
35
vendor/github.com/yuin/goldmark/extension/ast/tasklist.go
generated
vendored
Normal file
35
vendor/github.com/yuin/goldmark/extension/ast/tasklist.go
generated
vendored
Normal file
|
@ -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 impelemtns 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,
|
||||
}
|
||||
}
|
270
vendor/github.com/yuin/goldmark/extension/definition_list.go
generated
vendored
Normal file
270
vendor/github.com/yuin/goldmark/extension/definition_list.go
generated
vendored
Normal file
|
@ -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, 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),
|
||||
))
|
||||
}
|
336
vendor/github.com/yuin/goldmark/extension/footnote.go
generated
vendored
Normal file
336
vendor/github.com/yuin/goldmark/extension/footnote.go
generated
vendored
Normal file
|
@ -0,0 +1,336 @@
|
|||
package extension
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"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"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
var footnoteListKey = 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
|
||||
}
|
||||
|
||||
return ast.NewFootnoteLink(index)
|
||||
}
|
||||
|
||||
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
|
||||
if tlist := pc.Get(footnoteListKey); tlist != nil {
|
||||
list = tlist.(*ast.FootnoteList)
|
||||
} else {
|
||||
return
|
||||
}
|
||||
pc.Set(footnoteListKey, nil)
|
||||
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
|
||||
}
|
||||
index := footnote.(*ast.Footnote).Index
|
||||
if index < 0 {
|
||||
list.RemoveChild(list, footnote)
|
||||
} else {
|
||||
container.AppendChild(container, ast.NewFootnoteBackLink(index))
|
||||
}
|
||||
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)
|
||||
}
|
||||
|
||||
// FootnoteHTMLRenderer is a renderer.NodeRenderer implementation that
|
||||
// renders FootnoteLink nodes.
|
||||
type FootnoteHTMLRenderer struct {
|
||||
html.Config
|
||||
}
|
||||
|
||||
// NewFootnoteHTMLRenderer returns a new FootnoteHTMLRenderer.
|
||||
func NewFootnoteHTMLRenderer(opts ...html.Option) renderer.NodeRenderer {
|
||||
r := &FootnoteHTMLRenderer{
|
||||
Config: html.NewConfig(),
|
||||
}
|
||||
for _, opt := range opts {
|
||||
opt.SetHTMLOption(&r.Config)
|
||||
}
|
||||
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="fnref:`)
|
||||
_, _ = w.WriteString(is)
|
||||
_, _ = w.WriteString(`"><a href="#fn:`)
|
||||
_, _ = w.WriteString(is)
|
||||
_, _ = w.WriteString(`" class="footnote-ref" 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="#fnref:`)
|
||||
_, _ = w.WriteString(is)
|
||||
_, _ = w.WriteString(`" class="footnote-backref" role="doc-backlink">`)
|
||||
_, _ = w.WriteString("↩︎")
|
||||
_, _ = 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="fn:`)
|
||||
_, _ = w.WriteString(is)
|
||||
_, _ = w.WriteString(`" role="doc-endnote"`)
|
||||
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) {
|
||||
tag := "section"
|
||||
if r.Config.XHTML {
|
||||
tag = "div"
|
||||
}
|
||||
if entering {
|
||||
_, _ = w.WriteString("<")
|
||||
_, _ = w.WriteString(tag)
|
||||
_, _ = w.WriteString(` 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("</")
|
||||
_, _ = w.WriteString(tag)
|
||||
_, _ = w.WriteString(">\n")
|
||||
}
|
||||
return gast.WalkContinue, nil
|
||||
}
|
||||
|
||||
type footnote struct {
|
||||
}
|
||||
|
||||
// Footnote is an extension that allow you to use PHP Markdown Extra Footnotes.
|
||||
var Footnote = &footnote{}
|
||||
|
||||
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(), 500),
|
||||
))
|
||||
}
|
18
vendor/github.com/yuin/goldmark/extension/gfm.go
generated
vendored
Normal file
18
vendor/github.com/yuin/goldmark/extension/gfm.go
generated
vendored
Normal file
|
@ -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)
|
||||
}
|
150
vendor/github.com/yuin/goldmark/extension/linkify.go
generated
vendored
Normal file
150
vendor/github.com/yuin/goldmark/extension/linkify.go
generated
vendored
Normal file
|
@ -0,0 +1,150 @@
|
|||
package extension
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"github.com/yuin/goldmark"
|
||||
"github.com/yuin/goldmark/ast"
|
||||
"github.com/yuin/goldmark/parser"
|
||||
"github.com/yuin/goldmark/text"
|
||||
"github.com/yuin/goldmark/util"
|
||||
"regexp"
|
||||
)
|
||||
|
||||
var wwwURLRegxp = regexp.MustCompile(`^www\.[-a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,6}((?:/|[#?])[-a-zA-Z0-9@:%_\+.~#!?&//=\(\);,'">\^{}\[\]` + "`" + `]*)?`)
|
||||
|
||||
var urlRegexp = regexp.MustCompile(`^(?:http|https|ftp):\/\/(?:www\.)?[-a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,6}((?:/|[#?])[-a-zA-Z0-9@:%_+.~#$!?&//=\(\);,'">\^{}\[\]` + "`" + `]*)?`)
|
||||
|
||||
type linkifyParser struct {
|
||||
}
|
||||
|
||||
var defaultLinkifyParser = &linkifyParser{}
|
||||
|
||||
// NewLinkifyParser return a new InlineParser can parse
|
||||
// text that seems like a URL.
|
||||
func NewLinkifyParser() parser.InlineParser {
|
||||
return defaultLinkifyParser
|
||||
}
|
||||
|
||||
func (s *linkifyParser) Trigger() []byte {
|
||||
// ' ' indicates any white spaces and a line head
|
||||
return []byte{' ', '*', '_', '~', '('}
|
||||
}
|
||||
|
||||
var protoHTTP = []byte("http:")
|
||||
var protoHTTPS = []byte("https:")
|
||||
var protoFTP = []byte("ftp:")
|
||||
var 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 bytes.HasPrefix(line, protoHTTP) || bytes.HasPrefix(line, protoHTTPS) || bytes.HasPrefix(line, protoFTP) {
|
||||
m = urlRegexp.FindSubmatchIndex(line)
|
||||
}
|
||||
if m == nil && bytes.HasPrefix(line, domainWWW) {
|
||||
m = wwwURLRegxp.FindSubmatchIndex(line)
|
||||
protocol = []byte("http")
|
||||
}
|
||||
if m != nil {
|
||||
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 := util.FindEmailIndex(line)
|
||||
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)
|
||||
}
|
||||
consumes += m[1]
|
||||
block.Advance(consumes)
|
||||
n := ast.NewTextSegment(text.NewSegment(start, start+m[1]))
|
||||
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 {
|
||||
}
|
||||
|
||||
// Linkify is an extension that allow you to parse text that seems like a URL.
|
||||
var Linkify = &linkify{}
|
||||
|
||||
func (e *linkify) Extend(m goldmark.Markdown) {
|
||||
m.Parser().AddOptions(
|
||||
parser.WithInlineParsers(
|
||||
util.Prioritized(NewLinkifyParser(), 999),
|
||||
),
|
||||
)
|
||||
}
|
116
vendor/github.com/yuin/goldmark/extension/strikethrough.go
generated
vendored
Normal file
116
vendor/github.com/yuin/goldmark/extension/strikethrough.go
generated
vendored
Normal file
|
@ -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),
|
||||
))
|
||||
}
|
319
vendor/github.com/yuin/goldmark/extension/table.go
generated
vendored
Normal file
319
vendor/github.com/yuin/goldmark/extension/table.go
generated
vendored
Normal file
|
@ -0,0 +1,319 @@
|
|||
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 tableDelimRegexp = regexp.MustCompile(`^[\s\-\|\:]+$`)
|
||||
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 pargraphs 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
|
||||
}
|
||||
alignments := b.parseDelimiter(lines.At(1), reader)
|
||||
if alignments == nil {
|
||||
return
|
||||
}
|
||||
header := b.parseRow(lines.At(0), alignments, true, reader)
|
||||
if header == nil || len(alignments) != header.ChildCount() {
|
||||
return
|
||||
}
|
||||
table := ast.NewTable()
|
||||
table.Alignments = alignments
|
||||
table.AppendChild(table, ast.NewTableHeader(header))
|
||||
for i := 2; i < lines.Len(); i++ {
|
||||
table.AppendChild(table, b.parseRow(lines.At(i), alignments, false, reader))
|
||||
}
|
||||
node.Parent().InsertBefore(node.Parent(), node, table)
|
||||
node.Parent().RemoveChild(node.Parent(), node)
|
||||
}
|
||||
|
||||
func (b *tableParagraphTransformer) parseRow(segment text.Segment, alignments []ast.Alignment, isHeader bool, reader text.Reader) *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]
|
||||
}
|
||||
closure := util.FindClosure(line[pos:], byte(0), '|', true, false)
|
||||
if closure < 0 {
|
||||
closure = len(line[pos:])
|
||||
}
|
||||
node := ast.NewTableCell()
|
||||
seg := text.NewSegment(segment.Start+pos, segment.Start+pos+closure)
|
||||
seg = seg.TrimLeftSpace(source)
|
||||
seg = seg.TrimRightSpace(source)
|
||||
node.Lines().Append(seg)
|
||||
node.Alignment = alignment
|
||||
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 !tableDelimRegexp.Match(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
|
||||
}
|
||||
|
||||
// TableHTMLRenderer is a renderer.NodeRenderer implementation that
|
||||
// renders Table nodes.
|
||||
type TableHTMLRenderer struct {
|
||||
html.Config
|
||||
}
|
||||
|
||||
// NewTableHTMLRenderer returns a new TableHTMLRenderer.
|
||||
func NewTableHTMLRenderer(opts ...html.Option) renderer.NodeRenderer {
|
||||
r := &TableHTMLRenderer{
|
||||
Config: html.NewConfig(),
|
||||
}
|
||||
for _, opt := range opts {
|
||||
opt.SetHTMLOption(&r.Config)
|
||||
}
|
||||
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 {
|
||||
align := ""
|
||||
if n.Alignment != ast.AlignNone {
|
||||
if _, ok := n.AttributeString("align"); !ok { // Skip align render if overridden
|
||||
// TODO: "align" is deprecated. style="text-align:%s" instead?
|
||||
align = fmt.Sprintf(` align="%s"`, n.Alignment.String())
|
||||
}
|
||||
}
|
||||
fmt.Fprintf(w, "<%s", tag)
|
||||
if n.Attributes() != nil {
|
||||
if tag == "td" {
|
||||
html.RenderAttributes(w, n, TableTdCellAttributeFilter) // <td>
|
||||
} else {
|
||||
html.RenderAttributes(w, n, TableThCellAttributeFilter) // <th>
|
||||
}
|
||||
}
|
||||
fmt.Fprintf(w, "%s>", align)
|
||||
} else {
|
||||
fmt.Fprintf(w, "</%s>\n", tag)
|
||||
}
|
||||
return gast.WalkContinue, nil
|
||||
}
|
||||
|
||||
type table struct {
|
||||
}
|
||||
|
||||
// Table is an extension that allow you to use GFM tables .
|
||||
var Table = &table{}
|
||||
|
||||
func (e *table) Extend(m goldmark.Markdown) {
|
||||
m.Parser().AddOptions(parser.WithParagraphTransformers(
|
||||
util.Prioritized(NewTableParagraphTransformer(), 200),
|
||||
))
|
||||
m.Renderer().AddOptions(renderer.WithNodeRenderers(
|
||||
util.Prioritized(NewTableHTMLRenderer(), 500),
|
||||
))
|
||||
}
|
115
vendor/github.com/yuin/goldmark/extension/tasklist.go
generated
vendored
Normal file
115
vendor/github.com/yuin/goldmark/extension/tasklist.go
generated
vendored
Normal file
|
@ -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),
|
||||
))
|
||||
}
|
245
vendor/github.com/yuin/goldmark/extension/typographer.go
generated
vendored
Normal file
245
vendor/github.com/yuin/goldmark/extension/typographer.go
generated
vendored
Normal file
|
@ -0,0 +1,245 @@
|
|||
package extension
|
||||
|
||||
import (
|
||||
"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"
|
||||
)
|
||||
|
||||
// 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
|
||||
|
||||
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("»")
|
||||
|
||||
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 {
|
||||
before := block.PrecendingCharacter()
|
||||
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 == '"' {
|
||||
d := parser.ScanDelimiter(line, before, 1, defaultTypographerDelimiterProcessor)
|
||||
if d == nil {
|
||||
return nil
|
||||
}
|
||||
if c == '\'' {
|
||||
if s.Substitutions[LeftSingleQuote] != nil && d.CanOpen && !d.CanClose {
|
||||
node := gast.NewString(s.Substitutions[LeftSingleQuote])
|
||||
node.SetCode(true)
|
||||
block.Advance(1)
|
||||
return node
|
||||
}
|
||||
if s.Substitutions[RightSingleQuote] != nil && d.CanClose && !d.CanOpen {
|
||||
node := gast.NewString(s.Substitutions[RightSingleQuote])
|
||||
node.SetCode(true)
|
||||
block.Advance(1)
|
||||
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)
|
||||
return node
|
||||
}
|
||||
if s.Substitutions[RightDoubleQuote] != nil && d.CanClose && !d.CanOpen {
|
||||
node := gast.NewString(s.Substitutions[RightDoubleQuote])
|
||||
node.SetCode(true)
|
||||
block.Advance(1)
|
||||
return node
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *typographerParser) CloseBlock(parent gast.Node, pc parser.Context) {
|
||||
// nothing to do
|
||||
}
|
||||
|
||||
type typographer struct {
|
||||
options []TypographerOption
|
||||
}
|
||||
|
||||
// Typographer is an extension that repalace punctuations with typographic entities.
|
||||
var Typographer = &typographer{}
|
||||
|
||||
// NewTypographer returns a new Entender that repalace 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),
|
||||
))
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue