Skip to content

Commit

Permalink
feat: markers
Browse files Browse the repository at this point in the history
Signed-off-by: Amund Tenstad <github@amund.io>
  • Loading branch information
tenstad authored and Amund Tenstad committed Aug 15, 2023
1 parent 3abd5a4 commit c573a3d
Show file tree
Hide file tree
Showing 7 changed files with 377 additions and 103 deletions.
109 changes: 105 additions & 4 deletions processor/processor.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
gotypes "go/types"
"regexp"
"sort"
"strconv"
"strings"

"github.com/elastic/crd-ref-docs/config"
Expand All @@ -34,9 +35,18 @@ import (
)

const (
groupNameMarker = "groupName"
objectRootMarker = "kubebuilder:object:root"
versionNameMarker = "versionName"
groupNameMarker = "groupName"
objectRootMarker = "kubebuilder:object:root"
versionNameMarker = "versionName"
defaultMarker = "kubebuilder:default"
validationEnumMarker = "kubebuilder:validation:Enum"
validationFormatMarker = "kubebuilder:validation:Format"
validationMinimumMarker = "kubebuilder:validation:Minimum"
validationMaximumMarker = "kubebuilder:validation:Maximum"
validationMinLengthMarker = "kubebuilder:validation:MinLength"
validationMaxLengthMarker = "kubebuilder:validation:MaxLength"
validationPatternMarker = "kubebuilder:validation:Pattern"
docMarker = "doc:Doc"
)

var ignoredCommentRegex = regexp.MustCompile(`\s*^(?i:\+|copyright)`)
Expand All @@ -62,6 +72,8 @@ func Process(config *config.Config) ([]types.GroupVersionDetails, error) {
}

p.types.InlineTypes(p.propagateReference)
p.types.PropagateAttributes()
p.applyMarkers()

