...

Source file src/github.com/yuin/goldmark/extension/definition_list.go

Documentation: github.com/yuin/goldmark/extension

     1  package extension
     2  
     3  import (
     4  	"github.com/yuin/goldmark"
     5  	gast "github.com/yuin/goldmark/ast"
     6  	"github.com/yuin/goldmark/extension/ast"
     7  	"github.com/yuin/goldmark/parser"
     8  	"github.com/yuin/goldmark/renderer"
     9  	"github.com/yuin/goldmark/renderer/html"
    10  	"github.com/yuin/goldmark/text"
    11  	"github.com/yuin/goldmark/util"
    12  )
    13  
    14  type definitionListParser struct {
    15  }
    16  
    17  var defaultDefinitionListParser = &definitionListParser{}
    18  
    19  // NewDefinitionListParser return a new parser.BlockParser that
    20  // can parse PHP Markdown Extra Definition lists.
    21  func NewDefinitionListParser() parser.BlockParser {
    22  	return defaultDefinitionListParser
    23  }
    24  
    25  func (b *definitionListParser) Trigger() []byte {
    26  	return []byte{':'}
    27  }
    28  
    29  func (b *definitionListParser) Open(parent gast.Node, reader text.Reader, pc parser.Context) (gast.Node, parser.State) {
    30  	if _, ok := parent.(*ast.DefinitionList); ok {
    31  		return nil, parser.NoChildren
    32  	}
    33  	line, _ := reader.PeekLine()
    34  	pos := pc.BlockOffset()
    35  	indent := pc.BlockIndent()
    36  	if pos < 0 || line[pos] != ':' || indent != 0 {
    37  		return nil, parser.NoChildren
    38  	}
    39  
    40  	last := parent.LastChild()
    41  	// need 1 or more spaces after ':'
    42  	w, _ := util.IndentWidth(line[pos+1:], pos+1)
    43  	if w < 1 {
    44  		return nil, parser.NoChildren
    45  	}
    46  	if w >= 8 { // starts with indented code
    47  		w = 5
    48  	}
    49  	w += pos + 1 /* 1 = ':' */
    50  
    51  	para, lastIsParagraph := last.(*gast.Paragraph)
    52  	var list *ast.DefinitionList
    53  	status := parser.HasChildren
    54  	var ok bool
    55  	if lastIsParagraph {
    56  		list, ok = last.PreviousSibling().(*ast.DefinitionList)
    57  		if ok { // is not first item
    58  			list.Offset = w
    59  			list.TemporaryParagraph = para
    60  		} else { // is first item
    61  			list = ast.NewDefinitionList(w, para)
    62  			status |= parser.RequireParagraph
    63  		}
    64  	} else if list, ok = last.(*ast.DefinitionList); ok { // multiple description
    65  		list.Offset = w
    66  		list.TemporaryParagraph = nil
    67  	} else {
    68  		return nil, parser.NoChildren
    69  	}
    70  
    71  	return list, status
    72  }
    73  
    74  func (b *definitionListParser) Continue(node gast.Node, reader text.Reader, pc parser.Context) parser.State {
    75  	line, _ := reader.PeekLine()
    76  	if util.IsBlank(line) {
    77  		return parser.Continue | parser.HasChildren
    78  	}
    79  	list, _ := node.(*ast.DefinitionList)
    80  	w, _ := util.IndentWidth(line, reader.LineOffset())
    81  	if w < list.Offset {
    82  		return parser.Close
    83  	}
    84  	pos, padding := util.IndentPosition(line, reader.LineOffset(), list.Offset)
    85  	reader.AdvanceAndSetPadding(pos, padding)
    86  	return parser.Continue | parser.HasChildren
    87  }
    88  
    89  func (b *definitionListParser) Close(node gast.Node, reader text.Reader, pc parser.Context) {
    90  	// nothing to do
    91  }
    92  
    93  func (b *definitionListParser) CanInterruptParagraph() bool {
    94  	return true
    95  }
    96  
    97  func (b *definitionListParser) CanAcceptIndentedLine() bool {
    98  	return false
    99  }
   100  
   101  type definitionDescriptionParser struct {
   102  }
   103  
   104  var defaultDefinitionDescriptionParser = &definitionDescriptionParser{}
   105  
   106  // NewDefinitionDescriptionParser return a new parser.BlockParser that
   107  // can parse definition description starts with ':'.
   108  func NewDefinitionDescriptionParser() parser.BlockParser {
   109  	return defaultDefinitionDescriptionParser
   110  }
   111  
   112  func (b *definitionDescriptionParser) Trigger() []byte {
   113  	return []byte{':'}
   114  }
   115  
   116  func (b *definitionDescriptionParser) Open(
   117  	parent gast.Node, reader text.Reader, pc parser.Context) (gast.Node, parser.State) {
   118  	line, _ := reader.PeekLine()
   119  	pos := pc.BlockOffset()
   120  	indent := pc.BlockIndent()
   121  	if pos < 0 || line[pos] != ':' || indent != 0 {
   122  		return nil, parser.NoChildren
   123  	}
   124  	list, _ := parent.(*ast.DefinitionList)
   125  	if list == nil {
   126  		return nil, parser.NoChildren
   127  	}
   128  	para := list.TemporaryParagraph
   129  	list.TemporaryParagraph = nil
   130  	if para != nil {
   131  		lines := para.Lines()
   132  		l := lines.Len()
   133  		for i := 0; i < l; i++ {
   134  			term := ast.NewDefinitionTerm()
   135  			segment := lines.At(i)
   136  			term.Lines().Append(segment.TrimRightSpace(reader.Source()))
   137  			list.AppendChild(list, term)
   138  		}
   139  		para.Parent().RemoveChild(para.Parent(), para)
   140  	}
   141  	cpos, padding := util.IndentPosition(line[pos+1:], pos+1, list.Offset-pos-1)
   142  	reader.AdvanceAndSetPadding(cpos+1, padding)
   143  
   144  	return ast.NewDefinitionDescription(), parser.HasChildren
   145  }
   146  
   147  func (b *definitionDescriptionParser) Continue(node gast.Node, reader text.Reader, pc parser.Context) parser.State {
   148  	// definitionListParser detects end of the description.
   149  	// so this method will never be called.
   150  	return parser.Continue | parser.HasChildren
   151  }
   152  
   153  func (b *definitionDescriptionParser) Close(node gast.Node, reader text.Reader, pc parser.Context) {
   154  	desc := node.(*ast.DefinitionDescription)
   155  	desc.IsTight = !desc.HasBlankPreviousLines()
   156  	if desc.IsTight {
   157  		for gc := desc.FirstChild(); gc != nil; gc = gc.NextSibling() {
   158  			paragraph, ok := gc.(*gast.Paragraph)
   159  			if ok {
   160  				textBlock := gast.NewTextBlock()
   161  				textBlock.SetLines(paragraph.Lines())
   162  				desc.ReplaceChild(desc, paragraph, textBlock)
   163  			}
   164  		}
   165  	}
   166  }
   167  
   168  func (b *definitionDescriptionParser) CanInterruptParagraph() bool {
   169  	return true
   170  }
   171  
   172  func (b *definitionDescriptionParser) CanAcceptIndentedLine() bool {
   173  	return false
   174  }
   175  
   176  // DefinitionListHTMLRenderer is a renderer.NodeRenderer implementation that
   177  // renders DefinitionList nodes.
   178  type DefinitionListHTMLRenderer struct {
   179  	html.Config
   180  }
   181  
   182  // NewDefinitionListHTMLRenderer returns a new DefinitionListHTMLRenderer.
   183  func NewDefinitionListHTMLRenderer(opts ...html.Option) renderer.NodeRenderer {
   184  	r := &DefinitionListHTMLRenderer{
   185  		Config: html.NewConfig(),
   186  	}
   187  	for _, opt := range opts {
   188  		opt.SetHTMLOption(&r.Config)
   189  	}
   190  	return r
   191  }
   192  
   193  // RegisterFuncs implements renderer.NodeRenderer.RegisterFuncs.
   194  func (r *DefinitionListHTMLRenderer) RegisterFuncs(reg renderer.NodeRendererFuncRegisterer) {
   195  	reg.Register(ast.KindDefinitionList, r.renderDefinitionList)
   196  	reg.Register(ast.KindDefinitionTerm, r.renderDefinitionTerm)
   197  	reg.Register(ast.KindDefinitionDescription, r.renderDefinitionDescription)
   198  }
   199  
   200  // DefinitionListAttributeFilter defines attribute names which dl elements can have.
   201  var DefinitionListAttributeFilter = html.GlobalAttributeFilter
   202  
   203  func (r *DefinitionListHTMLRenderer) renderDefinitionList(
   204  	w util.BufWriter, source []byte, n gast.Node, entering bool) (gast.WalkStatus, error) {
   205  	if entering {
   206  		if n.Attributes() != nil {
   207  			_, _ = w.WriteString("<dl")
   208  			html.RenderAttributes(w, n, DefinitionListAttributeFilter)
   209  			_, _ = w.WriteString(">\n")
   210  		} else {
   211  			_, _ = w.WriteString("<dl>\n")
   212  		}
   213  	} else {
   214  		_, _ = w.WriteString("</dl>\n")
   215  	}
   216  	return gast.WalkContinue, nil
   217  }
   218  
   219  // DefinitionTermAttributeFilter defines attribute names which dd elements can have.
   220  var DefinitionTermAttributeFilter = html.GlobalAttributeFilter
   221  
   222  func (r *DefinitionListHTMLRenderer) renderDefinitionTerm(
   223  	w util.BufWriter, source []byte, n gast.Node, entering bool) (gast.WalkStatus, error) {
   224  	if entering {
   225  		if n.Attributes() != nil {
   226  			_, _ = w.WriteString("<dt")
   227  			html.RenderAttributes(w, n, DefinitionTermAttributeFilter)
   228  			_ = w.WriteByte('>')
   229  		} else {
   230  			_, _ = w.WriteString("<dt>")
   231  		}
   232  	} else {
   233  		_, _ = w.WriteString("</dt>\n")
   234  	}
   235  	return gast.WalkContinue, nil
   236  }
   237  
   238  // DefinitionDescriptionAttributeFilter defines attribute names which dd elements can have.
   239  var DefinitionDescriptionAttributeFilter = html.GlobalAttributeFilter
   240  
   241  func (r *DefinitionListHTMLRenderer) renderDefinitionDescription(
   242  	w util.BufWriter, source []byte, node gast.Node, entering bool) (gast.WalkStatus, error) {
   243  	if entering {
   244  		n := node.(*ast.DefinitionDescription)
   245  		_, _ = w.WriteString("<dd")
   246  		if n.Attributes() != nil {
   247  			html.RenderAttributes(w, n, DefinitionDescriptionAttributeFilter)
   248  		}
   249  		if n.IsTight {
   250  			_, _ = w.WriteString(">")
   251  		} else {
   252  			_, _ = w.WriteString(">\n")
   253  		}
   254  	} else {
   255  		_, _ = w.WriteString("</dd>\n")
   256  	}
   257  	return gast.WalkContinue, nil
   258  }
   259  
   260  type definitionList struct {
   261  }
   262  
   263  // DefinitionList is an extension that allow you to use PHP Markdown Extra Definition lists.
   264  var DefinitionList = &definitionList{}
   265  
   266  func (e *definitionList) Extend(m goldmark.Markdown) {
   267  	m.Parser().AddOptions(parser.WithBlockParsers(
   268  		util.Prioritized(NewDefinitionListParser(), 101),
   269  		util.Prioritized(NewDefinitionDescriptionParser(), 102),
   270  	))
   271  	m.Renderer().AddOptions(renderer.WithNodeRenderers(
   272  		util.Prioritized(NewDefinitionListHTMLRenderer(), 500),
   273  	))
   274  }
   275  

View as plain text