mukan-ignite/ignite/pkg/clidoc/struct.go
Mukan Erkin Törük c32551b6f7
Some checks failed
Docs Deploy / build_and_deploy (push) Has been cancelled
Generate Docs / cli (push) Has been cancelled
Generate Config Doc / cli (push) Has been cancelled
Go formatting / go-formatting (push) Has been cancelled
Check links / markdown-link-check (push) Has been cancelled
Integration / pre-test (push) Has been cancelled
Integration / test on (push) Has been cancelled
Integration / status (push) Has been cancelled
Lint / Lint Go code (push) Has been cancelled
Test / test (ubuntu-latest) (push) Has been cancelled
refactor: replace all github.com upstream refs with git.cw.tr/mukan-network
2026-05-11 03:36:24 +03:00

135 lines
3.1 KiB
Go

package clidoc
import (
"fmt"
"reflect"
"strings"
"git.cw.tr/mukan-network/mukan-ignite/ignite/pkg/errors"
)
const listSuffix = "list"
type (
// Docs represents a slice of Doc.
Docs []Doc
// Doc represents the struct documentation with tag comments.
Doc struct {
Key string
Type string
Value Docs
Comment string
}
)
// String converts Docs to a string.
func (d Docs) String() string {
var sb strings.Builder
// Initial call with a negative level to avoid unwanted dash at the top level
d.writeString(&sb, -1)
return strings.TrimSpace(sb.String())
}
// writeString appends the contents of Docs to sb's buffer at level.
func (d Docs) writeString(sb *strings.Builder, level int) {
indent := strings.Repeat(" ", level+1) // Two spaces per YAML indentation standard
for _, doc := range d {
sb.WriteString(indent)
switch doc.Type {
case "":
sb.WriteString(fmt.Sprintf("%s: # %s\n", doc.Key, doc.Comment))
default:
sb.WriteString(fmt.Sprintf("%s: (%s) # %s\n", doc.Key, doc.Type, doc.Comment))
}
if len(doc.Value) > 0 {
doc.Value.writeString(sb, level+1)
}
}
}
// GenDoc to generate documentation from a struct.
func GenDoc(v interface{}) (fields Docs, err error) {
t := reflect.TypeOf(v)
if t.Kind() != reflect.Struct && t.Kind() != reflect.Ptr {
return fields, nil
}
for i := 0; i < t.NumField(); i++ {
var (
field = t.Field(i)
yaml = field.Tag.Get("yaml")
doc = field.Tag.Get("doc")
)
tags := strings.Split(yaml, ",")
if len(tags) == 0 {
return fields, errors.Errorf("no tags found in struct field %s", field.Name)
}
name := tags[0]
if name == "" {
name = strings.ToLower(field.Name)
}
if len(tags) > 1 && strings.Contains(tags[1], "inline") {
elemFields, err := GenDoc(reflect.New(field.Type).Elem().Interface())
if err != nil {
return nil, err
}
fields = append(fields, elemFields...)
continue
}
var (
elemFields Docs
elemType string
)
switch field.Type.Kind() { //nolint
case reflect.Struct:
elemType = field.Type.Kind().String()
elemFields, err = GenDoc(reflect.New(field.Type).Elem().Interface())
if err != nil {
return nil, err
}
case reflect.Ptr:
elemType = field.Type.Elem().Kind().String()
elemFields, err = GenDoc(reflect.New(field.Type.Elem()).Elem().Interface())
if err != nil {
return nil, err
}
case reflect.Slice:
elemType = listName(field.Type.Elem().Kind().String())
elemFields, err = GenDoc(reflect.New(field.Type.Elem()).Elem().Interface())
if err != nil {
return nil, err
}
default:
elemType = field.Type.Kind().String()
}
fields = append(fields, Doc{
Key: name,
Comment: doc,
Value: elemFields,
Type: mapTypes(elemType),
})
}
return fields, nil
}
func listName(name string) string {
return fmt.Sprintf("%s %s", name, listSuffix)
}
func mapTypes(doc string) string {
docTypes := map[string]string{
"struct": "",
"map": "key/value",
"slice": listSuffix,
listName("map"): listSuffix,
listName("slice"): listSuffix,
listName("struct"): listSuffix,
}
if docType, ok := docTypes[doc]; ok {
return docType
}
return doc
}