diff --git a/go.mod b/go.mod index 18866e8..3c666be 100644 --- a/go.mod +++ b/go.mod @@ -36,6 +36,7 @@ require ( github.com/spf13/cast v1.10.0 // indirect github.com/spf13/pflag v1.0.10 // indirect github.com/x448/float16 v0.8.4 // indirect + github.com/yuin/goldmark v1.8.5 // indirect go.uber.org/multierr v1.11.0 // indirect go.yaml.in/yaml/v2 v2.4.3 // indirect golang.org/x/crypto v0.47.0 // indirect diff --git a/go.sum b/go.sum index 4ef7f61..5895126 100644 --- a/go.sum +++ b/go.sum @@ -124,6 +124,8 @@ github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= +github.com/yuin/goldmark v1.8.5 h1:r6N5afV5qj/5S4UTch8agZHJ8UxNCMwX7WjkkJam2NA= +github.com/yuin/goldmark v1.8.5/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg= go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 h1:F7Jx+6hwnZ41NSFTO5q4LYDtJRXBf2PD0rNBkeB/lus= diff --git a/renderer/markdown.go b/renderer/markdown.go index aa24f11..6c491f4 100644 --- a/renderer/markdown.go +++ b/renderer/markdown.go @@ -17,6 +17,7 @@ package renderer import ( + "bytes" "fmt" "io/fs" "os" @@ -24,6 +25,11 @@ import ( "strings" "text/template" + "github.com/yuin/goldmark" + "github.com/yuin/goldmark/extension" + "github.com/yuin/goldmark/parser" + "github.com/yuin/goldmark/renderer/html" + "github.com/Masterminds/sprig/v3" "github.com/elastic/crd-ref-docs/config" "github.com/elastic/crd-ref-docs/templates" @@ -78,7 +84,9 @@ func (m *MarkdownRenderer) ToFuncMap() template.FuncMap { "ShouldRenderType": m.ShouldRenderType, "TypeID": m.TypeID, "RenderFieldDoc": m.RenderFieldDoc, + "RenderFieldHTML": m.RenderFieldHTML, "RenderDefault": m.RenderDefault, + "RenderValidation": m.RenderValidation, "TemplateValue": m.TemplateValue, } } @@ -186,9 +194,74 @@ func (m *MarkdownRenderer) RenderFieldDoc(text string) string { return strings.ReplaceAll(out, "

", "
") } +// RenderFieldHTML renders a field whose docs are written in Markdown themselves +// as a single line of formatted HTML, that can be included in a Markdown table. +func (m *MarkdownRenderer) RenderFieldHTML(text string) string { + out := m.RewriteLinks(text) + + md := goldmark.New( + goldmark.WithExtensions(extension.GFM), + goldmark.WithParserOptions( + parser.WithAutoHeadingID(), + ), + goldmark.WithRendererOptions( + html.WithXHTML(), + ), + ) + + var buf bytes.Buffer + docstringBytes := []byte(text) + + if err := md.Convert(docstringBytes, &buf); err != nil { + panic(err) + } + + out = buf.String() + + // Escape the pipe character, which has special meaning for Markdown as a way to format tables + // so that including | in a comment does not result in wonky tables. + out = strings.ReplaceAll(out, "|", "\\|") + + // Escape the curly bracket character. + out = strings.ReplaceAll(out, "{", "\\{") + out = strings.ReplaceAll(out, "}", "\\}") + + // Replace any newlines that are left with spaces. + out = strings.ReplaceAll(out, "\n", " ") + // and remove double newline generated for empty lines + // empty line is still rendered in the table, without removing the duplicate + // newline it would be rendered as two empty lines + return strings.ReplaceAll(out, "

", "
") +} + func (m *MarkdownRenderer) RenderDefault(text string) string { - return strings.NewReplacer( - "{", "\\{", - "}", "\\}", - ).Replace(text) + out := text + + if strings.Contains(out, "{") || + strings.Contains(out, "[") { + out = fmt.Sprintf("`%s`", out) + } + + return out +} + +// RenderValidation renders validation with special handling +// for Required and Optional validations. +func (m *MarkdownRenderer) RenderValidation(text string) string { + out := text + + if strings.HasPrefix(out, "Optional") { + return "_Optional_" + } + + if strings.HasPrefix(out, "Required") { + return "_Required_" + } + + out = strings.ReplaceAll(out, "|", "\\|") + // Escape the curly bracket character. + out = strings.ReplaceAll(out, "{", "\\{") + out = strings.ReplaceAll(out, "}", "\\}") + + return out }