// Package renderer renders the given AST to certain formats. package renderer import ( "bufio" "io" "sync" "github.com/yuin/goldmark/ast" "github.com/yuin/goldmark/util" ) // A Config struct is a data structure that holds configuration of the Renderer. type Config struct { Options map[OptionName]interface{} NodeRenderers util.PrioritizedSlice } // NewConfig returns a new Config func NewConfig() *Config { return &Config{ Options: map[OptionName]interface{}{}, NodeRenderers: util.PrioritizedSlice{}, } } // An OptionName is a name of the option. type OptionName string // An Option interface is a functional option type for the Renderer. type Option interface { SetConfig(*Config) } type withNodeRenderers struct { value []util.PrioritizedValue } func (o *withNodeRenderers) SetConfig(c *Config) { c.NodeRenderers = append(c.NodeRenderers, o.value...) } // WithNodeRenderers is a functional option that allow you to add // NodeRenderers to the renderer. func WithNodeRenderers(ps ...util.PrioritizedValue) Option { return &withNodeRenderers{ps} } type withOption struct { name OptionName value interface{} } func (o *withOption) SetConfig(c *Config) { c.Options[o.name] = o.value } // WithOption is a functional option that allow you to set // an arbitrary option to the parser. func WithOption(name OptionName, value interface{}) Option { return &withOption{name, value} } // A SetOptioner interface sets given option to the object. type SetOptioner interface { // SetOption sets given option to the object. // Unacceptable options may be passed. // Thus implementations must ignore unacceptable options. SetOption(name OptionName, value interface{}) } // NodeRendererFunc is a function that renders a given node. type NodeRendererFunc func(writer util.BufWriter, source []byte, n ast.Node, entering bool) (ast.WalkStatus, error) // A NodeRenderer interface offers NodeRendererFuncs. type NodeRenderer interface { // RendererFuncs registers NodeRendererFuncs to given NodeRendererFuncRegisterer. RegisterFuncs(NodeRendererFuncRegisterer) } // A NodeRendererFuncRegisterer registers type NodeRendererFuncRegisterer interface { // Register registers given NodeRendererFunc to this object. Register(ast.NodeKind, NodeRendererFunc) } // A Renderer interface renders given AST node to given // writer with given Renderer. type Renderer interface { Render(w io.Writer, source []byte, n ast.Node) error // AddOptions adds given option to this renderer. AddOptions(...Option) } type renderer struct { config *Config options map[OptionName]interface{} nodeRendererFuncsTmp map[ast.NodeKind]NodeRendererFunc maxKind int nodeRendererFuncs []NodeRendererFunc initSync sync.Once } // NewRenderer returns a new Renderer with given options. func NewRenderer(options ...Option) Renderer { config := NewConfig() for _, opt := range options { opt.SetConfig(config) } r := &renderer{ options: map[OptionName]interface{}{}, config: config, nodeRendererFuncsTmp: map[ast.NodeKind]NodeRendererFunc{}, } return r } func (r *renderer) AddOptions(opts ...Option) { for _, opt := range opts { opt.SetConfig(r.config) } } func (r *renderer) Register(kind ast.NodeKind, v NodeRendererFunc) { r.nodeRendererFuncsTmp[kind] = v if int(kind) > r.maxKind { r.maxKind = int(kind) } } // Render renders the given AST node to the given writer with the given Renderer. func (r *renderer) Render(w io.Writer, source []byte, n ast.Node) error { r.initSync.Do(func() { r.options = r.config.Options r.config.NodeRenderers.Sort() l := len(r.config.NodeRenderers) for i := l - 1; i >= 0; i-- { v := r.config.NodeRenderers[i] nr, _ := v.Value.(NodeRenderer) if se, ok := v.Value.(SetOptioner); ok { for oname, ovalue := range r.options { se.SetOption(oname, ovalue) } } nr.RegisterFuncs(r) } r.nodeRendererFuncs = make([]NodeRendererFunc, r.maxKind+1) for kind, nr := range r.nodeRendererFuncsTmp { r.nodeRendererFuncs[kind] = nr } r.config = nil r.nodeRendererFuncsTmp = nil }) writer, ok := w.(util.BufWriter) if !ok { writer = bufio.NewWriter(w) } err := ast.Walk(n, func(n ast.Node, entering bool) (ast.WalkStatus, error) { s := ast.WalkStatus(ast.WalkContinue) var err error f := r.nodeRendererFuncs[n.Kind()] if f != nil { s, err = f(writer, source, n, entering) } return s, err }) if err != nil { return err } return writer.Flush() }