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
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,11 @@ render:
- name: SecretObjectReference
package: sigs.k8s.io/gateway-api/apis/v1beta1
link: https://gateway-api.sigs.k8s.io/references/spec/#gateway.networking.k8s.io/v1beta1.SecretObjectReference
# Rewrite plain URLs in field and type doc comments to Markdown links. Markdown renderer only.
linkMappings:
- url: https://example.com/old-page
link: docs-content://new/page.md
text: New page
```

### Advanced Features
Expand Down
11 changes: 9 additions & 2 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,9 @@ const (
)

type RenderConfig struct {
KnownTypes []*KnownType `json:"knownTypes"`
KubernetesVersion string `json:"kubernetesVersion"`
KnownTypes []*KnownType `json:"knownTypes"`
KubernetesVersion string `json:"kubernetesVersion"`
LinkMappings []*LinkMapping `json:"linkMappings"`
}

type KnownType struct {
Expand All @@ -62,6 +63,12 @@ type KnownType struct {
Link string `json:"link"`
}

type LinkMapping struct {
URL string `json:"url"`
Link string `json:"link"`
Text string `json:"text"`
}

const (
OutputModeSingle = "single"
OutputModeGroup = "group"
Expand Down
15 changes: 14 additions & 1 deletion renderer/markdown.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ func (m *MarkdownRenderer) ToFuncMap() template.FuncMap {
"RenderLocalLink": m.RenderLocalLink,
"RenderType": m.RenderType,
"RenderTypeLink": m.RenderTypeLink,
"RewriteLinks": m.RewriteLinks,
"SafeID": m.SafeID,
"ShouldRenderType": m.ShouldRenderType,
"TypeID": m.TypeID,
Expand Down Expand Up @@ -148,10 +149,22 @@ func (m *MarkdownRenderer) RenderGVLink(gv types.GroupVersionDetails) string {
return m.RenderLocalLink(gv.GroupVersionString())
}

func (m *MarkdownRenderer) RewriteLinks(text string) string {
if m == nil || m.conf == nil {
return text
}
for _, lm := range m.conf.Render.LinkMappings {
text = strings.ReplaceAll(text, lm.URL, m.RenderExternalLink(lm.Link, lm.Text))
}
return text
}

func (m *MarkdownRenderer) RenderFieldDoc(text string) string {
out := m.RewriteLinks(text)

// 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, "|", "\\|")
out = strings.ReplaceAll(out, "|", "\\|")

// Escape the curly bracket character.
out = strings.ReplaceAll(out, "{", "\\{")
Expand Down
113 changes: 113 additions & 0 deletions renderer/markdown_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,119 @@ func newTestConfig(t *testing.T) *config.Config {
return conf
}

func TestMarkdownRenderer_RewriteLinks(t *testing.T) {
conf := &config.Config{
Render: config.RenderConfig{
LinkMappings: []*config.LinkMapping{
{
URL: "https://example.com/old",
Link: "docs-content://new/page.md",
Text: "New page",
},
{
URL: "https://example.com/other",
Link: "kibana://reference/other.md",
Text: "Other",
},
},
},
}
r := &MarkdownRenderer{conf: conf}

tests := []struct {
name string
renderer *MarkdownRenderer
text string
want string
}{
{
name: "single substitution",
renderer: r,
text: "See https://example.com/old for details.",
want: "See [New page](docs-content://new/page.md) for details.",
},
{
name: "multiple substitutions",
renderer: r,
text: "See https://example.com/old and https://example.com/other.",
want: "See [New page](docs-content://new/page.md) and [Other](kibana://reference/other.md).",
},
{
name: "no match leaves text unchanged",
renderer: r,
text: "See https://example.com/unmapped for details.",
want: "See https://example.com/unmapped for details.",
},
{
name: "no mappings configured",
renderer: &MarkdownRenderer{conf: &config.Config{}},
text: "See https://example.com/old for details.",
want: "See https://example.com/old for details.",
},
{
name: "nil config",
renderer: &MarkdownRenderer{conf: nil},
text: "See https://example.com/old for details.",
want: "See https://example.com/old for details.",
},
{
name: "nil renderer",
renderer: nil,
text: "See https://example.com/old for details.",
want: "See https://example.com/old for details.",
},
{
name: "multiple occurrences of the same URL are all rewritten",
renderer: r,
text: "See https://example.com/old and again https://example.com/old.",
want: "See [New page](docs-content://new/page.md) and again [New page](docs-content://new/page.md).",
},
{
name: "longer URL listed first wins over shorter prefix",
renderer: &MarkdownRenderer{conf: &config.Config{
Render: config.RenderConfig{
LinkMappings: []*config.LinkMapping{
{URL: "https://example.com/old-page", Link: "docs-content://new/page.md", Text: "New page"},
{URL: "https://example.com/old", Link: "docs-content://other.md", Text: "Other"},
},
},
}},
text: "See https://example.com/old-page for details.",
want: "See [New page](docs-content://new/page.md) for details.",
},
{
name: "URL inside an existing Markdown link produces nested output",
renderer: r,
text: "See [the page](https://example.com/old) for details.",
want: "See [the page]([New page](docs-content://new/page.md)) for details.",
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert.Equal(t, tt.want, tt.renderer.RewriteLinks(tt.text))
})
}
}

func TestMarkdownRenderer_RenderFieldDoc_appliesLinkMappings(t *testing.T) {
conf := &config.Config{
Render: config.RenderConfig{
LinkMappings: []*config.LinkMapping{
{
URL: "https://example.com/old",
Link: "docs-content://new/page.md",
Text: "New page",
},
},
},
}
r := &MarkdownRenderer{conf: conf}

got := r.RenderFieldDoc("See https://example.com/old for details.")
assert.Equal(t, "See [New page](docs-content://new/page.md) for details.", got)
}

func TestMarkdownRenderer_TemplateValue(t *testing.T) {
tests := []struct {
name string
Expand Down
2 changes: 1 addition & 1 deletion templates/markdown/type.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

{{ if $type.IsAlias }}_Underlying type:_ _{{ markdownRenderTypeLink $type.UnderlyingType }}_{{ end }}

{{ $type.Doc }}
{{ markdownRewriteLinks $type.Doc }}

{{ if $type.Validation -}}
_Validation:_
Expand Down
4 changes: 2 additions & 2 deletions test/api/v1/guestbook_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -121,9 +121,9 @@ const (
// +kubebuilder:validation:Minimum=1
type PositiveInt int

// GuestbookEntry defines an entry in a guest book.
// GuestbookEntry defines an entry in a guest book. See https://example.com/old-page for more.
type GuestbookEntry struct {
// Name of the guest (pipe | should be escaped)
// Name of the guest (pipe | should be escaped). See https://example.com/old-page for naming guidance.
// +kubebuilder:validation:Required
// +kubebuilder:validation:MaxLength=80
// +kubebuilder:validation:Pattern=`0*[a-z0-9]*[a-z]*[0-9]`
Expand Down
4 changes: 4 additions & 0 deletions test/config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,7 @@ render:
- name: SecretObjectReference
package: sigs.k8s.io/gateway-api/apis/v1beta1
link: https://gateway-api.sigs.k8s.io/references/spec/#gateway.networking.k8s.io/v1beta1.SecretObjectReference
linkMappings:
- url: https://example.com/old-page
link: docs-content://new/page.md
text: New page
4 changes: 2 additions & 2 deletions test/expected.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ Guestbook is the Schema for the guestbooks API.



GuestbookEntry defines an entry in a guest book.
GuestbookEntry defines an entry in a guest book. See https://example.com/old-page for more.



Expand All @@ -160,7 +160,7 @@ GuestbookEntry defines an entry in a guest book.
[cols="20a,50a,15a,15a", options="header"]
|===
| Field | Description | Default | Validation
| *`name`* __string__ | Name of the guest (pipe \| should be escaped) + | | MaxLength: 80 +
| *`name`* __string__ | Name of the guest (pipe \| should be escaped). See https://example.com/old-page for naming guidance. + | | MaxLength: 80 +
Pattern: `0\*[a-z0-9]*[a-z]*[0-9]` +
Required: \{} +

Expand Down
4 changes: 2 additions & 2 deletions test/expected.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ _Appears in:_



GuestbookEntry defines an entry in a guest book.
GuestbookEntry defines an entry in a guest book. See [New page](docs-content://new/page.md) for more.



Expand All @@ -127,7 +127,7 @@ _Appears in:_

| Field | Description | Default | Validation |
| --- | --- | --- | --- |
| `name` _string_ | Name of the guest (pipe \| should be escaped) | | MaxLength: 80 <br />Pattern: `0*[a-z0-9]*[a-z]*[0-9]` <br />Required: \{\} <br /> |
| `name` _string_ | Name of the guest (pipe \| should be escaped). See [New page](docs-content://new/page.md) for naming guidance. | | MaxLength: 80 <br />Pattern: `0*[a-z0-9]*[a-z]*[0-9]` <br />Required: \{\} <br /> |
| `tags` _string array_ | Tags of the entry. | | items:Pattern: `[a-z]*` <br /> |
| `time` _[Time](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.25/#time-v1-meta)_ | Time of entry | | |
| `comment` _string_ | Comment by guest. This can be a multi-line comment.<br />Like this one.<br />Now let's test a list:<br />* a<br />* b<br />Another isolated comment.<br />Looks good? | | Pattern: `0*[a-z0-9]*[a-z]*[0-9]*\|\s` <br /> |
Expand Down
4 changes: 2 additions & 2 deletions test/hide.md
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ _Appears in:_



GuestbookEntry defines an entry in a guest book.
GuestbookEntry defines an entry in a guest book. See [New page](docs-content://new/page.md) for more.



Expand All @@ -128,7 +128,7 @@ _Appears in:_

| Field | Description | Default | Validation |
| --- | --- | --- | --- |
| `name` _string_ | Name of the guest (pipe \| should be escaped) | | MaxLength: 80 <br />Pattern: `0*[a-z0-9]*[a-z]*[0-9]` <br />Required: \{\} <br /> |
| `name` _string_ | Name of the guest (pipe \| should be escaped). See [New page](docs-content://new/page.md) for naming guidance. | | MaxLength: 80 <br />Pattern: `0*[a-z0-9]*[a-z]*[0-9]` <br />Required: \{\} <br /> |
| `tags` _string array_ | Tags of the entry. | | items:Pattern: `[a-z]*` <br /> |
| `time` _[Time](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.25/#time-v1-meta)_ | Time of entry | | |
| `comment` _string_ | Comment by guest. This can be a multi-line comment.<br />Like this one.<br />Now let's test a list:<br />* a<br />* b<br />Another isolated comment.<br />Looks good? | | Pattern: `0*[a-z0-9]*[a-z]*[0-9]*\|\s` <br /> |
Expand Down
2 changes: 1 addition & 1 deletion test/templates/markdown/type.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

{{ if $type.IsAlias }}_Underlying type:_ _{{ markdownRenderTypeLink $type.UnderlyingType }}_{{ end }}

{{ $type.Doc }}
{{ markdownRewriteLinks $type.Doc }}

{{ if $type.Validation -}}
_Validation:_
Expand Down
Loading