package wikilink import ( "fmt" "sync" "github.com/yuin/goldmark/ast" "github.com/yuin/goldmark/renderer" "github.com/yuin/goldmark/util" ) // Renderer renders wikilinks as HTML. // // Install it on your goldmark Markdown object with Extender, or directly on a // goldmark Renderer by using the WithNodeRenderers option. // // wikilinkRenderer := util.Prioritized(&wikilink.Renderer{...}, 199) // goldmarkRenderer.AddOptions(renderer.WithNodeRenderers(wikilinkRenderer)) type Renderer struct { // Resolver determines destinations for wikilink pages. // // If a Resolver returns an empty destination, the Renderer will skip // the link and render just its contents. That is, instead of, // // bar // // The renderer will render just the following. // // bar // // Defaults to DefaultResolver if unspecified. Resolver Resolver once sync.Once // guards init // hasDest records whether a node had a destination when we resolved // it. This is needed to decide whether a closing must be added // when exiting a Node render. hasDest map[*Node]struct{} } func (r *Renderer) init() { r.once.Do(func() { r.hasDest = make(map[*Node]struct{}) if r.Resolver == nil { r.Resolver = DefaultResolver } }) } // RegisterFuncs registers wikilink rendering functions with the provided // goldmark registerer. This teaches goldmark to call us when it encounters a // wikilink in the AST. func (r *Renderer) RegisterFuncs(reg renderer.NodeRendererFuncRegisterer) { reg.Register(Kind, r.Render) } // Render renders the provided Node. It must be a Wikilink node. // // goldmark will call this method if this renderer was registered with it // using the WithNodeRenderers option. func (r *Renderer) Render(w util.BufWriter, _ []byte, node ast.Node, entering bool) (ast.WalkStatus, error) { r.init() n, ok := node.(*Node) if !ok { return ast.WalkStop, fmt.Errorf("unexpected node %T, expected *goldmarkwikilink.Node", node) } if entering { if err := r.enter(w, n); err != nil { return ast.WalkStop, err } } else { r.exit(w, n) } return ast.WalkContinue, nil } func (r *Renderer) enter(w util.BufWriter, n *Node) error { dest, err := r.Resolver.ResolveWikilink(n) if err != nil { return fmt.Errorf("resolve %q: %w", n.Target, err) } if len(dest) == 0 { return nil } r.hasDest[n] = struct{}{} w.WriteString(``) return nil } func (r *Renderer) exit(w util.BufWriter, n *Node) { _, ok := r.hasDest[n] if !ok { return } w.WriteString("") // Avoid memory leaks by cleaning up after exiting the node. delete(r.hasDest, n) }