package main import ( "errors" "fmt" "io/ioutil" "os" "path/filepath" "strconv" "strings" "cuelang.org/go/cue" "github.com/oam-dev/kubevela/apis/types" mycue "github.com/oam-dev/kubevela/pkg/cue" "github.com/oam-dev/kubevela/pkg/plugins" ) const BaseRefPath = "docs/en/developers/references" type ReferenceMarkdown struct { CapabilityName string `json:"capabilityName"` CapabilityType string `json:"capabilityType"` } type Parameter struct { types.Parameter `json:",inline,omitempty"` // PrintableType is same to `parameter.Type` which could be printable PrintableType string `json:"printableType"` // Depth marks the depth for calling of function `parseParameters` Depth *int `json:"depth"` } var refContent string var recurseDepth *int func main() { var capabilityType string var specificationType string caps, err := plugins.LoadAllInstalledCapability() if err != nil { fmt.Printf("failed to generate reference docs for all capabilities: %s", err) os.Exit(1) } for _, c := range caps { switch c.Type { case "workload": capabilityType = "workload-types" specificationType = "workload type" case "trait": capabilityType = "traits" specificationType = "trait" default: fmt.Printf("the type of the capability is not right") os.Exit(1) } fileName := fmt.Sprintf("%s.md", c.Name) filePath := filepath.Join(BaseRefPath, capabilityType, fileName) f, err := os.OpenFile(filePath, os.O_WRONLY|os.O_CREATE, 0644) if err != nil { fmt.Printf("failed to open file %s: %s", filePath, err) os.Exit(1) } capName := c.Name ref := ReferenceMarkdown{ CapabilityName: capName, CapabilityType: capabilityType, } cueValue, err := getCUEParameterValue(c.CueTemplate) if err != nil { fmt.Printf("failed to retrieve `parameters` value from %s with err: %s", c.Name, err) os.Exit(1) } refContent = "" var defaultDepth = 0 recurseDepth = &defaultDepth capNameInTitle := strings.Title(capName) if err := ref.parseParameters(cueValue, "Properties", defaultDepth); err != nil { fmt.Printf(err.Error()) os.Exit(1) } title := fmt.Sprintf("# %s", capNameInTitle) description := fmt.Sprintf("\n\n## Description\n\n%s", c.Description) specificationIntro := fmt.Sprintf("List of all configuration options for a `%s` %s.", capNameInTitle, specificationType) specificationContent, err := generateSpecification(capName) if err != nil { fmt.Printf(err.Error()) os.Exit(1) } specification := fmt.Sprintf("\n\n## Specification\n\n%s\n\n%s", specificationIntro, specificationContent) refContent = title + description + specification + refContent f.WriteString(refContent) f.Close() } } // prepareParameterTable prepares the table content for each property func (ref *ReferenceMarkdown) prepareParameterTable(tableName string, parameterList []Parameter) string { refContent := fmt.Sprintf("\n\n%s\n\n", tableName) refContent += "Name | Description | Type | Required | Default \n" refContent += "------------ | ------------- | ------------- | ------------- | ------------- \n" for _, p := range parameterList { //defaultValue := p.Default //if defaultValue == nil { // defaultValue = "" //} printableDefaultValue := getPrintableDefaultValue(p.Default) refContent += fmt.Sprintf(" %s | %s | %s | %t | %s \n", p.Name, p.Usage, p.PrintableType, p.Required, printableDefaultValue) } return refContent } // getCUEParameterValue converts definitions to cue format func getCUEParameterValue(cueStr string) (cue.Value, error) { r := cue.Runtime{} template, err := r.Compile("", cueStr+mycue.BaseTemplate) if err != nil { return cue.Value{}, err } tempStruct, err := template.Value().Struct() if err != nil { return cue.Value{}, err } // find the parameter definition var paraDef cue.FieldInfo var found bool for i := 0; i < tempStruct.Len(); i++ { paraDef = tempStruct.Field(i) if paraDef.Name == "parameter" { found = true break } } if !found { return cue.Value{}, errors.New("arguments not exist") } arguments := paraDef.Value return arguments, nil } // parseParameters parses every parameter func (ref *ReferenceMarkdown) parseParameters(paraValue cue.Value, paramKey string, depth int) error { var params []Parameter *recurseDepth++ switch paraValue.Kind() { case cue.StructKind: arguments, err := paraValue.Struct() if err != nil { return errors.New(fmt.Sprintf("arguments not defined as struct %v", err)) } for i := 0; i < arguments.Len(); i++ { var param Parameter fi := arguments.Field(i) if fi.IsDefinition { continue } val := fi.Value name := fi.Name param.Name = name param.Required = !fi.IsOptional if def, ok := val.Default(); ok && def.IsConcrete() { param.Default = mycue.GetDefault(def) } param.Short, param.Usage, param.Alias = mycue.RetrieveComments(val) param.Type = val.IncompleteKind() switch val.IncompleteKind() { case cue.StructKind: depth := *recurseDepth // TODO(zzxwill) this case not processed `selector?: [string]: string` if name == "selector" { param.PrintableType = "map[string]string" } else { if err := ref.parseParameters(val, name, depth); err != nil { return err } param.PrintableType = fmt.Sprintf("[%s](#%s)", name, name) } case cue.ListKind: elem, success := val.Elem() if !success { return errors.New(fmt.Sprintf("failed to get elements from %s", val)) } switch elem.Kind() { case cue.StructKind: param.PrintableType = fmt.Sprintf("[[]%s](#%s)", name, name) depth := *recurseDepth if err := ref.parseParameters(elem, name, depth); err != nil { return err } default: param.Type = elem.Kind() param.PrintableType = fmt.Sprintf("[]%s", elem.IncompleteKind().String()) } default: param.PrintableType = param.Type.String() } params = append(params, param) } } tableName := fmt.Sprintf("%s %s", strings.Repeat("#", depth+2), paramKey) refContent = ref.prepareParameterTable(tableName, params) + refContent return nil } // getPrintableDefaultValue converts the value in `interface{}` type to be printable func getPrintableDefaultValue(v interface{}) string { if v == nil { return "" } switch v.(type) { case int64: return strconv.FormatInt(v.(int64), 10) case string: if v == "" { return "empty" } return v.(string) case bool: return strconv.FormatBool(v.(bool)) } return "" } // generateSpecification generates Specification part for reference docs func generateSpecification(capability string) (string, error) { configurationPath, err := filepath.Abs(filepath.Join("hack/references/configurations", fmt.Sprintf("%s.yaml", capability))) if err != nil { return "", errors.New(fmt.Sprintf("failed to get configuration path: %v", err)) } f, err := os.Open(configurationPath) if err != nil { return "", errors.New(fmt.Sprintf("failed to open configuration file: %v", err)) } spec, err := ioutil.ReadAll(f) if err != nil { return "", errors.New(fmt.Sprintf("failed to read configuration file: %v", err)) } return fmt.Sprintf("```yaml\n%s```", spec), nil }