diff --git a/README.md b/README.md
index 789f795..88ec91d 100644
--- a/README.md
+++ b/README.md
@@ -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
diff --git a/config/config.go b/config/config.go
index b1c792e..9c40f3d 100644
--- a/config/config.go
+++ b/config/config.go
@@ -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 {
@@ -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"
diff --git a/renderer/markdown.go b/renderer/markdown.go
index a270c10..9ded306 100644
--- a/renderer/markdown.go
+++ b/renderer/markdown.go
@@ -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,
@@ -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, "{", "\\{")
diff --git a/renderer/markdown_test.go b/renderer/markdown_test.go
index ddd1063..2760e1c 100644
--- a/renderer/markdown_test.go
+++ b/renderer/markdown_test.go
@@ -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
diff --git a/templates/markdown/type.tpl b/templates/markdown/type.tpl
index 7d89d04..f8b91a6 100644
--- a/templates/markdown/type.tpl
+++ b/templates/markdown/type.tpl
@@ -6,7 +6,7 @@
{{ if $type.IsAlias }}_Underlying type:_ _{{ markdownRenderTypeLink $type.UnderlyingType }}_{{ end }}
-{{ $type.Doc }}
+{{ markdownRewriteLinks $type.Doc }}
{{ if $type.Validation -}}
_Validation:_
diff --git a/test/api/v1/guestbook_types.go b/test/api/v1/guestbook_types.go
index 8eb71bc..d89557c 100644
--- a/test/api/v1/guestbook_types.go
+++ b/test/api/v1/guestbook_types.go
@@ -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]`
diff --git a/test/config.yaml b/test/config.yaml
index 2ebede7..4f655e2 100644
--- a/test/config.yaml
+++ b/test/config.yaml
@@ -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
diff --git a/test/expected.asciidoc b/test/expected.asciidoc
index dff065f..c979eaf 100644
--- a/test/expected.asciidoc
+++ b/test/expected.asciidoc
@@ -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.
@@ -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: \{} +
diff --git a/test/expected.md b/test/expected.md
index 538adf6..0b7de7f 100644
--- a/test/expected.md
+++ b/test/expected.md
@@ -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.
@@ -127,7 +127,7 @@ _Appears in:_
| Field | Description | Default | Validation |
| --- | --- | --- | --- |
-| `name` _string_ | Name of the guest (pipe \| should be escaped) | | MaxLength: 80
Pattern: `0*[a-z0-9]*[a-z]*[0-9]`
Required: \{\}
|
+| `name` _string_ | Name of the guest (pipe \| should be escaped). See [New page](docs-content://new/page.md) for naming guidance. | | MaxLength: 80
Pattern: `0*[a-z0-9]*[a-z]*[0-9]`
Required: \{\}
|
| `tags` _string array_ | Tags of the entry. | | items:Pattern: `[a-z]*`
|
| `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.
Like this one.
Now let's test a list:
* a
* b
Another isolated comment.
Looks good? | | Pattern: `0*[a-z0-9]*[a-z]*[0-9]*\|\s`
|
diff --git a/test/hide.md b/test/hide.md
index d274f96..191fbdf 100644
--- a/test/hide.md
+++ b/test/hide.md
@@ -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.
@@ -128,7 +128,7 @@ _Appears in:_
| Field | Description | Default | Validation |
| --- | --- | --- | --- |
-| `name` _string_ | Name of the guest (pipe \| should be escaped) | | MaxLength: 80
Pattern: `0*[a-z0-9]*[a-z]*[0-9]`
Required: \{\}
|
+| `name` _string_ | Name of the guest (pipe \| should be escaped). See [New page](docs-content://new/page.md) for naming guidance. | | MaxLength: 80
Pattern: `0*[a-z0-9]*[a-z]*[0-9]`
Required: \{\}
|
| `tags` _string array_ | Tags of the entry. | | items:Pattern: `[a-z]*`
|
| `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.
Like this one.
Now let's test a list:
* a
* b
Another isolated comment.
Looks good? | | Pattern: `0*[a-z0-9]*[a-z]*[0-9]*\|\s`
|
diff --git a/test/templates/markdown/type.tpl b/test/templates/markdown/type.tpl
index 1ef5d62..61f39e0 100644
--- a/test/templates/markdown/type.tpl
+++ b/test/templates/markdown/type.tpl
@@ -6,7 +6,7 @@
{{ if $type.IsAlias }}_Underlying type:_ _{{ markdownRenderTypeLink $type.UnderlyingType }}_{{ end }}
-{{ $type.Doc }}
+{{ markdownRewriteLinks $type.Doc }}
{{ if $type.Validation -}}
_Validation:_