27757714d0
* Move to goldmark Markdown rendering moved from blackfriday to the goldmark. Multiple subtle changes required to the goldmark extensions to keep current rendering and defaults. Can go further with goldmark linkify and have this work within markdown rendering making the link processor unnecessary. Need to think about how to go about allowing extensions - at present it seems that these would be hard to do without recompilation. * linter fixes Co-authored-by: Lauris BH <lauris@nix.lv>
474 lines
11 KiB
Go
474 lines
11 KiB
Go
package ast
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
|
|
textm "github.com/yuin/goldmark/text"
|
|
)
|
|
|
|
// A BaseBlock struct implements the Node interface.
|
|
type BaseBlock struct {
|
|
BaseNode
|
|
blankPreviousLines bool
|
|
lines *textm.Segments
|
|
}
|
|
|
|
// Type implements Node.Type
|
|
func (b *BaseBlock) Type() NodeType {
|
|
return TypeBlock
|
|
}
|
|
|
|
// IsRaw implements Node.IsRaw
|
|
func (b *BaseBlock) IsRaw() bool {
|
|
return false
|
|
}
|
|
|
|
// HasBlankPreviousLines implements Node.HasBlankPreviousLines.
|
|
func (b *BaseBlock) HasBlankPreviousLines() bool {
|
|
return b.blankPreviousLines
|
|
}
|
|
|
|
// SetBlankPreviousLines implements Node.SetBlankPreviousLines.
|
|
func (b *BaseBlock) SetBlankPreviousLines(v bool) {
|
|
b.blankPreviousLines = v
|
|
}
|
|
|
|
// Lines implements Node.Lines
|
|
func (b *BaseBlock) Lines() *textm.Segments {
|
|
if b.lines == nil {
|
|
b.lines = textm.NewSegments()
|
|
}
|
|
return b.lines
|
|
}
|
|
|
|
// SetLines implements Node.SetLines
|
|
func (b *BaseBlock) SetLines(v *textm.Segments) {
|
|
b.lines = v
|
|
}
|
|
|
|
// A Document struct is a root node of Markdown text.
|
|
type Document struct {
|
|
BaseBlock
|
|
}
|
|
|
|
// KindDocument is a NodeKind of the Document node.
|
|
var KindDocument = NewNodeKind("Document")
|
|
|
|
// Dump implements Node.Dump .
|
|
func (n *Document) Dump(source []byte, level int) {
|
|
DumpHelper(n, source, level, nil, nil)
|
|
}
|
|
|
|
// Type implements Node.Type .
|
|
func (n *Document) Type() NodeType {
|
|
return TypeDocument
|
|
}
|
|
|
|
// Kind implements Node.Kind.
|
|
func (n *Document) Kind() NodeKind {
|
|
return KindDocument
|
|
}
|
|
|
|
// NewDocument returns a new Document node.
|
|
func NewDocument() *Document {
|
|
return &Document{
|
|
BaseBlock: BaseBlock{},
|
|
}
|
|
}
|
|
|
|
// A TextBlock struct is a node whose lines
|
|
// should be rendered without any containers.
|
|
type TextBlock struct {
|
|
BaseBlock
|
|
}
|
|
|
|
// Dump implements Node.Dump .
|
|
func (n *TextBlock) Dump(source []byte, level int) {
|
|
DumpHelper(n, source, level, nil, nil)
|
|
}
|
|
|
|
// KindTextBlock is a NodeKind of the TextBlock node.
|
|
var KindTextBlock = NewNodeKind("TextBlock")
|
|
|
|
// Kind implements Node.Kind.
|
|
func (n *TextBlock) Kind() NodeKind {
|
|
return KindTextBlock
|
|
}
|
|
|
|
// NewTextBlock returns a new TextBlock node.
|
|
func NewTextBlock() *TextBlock {
|
|
return &TextBlock{
|
|
BaseBlock: BaseBlock{},
|
|
}
|
|
}
|
|
|
|
// A Paragraph struct represents a paragraph of Markdown text.
|
|
type Paragraph struct {
|
|
BaseBlock
|
|
}
|
|
|
|
// Dump implements Node.Dump .
|
|
func (n *Paragraph) Dump(source []byte, level int) {
|
|
DumpHelper(n, source, level, nil, nil)
|
|
}
|
|
|
|
// KindParagraph is a NodeKind of the Paragraph node.
|
|
var KindParagraph = NewNodeKind("Paragraph")
|
|
|
|
// Kind implements Node.Kind.
|
|
func (n *Paragraph) Kind() NodeKind {
|
|
return KindParagraph
|
|
}
|
|
|
|
// NewParagraph returns a new Paragraph node.
|
|
func NewParagraph() *Paragraph {
|
|
return &Paragraph{
|
|
BaseBlock: BaseBlock{},
|
|
}
|
|
}
|
|
|
|
// IsParagraph returns true if the given node implements the Paragraph interface,
|
|
// otherwise false.
|
|
func IsParagraph(node Node) bool {
|
|
_, ok := node.(*Paragraph)
|
|
return ok
|
|
}
|
|
|
|
// A Heading struct represents headings like SetextHeading and ATXHeading.
|
|
type Heading struct {
|
|
BaseBlock
|
|
// Level returns a level of this heading.
|
|
// This value is between 1 and 6.
|
|
Level int
|
|
}
|
|
|
|
// Dump implements Node.Dump .
|
|
func (n *Heading) Dump(source []byte, level int) {
|
|
m := map[string]string{
|
|
"Level": fmt.Sprintf("%d", n.Level),
|
|
}
|
|
DumpHelper(n, source, level, m, nil)
|
|
}
|
|
|
|
// KindHeading is a NodeKind of the Heading node.
|
|
var KindHeading = NewNodeKind("Heading")
|
|
|
|
// Kind implements Node.Kind.
|
|
func (n *Heading) Kind() NodeKind {
|
|
return KindHeading
|
|
}
|
|
|
|
// NewHeading returns a new Heading node.
|
|
func NewHeading(level int) *Heading {
|
|
return &Heading{
|
|
BaseBlock: BaseBlock{},
|
|
Level: level,
|
|
}
|
|
}
|
|
|
|
// A ThematicBreak struct represents a thematic break of Markdown text.
|
|
type ThematicBreak struct {
|
|
BaseBlock
|
|
}
|
|
|
|
// Dump implements Node.Dump .
|
|
func (n *ThematicBreak) Dump(source []byte, level int) {
|
|
DumpHelper(n, source, level, nil, nil)
|
|
}
|
|
|
|
// KindThematicBreak is a NodeKind of the ThematicBreak node.
|
|
var KindThematicBreak = NewNodeKind("ThematicBreak")
|
|
|
|
// Kind implements Node.Kind.
|
|
func (n *ThematicBreak) Kind() NodeKind {
|
|
return KindThematicBreak
|
|
}
|
|
|
|
// NewThematicBreak returns a new ThematicBreak node.
|
|
func NewThematicBreak() *ThematicBreak {
|
|
return &ThematicBreak{
|
|
BaseBlock: BaseBlock{},
|
|
}
|
|
}
|
|
|
|
// A CodeBlock interface represents an indented code block of Markdown text.
|
|
type CodeBlock struct {
|
|
BaseBlock
|
|
}
|
|
|
|
// IsRaw implements Node.IsRaw.
|
|
func (n *CodeBlock) IsRaw() bool {
|
|
return true
|
|
}
|
|
|
|
// Dump implements Node.Dump .
|
|
func (n *CodeBlock) Dump(source []byte, level int) {
|
|
DumpHelper(n, source, level, nil, nil)
|
|
}
|
|
|
|
// KindCodeBlock is a NodeKind of the CodeBlock node.
|
|
var KindCodeBlock = NewNodeKind("CodeBlock")
|
|
|
|
// Kind implements Node.Kind.
|
|
func (n *CodeBlock) Kind() NodeKind {
|
|
return KindCodeBlock
|
|
}
|
|
|
|
// NewCodeBlock returns a new CodeBlock node.
|
|
func NewCodeBlock() *CodeBlock {
|
|
return &CodeBlock{
|
|
BaseBlock: BaseBlock{},
|
|
}
|
|
}
|
|
|
|
// A FencedCodeBlock struct represents a fenced code block of Markdown text.
|
|
type FencedCodeBlock struct {
|
|
BaseBlock
|
|
// Info returns a info text of this fenced code block.
|
|
Info *Text
|
|
|
|
language []byte
|
|
}
|
|
|
|
// Language returns an language in an info string.
|
|
// Language returns nil if this node does not have an info string.
|
|
func (n *FencedCodeBlock) Language(source []byte) []byte {
|
|
if n.language == nil && n.Info != nil {
|
|
segment := n.Info.Segment
|
|
info := segment.Value(source)
|
|
i := 0
|
|
for ; i < len(info); i++ {
|
|
if info[i] == ' ' {
|
|
break
|
|
}
|
|
}
|
|
n.language = info[:i]
|
|
}
|
|
return n.language
|
|
}
|
|
|
|
// IsRaw implements Node.IsRaw.
|
|
func (n *FencedCodeBlock) IsRaw() bool {
|
|
return true
|
|
}
|
|
|
|
// Dump implements Node.Dump .
|
|
func (n *FencedCodeBlock) Dump(source []byte, level int) {
|
|
m := map[string]string{}
|
|
if n.Info != nil {
|
|
m["Info"] = fmt.Sprintf("\"%s\"", n.Info.Text(source))
|
|
}
|
|
DumpHelper(n, source, level, m, nil)
|
|
}
|
|
|
|
// KindFencedCodeBlock is a NodeKind of the FencedCodeBlock node.
|
|
var KindFencedCodeBlock = NewNodeKind("FencedCodeBlock")
|
|
|
|
// Kind implements Node.Kind.
|
|
func (n *FencedCodeBlock) Kind() NodeKind {
|
|
return KindFencedCodeBlock
|
|
}
|
|
|
|
// NewFencedCodeBlock return a new FencedCodeBlock node.
|
|
func NewFencedCodeBlock(info *Text) *FencedCodeBlock {
|
|
return &FencedCodeBlock{
|
|
BaseBlock: BaseBlock{},
|
|
Info: info,
|
|
}
|
|
}
|
|
|
|
// A Blockquote struct represents an blockquote block of Markdown text.
|
|
type Blockquote struct {
|
|
BaseBlock
|
|
}
|
|
|
|
// Dump implements Node.Dump .
|
|
func (n *Blockquote) Dump(source []byte, level int) {
|
|
DumpHelper(n, source, level, nil, nil)
|
|
}
|
|
|
|
// KindBlockquote is a NodeKind of the Blockquote node.
|
|
var KindBlockquote = NewNodeKind("Blockquote")
|
|
|
|
// Kind implements Node.Kind.
|
|
func (n *Blockquote) Kind() NodeKind {
|
|
return KindBlockquote
|
|
}
|
|
|
|
// NewBlockquote returns a new Blockquote node.
|
|
func NewBlockquote() *Blockquote {
|
|
return &Blockquote{
|
|
BaseBlock: BaseBlock{},
|
|
}
|
|
}
|
|
|
|
// A List structr represents a list of Markdown text.
|
|
type List struct {
|
|
BaseBlock
|
|
|
|
// Marker is a markar character like '-', '+', ')' and '.'.
|
|
Marker byte
|
|
|
|
// IsTight is a true if this list is a 'tight' list.
|
|
// See https://spec.commonmark.org/0.29/#loose for details.
|
|
IsTight bool
|
|
|
|
// Start is an initial number of this ordered list.
|
|
// If this list is not an ordered list, Start is 0.
|
|
Start int
|
|
}
|
|
|
|
// IsOrdered returns true if this list is an ordered list, otherwise false.
|
|
func (l *List) IsOrdered() bool {
|
|
return l.Marker == '.' || l.Marker == ')'
|
|
}
|
|
|
|
// CanContinue returns true if this list can continue with
|
|
// the given mark and a list type, otherwise false.
|
|
func (l *List) CanContinue(marker byte, isOrdered bool) bool {
|
|
return marker == l.Marker && isOrdered == l.IsOrdered()
|
|
}
|
|
|
|
// Dump implements Node.Dump.
|
|
func (l *List) Dump(source []byte, level int) {
|
|
m := map[string]string{
|
|
"Ordered": fmt.Sprintf("%v", l.IsOrdered()),
|
|
"Marker": fmt.Sprintf("%c", l.Marker),
|
|
"Tight": fmt.Sprintf("%v", l.IsTight),
|
|
}
|
|
if l.IsOrdered() {
|
|
m["Start"] = fmt.Sprintf("%d", l.Start)
|
|
}
|
|
DumpHelper(l, source, level, m, nil)
|
|
}
|
|
|
|
// KindList is a NodeKind of the List node.
|
|
var KindList = NewNodeKind("List")
|
|
|
|
// Kind implements Node.Kind.
|
|
func (l *List) Kind() NodeKind {
|
|
return KindList
|
|
}
|
|
|
|
// NewList returns a new List node.
|
|
func NewList(marker byte) *List {
|
|
return &List{
|
|
BaseBlock: BaseBlock{},
|
|
Marker: marker,
|
|
IsTight: true,
|
|
}
|
|
}
|
|
|
|
// A ListItem struct represents a list item of Markdown text.
|
|
type ListItem struct {
|
|
BaseBlock
|
|
|
|
// Offset is an offset potision of this item.
|
|
Offset int
|
|
}
|
|
|
|
// Dump implements Node.Dump.
|
|
func (n *ListItem) Dump(source []byte, level int) {
|
|
m := map[string]string{
|
|
"Offset": fmt.Sprintf("%d", n.Offset),
|
|
}
|
|
DumpHelper(n, source, level, m, nil)
|
|
}
|
|
|
|
// KindListItem is a NodeKind of the ListItem node.
|
|
var KindListItem = NewNodeKind("ListItem")
|
|
|
|
// Kind implements Node.Kind.
|
|
func (n *ListItem) Kind() NodeKind {
|
|
return KindListItem
|
|
}
|
|
|
|
// NewListItem returns a new ListItem node.
|
|
func NewListItem(offset int) *ListItem {
|
|
return &ListItem{
|
|
BaseBlock: BaseBlock{},
|
|
Offset: offset,
|
|
}
|
|
}
|
|
|
|
// HTMLBlockType represents kinds of an html blocks.
|
|
// See https://spec.commonmark.org/0.29/#html-blocks
|
|
type HTMLBlockType int
|
|
|
|
const (
|
|
// HTMLBlockType1 represents type 1 html blocks
|
|
HTMLBlockType1 HTMLBlockType = iota + 1
|
|
// HTMLBlockType2 represents type 2 html blocks
|
|
HTMLBlockType2
|
|
// HTMLBlockType3 represents type 3 html blocks
|
|
HTMLBlockType3
|
|
// HTMLBlockType4 represents type 4 html blocks
|
|
HTMLBlockType4
|
|
// HTMLBlockType5 represents type 5 html blocks
|
|
HTMLBlockType5
|
|
// HTMLBlockType6 represents type 6 html blocks
|
|
HTMLBlockType6
|
|
// HTMLBlockType7 represents type 7 html blocks
|
|
HTMLBlockType7
|
|
)
|
|
|
|
// An HTMLBlock struct represents an html block of Markdown text.
|
|
type HTMLBlock struct {
|
|
BaseBlock
|
|
|
|
// Type is a type of this html block.
|
|
HTMLBlockType HTMLBlockType
|
|
|
|
// ClosureLine is a line that closes this html block.
|
|
ClosureLine textm.Segment
|
|
}
|
|
|
|
// IsRaw implements Node.IsRaw.
|
|
func (n *HTMLBlock) IsRaw() bool {
|
|
return true
|
|
}
|
|
|
|
// HasClosure returns true if this html block has a closure line,
|
|
// otherwise false.
|
|
func (n *HTMLBlock) HasClosure() bool {
|
|
return n.ClosureLine.Start >= 0
|
|
}
|
|
|
|
// Dump implements Node.Dump.
|
|
func (n *HTMLBlock) Dump(source []byte, level int) {
|
|
indent := strings.Repeat(" ", level)
|
|
fmt.Printf("%s%s {\n", indent, "HTMLBlock")
|
|
indent2 := strings.Repeat(" ", level+1)
|
|
fmt.Printf("%sRawText: \"", indent2)
|
|
for i := 0; i < n.Lines().Len(); i++ {
|
|
s := n.Lines().At(i)
|
|
fmt.Print(string(source[s.Start:s.Stop]))
|
|
}
|
|
fmt.Printf("\"\n")
|
|
for c := n.FirstChild(); c != nil; c = c.NextSibling() {
|
|
c.Dump(source, level+1)
|
|
}
|
|
if n.HasClosure() {
|
|
cl := n.ClosureLine
|
|
fmt.Printf("%sClosure: \"%s\"\n", indent2, string(cl.Value(source)))
|
|
}
|
|
fmt.Printf("%s}\n", indent)
|
|
}
|
|
|
|
// KindHTMLBlock is a NodeKind of the HTMLBlock node.
|
|
var KindHTMLBlock = NewNodeKind("HTMLBlock")
|
|
|
|
// Kind implements Node.Kind.
|
|
func (n *HTMLBlock) Kind() NodeKind {
|
|
return KindHTMLBlock
|
|
}
|
|
|
|
// NewHTMLBlock returns a new HTMLBlock node.
|
|
func NewHTMLBlock(typ HTMLBlockType) *HTMLBlock {
|
|
return &HTMLBlock{
|
|
BaseBlock: BaseBlock{},
|
|
HTMLBlockType: typ,
|
|
ClosureLine: textm.NewSegment(-1, -1),
|
|
}
|
|
}
|