diff --git a/processor/processor.go b/processor/processor.go index 6939e4d..bdcf5c7 100644 --- a/processor/processor.go +++ b/processor/processor.go @@ -505,6 +505,14 @@ func mkRegistry(customMarkers []config.Marker) (*markers.Registry, error) { } } + // Register k8s:* markers - sig apimachinery plans to unify CRD and native types on these. + if err := registry.Define("k8s:required", markers.DescribesField, struct{}{}); err != nil { + return nil, err + } + if err := registry.Define("k8s:optional", markers.DescribesField, struct{}{}); err != nil { + return nil, err + } + for _, marker := range customMarkers { t := markers.DescribesField switch marker.Target { @@ -554,6 +562,17 @@ func parseMarkers(markers markers.MarkerValues) (string, []string) { validation = append(validation, fmt.Sprintf("%s: %v", name, value)) } + // Handle standalone +required and +k8s:required marker + // This is equivalent to +kubebuilder:validation:Required + if name == "required" || name == "k8s:required" { + validation = append(validation, "Required: {}") + } + // Handle standalone +optional and +k8s:optional marker + // This is equivalent to +kubebuilder:validation:Optional + if name == "optional" || name == "k8s:optional" { + validation = append(validation, "Optional: {}") + } + if name == "kubebuilder:default" { if value, ok := value.(crdmarkers.Default); ok { defaultValue = fmt.Sprintf("%v", value.Value) diff --git a/renderer/asciidoctor.go b/renderer/asciidoctor.go index 6598266..695f2c2 100644 --- a/renderer/asciidoctor.go +++ b/renderer/asciidoctor.go @@ -151,7 +151,7 @@ func (adr *AsciidoctorRenderer) TemplateValue(key string) string { func (adr *AsciidoctorRenderer) RenderFieldDoc(text string) string { // Escape the pipe character, which has special meaning for asciidoc as a way to format tables, // so that including | in a comment does not result in wonky tables. - out := strings.ReplaceAll(text, "|", "\\|") + out := escapePipe(text) // Trim any leading and trailing whitespace from each line. lines := strings.Split(out, "\n") @@ -169,6 +169,7 @@ func (adr *AsciidoctorRenderer) RenderFieldDoc(text string) string { func (adr *AsciidoctorRenderer) RenderValidation(text string) string { renderedText := escapeFirstAsterixInEachPair(text) + renderedText = escapePipe(renderedText) return escapeCurlyBraces(renderedText) } @@ -190,9 +191,15 @@ func escapeFirstAsterixInEachPair(text string) string { return text } +// escapePipe ensures sufficient escapes are added to pipe characters, so they are not mistaken +// for asciidoctor table formatting. +func escapePipe(text string) string { + return strings.ReplaceAll(text, "|", "\\|") +} + // escapeCurlyBraces ensures sufficient escapes are added to curly braces, so they are not mistaken // for asciidoctor id attributes. func escapeCurlyBraces(text string) string { // Per asciidoctor docs, only the leading curly brace needs to be escaped. - return strings.Replace(text, "{", "\\{", -1) + return strings.ReplaceAll(text, "{", "\\{") } diff --git a/renderer/asciidoctor_test.go b/renderer/asciidoctor_test.go index 124f659..2fb1e92 100644 --- a/renderer/asciidoctor_test.go +++ b/renderer/asciidoctor_test.go @@ -19,3 +19,8 @@ func Test_escapeCurlyBraces(t *testing.T) { assert.Equal(t, "[a-fA-F0-9]\\{64}", escapeCurlyBraces("[a-fA-F0-9]{64}")) assert.Equal(t, "[a-fA-F0-9]\\\\{64\\}", escapeCurlyBraces("[a-fA-F0-9]\\{64\\}")) } + +func Test_escapePipe(t *testing.T) { + assert.Equal(t, "[0-9]", escapePipe("[0-9]")) + assert.Equal(t, `[0-9]*\|\s`, escapePipe(`[0-9]*|\s`)) +} diff --git a/test/api/v1/guestbook_types.go b/test/api/v1/guestbook_types.go index 5dbdcb0..1dac6e1 100644 --- a/test/api/v1/guestbook_types.go +++ b/test/api/v1/guestbook_types.go @@ -124,6 +124,7 @@ type PositiveInt int // GuestbookEntry defines an entry in a guest book. type GuestbookEntry struct { // Name of the guest (pipe | should be escaped) + // +kubebuilder:validation:Required // +kubebuilder:validation:MaxLength=80 // +kubebuilder:validation:Pattern=`0*[a-z0-9]*[a-z]*[0-9]` Name string `json:"name,omitempty"` @@ -146,6 +147,19 @@ type GuestbookEntry struct { Comment string `json:"comment,omitempty"` // Rating provided by the guest Rating Rating `json:"rating,omitempty"` + + // Email is the email address of the guest (required field using +required marker) + // +required + Email string `json:"email"` + // Location is the location of the guest (required field using +k8s:required marker) + // +k8s:required + Location string `json:"location"` + // Phone is the phone number of the guest (optional field using +optional marker) + // +optional + Phone string `json:"phone"` + // Company is the company of the guest (optional field using +k8s:optional marker) + // +k8s:optional + Company string `json:"company"` } // GuestbookStatus defines the observed state of Guestbook. diff --git a/test/expected.asciidoc b/test/expected.asciidoc index a5a642e..dff065f 100644 --- a/test/expected.asciidoc +++ b/test/expected.asciidoc @@ -162,6 +162,7 @@ GuestbookEntry defines an entry in a guest book. | 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: \{} + | *`tags`* __string array__ | Tags of the entry. + | | items:Pattern: `[a-z]*` + @@ -174,11 +175,19 @@ Now let's test a list: + Another isolated comment. + -Looks good? + | | Pattern: `0\*[a-z0-9]*[a-z]\*[0-9]*|\s` + +Looks good? + | | Pattern: `0\*[a-z0-9]*[a-z]\*[0-9]*\|\s` + | *`rating`* __xref:{anchor_prefix}-github-com-elastic-crd-ref-docs-api-v1-rating[$$Rating$$]__ | Rating provided by the guest + | | Maximum: 5 + Minimum: 1 + +| *`email`* __string__ | Email is the email address of the guest (required field using +required marker) + | | Required: \{} + + +| *`location`* __string__ | Location is the location of the guest (required field using +k8s:required marker) + | | Required: \{} + + +| *`phone`* __string__ | Phone is the phone number of the guest (optional field using +optional marker) + | | Optional: \{} + + +| *`company`* __string__ | Company is the company of the guest (optional field using +k8s:optional marker) + | | Optional: \{} + + |=== diff --git a/test/expected.md b/test/expected.md index a3678da..538adf6 100644 --- a/test/expected.md +++ b/test/expected.md @@ -127,11 +127,15 @@ _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]`
| +| `name` _string_ | Name of the guest (pipe \| should be escaped) | | 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`
| | `rating` _[Rating](#rating)_ | Rating provided by the guest | | Maximum: 5
Minimum: 1
| +| `email` _string_ | Email is the email address of the guest (required field using +required marker) | | Required: \{\}
| +| `location` _string_ | Location is the location of the guest (required field using +k8s:required marker) | | Required: \{\}
| +| `phone` _string_ | Phone is the phone number of the guest (optional field using +optional marker) | | Optional: \{\}
| +| `company` _string_ | Company is the company of the guest (optional field using +k8s:optional marker) | | Optional: \{\}
| #### GuestbookHeader diff --git a/test/hide.md b/test/hide.md index 1757e3b..d274f96 100644 --- a/test/hide.md +++ b/test/hide.md @@ -128,11 +128,15 @@ _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]`
| +| `name` _string_ | Name of the guest (pipe \| should be escaped) | | 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`
| | `rating` _[Rating](#rating)_ | Rating provided by the guest | | Maximum: 5
Minimum: 1
| +| `email` _string_ | Email is the email address of the guest (required field using +required marker) | | Required: \{\}
| +| `location` _string_ | Location is the location of the guest (required field using +k8s:required marker) | | Required: \{\}
| +| `phone` _string_ | Phone is the phone number of the guest (optional field using +optional marker) | | Optional: \{\}
| +| `company` _string_ | Company is the company of the guest (optional field using +k8s:optional marker) | | Optional: \{\}
| #### GuestbookHeader