// collect references between types
for typeName, refs := range p.references {
Expand Down Expand Up @@ -124,7 +136,7 @@ func newProcessor(compiledConfig *compiledConfig, maxDepth int) *processor {
compiledConfig: compiledConfig,
maxDepth: maxDepth,
parser: &crd.Parser{
Collector: &markers.Collector{Registry: &markers.Registry{}},
Collector: &markers.Collector{Registry: mkRegistry()},
Checker: &loader.TypeChecker{},
},
groupVersions: make(map[schema.GroupVersion]*groupVersionInfo),
Expand Down Expand Up @@ -270,6 +282,7 @@ func (p *processor) processType(pkg *loader.Package, info *markers.TypeInfo, dep
Name: info.Name,
Package: pkg.PkgPath,
Doc: info.Doc,
Markers: info.Markers,
}

if p.useRawDocstring && info.RawDecl != nil {
Expand Down Expand Up @@ -299,6 +312,7 @@ func (p *processor) processType(pkg *loader.Package, info *markers.TypeInfo, dep
tmpType.Name = typeDef.Name
tmpType.Package = typeDef.Package
tmpType.Doc = typeDef.Doc
tmpType.Markers = typeDef.Markers
return tmpType
}

Expand All @@ -317,6 +331,7 @@ func (p *processor) processStructFields(parentType *types.Type, pkg *loader.Pack
fieldDef := &types.Field{
Name: f.Name,
Doc: f.Doc,
Markers: f.Markers,
Embedded: f.Name == "",
}

Expand Down Expand Up @@ -354,6 +369,50 @@ func (p *processor) processStructFields(parentType *types.Type, pkg *loader.Pack
return parentType
}

func parseMarkers(markers markers.MarkerValues) (string, string, []string) {
doc := []string{}
defaultValue := ""
validation := []string{}

if value, exists := markers[validationMinimumMarker]; exists {
validation = append(validation, "Min: "+strconv.Itoa(value[0].(int)))
}
if value, exists := markers[validationMaximumMarker]; exists {
validation = append(validation, "Max: "+strconv.Itoa(value[0].(int)))
}
if value, exists := markers[validationEnumMarker]; exists {
validation = append(validation, "Values: "+strings.Join(value[0].([]string), ", "))
}
if value, exists := markers[validationMinLengthMarker]; exists {
validation = append(validation, "MinLength: "+strconv.Itoa(value[0].(int)))
}
if value, exists := markers[validationMaxLengthMarker]; exists {
validation = append(validation, "MaxLength: "+strconv.Itoa(value[0].(int)))
}
if value, exists := markers[validationFormatMarker]; exists {
validation = append(validation, "Format: "+value[0].(string))
}
if value, exists := markers[validationPatternMarker]; exists {
validation = append(validation, fmt.Sprintf("Pattern: `%s`", value[0].(string)))
}

if value, exists := markers[defaultMarker]; exists {
defaultValue = fmt.Sprintf("%v", value[0].(Default).Value)
if strings.HasPrefix(defaultValue, "map[") {
defaultValue = strings.TrimPrefix(defaultValue, "map[")
defaultValue = strings.TrimSuffix(defaultValue, "]")
defaultValue = fmt.Sprintf("{ %s }", defaultValue)
}
}
if value, exists := markers[docMarker]; exists {
for _, v := range value {
doc = append(doc, v.(string))
}
}

return strings.Join(doc, " "), defaultValue, validation
}

func (p *processor) loadType(pkg *loader.Package, t gotypes.Type, depth int) *types.Type {
if depth > p.maxDepth {
zap.S().Debugw("Not loading type due to reaching max recursion depth", "type", t.String())
Expand Down Expand Up @@ -484,6 +543,7 @@ func (p *processor) loadAliasType(typeDef *types.Type, pkg *loader.Package, unde
if bt, ok := underlying.(*gotypes.Basic); ok {
typeDef.UnderlyingType = &types.Type{Name: bt.String(), Kind: types.BasicKind}
typeDef.Doc = tInfo.Doc
typeDef.Markers = tInfo.Markers
return typeDef
}

Expand Down Expand Up @@ -535,6 +595,47 @@ func mkRegistry() *markers.Registry {
registry := &markers.Registry{}
registry.Define(groupNameMarker, markers.DescribesPackage, "")
registry.Define(objectRootMarker, markers.DescribesType, true)
registry.Define(docMarker, markers.DescribesField, "")
registry.Define(validationEnumMarker, markers.DescribesField, []string{})
registry.Define(validationEnumMarker, markers.DescribesType, []string{})
registry.Define(validationFormatMarker, markers.DescribesField, "")
registry.Define(validationFormatMarker, markers.DescribesType, "")
registry.Define(validationMinimumMarker, markers.DescribesField, 0)
registry.Define(validationMinimumMarker, markers.DescribesType, 0)
registry.Define(validationMaximumMarker, markers.DescribesField, 0)
registry.Define(validationMaximumMarker, markers.DescribesType, 0)
registry.Define(validationMinLengthMarker, markers.DescribesField, 0)
registry.Define(validationMinLengthMarker, markers.DescribesType, 0)
registry.Define(validationMaxLengthMarker, markers.DescribesField, 0)
registry.Define(validationMaxLengthMarker, markers.DescribesType, 0)
registry.Define(validationPatternMarker, markers.DescribesField, "")
registry.Define(validationPatternMarker, markers.DescribesType, "")
registry.Define(versionNameMarker, markers.DescribesPackage, "")

def, err := markers.MakeAnyTypeDefinition(defaultMarker, markers.DescribesField, Default{})
registry.Register(markers.Must(def, err))
def, err = markers.MakeAnyTypeDefinition(defaultMarker, markers.DescribesType, Default{})
registry.Register(markers.Must(def, err))

return registry
}

type Default struct {
Value interface{}
}

func (p *processor) applyMarkers() {
for _, t := range p.types {
var doc string
doc, t.Default, t.Validation = parseMarkers(t.Markers)
if doc != "" {
t.Doc = doc
}
for _, f := range t.Fields {
doc, f.Default, f.Validation = parseMarkers(f.Markers)
if doc != "" {
f.Doc = doc
}
}
}
}
23 changes: 18 additions & 5 deletions templates/asciidoctor/type.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,18 @@

{{ $type.Doc }}

{{ if $type.Default -}}
_Default_: {{ $type.Default }}

{{ end -}}

{{ if $type.Validation -}}
.Validation:
{{- range $type.Validation }}
- {{ . }}
{{- end }}
{{- end }}

{{ if $type.References -}}
.Appears In:
****
Expand All @@ -17,16 +29,17 @@
{{- end }}

{{ if $type.Members -}}
[cols="25a,75a", options="header"]
[cols="20a,50a,15a,15a", options="header"]
|===
| Field | Description
| Field | Description | Default | Validation
{{ if $type.GVK -}}
| *`apiVersion`* __string__ | `{{ $type.GVK.Group }}/{{ $type.GVK.Version }}`
| *`kind`* __string__ | `{{ $type.GVK.Kind }}`
| *`apiVersion`* __string__ | `{{ $type.GVK.Group }}/{{ $type.GVK.Version }}` | |
| *`kind`* __string__ | `{{ $type.GVK.Kind }}` | |
{{ end -}}

{{ range $type.Members -}}
| *`{{ .Name }}`* __{{ asciidocRenderType .Type }}__ | {{ template "type_members" . }}
| *`{{ .Name }}`* __{{ asciidocRenderType .Type }}__ | {{ template "type_members" . }} | {{ .Default }} | {{ range .Validation -}} {{ . }} +
{{ end }}
{{ end -}}
|===
{{ end -}}
Expand Down
22 changes: 17 additions & 5 deletions templates/markdown/type.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,18 @@

{{ $type.Doc }}

{{ if $type.Default -}}
_Default_: {{ $type.Default }}

{{ end -}}

{{ if $type.Validation -}}
_Validation:_
{{- range $type.Validation }}
- {{ . }}
{{- end }}
{{- end }}

{{ if $type.References -}}
_Appears in:_
{{- range $type.SortedReferences }}
Expand All @@ -16,15 +28,15 @@ _Appears in:_
{{- end }}

{{ if $type.Members -}}
| Field | Description |
| --- | --- |
| Field | Description | Default | Validation |
| --- | --- | --- | --- |
{{ if $type.GVK -}}
| `apiVersion` _string_ | `{{ $type.GVK.Group }}/{{ $type.GVK.Version }}`
| `kind` _string_ | `{{ $type.GVK.Kind }}`
| `apiVersion` _string_ | `{{ $type.GVK.Group }}/{{ $type.GVK.Version }}` | | |
| `kind` _string_ | `{{ $type.GVK.Kind }}` | | |
{{ end -}}

{{ range $type.Members -}}
| `{{ .Name }}` _{{ markdownRenderType .Type }}_ | {{ template "type_members" . }} |
| `{{ .Name }}` _{{ markdownRenderType .Type }}_ | {{ template "type_members" . }} | {{ .Default }} | {{ range .Validation -}} {{ . }} <br>{{ end }} |
{{ end -}}

{{ end -}}
Expand Down
18 changes: 12 additions & 6 deletions test/api/v1/guestbook_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,16 +51,19 @@ type EmbeddedX struct {
X string `json:"x,omitempty"`
}

// NOTE: Rating is placed here to ensure that it is parsed as a standalone type
// NOTE: PositiveInt is placed here to ensure that it is parsed as a standalone type
// before it is parsed as a struct field.

// Rating is the rating provided by a guest.
type Rating string
// PositiveInt is a positie integer
//+kubebuilder:default=1
//+kubebuilder:validation:Minimum=1
type PositiveInt int

// GuestbookSpec defines the desired state of Guestbook.
type GuestbookSpec struct {
// Page indicates the page number
Page *int `json:"page,omitempty"`
//+kubebuilder:default=1
Page *PositiveInt `json:"page,omitempty"`
// Entries contain guest book entries for the page
Entries []GuestbookEntry `json:"entries,omitempty"`
// Selector selects something
Expand All @@ -79,10 +82,13 @@ type GuestbookEntry struct {
Time metav1.Time `json:"time,omitempty"`
// Comment by guest
Comment string `json:"comment,omitempty"`
// Rating provided by the guest
Rating Rating `json:"rating,omitempty"`
Rating Rating `json:"rating,omitempty"`
}

// Rating is the rating provided by a guest.
// +kubebuilder:validation:MaxLength=1023
type Rating string

// GuestbookStatus defines the observed state of Guestbook.
type GuestbookStatus struct {
Status string `json:"status"`
Expand Down
Loading

0 comments on commit c573a3d

Please sign in to comment.