219 lines
		
	
	
		
			4.7 KiB
		
	
	
	
		
			Go
		
	
	
	
			
		
		
	
	
			219 lines
		
	
	
		
			4.7 KiB
		
	
	
	
		
			Go
		
	
	
	
// package meta is a extension for the goldmark(http://github.com/yuin/goldmark).
 | 
						|
//
 | 
						|
// This extension parses YAML metadata blocks and store metadata to a
 | 
						|
// parser.Context.
 | 
						|
package meta
 | 
						|
 | 
						|
import (
 | 
						|
	"bytes"
 | 
						|
	"fmt"
 | 
						|
	"github.com/yuin/goldmark"
 | 
						|
	gast "github.com/yuin/goldmark/ast"
 | 
						|
	east "github.com/yuin/goldmark/extension/ast"
 | 
						|
	"github.com/yuin/goldmark/parser"
 | 
						|
	"github.com/yuin/goldmark/text"
 | 
						|
	"github.com/yuin/goldmark/util"
 | 
						|
 | 
						|
	"gopkg.in/yaml.v2"
 | 
						|
)
 | 
						|
 | 
						|
type data struct {
 | 
						|
	Map   map[string]interface{}
 | 
						|
	Items yaml.MapSlice
 | 
						|
	Error error
 | 
						|
	Node  gast.Node
 | 
						|
}
 | 
						|
 | 
						|
var contextKey = parser.NewContextKey()
 | 
						|
 | 
						|
// Get returns a YAML metadata.
 | 
						|
func Get(pc parser.Context) map[string]interface{} {
 | 
						|
	v := pc.Get(contextKey)
 | 
						|
	if v == nil {
 | 
						|
		return nil
 | 
						|
	}
 | 
						|
	d := v.(*data)
 | 
						|
	return d.Map
 | 
						|
}
 | 
						|
 | 
						|
// GetItems returns a YAML metadata.
 | 
						|
// GetItems preserves defined key order.
 | 
						|
func GetItems(pc parser.Context) yaml.MapSlice {
 | 
						|
	v := pc.Get(contextKey)
 | 
						|
	if v == nil {
 | 
						|
		return nil
 | 
						|
	}
 | 
						|
	d := v.(*data)
 | 
						|
	return d.Items
 | 
						|
}
 | 
						|
 | 
						|
type metaParser struct {
 | 
						|
}
 | 
						|
 | 
						|
var defaultMetaParser = &metaParser{}
 | 
						|
 | 
						|
// NewParser returns a BlockParser that can parse YAML metadata blocks.
 | 
						|
func NewParser() parser.BlockParser {
 | 
						|
	return defaultMetaParser
 | 
						|
}
 | 
						|
 | 
						|
