Merge commit 'ad273dbe5dba7bd0e901270464e25fc1f030a5b5' as 'backend/goldmark'
This commit is contained in:
@@ -0,0 +1,274 @@
|
||||
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 := range l {
|
||||
term := ast.NewDefinitionTerm()
|
||||
segment := lines.At(i)
|
||||
term.Lines().Append(segment.TrimRightSpace(reader.Source()))
|
||||
list.AppendChild(list, term)
|
||||
}
|
||||
para.Parent().RemoveChild(para.Parent(), para)
|
||||
}
|
||||
cpos, padding := util.IndentPosition(line[pos+1:], pos+1, list.Offset-pos-1)
|
||||
reader.AdvanceAndSetPadding(cpos+1, padding)
|
||||
|
||||
return ast.NewDefinitionDescription(), parser.HasChildren
|
||||
}
|
||||
|
||||
func (b *definitionDescriptionParser) Continue(node gast.Node, reader text.Reader, pc parser.Context) parser.State {
|
||||
// definitionListParser detects end of the description.
|
||||
// so this method will never be called.
|
||||
return parser.Continue | parser.HasChildren
|
||||
}
|
||||
|
||||
func (b *definitionDescriptionParser) Close(node gast.Node, reader text.Reader, pc parser.Context) {
|
||||
desc := node.(*ast.DefinitionDescription)
|
||||
desc.IsTight = !desc.HasBlankPreviousLines()
|
||||
if desc.IsTight {
|
||||
for gc := desc.FirstChild(); gc != nil; gc = gc.NextSibling() {
|
||||
paragraph, ok := gc.(*gast.Paragraph)
|
||||
if ok {
|
||||
textBlock := gast.NewTextBlock()
|
||||
textBlock.SetLines(paragraph.Lines())
|
||||
desc.ReplaceChild(desc, paragraph, textBlock)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (b *definitionDescriptionParser) CanInterruptParagraph() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (b *definitionDescriptionParser) CanAcceptIndentedLine() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// DefinitionListHTMLRenderer is a renderer.NodeRenderer implementation that
|
||||
// renders DefinitionList nodes.
|
||||
type DefinitionListHTMLRenderer struct {
|
||||
html.Config
|
||||
}
|
||||
|
||||
// NewDefinitionListHTMLRenderer returns a new DefinitionListHTMLRenderer.
|
||||
func NewDefinitionListHTMLRenderer(opts ...html.Option) renderer.NodeRenderer {
|
||||
r := &DefinitionListHTMLRenderer{
|
||||
Config: html.NewConfig(),
|
||||
}
|
||||
for _, opt := range opts {
|
||||
opt.SetHTMLOption(&r.Config)
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
// RegisterFuncs implements renderer.NodeRenderer.RegisterFuncs.
|
||||
func (r *DefinitionListHTMLRenderer) RegisterFuncs(reg renderer.NodeRendererFuncRegisterer) {
|
||||
reg.Register(ast.KindDefinitionList, r.renderDefinitionList)
|
||||
reg.Register(ast.KindDefinitionTerm, r.renderDefinitionTerm)
|
||||
reg.Register(ast.KindDefinitionDescription, r.renderDefinitionDescription)
|
||||
}
|
||||
|
||||
// DefinitionListAttributeFilter defines attribute names which dl elements can have.
|
||||
var DefinitionListAttributeFilter = html.GlobalAttributeFilter
|
||||
|
||||
func (r *DefinitionListHTMLRenderer) renderDefinitionList(
|
||||
w util.BufWriter, source []byte, n gast.Node, entering bool) (gast.WalkStatus, error) {
|
||||
if entering {
|
||||
if n.Attributes() != nil {
|
||||
_, _ = w.WriteString("<dl")
|
||||
html.RenderAttributes(w, n, DefinitionListAttributeFilter)
|
||||
_, _ = w.WriteString(">\n")
|
||||
} else {
|
||||
_, _ = w.WriteString("<dl>\n")
|
||||
}
|
||||
} else {
|
||||
_, _ = w.WriteString("</dl>\n")
|
||||
}
|
||||
return gast.WalkContinue, nil
|
||||
}
|
||||
|
||||
// DefinitionTermAttributeFilter defines attribute names which dd elements can have.
|
||||
var DefinitionTermAttributeFilter = html.GlobalAttributeFilter
|
||||
|
||||
func (r *DefinitionListHTMLRenderer) renderDefinitionTerm(
|
||||
w util.BufWriter, source []byte, n gast.Node, entering bool) (gast.WalkStatus, error) {
|
||||
if entering {
|
||||
if n.Attributes() != nil {
|
||||
_, _ = w.WriteString("<dt")
|
||||
html.RenderAttributes(w, n, DefinitionTermAttributeFilter)
|
||||
_ = w.WriteByte('>')
|
||||
} else {
|
||||
_, _ = w.WriteString("<dt>")
|
||||
}
|
||||
} else {
|
||||
_, _ = w.WriteString("</dt>\n")
|
||||
}
|
||||
return gast.WalkContinue, nil
|
||||
}
|
||||
|
||||
// DefinitionDescriptionAttributeFilter defines attribute names which dd elements can have.
|
||||
var DefinitionDescriptionAttributeFilter = html.GlobalAttributeFilter
|
||||
|
||||
func (r *DefinitionListHTMLRenderer) renderDefinitionDescription(
|
||||
w util.BufWriter, source []byte, node gast.Node, entering bool) (gast.WalkStatus, error) {
|
||||
if entering {
|
||||
n := node.(*ast.DefinitionDescription)
|
||||
_, _ = w.WriteString("<dd")
|
||||
if n.Attributes() != nil {
|
||||
html.RenderAttributes(w, n, DefinitionDescriptionAttributeFilter)
|
||||
}
|
||||
if n.IsTight {
|
||||
_, _ = w.WriteString(">")
|
||||
} else {
|
||||
_, _ = w.WriteString(">\n")
|
||||
}
|
||||
} else {
|
||||
_, _ = w.WriteString("</dd>\n")
|
||||
}
|
||||
return gast.WalkContinue, nil
|
||||
}
|
||||
|
||||
type definitionList struct {
|
||||
}
|
||||
|
||||
// DefinitionList is an extension that allow you to use PHP Markdown Extra Definition lists.
|
||||
var DefinitionList = &definitionList{}
|
||||
|
||||
func (e *definitionList) Extend(m goldmark.Markdown) {
|
||||
m.Parser().AddOptions(parser.WithBlockParsers(
|
||||
util.Prioritized(NewDefinitionListParser(), 101),
|
||||
util.Prioritized(NewDefinitionDescriptionParser(), 102),
|
||||
))
|
||||
m.Renderer().AddOptions(renderer.WithNodeRenderers(
|
||||
util.Prioritized(NewDefinitionListHTMLRenderer(), 500),
|
||||
))
|
||||
}
|
||||
Reference in New Issue
Block a user