Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 7 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,13 +43,13 @@ Resulting in a resulting README section like so:

| Key | Type | Default | Description |
|-----|------|---------|-------------|
| config.databasesToCreate[0] | string | `"postgresql"` | default database for storage of database metadata |
| config.databasesToCreate[1] | string | `"hashbash"` | database for the [hashbash](https://github.com/norwoodj/hashbash-backend-go) project |
| config.usersToCreate[0] | object | `{"admin":true,"name":"root"}` | admin user |
| config.usersToCreate[1] | object | `{"name":"hashbash","readwriteDatabases":["hashbash"]}` | user with access to the database with the same name |
| statefulset.extraVolumes | list | `[{"emptyDir":{},"name":"data"}]` | Additional volumes to be mounted into the database container |
| statefulset.image.repository | string | `"jnorwood/postgresql:11"` | Image to use for deploying, must support an entrypoint which creates users/databases from appropriate config files |
| statefulset.image.tag | string | `"18.0831"` | |
| config.databasesToCreate[0] | string | <pre lang="json">&#34;postgres&#34;</pre> | default database for storage of database metadata |
| config.databasesToCreate[1] | string | <pre lang="json">&#34;hashbash&#34;</pre> | database for the [hashbash](https://github.com/norwoodj/hashbash-backend-go) project |
| config.usersToCreate[0] | object | <pre lang="json">{<br/> &#34;admin&#34;: true,<br/> &#34;name&#34;: &#34;root&#34;<br/>}</pre> | admin user |
| config.usersToCreate[1] | object | <pre lang="json">{<br/> &#34;name&#34;: &#34;hashbash&#34;,<br/> &#34;readwriteDatabases&#34;: [<br/> &#34;hashbash&#34;<br/> ]<br/>}</pre> | user with access to the database with the same name |
| statefulset.extraVolumes | list | <pre lang="json">[<br/> {<br/> &#34;emptyDir&#34;: {},<br/> &#34;name&#34;: &#34;data&#34;<br/> }<br/>]</pre> | Additional volumes to be mounted into the database container |
| statefulset.image.repository | string | <pre lang="json">&#34;jnorwood/postgresql&#34;</pre> | Image to use for deploying, must support an entrypoint which creates users/databases from appropriate config files |
| statefulset.image.tag | string | <pre lang="json">&#34;11&#34;</pre> | |

You'll notice that some complex fields (lists and objects) are documented while others aren't, and that some simple fields
like `statefulset.image.tag` are documented even without a description comment. The rules for what is and isn't documented in
Expand Down
19 changes: 16 additions & 3 deletions pkg/document/template.go
Original file line number Diff line number Diff line change
Expand Up @@ -216,9 +216,22 @@ func getValuesTableTemplates() string {
valuesSectionBuilder.WriteString("{{ .Type }}")
valuesSectionBuilder.WriteString("{{ end }}")

valuesSectionBuilder.WriteString(`{{ define "chart.valueDefaultColumnRenderMd" }}`)
valuesSectionBuilder.WriteString("{{ if .Default }}{{ .Default }}{{ else }}{{ .AutoDefault }}{{ end }}")
valuesSectionBuilder.WriteString("{{ end }}")
valuesSectionBuilder.WriteString(`
{{ define "chart.valueDefaultColumnRenderMd" }}
{{- $defaultValue := (default .Default .AutoDefault) -}}
{{- $notationType := .NotationType }}
{{- if (and (hasPrefix "` + "`" + `" $defaultValue) (hasSuffix "` + "`" + `" $defaultValue) ) -}}
{{- $defaultValue = htmlEscape (toPrettyJson (fromJson (trimAll "` + "`" + `" $defaultValue ) ) ) -}}
{{- $notationType = "json" }}
{{- end -}}
<pre{{ if $notationType }} lang="{{ $notationType }}"{{ end }}>
{{- if (eq $notationType "tpl" ) }}
{{- .Key }}: |<br/> {{ $defaultValue | replace "\n" "<br/> " }}
{{- else }}
{{- $defaultValue | replace "\n" "<br/>" }}
{{- end -}}
</pre>
{{- end }}`)

valuesSectionBuilder.WriteString(`{{ define "chart.valueDescriptionColumnRenderMd" }}`)
valuesSectionBuilder.WriteString("{{ if .Description }}{{ .Description }}{{ else }}{{ .AutoDescription }}{{ end }}")
Expand Down
113 changes: 113 additions & 0 deletions pkg/document/template_test.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
package document

import (
"bytes"
"testing"
"text/template"

"github.com/norwoodj/helm-docs/pkg/util"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
Expand All @@ -26,3 +29,113 @@ func TestGetDocumentationTemplate_LoadDefaultOnNotFound(t *testing.T) {
require.NoError(t, err)
assert.Equal(t, expected, tpl)
}

func renderTemplate(t *testing.T, templateName string, templateBody string, data interface{}) string {
t.Helper()

tpl, err := template.New("values").Funcs(util.FuncMap()).Parse(templateBody)
require.NoError(t, err)

var buf bytes.Buffer
require.NoError(t, tpl.ExecuteTemplate(&buf, templateName, data))

return buf.String()
}

func TestValuesTable_DefaultValue(t *testing.T) {
tests := []struct {
name string
def string
notationType string
want string
}{
{
name: "string",
def: "`\"bar\"`",
want: "<pre lang=\"json\">&#34;bar&#34;</pre>",
},
{
name: "int",
def: "`42`",
want: "<pre lang=\"json\">42</pre>",
},
{
name: "float",
def: "`3.14`",
want: "<pre lang=\"json\">3.14</pre>",
},
{
name: "bool",
def: "`true`",
want: "<pre lang=\"json\">true</pre>",
},
{
name: "object",
def: "`{\"admin\":true,\"name\":\"root\"}`",
want: "<pre lang=\"json\">{<br/> &#34;admin&#34;: true,<br/> &#34;name&#34;: &#34;root&#34;<br/>}</pre>",
},
{
name: "list",
def: "`[\"a\",\"b\",\"c\"]`",
want: "<pre lang=\"json\">[<br/> &#34;a&#34;,<br/> &#34;b&#34;,<br/> &#34;c&#34;<br/>]</pre>",
},
{
name: "html escape",
def: "`\"This <span>HTML tag</span> should be escaped\"`",
want: "<pre lang=\"json\">&#34;This &lt;span&gt;HTML tag&lt;/span&gt; should be escaped&#34;</pre>",
},
{
name: "unicode",
def: "`\"\\u003chtml\\u003e\\u003c/html\\u003e\"`",
want: "<pre lang=\"json\">&#34;&lt;html&gt;&lt;/html&gt;&#34;</pre>",
},
{
name: "custom",
def: "This is a custom default value with\nan <span>HTML tag</span> that should not be escaped",
notationType: "custom",
want: "<pre lang=\"custom\">This is a custom default value with<br/>an <span>HTML tag</span> that should not be escaped</pre>",
},
{
name: "tpl",
def: "- name: DEBUG\n value: {{ .Values.global.debug | quote }}",
notationType: "tpl",
want: "<pre lang=\"tpl\">some.key: |<br/> - name: DEBUG<br/> value: {{ .Values.global.debug | quote }}</pre>",
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
rows := []valueRow{{
Key: "some.key",
Default: tt.def,
NotationType: tt.notationType,
}}
data := chartTemplateData{
Values: rows,
Sections: sections{},
}
out := renderTemplate(t, "chart.valuesTable", getValuesTableTemplates(), data)
assert.Contains(t, out, tt.want)
})
t.Run(tt.name+" with sections", func(t *testing.T) {
rows := []valueRow{{
Key: "some.key",
Default: tt.def,
NotationType: tt.notationType,
Section: "Some Section",
}}
data := chartTemplateData{
Sections: sections{
Sections: []section{
{
SectionName: "Some Section",
SectionItems: rows,
},
},
},
}
out := renderTemplate(t, "chart.valuesTable", getValuesTableTemplates(), data)
assert.Contains(t, out, tt.want)
})
}
}
21 changes: 21 additions & 0 deletions pkg/util/funcs.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package util

import (
"html"
"strings"
"text/template"

Expand All @@ -12,6 +13,7 @@ func FuncMap() template.FuncMap {
f := sprig.TxtFuncMap()
f["toYaml"] = toYAML
f["fromYaml"] = fromYAML
f["htmlEscape"] = htmlEscape
return f
}

Expand Down Expand Up @@ -42,3 +44,22 @@ func fromYAML(str string) map[string]interface{} {
}
return m
}

// htmlEscape escapes special HTML characters in a string to their HTML entity equivalents.
// It also converts Unicode escape sequences (\u003c, \u003e, \u0026) produced by Go's json.Marshal
// to their HTML entity equivalents (&lt;, &gt;, &amp;).
//
// This is necessary because Sprig's toPrettyJson function uses json.MarshalIndent without
// SetEscapeHTML(false), which means it escapes <, >, and & to Unicode sequences.
// We want proper HTML entities instead for better readability in markdown/HTML output.
//
// This is designed to be called from a template.
func htmlEscape(s string) string {
// First, replace Unicode escape sequences with actual characters
s = strings.ReplaceAll(s, `\u003c`, "<")
s = strings.ReplaceAll(s, `\u003e`, ">")
s = strings.ReplaceAll(s, `\u0026`, "&")

// Then apply HTML escaping to convert them to HTML entities
return html.EscapeString(s)
}
43 changes: 43 additions & 0 deletions pkg/util/funcs_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package util

import (
"testing"

"github.com/stretchr/testify/assert"
)

func TestHtmlEscape(t *testing.T) {
tests := []struct {
name string
input string
expected string
}{
{
name: "html",
input: `<div>Hello & goodbye</div>`,
expected: `&lt;div&gt;Hello &amp; goodbye&lt;/div&gt;`,
},
{
name: "unicode",
input: `\u003cdiv\u003eHello \u0026 goodbye\u003c/div\u003e`,
expected: `&lt;div&gt;Hello &amp; goodbye&lt;/div&gt;`,
},
{
name: "no escaping needed",
input: `hello world`,
expected: `hello world`,
},
{
name: "empty string",
input: ``,
expected: ``,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := htmlEscape(tt.input)
assert.Equal(t, tt.expected, result)
})
}
}