In particular, use the parser to filter and extract examples. This also sets up support for entity references in documentation that can be used in order to render language-specific names for resources, functions, types, and properties. Related to #4632 and #4159.
119 lines
3.4 KiB
Go
119 lines
3.4 KiB
Go
package schema
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"io"
|
|
"net/url"
|
|
|
|
"github.com/pgavlin/goldmark/ast"
|
|
"github.com/pgavlin/goldmark/renderer"
|
|
"github.com/pgavlin/goldmark/renderer/markdown"
|
|
"github.com/pgavlin/goldmark/util"
|
|
"github.com/pulumi/pulumi/sdk/v2/go/common/util/contract"
|
|
)
|
|
|
|
// A RendererOption controls the behavior of a Renderer.
|
|
type RendererOption func(*Renderer)
|
|
|
|
// A ReferenceRenderer is responsible for rendering references to entities in a schema.
|
|
type ReferenceRenderer func(r *Renderer, w io.Writer, source []byte, link *ast.Link, enter bool) (ast.WalkStatus, error)
|
|
|
|
// WithReferenceRenderer sets the reference renderer for a renderer.
|
|
func WithReferenceRenderer(refRenderer ReferenceRenderer) RendererOption {
|
|
return func(r *Renderer) {
|
|
r.refRenderer = refRenderer
|
|
}
|
|
}
|
|
|
|
// A Renderer provides the ability to render parsed documentation back to Markdown source.
|
|
type Renderer struct {
|
|
md *markdown.Renderer
|
|
|
|
refRenderer ReferenceRenderer
|
|
}
|
|
|
|
// MarkdownRenderer returns the underlying Markdown renderer used by the Renderer.
|
|
func (r *Renderer) MarkdownRenderer() *markdown.Renderer {
|
|
return r.md
|
|
}
|
|
|
|
func (r *Renderer) RegisterFuncs(reg renderer.NodeRendererFuncRegisterer) {
|
|
// blocks
|
|
reg.Register(KindShortcode, r.renderShortcode)
|
|
|
|
// inlines
|
|
reg.Register(ast.KindLink, r.renderLink)
|
|
}
|
|
|
|
func (r *Renderer) renderShortcode(w util.BufWriter, source []byte, node ast.Node, enter bool) (ast.WalkStatus, error) {
|
|
if enter {
|
|
if err := r.md.OpenBlock(w, source, node); err != nil {
|
|
return ast.WalkStop, err
|
|
}
|
|
if _, err := fmt.Fprintf(r.md.Writer(w), "{{%% %s %%}}\n", string(node.(*Shortcode).Name)); err != nil {
|
|
return ast.WalkStop, err
|
|
}
|
|
} else {
|
|
if _, err := fmt.Fprintf(r.md.Writer(w), "{{%% /%s %%}}\n", string(node.(*Shortcode).Name)); err != nil {
|
|
return ast.WalkStop, err
|
|
}
|
|
if err := r.md.CloseBlock(w); err != nil {
|
|
return ast.WalkStop, err
|
|
}
|
|
}
|
|
|
|
return ast.WalkContinue, nil
|
|
}
|
|
|
|
func isEntityReference(dest []byte) bool {
|
|
if len(dest) == 0 {
|
|
return false
|
|
}
|
|
|
|
parsed, err := url.Parse(string(dest))
|
|
if err != nil {
|
|
return false
|
|
}
|
|
|
|
if parsed.IsAbs() {
|
|
return parsed.Scheme == "schema"
|
|
}
|
|
|
|
return parsed.Host == "" && parsed.Path == "" && parsed.RawQuery == "" && parsed.Fragment != ""
|
|
}
|
|
|
|
func (r *Renderer) renderLink(w util.BufWriter, source []byte, node ast.Node, enter bool) (ast.WalkStatus, error) {
|
|
// If this is an entity reference, pass it off to the reference renderer (if any).
|
|
link := node.(*ast.Link)
|
|
if r.refRenderer != nil && isEntityReference(link.Destination) {
|
|
return r.refRenderer(r, w, source, link, enter)
|
|
}
|
|
|
|
return r.md.RenderLink(w, source, node, enter)
|
|
}
|
|
|
|
// RenderDocs renders parsed documentation to the given Writer. The source that was used to parse the documentation
|
|
// must be provided.
|
|
func RenderDocs(w io.Writer, source []byte, node ast.Node, options ...RendererOption) error {
|
|
md := &markdown.Renderer{}
|
|
dr := &Renderer{md: md}
|
|
for _, o := range options {
|
|
o(dr)
|
|
}
|
|
|
|
nodeRenderers := []util.PrioritizedValue{
|
|
util.Prioritized(dr, 100),
|
|
util.Prioritized(md, 200),
|
|
}
|
|
r := renderer.NewRenderer(renderer.WithNodeRenderers(nodeRenderers...))
|
|
return r.Render(w, source, node)
|
|
}
|
|
|
|
// RenderDocsToString is like RenderDocs, but renders to a string instead of a Writer.
|
|
func RenderDocsToString(source []byte, node ast.Node, options ...RendererOption) string {
|
|
var buf bytes.Buffer
|
|
err := RenderDocs(&buf, source, node, options...)
|
|
contract.AssertNoError(err)
|
|
return buf.String()
|
|
}
|