mirror of
				https://gitea.com/gitea/tea.git
				synced 2025-10-31 17:25:27 +01:00 
			
		
		
		
	Use glamour and termev to render/colorize content (#181)
Merge branch 'master' into use-glamour select Glamour Theme based on BackgroundColor Merge branch 'master' into use-glamour Merge branch 'master' into use-glamour update termev update go.mod label color colorate use glamour for issue content Vendor: Add glamour Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com> Co-authored-by: 6543 <6543@obermui.de> Reviewed-on: https://gitea.com/gitea/tea/pulls/181 Reviewed-by: techknowlogick <techknowlogick@gitea.io> Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
This commit is contained in:
		
							
								
								
									
										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 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, | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										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) | ||||
| } | ||||
							
								
								
									
										303
									
								
								vendor/github.com/yuin/goldmark/extension/linkify.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										303
									
								
								vendor/github.com/yuin/goldmark/extension/linkify.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,303 @@ | ||||
| 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@:%._\+~#=]{2,256}\.[a-z]+(?:(?:/|[#?])[-a-zA-Z0-9@:%_\+.~#!?&//=\(\);,'">\^{}\[\]` + "`" + `]*)?`) | ||||
|  | ||||
| var urlRegexp = regexp.MustCompile(`^(?:http|https|ftp):\/\/(?:www\.)?[-a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]+(?:(?:/|[#?])[-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" | ||||
| const optLinkifyURLRegexp parser.OptionName = "LinkifyURLRegexp" | ||||
| const optLinkifyWWWRegexp parser.OptionName = "LinkifyWWWRegexp" | ||||
| const 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:") | ||||
| 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 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) | ||||
| 	} | ||||
| 	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 { | ||||
| 	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), | ||||
| 		), | ||||
| 	) | ||||
| } | ||||
							
								
								
									
										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), | ||||
| 	)) | ||||
| } | ||||
							
								
								
									
										446
									
								
								vendor/github.com/yuin/goldmark/extension/table.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										446
									
								
								vendor/github.com/yuin/goldmark/extension/table.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,446 @@ | ||||
| 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" | ||||
| ) | ||||
|  | ||||
| // 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} | ||||
| } | ||||
|  | ||||
| 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 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 | ||||
| 	} | ||||
| 	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 { | ||||
| 	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.Append(util.StringToReadOnlyBytes(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), | ||||
| 	)) | ||||
| 	m.Renderer().AddOptions(renderer.WithNodeRenderers( | ||||
| 		util.Prioritized(NewTableHTMLRenderer(e.options...), 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), | ||||
| 	)) | ||||
| } | ||||
							
								
								
									
										323
									
								
								vendor/github.com/yuin/goldmark/extension/typographer.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										323
									
								
								vendor/github.com/yuin/goldmark/extension/typographer.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,323 @@ | ||||
| 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 | ||||
| 					} | ||||
| 				} | ||||
| 				// 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 && counter.Single > 0 { | ||||
| 				isClose := d.CanClose && !d.CanOpen | ||||
| 				maybeClose := d.CanClose && d.CanOpen && len(line) > 1 && (line[1] == ',' || line[1] == '.' || line[1] == '!' || 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 && (line[1] == ',' || line[1] == '.' || line[1] == '!' || 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), | ||||
| 	)) | ||||
| } | ||||
		Reference in New Issue
	
	Block a user
	 6543
					6543