mirror of
https://github.com/siderolabs/talos.git
synced 2026-04-18 04:02:02 +02:00
- replace `interface{}` with `any` using `gofmt -r 'interface{} -> any -w'`
- replace `a = []T{}` with `var a []T` where possible.
- replace `a = []T{}` with `a = make([]T, 0, len(b))` where possible.
Signed-off-by: Dmitriy Matrenichev <dmitry.matrenichev@siderolabs.com>
323 lines
6.2 KiB
Go
323 lines
6.2 KiB
Go
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
|
|
package encoder
|
|
|
|
import (
|
|
"reflect"
|
|
"regexp"
|
|
"strings"
|
|
"sync"
|
|
|
|
yaml "gopkg.in/yaml.v3"
|
|
)
|
|
|
|
const (
|
|
// HeadComment populates `yaml.Node` `HeadComment`.
|
|
HeadComment = iota
|
|
// LineComment populates `yaml.Node` `LineComment`.
|
|
LineComment
|
|
// FootComment populates `yaml.Node` `FootComment`.
|
|
FootComment
|
|
)
|
|
|
|
// Doc represents a struct documentation rendered from comments by docgen.
|
|
type Doc struct {
|
|
// Comments stores foot, line and head comments.
|
|
Comments [3]string
|
|
// Fields contains fields documentation if related item is a struct.
|
|
Fields []Doc
|
|
// Examples list of example values for the item.
|
|
Examples []*Example
|
|
// Values is only used to render valid values list in the documentation.
|
|
Values []string
|
|
// Description represents the full description for the item.
|
|
Description string
|
|
// Name represents struct name or field name.
|
|
Name string
|
|
// Type represents struct name or field type.
|
|
Type string
|
|
// Note is rendered as a note for the example in markdown file.
|
|
Note string
|
|
// AppearsIn describes back references for the type.
|
|
AppearsIn []Appearance
|
|
}
|
|
|
|
// AddExample adds a new example snippet to the doc.
|
|
func (d *Doc) AddExample(name string, value any) {
|
|
if d.Examples == nil {
|
|
d.Examples = []*Example{}
|
|
}
|
|
|
|
d.Examples = append(d.Examples, &Example{
|
|
Name: name,
|
|
value: value,
|
|
})
|
|
}
|
|
|
|
// Describe returns a field description.
|
|
func (d *Doc) Describe(field string, short bool) string {
|
|
desc := ""
|
|
|
|
for _, f := range d.Fields {
|
|
if f.Name == field {
|
|
desc = f.Description
|
|
}
|
|
}
|
|
|
|
if short {
|
|
desc = strings.Split(desc, "\n")[0]
|
|
}
|
|
|
|
return desc
|
|
}
|
|
|
|
// Example represents one example snippet for a type.
|
|
type Example struct {
|
|
populate sync.Once
|
|
Name string
|
|
|
|
valueMutex sync.RWMutex
|
|
value any
|
|
}
|
|
|
|
// Populate populates example value.
|
|
func (e *Example) Populate(index int) {
|
|
e.populate.Do(func() {
|
|
if reflect.TypeOf(e.value).Kind() != reflect.Ptr {
|
|
return
|
|
}
|
|
|
|
v := reflect.ValueOf(e.value).Elem()
|
|
|
|
defaultValue := getExample(v, getDoc(e.value), index)
|
|
|
|
e.valueMutex.Lock()
|
|
defer e.valueMutex.Unlock()
|
|
|
|
if defaultValue != nil {
|
|
v.Set(defaultValue.Convert(v.Type()))
|
|
}
|
|
|
|
populateNestedExamples(v, index)
|
|
})
|
|
}
|
|
|
|
// GetValue returns example value.
|
|
func (e *Example) GetValue() any {
|
|
e.valueMutex.RLock()
|
|
defer e.valueMutex.RUnlock()
|
|
|
|
return e.value
|
|
}
|
|
|
|
// Field gets field from the list of fields.
|
|
func (d *Doc) Field(i int) *Doc {
|
|
if i < len(d.Fields) {
|
|
return &d.Fields[i]
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Appearance of a type in a different type.
|
|
type Appearance struct {
|
|
TypeName string
|
|
FieldName string
|
|
}
|
|
|
|
// Documented is used to check if struct has any documentation defined for it.
|
|
type Documented interface {
|
|
// Doc requests documentation object.
|
|
Doc() *Doc
|
|
}
|
|
|
|
func mergeDoc(a, b *Doc) *Doc {
|
|
var res Doc
|
|
if a != nil {
|
|
res = *a
|
|
}
|
|
|
|
if b == nil {
|
|
return &res
|
|
}
|
|
|
|
for i, comment := range b.Comments {
|
|
if comment != "" {
|
|
res.Comments[i] = comment
|
|
}
|
|
}
|
|
|
|
if len(res.Examples) == 0 {
|
|
res.Examples = b.Examples
|
|
}
|
|
|
|
return &res
|
|
}
|
|
|
|
func getDoc(in any) *Doc {
|
|
v := reflect.ValueOf(in)
|
|
if v.Kind() == reflect.Ptr && v.IsNil() {
|
|
in = reflect.New(v.Type().Elem()).Interface()
|
|
}
|
|
|
|
if d, ok := in.(Documented); ok {
|
|
return d.Doc()
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func addComments(node *yaml.Node, doc *Doc, comments ...int) {
|
|
if doc != nil {
|
|
dest := []*string{
|
|
&node.HeadComment,
|
|
&node.LineComment,
|
|
&node.FootComment,
|
|
}
|
|
|
|
if len(comments) == 0 {
|
|
comments = []int{
|
|
HeadComment,
|
|
LineComment,
|
|
FootComment,
|
|
}
|
|
}
|
|
|
|
for _, i := range comments {
|
|
if doc.Comments[i] != "" {
|
|
*dest[i] = doc.Comments[i]
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//nolint:gocyclo
|
|
func renderExample(key string, doc *Doc, options *Options) string {
|
|
if doc == nil {
|
|
return ""
|
|
}
|
|
|
|
examples := make([]string, 0, len(doc.Examples))
|
|
|
|
for i, e := range doc.Examples {
|
|
v := reflect.ValueOf(e.GetValue())
|
|
|
|
if isEmpty(v) {
|
|
continue
|
|
}
|
|
|
|
if v.Kind() != reflect.Ptr {
|
|
v = reflect.Indirect(v)
|
|
}
|
|
|
|
defaultValue := v.Interface()
|
|
|
|
e.Populate(i)
|
|
|
|
node, err := toYamlNode(defaultValue, options)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
|
|
if key != "" {
|
|
node, err = toYamlNode(map[string]*yaml.Node{
|
|
key: node,
|
|
}, options)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
}
|
|
|
|
if i == 0 && options.Comments.enabled(CommentsDocs) {
|
|
addComments(node, doc, HeadComment, LineComment)
|
|
}
|
|
|
|
// replace head comment with line comment
|
|
if node.HeadComment == "" {
|
|
node.HeadComment = node.LineComment
|
|
}
|
|
|
|
node.LineComment = ""
|
|
if e.Name != "" {
|
|
if node.HeadComment != "" {
|
|
node.HeadComment += "\n\n"
|
|
}
|
|
|
|
node.HeadComment = node.HeadComment + e.Name + "\n"
|
|
}
|
|
|
|
data, err := yaml.Marshal(node)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
|
|
if key == "" {
|
|
// re-indent
|
|
data = regexp.MustCompile(`(?m)^(.)`).ReplaceAll(data, []byte(" $1"))
|
|
} else {
|
|
// don't collapse comment
|
|
data = regexp.MustCompile(`(?m)^#`).ReplaceAll(data, []byte("# #"))
|
|
}
|
|
|
|
examples = append(examples, string(data))
|
|
}
|
|
|
|
return strings.Join(examples, "")
|
|
}
|
|
|
|
func getExample(v reflect.Value, doc *Doc, index int) *reflect.Value {
|
|
if doc == nil || len(doc.Examples) == 0 {
|
|
return nil
|
|
}
|
|
|
|
numExamples := len(doc.Examples)
|
|
if index >= numExamples {
|
|
index = numExamples - 1
|
|
}
|
|
|
|
defaultValue := reflect.ValueOf(doc.Examples[index].GetValue())
|
|
if !isEmpty(defaultValue) {
|
|
if v.Kind() != reflect.Ptr && defaultValue.Kind() == reflect.Ptr {
|
|
defaultValue = defaultValue.Elem()
|
|
}
|
|
}
|
|
|
|
return &defaultValue
|
|
}
|
|
|
|
//nolint:gocyclo
|
|
func populateNestedExamples(v reflect.Value, index int) {
|
|
//nolint:exhaustive
|
|
switch v.Kind() {
|
|
case reflect.Struct:
|
|
doc := getDoc(v.Interface())
|
|
|
|
for i := range v.NumField() {
|
|
field := v.Field(i)
|
|
if !field.CanInterface() {
|
|
continue
|
|
}
|
|
|
|
if doc != nil && i < len(doc.Fields) {
|
|
defaultValue := getExample(field, doc.Field(i), index)
|
|
|
|
if defaultValue != nil {
|
|
field.Set(defaultValue.Convert(field.Type()))
|
|
}
|
|
}
|
|
|
|
populateNestedExamples(field, index)
|
|
}
|
|
case reflect.Map:
|
|
for _, key := range v.MapKeys() {
|
|
populateNestedExamples(v.MapIndex(key), index)
|
|
}
|
|
case reflect.Slice:
|
|
for i := range v.Len() {
|
|
populateNestedExamples(v.Index(i), index)
|
|
}
|
|
}
|
|
}
|