Платформа ЦРНП "Мирокод" для разработки проектов
https://git.mirocod.ru
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
174 lines
4.5 KiB
174 lines
4.5 KiB
// 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() |
|
}
|
|
|