renderer/markdown.go (120 lines of code) (raw):

// Licensed to Elasticsearch B.V. under one or more contributor // license agreements. See the NOTICE file distributed with // this work for additional information regarding copyright // ownership. Elasticsearch B.V. licenses this file to you under // the Apache License, Version 2.0 (the "License"); you may // not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, // software distributed under the License is distributed on an // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY // KIND, either express or implied. See the License for the // specific language governing permissions and limitations // under the License. package renderer import ( "fmt" "io/fs" "os" "strings" "text/template" "github.com/Masterminds/sprig" "github.com/elastic/crd-ref-docs/config" "github.com/elastic/crd-ref-docs/templates" "github.com/elastic/crd-ref-docs/types" ) type MarkdownRenderer struct { conf *config.Config *Functions } func NewMarkdownRenderer(conf *config.Config) (*MarkdownRenderer, error) { baseFuncs, err := NewFunctions(conf) if err != nil { return nil, err } return &MarkdownRenderer{conf: conf, Functions: baseFuncs}, nil } func (m *MarkdownRenderer) Render(gvd []types.GroupVersionDetails) error { funcMap := combinedFuncMap(funcMap{prefix: "markdown", funcs: m.ToFuncMap()}, funcMap{funcs: sprig.TxtFuncMap()}) var tpls fs.FS if m.conf.TemplatesDir != "" { tpls = os.DirFS(m.conf.TemplatesDir) } else { sub, err := fs.Sub(templates.Root, "markdown") if err != nil { return err } tpls = sub } tmpl, err := loadTemplate(tpls, funcMap) if err != nil { return err } return renderTemplate(tmpl, m.conf, "md", gvd) } func (m *MarkdownRenderer) ToFuncMap() template.FuncMap { return template.FuncMap{ "GroupVersionID": m.GroupVersionID, "RenderExternalLink": m.RenderExternalLink, "RenderGVLink": m.RenderGVLink, "RenderLocalLink": m.RenderLocalLink, "RenderType": m.RenderType, "RenderTypeLink": m.RenderTypeLink, "SafeID": m.SafeID, "ShouldRenderType": m.ShouldRenderType, "TypeID": m.TypeID, "RenderFieldDoc": m.RenderFieldDoc, "RenderDefault": m.RenderDefault, } } func (m *MarkdownRenderer) ShouldRenderType(t *types.Type) bool { return t != nil && (t.GVK != nil || len(t.References) > 0) } func (m *MarkdownRenderer) RenderType(t *types.Type) string { var sb strings.Builder switch t.Kind { case types.MapKind: sb.WriteString("object (") sb.WriteString("keys:") sb.WriteString(m.RenderTypeLink(t.KeyType)) sb.WriteString(", values:") sb.WriteString(m.RenderTypeLink(t.ValueType)) sb.WriteString(")") case types.SliceKind: sb.WriteString(m.RenderTypeLink(t.UnderlyingType)) sb.WriteString(" array") default: sb.WriteString(m.RenderTypeLink(t)) } return sb.String() } func (m *MarkdownRenderer) RenderTypeLink(t *types.Type) string { text := m.SimplifiedTypeName(t) link, local := m.LinkForType(t) if link == "" { return text } if local { return m.RenderLocalLink(text) } else { return m.RenderExternalLink(link, text) } } func (m *MarkdownRenderer) RenderLocalLink(text string) string { anchor := strings.ToLower( strings.NewReplacer( " ", "-", ".", "", "/", "", "(", "", ")", "", ).Replace(text), ) return fmt.Sprintf("[%s](#%s)", text, anchor) } func (m *MarkdownRenderer) RenderExternalLink(link, text string) string { return fmt.Sprintf("[%s](%s)", text, link) } func (m *MarkdownRenderer) RenderGVLink(gv types.GroupVersionDetails) string { return m.RenderLocalLink(gv.GroupVersionString()) } func (m *MarkdownRenderer) RenderFieldDoc(text string) 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(text, "|", "\\|") // Escape the curly bracket character. out = strings.ReplaceAll(out, "{", "\\{") out = strings.ReplaceAll(out, "}", "\\}") // Replace newlines with 1 line break so that they don't break the Markdown table formatting. out = strings.ReplaceAll(out, "\n", "<br />") // 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, "<br /><br />", "<br />") } func (m *MarkdownRenderer) RenderDefault(text string) string { return strings.NewReplacer( "{", "\\{", "}", "\\}", ).Replace(text) }