func isSeparator(line []byte) bool {
 | 
						|
	line = util.TrimRightSpace(util.TrimLeftSpace(line))
 | 
						|
	for i := 0; i < len(line); i++ {
 | 
						|
		if line[i] != '-' {
 | 
						|
			return false
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return true
 | 
						|
}
 | 
						|
 | 
						|
func (b *metaParser) Trigger() []byte {
 | 
						|
	return []byte{'-'}
 | 
						|
}
 | 
						|
 | 
						|
func (b *metaParser) Open(parent gast.Node, reader text.Reader, pc parser.Context) (gast.Node, parser.State) {
 | 
						|
	linenum, _ := reader.Position()
 | 
						|
	if linenum != 0 {
 | 
						|
		return nil, parser.NoChildren
 | 
						|
	}
 | 
						|
	line, _ := reader.PeekLine()
 | 
						|
	if isSeparator(line) {
 | 
						|
		return gast.NewTextBlock(), parser.NoChildren
 | 
						|
	}
 | 
						|
	return nil, parser.NoChildren
 | 
						|
}
 | 
						|
 | 
						|
func (b *metaParser) Continue(node gast.Node, reader text.Reader, pc parser.Context) parser.State {
 | 
						|
	line, segment := reader.PeekLine()
 | 
						|
	if isSeparator(line) {
 | 
						|
		reader.Advance(segment.Len())
 | 
						|
		return parser.Close
 | 
						|
	}
 | 
						|
	node.Lines().Append(segment)
 | 
						|
	return parser.Continue | parser.NoChildren
 | 
						|
}
 | 
						|
 | 
						|
func (b *metaParser) Close(node gast.Node, reader text.Reader, pc parser.Context) {
 | 
						|
	lines := node.Lines()
 | 
						|
	var buf bytes.Buffer
 | 
						|
	for i := 0; i < lines.Len(); i++ {
 | 
						|
		segment := lines.At(i)
 | 
						|
		buf.Write(segment.Value(reader.Source()))
 | 
						|
	}
 | 
						|
	d := &data{}
 | 
						|
	d.Node = node
 | 
						|
	meta := map[string]interface{}{}
 | 
						|
	if err := yaml.Unmarshal(buf.Bytes(), &meta); err != nil {
 | 
						|
		d.Error = err
 | 
						|
	} else {
 | 
						|
		d.Map = meta
 | 
						|
	}
 | 
						|
 | 
						|
	metaMapSlice := yaml.MapSlice{}
 | 
						|
	if err := yaml.Unmarshal(buf.Bytes(), &metaMapSlice); err != nil {
 | 
						|
		d.Error = err
 | 
						|
	} else {
 | 
						|
		d.Items = metaMapSlice
 | 
						|
	}
 | 
						|
 | 
						|
	pc.Set(contextKey, d)
 | 
						|
 | 
						|
	if d.Error == nil {
 | 
						|
		node.Parent().RemoveChild(node.Parent(), node)
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func (b *metaParser) CanInterruptParagraph() bool {
 | 
						|
	return false
 | 
						|
}
 | 
						|
 | 
						|
func (b *metaParser) CanAcceptIndentedLine() bool {
 | 
						|
	return false
 | 
						|
}
 | 
						|
 | 
						|
type astTransformer struct {
 | 
						|
}
 | 
						|
 | 
						|
var defaultASTTransformer = &astTransformer{}
 | 
						|
 | 
						|
func (a *astTransformer) Transform(node *gast.Document, reader text.Reader, pc parser.Context) {
 | 
						|
	dtmp := pc.Get(contextKey)
 | 
						|
	if dtmp == nil {
 | 
						|
		return
 | 
						|
	}
 | 
						|
	d := dtmp.(*data)
 | 
						|
	if d.Error != nil {
 | 
						|
		msg := gast.NewString([]byte(fmt.Sprintf("<!-- %s -->", d.Error)))
 | 
						|
		msg.SetCode(true)
 | 
						|
		d.Node.AppendChild(d.Node, msg)
 | 
						|
		return
 | 
						|
	}
 | 
						|
 | 
						|
	meta := GetItems(pc)
 | 
						|
	if meta == nil {
 | 
						|
		return
 | 
						|
	}
 | 
						|
	table := east.NewTable()
 | 
						|
	alignments := []east.Alignment{}
 | 
						|
	for range meta {
 | 
						|
		alignments = append(alignments, east.AlignNone)
 | 
						|
	}
 | 
						|
	row := east.NewTableRow(alignments)
 | 
						|
	for _, item := range meta {
 | 
						|
		cell := east.NewTableCell()
 | 
						|
		cell.AppendChild(cell, gast.NewString([]byte(fmt.Sprintf("%v", item.Key))))
 | 
						|
		row.AppendChild(row, cell)
 | 
						|
	}
 | 
						|
	table.AppendChild(table, east.NewTableHeader(row))
 | 
						|
 | 
						|
	row = east.NewTableRow(alignments)
 | 
						|
	for _, item := range meta {
 | 
						|
		cell := east.NewTableCell()
 | 
						|
		cell.AppendChild(cell, gast.NewString([]byte(fmt.Sprintf("%v", item.Value))))
 | 
						|
		row.AppendChild(row, cell)
 | 
						|
	}
 | 
						|
	table.AppendChild(table, row)
 | 
						|
	node.InsertBefore(node, node.FirstChild(), table)
 | 
						|
}
 | 
						|
 | 
						|
// Option is a functional option type for this extension.
 | 
						|
type Option func(*meta)
 | 
						|
 | 
						|
// WithTable is a functional option that renders a YAML metadata as a table.
 | 
						|
func WithTable() Option {
 | 
						|
	return func(m *meta) {
 | 
						|
		m.Table = true
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
type meta struct {
 | 
						|
	Table bool
 | 
						|
}
 | 
						|
 | 
						|
// Meta is a extension for the goldmark.
 | 
						|
var Meta = &meta{}
 | 
						|
 | 
						|
// New returns a new Meta extension.
 | 
						|
func New(opts ...Option) goldmark.Extender {
 | 
						|
	e := &meta{}
 | 
						|
	for _, opt := range opts {
 | 
						|
		opt(e)
 | 
						|
	}
 | 
						|
	return e
 | 
						|
}
 | 
						|
 | 
						|
func (e *meta) Extend(m goldmark.Markdown) {
 | 
						|
	m.Parser().AddOptions(
 | 
						|
		parser.WithBlockParsers(
 | 
						|
			util.Prioritized(NewParser(), 0),
 | 
						|
		),
 | 
						|
	)
 | 
						|
	if e.Table {
 | 
						|
		m.Parser().AddOptions(
 | 
						|
			parser.WithASTTransformers(
 | 
						|
				util.Prioritized(defaultASTTransformer, 0),
 | 
						|
			),
 | 
						|
		)
 | 
						|
	}
 | 
						|
}
 |