From 3546001bf61b5344e2c5ae4b9f6bcc920af6e36d Mon Sep 17 00:00:00 2001 From: zzxwill Date: Thu, 14 Jan 2021 23:58:56 +0800 Subject: [PATCH] Generate a local site to host all reference docs By Cli `vela reference` to generate reference docs for all workload types and traits, and host them in a local website. To fix #880 --- hack/references/generate.go | 226 +----------------------------- pkg/commands/cli.go | 1 + pkg/commands/references.go | 271 ++++++++++++++++++++++++++++++++++++ pkg/commands/refresh.go | 25 +--- pkg/plugins/cluster.go | 31 +++++ pkg/plugins/references.go | 262 ++++++++++++++++++++++++++++++++++ 6 files changed, 570 insertions(+), 246 deletions(-) create mode 100644 pkg/commands/references.go create mode 100644 pkg/plugins/references.go diff --git a/hack/references/generate.go b/hack/references/generate.go index bf281a1eb..d7fab200f 100644 --- a/hack/references/generate.go +++ b/hack/references/generate.go @@ -2,236 +2,14 @@ package main import ( "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" - "github.com/oam-dev/kubevela/pkg/utils/common" ) -const ( - BaseRefPath = "docs/en/developers/references" - ReferencePath = "hack/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) + if err := plugins.GenerateReferenceDocs(); err != nil { + fmt.Println(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) - } - if err = os.Truncate(filePath, 0); err != nil { - fmt.Printf("failed to truncate file %s: %s", filePath, err) - os.Exit(1) - } - capName := c.Name - ref := ReferenceMarkdown{ - CapabilityName: capName, - CapabilityType: capabilityType, - } - - cueValue, err := common.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) - - conflictWithAndMoreSection, err := generateConflictWithAndMore(capName) - if err != nil { - fmt.Printf(err.Error()) - os.Exit(1) - } - refContent = title + description + specification + refContent + conflictWithAndMoreSection - 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 -} - -// 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 fmt.Errorf("arguments not defined as struct %w", 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 fmt.Errorf("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(ReferencePath, "configurations", fmt.Sprintf("%s.yaml", capability))) - if err != nil { - return "", fmt.Errorf("failed to get configuration path: %w", err) - } - - spec, err := ioutil.ReadFile(configurationPath) - // skip if Configuration usage of a capability doesn't exist. - if err != nil { - spec = nil - } - return fmt.Sprintf("```yaml\n%s```", string(spec)), nil -} - -// generateConflictWithAndMore generates Section `Conflicts With` and more like `How xxx works` in reference docs -func generateConflictWithAndMore(capabilityName string) (string, error) { - conflictWithFile, err := filepath.Abs(filepath.Join(ReferencePath, "conflictsWithAndMore", fmt.Sprintf("%s.md", capabilityName))) - if err != nil { - return "", fmt.Errorf("failed to locate conflictWith file: %w", err) - } - data, err := ioutil.ReadFile(conflictWithFile) - if err != nil { - return "", nil - } - return "\n" + string(data), nil } diff --git a/pkg/commands/cli.go b/pkg/commands/cli.go index 6276c8e41..6074e5c15 100644 --- a/pkg/commands/cli.go +++ b/pkg/commands/cli.go @@ -58,6 +58,7 @@ func NewCommand() *cobra.Command { NewInitCommand(commandArgs, ioStream), NewUpCommand(commandArgs, ioStream), NewExportCommand(commandArgs, ioStream), + NewReferencesCommand(commandArgs, ioStream), // Apps NewListCommand(commandArgs, ioStream), diff --git a/pkg/commands/references.go b/pkg/commands/references.go new file mode 100644 index 000000000..c14d594de --- /dev/null +++ b/pkg/commands/references.go @@ -0,0 +1,271 @@ +package commands + +import ( + "context" + "errors" + "fmt" + "io/ioutil" + "net/http" + "os" + "os/signal" + "path/filepath" + "syscall" + "time" + + "github.com/spf13/cobra" + + "github.com/oam-dev/kubevela/apis/types" + cmdutil "github.com/oam-dev/kubevela/pkg/commands/util" + "github.com/oam-dev/kubevela/pkg/plugins" + "github.com/oam-dev/kubevela/pkg/utils/system" +) + +const ( + // SideBar file name for docsify + SideBar = "_sidebar.md" + // NavBar file name for docsify + NavBar = "_navbar.md" + // IndexHTML file name for docsify + IndexHTML = "index.html" + // README file name for docsify + README = "README.md" +) + +// Port is the port for reference docs website +const Port = ":18081" + +// NewReferencesCommand shows the website which hosts the reference docs for workload types and trait types +func NewReferencesCommand(c types.Args, ioStreams cmdutil.IOStreams) *cobra.Command { + cmd := &cobra.Command{ + Use: "reference", + Aliases: []string{"ref", "ref-docs"}, + Short: "Opens up the reference docs website for workload types and trait types", + Long: "Opens up the reference docs website for workload types and trait types", + Example: `vela reference`, + PersistentPreRunE: func(cmd *cobra.Command, args []string) error { + return c.SetConfig() + }, + RunE: func(cmd *cobra.Command, args []string) error { + ctx := context.Background() + return startReferenceDocsSite(ctx, c, ioStreams) + }, + Annotations: map[string]string{ + types.TagCommandType: types.TypeStart, + }, + } + cmd.Flags().StringP("svc", "s", "", "service name") + cmd.SetOut(ioStreams.Out) + return cmd +} + +func startReferenceDocsSite(ctx context.Context, c types.Args, ioStreams cmdutil.IOStreams) error { + home, err := system.GetVelaHomeDir() + if err != nil { + return err + } + referenceHome := filepath.Join(home, "reference") + + definitionPath := filepath.Join(referenceHome, "capabilities") + if _, err := os.Stat(definitionPath); err != nil && os.IsNotExist(err) { + if err := os.MkdirAll(definitionPath, 0750); err != nil { + return err + } + } + docsPath := filepath.Join(referenceHome, "docs") + if _, err := os.Stat(docsPath); err != nil && os.IsNotExist(err) { + if err := os.MkdirAll(docsPath, 0750); err != nil { + return err + } + } + + capabilities, _, err := plugins.SyncDefinitionToLocal(ctx, c, definitionPath) + if err != nil { + return err + } + + if err := plugins.CreateMarkdown(capabilities, docsPath, plugins.ReferenceSourcePath); err != nil { + return err + } + + if err := generateSideBar(capabilities, docsPath); err != nil { + return err + } + + if err := generateNavBar(docsPath); err != nil { + return err + } + + if err := generateIndexHTML(docsPath); err != nil { + return err + } + + if err := generateREADME(capabilities, docsPath); err != nil { + return err + } + + server := &http.Server{ + Addr: Port, + Handler: http.FileServer(http.Dir(docsPath)), + ReadTimeout: 5 * time.Second, + WriteTimeout: 10 * time.Second, + } + server.SetKeepAlivesEnabled(true) + errCh := make(chan error, 1) + + launch(server, errCh) + + select { + case err = <-errCh: + return err + case <-time.After(time.Second): + var url = "http://127.0.0.1" + Port + if err := OpenBrowser(url); err != nil { + ioStreams.Infof("automatically invoking browser failed: %v\nPlease visit %s for reference docs", err, url) + } + } + + // handle signal: SIGTERM(15) + sc := make(chan os.Signal, 1) + signal.Notify(sc, syscall.SIGTERM) + + <-sc + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + defer cancel() + return server.Shutdown(ctx) +} + +func launch(server *http.Server, errChan chan<- error) { + go func() { + // http.Handle("/", http.FileServer(http.Dir(docsPath))) + err := server.ListenAndServe() + if err != nil && errors.Is(err, http.ErrServerClosed) { + errChan <- err + } + }() +} + +func generateSideBar(capabilities []types.Capability, docsPath string) error { + sideBar := filepath.Join(docsPath, SideBar) + workloads, traits := getWorkloadsAndTraits(capabilities) + f, err := os.Create(sideBar) + if err != nil { + return err + } + if _, err := f.WriteString("- Workload Types\n"); err != nil { + return nil + } + workloadFolderName := "workload-types" + for _, w := range workloads { + if _, err := f.WriteString(fmt.Sprintf(" - [%s](%s/%s.md)\n", w, workloadFolderName, w)); err != nil { + return nil + } + } + if _, err := f.WriteString("- Traits\n"); err != nil { + return nil + } + traitFolderName := "traits" + for _, t := range traits { + if _, err := f.WriteString(fmt.Sprintf(" - [%s](%s/%s.md)\n", t, traitFolderName, t)); err != nil { + return nil + } + } + return nil +} + +func generateNavBar(docsPath string) error { + sideBar := filepath.Join(docsPath, NavBar) + _, err := os.Create(sideBar) + if err != nil { + return err + } + return nil +} + +func generateIndexHTML(docsPath string) error { + indexHTML := ` + + + + + KubeVela Reference Docs + + + + + + + + +
+ + + + + + + + +` + return ioutil.WriteFile(filepath.Join(docsPath, IndexHTML), []byte(indexHTML), 0600) +} + +func generateREADME(capabilities []types.Capability, docsPath string) error { + readmeMD := filepath.Join(docsPath, README) + f, err := os.Create(readmeMD) + if err != nil { + return err + } + if _, err := f.WriteString("# KubeVela Reference Docs for Workload Types and Traits\n" + + "Click the navigation bar on the left or the links below to look into the detailed referennce of a Workload type or a Trait.\n"); err != nil { + return err + } + + workloads, traits := getWorkloadsAndTraits(capabilities) + + if _, err := f.WriteString("## Workload Types\n"); err != nil { + return err + } + workloadFolderName := "workload-types" + for _, w := range workloads { + if _, err := f.WriteString(fmt.Sprintf(" - [%s](%s/%s.md)\n", w, workloadFolderName, w)); err != nil { + return err + } + } + if _, err := f.WriteString("## Traits\n"); err != nil { + return err + } + traitFolderName := "traits" + for _, t := range traits { + if _, err := f.WriteString(fmt.Sprintf(" - [%s](%s/%s.md)\n", t, traitFolderName, t)); err != nil { + return err + } + } + return nil +} + +func getWorkloadsAndTraits(capabilities []types.Capability) ([]string, []string) { + var workloads, traits []string + for _, c := range capabilities { + switch c.Type { + case types.TypeWorkload: + workloads = append(workloads, c.Name) + case types.TypeTrait: + traits = append(traits, c.Name) + case types.TypeScope: + + } + } + return workloads, traits +} diff --git a/pkg/commands/refresh.go b/pkg/commands/refresh.go index a2997a248..268d0af39 100644 --- a/pkg/commands/refresh.go +++ b/pkg/commands/refresh.go @@ -49,33 +49,14 @@ func RefreshDefinitions(ctx context.Context, c types.Args, ioStreams cmdutil.IOS return nil } - var syncedTemplates []types.Capability - - templates, templateErrors, err := plugins.GetWorkloadsFromCluster(ctx, types.DefaultKubeVelaNS, c, dir, nil) + syncedTemplates, warnings, err := plugins.SyncDefinitionToLocal(ctx, c, dir) if err != nil { return err } - if len(templateErrors) > 0 { - for _, e := range templateErrors { - ioStreams.Infof("WARN: %v, you will unable to use this workload capability\n", e) - } + for _, w := range warnings { + ioStreams.Infof(w) } - syncedTemplates = append(syncedTemplates, templates...) - plugins.SinkTemp2Local(templates, dir) - - templates, templateErrors, err = plugins.GetTraitsFromCluster(ctx, types.DefaultKubeVelaNS, c, dir, nil) - if err != nil { - return err - } - if len(templateErrors) > 0 { - for _, e := range templateErrors { - ioStreams.Infof("WARN: %v, you will unable to use this trait capability\n", e) - } - } - syncedTemplates = append(syncedTemplates, templates...) - plugins.SinkTemp2Local(templates, dir) plugins.RemoveLegacyTemps(syncedTemplates, dir) - printRefreshReport(syncedTemplates, oldCaps, ioStreams, silentOutput, false) return nil } diff --git a/pkg/plugins/cluster.go b/pkg/plugins/cluster.go index ec30339be..40218a015 100644 --- a/pkg/plugins/cluster.go +++ b/pkg/plugins/cluster.go @@ -198,3 +198,34 @@ func HandleTemplate(in *runtime.RawExtension, name, syncDir string) (types.Capab } return tmp, nil } + +// SyncDefinitionToLocal sync definitions to local +func SyncDefinitionToLocal(ctx context.Context, c types.Args, localDefinitionDir string) ([]types.Capability, []string, error) { + var syncedTemplates []types.Capability + var warnings []string + + templates, templateErrors, err := GetWorkloadsFromCluster(ctx, types.DefaultKubeVelaNS, c, localDefinitionDir, nil) + if err != nil { + return nil, nil, err + } + if len(templateErrors) > 0 { + for _, e := range templateErrors { + warnings = append(warnings, fmt.Sprintf("WARN: %v, you will unable to use this workload capability\n", e)) + } + } + syncedTemplates = append(syncedTemplates, templates...) + SinkTemp2Local(templates, localDefinitionDir) + + templates, templateErrors, err = GetTraitsFromCluster(ctx, types.DefaultKubeVelaNS, c, localDefinitionDir, nil) + if err != nil { + return nil, warnings, err + } + if len(templateErrors) > 0 { + for _, e := range templateErrors { + warnings = append(warnings, fmt.Sprintf("WARN: %v, you will unable to use this trait capability\n", e)) + } + } + syncedTemplates = append(syncedTemplates, templates...) + SinkTemp2Local(templates, localDefinitionDir) + return syncedTemplates, warnings, nil +} diff --git a/pkg/plugins/references.go b/pkg/plugins/references.go new file mode 100644 index 000000000..0af1fecfc --- /dev/null +++ b/pkg/plugins/references.go @@ -0,0 +1,262 @@ +package plugins + +import ( + "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/utils/common" +) + +const ( + // BaseRefPath is the target path for reference docs + BaseRefPath = "docs/en/developers/references" + // ReferenceSourcePath is the location for source reference + ReferenceSourcePath = "hack/references" +) + +// Int64Type is int64 type +type Int64Type int64 + +// StringType is string type +type StringType string + +// BoolType is bool type +type BoolType bool + +// ReferenceMarkdown is the struct for capability information +type ReferenceMarkdown struct { + // CapabilityName is the name of a capability + CapabilityName string `json:"capabilityName"` + // CapabilityType is the type of a capability + CapabilityType string `json:"capabilityType"` +} + +// Parameter is the parameter section of CUE template +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 + +// GenerateReferenceDocs generates reference docs +func GenerateReferenceDocs() error { + + caps, err := LoadAllInstalledCapability() + if err != nil { + return fmt.Errorf("failed to generate reference docs for all capabilities: %s", err) + } + + return CreateMarkdown(caps, BaseRefPath, ReferenceSourcePath) +} + +// CreateMarkdown creates markdown based on capabilities +func CreateMarkdown(caps []types.Capability, baseRefPath, referenceSourcePath string) error { + var capabilityType string + var specificationType string + for _, c := range caps { + switch c.Type { + case types.TypeWorkload: + capabilityType = "workload-types" + specificationType = "workload type" + case types.TypeTrait: + capabilityType = "traits" + specificationType = "trait" + default: + return fmt.Errorf("the type of the capability is not right") + } + + fileName := fmt.Sprintf("%s.md", c.Name) + filePath := filepath.Join(baseRefPath, capabilityType) + if _, err := os.Stat(filePath); err != nil && os.IsNotExist(err) { + if err := os.MkdirAll(filePath, 0750); err != nil { + return err + } + } + + markdownFile := filepath.Join(baseRefPath, capabilityType, fileName) + f, err := os.OpenFile(filepath.Clean(markdownFile), os.O_WRONLY|os.O_CREATE, 0600) + if err != nil { + return fmt.Errorf("failed to open file %s: %s", markdownFile, err) + } + if err = os.Truncate(markdownFile, 0); err != nil { + return fmt.Errorf("failed to truncate file %s: %s", markdownFile, err) + } + capName := c.Name + ref := ReferenceMarkdown{ + CapabilityName: capName, + CapabilityType: capabilityType, + } + + cueValue, err := common.GetCUEParameterValue(c.CueTemplate) + if err != nil { + return fmt.Errorf("failed to retrieve `parameters` value from %s with err: %s", c.Name, err) + } + refContent = "" + var defaultDepth = 0 + recurseDepth = &defaultDepth + capNameInTitle := strings.Title(capName) + if err := ref.parseParameters(cueValue, "Properties", defaultDepth); err != nil { + return err + } + 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, referenceSourcePath) + if err != nil { + return err + } + specification := fmt.Sprintf("\n\n## Specification\n\n%s\n\n%s", specificationIntro, specificationContent) + + conflictWithAndMoreSection, err := generateConflictWithAndMore(capName, referenceSourcePath) + if err != nil { + return err + } + refContent = title + description + specification + refContent + conflictWithAndMoreSection + if _, err := f.WriteString(refContent); err != nil { + return nil + } + if err := f.Close(); err != nil { + return err + } + } + return nil +} + +// 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 { + printableDefaultValue := getPrintableDefaultValue(p.Default) + refContent += fmt.Sprintf(" %s | %s | %s | %t | %s \n", p.Name, p.Usage, p.PrintableType, p.Required, printableDefaultValue) + } + return refContent +} + +// 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 fmt.Errorf("arguments not defined as struct %w", 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 fmt.Errorf("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) + } + default: + // + } + + 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 Int64Type: + return strconv.FormatInt(v.(int64), 10) + case StringType: + if v == "" { + return "empty" + } + return v.(string) + case BoolType: + return strconv.FormatBool(v.(bool)) + } + return "" +} + +// generateSpecification generates Specification part for reference docs +func generateSpecification(capability string, referenceSourcePath string) (string, error) { + configurationPath, err := filepath.Abs(filepath.Join(referenceSourcePath, "configurations", fmt.Sprintf("%s.yaml", capability))) + if err != nil { + return "", fmt.Errorf("failed to get configuration path: %w", err) + } + + spec, err := ioutil.ReadFile(filepath.Clean(configurationPath)) + // skip if Configuration usage of a capability doesn't exist. + if err != nil { + spec = nil + } + return fmt.Sprintf("```yaml\n%s```", string(spec)), nil +} + +// generateConflictWithAndMore generates Section `Conflicts With` and more like `How xxx works` in reference docs +func generateConflictWithAndMore(capabilityName string, referenceSourcePath string) (string, error) { + conflictWithFile, err := filepath.Abs(filepath.Join(referenceSourcePath, "conflictsWithAndMore", fmt.Sprintf("%s.md", capabilityName))) + if err != nil { + return "", fmt.Errorf("failed to locate conflictWith file: %w", err) + } + data, err := ioutil.ReadFile(filepath.Clean(conflictWithFile)) + if err != nil { + return "", nil + } + return "\n" + string(data), nil +}