From 66929f8a63a00fe0ec0d582823afb872c6a5864a Mon Sep 17 00:00:00 2001 From: Sam Alba Date: Wed, 2 Jun 2021 11:42:59 +0200 Subject: [PATCH 1/6] compiler.Value abstraction for docstring and incompletekind Signed-off-by: Sam Alba --- cmd/dagger/cmd/common/common.go | 4 ++-- cmd/dagger/cmd/input/list.go | 9 --------- compiler/value.go | 9 +++++++++ 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/cmd/dagger/cmd/common/common.go b/cmd/dagger/cmd/common/common.go index a83560e8..712757f2 100644 --- a/cmd/dagger/cmd/common/common.go +++ b/cmd/dagger/cmd/common/common.go @@ -110,7 +110,7 @@ func FormatValue(val *compiler.Value) string { return "dagger.#Secret" } if val.IsConcreteR() != nil { - return val.Cue().IncompleteKind().String() + return val.IncompleteKindString() } // value representation in Cue valStr := fmt.Sprintf("%v", val.Cue()) @@ -121,7 +121,7 @@ func FormatValue(val *compiler.Value) string { // ValueDocString returns the value doc from the comment lines func ValueDocString(val *compiler.Value) string { docs := []string{} - for _, c := range val.Cue().Doc() { + for _, c := range val.Doc() { docs = append(docs, strings.TrimSpace(c.Text())) } doc := strings.Join(docs, " ") diff --git a/cmd/dagger/cmd/input/list.go b/cmd/dagger/cmd/input/list.go index a4c8130d..0d97d0f4 100644 --- a/cmd/dagger/cmd/input/list.go +++ b/cmd/dagger/cmd/input/list.go @@ -57,15 +57,6 @@ var listCmd = &cobra.Command{ for _, inp := range inputs { isConcrete := (inp.IsConcreteR() == nil) _, hasDefault := inp.Default() - // valStr := "-" - // if isConcrete { - // valStr, _ = inp.Cue().String() - // } - // if hasDefault { - // valStr = fmt.Sprintf("%s (default)", valStr) - // } - - // valStr = strings.ReplaceAll(valStr, "\n", "\\n") if !viper.GetBool("all") { // skip input that is not overridable diff --git a/compiler/value.go b/compiler/value.go index ded6e810..fb772b5f 100644 --- a/compiler/value.go +++ b/compiler/value.go @@ -5,6 +5,7 @@ import ( "strconv" "cuelang.org/go/cue" + "cuelang.org/go/cue/ast" cueformat "cuelang.org/go/cue/format" ) @@ -270,3 +271,11 @@ func (v *Value) Default() (*Value, bool) { val, hasDef := v.val.Default() return v.cc.Wrap(val), hasDef } + +func (v *Value) Doc() []*ast.CommentGroup { + return v.Cue().Doc() +} + +func (v *Value) IncompleteKindString() string { + return v.Cue().IncompleteKind().String() +} From 7b4421b9a00855c843e915341638d62e303d4c58 Mon Sep 17 00:00:00 2001 From: Sam Alba Date: Wed, 2 Jun 2021 16:22:38 +0200 Subject: [PATCH 2/6] cmd/doc: boiler plate and for inputs / outputs scanning Signed-off-by: Sam Alba --- cmd/dagger/cmd/doc.go | 144 +++++++++++++++++++++++++++++++++++++ cmd/dagger/cmd/root.go | 1 + environment/environment.go | 4 +- environment/inputs_scan.go | 4 +- go.mod | 1 + 5 files changed, 150 insertions(+), 4 deletions(-) create mode 100644 cmd/dagger/cmd/doc.go diff --git a/cmd/dagger/cmd/doc.go b/cmd/dagger/cmd/doc.go new file mode 100644 index 00000000..d55b4f1c --- /dev/null +++ b/cmd/dagger/cmd/doc.go @@ -0,0 +1,144 @@ +package cmd + +import ( + "context" + "fmt" + "io/ioutil" + "regexp" + "strings" + "unicode/utf8" + + "cuelang.org/go/cue" + "cuelang.org/go/cue/format" + "github.com/rs/zerolog/log" + "github.com/spf13/cobra" + "github.com/spf13/viper" + "go.dagger.io/dagger/cmd/dagger/cmd/common" + "go.dagger.io/dagger/cmd/dagger/logger" + "go.dagger.io/dagger/compiler" + "go.dagger.io/dagger/environment" + "golang.org/x/crypto/ssh/terminal" +) + +const ( + textFormat = "txt" + markdownFormat = "md" +) + +var docCmd = &cobra.Command{ + Use: "doc [PACKAGE | PATH]", + Short: "document a package", + Args: cobra.MaximumNArgs(1), + PreRun: func(cmd *cobra.Command, args []string) { + // Fix Viper bug for duplicate flags: + // https://github.com/spf13/viper/issues/233 + if err := viper.BindPFlags(cmd.Flags()); err != nil { + panic(err) + } + }, + Run: func(cmd *cobra.Command, args []string) { + lg := logger.New() + ctx := lg.WithContext(cmd.Context()) + workspace := common.CurrentWorkspace(ctx) + st := common.CurrentEnvironmentState(ctx, workspace) + + format := viper.GetString("output") + if format != textFormat && format != markdownFormat { + lg.Fatal().Msg("output must be either `txt` or `md`") + } + + val, err := loadCode(args[0]) + if err != nil { + lg.Fatal().Err(err).Msg("cannot compile code") + } + PrintDoc(ctx, val, format) + }, +} + +func init() { + docCmd.Flags().StringP("output", "o", textFormat, "Output format (txt|md)") + + if err := viper.BindPFlags(docCmd.Flags()); err != nil { + panic(err) + } +} + +func extractComment(v cue.Value) string { + docs := []string{} + for _, c := range v.Doc() { + docs = append(docs, strings.TrimSpace(c.Text())) + } + doc := strings.Join(docs, " ") + + lines := strings.Split(doc, "\n") + + // Strip out FIXME, TODO, and INTERNAL comments + docs = []string{} + for _, line := range lines { + if strings.HasPrefix(line, "FIXME: ") || + strings.HasPrefix(line, "TODO: ") || + strings.HasPrefix(line, "INTERNAL: ") { + continue + } + docs = append(docs, line) + } + return strings.Join(docs, " ") +} + +func extractSpec(v cue.Value) string { + node := v.Source() + if node == nil { + return fmt.Sprintf("%v", v) + } + src, err := format.Node(node) + if err != nil { + panic(err) + } + space := regexp.MustCompile(`[\s\n]+`) + return strings.TrimSpace( + space.ReplaceAllString(string(src), " "), + ) +} + +func mdEscape(s string) string { + escape := []string{"|", "<", ">"} + for _, c := range escape { + s = strings.ReplaceAll(s, c, `\`+c) + } + return s +} + +func terminalTrim(msg string) string { + // If we're not running on a terminal, return the whole string + size, _, err := terminal.GetSize(1) + if err != nil { + return msg + } + + // Otherwise, trim to fit half the terminal + size /= 2 + for utf8.RuneCountInString(msg) > size { + msg = msg[0:len(msg)-4] + "…" + } + return msg +} + +func loadCode(path string) (*compiler.Value, error) { + src, err := ioutil.ReadFile(path) + if err != nil { + return nil, err + } + + val, err := compiler.Compile("", string(src)) + if err != nil { + return nil, err + } + + return val, nil +} + +func PrintDoc(ctx context.Context, val *compiler.Value, format string) { + lg := log.Ctx(ctx) + + environment.ScanOutputs(ctx, val) +} diff --git a/cmd/dagger/cmd/root.go b/cmd/dagger/cmd/root.go index 19ab0229..a4d0a061 100644 --- a/cmd/dagger/cmd/root.go +++ b/cmd/dagger/cmd/root.go @@ -46,6 +46,7 @@ func init() { input.Cmd, output.Cmd, versionCmd, + docCmd, ) if err := viper.BindPFlags(rootCmd.PersistentFlags()); err != nil { diff --git a/environment/environment.go b/environment/environment.go index a0aa24b7..260fd47d 100644 --- a/environment/environment.go +++ b/environment/environment.go @@ -320,7 +320,7 @@ func (e *Environment) ScanInputs(ctx context.Context, mergeUserInputs bool) ([]* } } - return scanInputs(ctx, src), nil + return ScanInputs(ctx, src), nil } func (e *Environment) ScanOutputs(ctx context.Context) ([]*compiler.Value, error) { @@ -340,5 +340,5 @@ func (e *Environment) ScanOutputs(ctx context.Context) ([]*compiler.Value, error } } - return scanOutputs(ctx, src), nil + return ScanOutputs(ctx, src), nil } diff --git a/environment/inputs_scan.go b/environment/inputs_scan.go index 0939a9ac..7b3a3648 100644 --- a/environment/inputs_scan.go +++ b/environment/inputs_scan.go @@ -42,7 +42,7 @@ func isReference(val cue.Value) bool { return isRef(val) } -func scanInputs(ctx context.Context, value *compiler.Value) []*compiler.Value { +func ScanInputs(ctx context.Context, value *compiler.Value) []*compiler.Value { lg := log.Ctx(ctx) inputs := []*compiler.Value{} @@ -67,7 +67,7 @@ func scanInputs(ctx context.Context, value *compiler.Value) []*compiler.Value { return inputs } -func scanOutputs(ctx context.Context, value *compiler.Value) []*compiler.Value { +func ScanOutputs(ctx context.Context, value *compiler.Value) []*compiler.Value { lg := log.Ctx(ctx) inputs := []*compiler.Value{} diff --git a/go.mod b/go.mod index 0ed3cba4..8352a529 100644 --- a/go.mod +++ b/go.mod @@ -30,6 +30,7 @@ require ( github.com/uber/jaeger-lib v2.4.1+incompatible // indirect go.mozilla.org/sops/v3 v3.7.1 go.uber.org/atomic v1.7.0 // indirect + golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83 golang.org/x/net v0.0.0-20210331212208-0fccb6fa2b5c // indirect golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 From 8c3c934f3c4fcb1b1101926076c1f1fbc5fb0f35 Mon Sep 17 00:00:00 2001 From: Sam Alba Date: Wed, 2 Jun 2021 18:20:49 +0200 Subject: [PATCH 3/6] doc: finished structure, supports title Signed-off-by: Sam Alba --- cmd/dagger/cmd/doc.go | 78 ++++++++++++++++++++++++++++++++++--------- compiler/value.go | 4 +-- 2 files changed, 65 insertions(+), 17 deletions(-) diff --git a/cmd/dagger/cmd/doc.go b/cmd/dagger/cmd/doc.go index d55b4f1c..f3815ef8 100644 --- a/cmd/dagger/cmd/doc.go +++ b/cmd/dagger/cmd/doc.go @@ -3,7 +3,7 @@ package cmd import ( "context" "fmt" - "io/ioutil" + "io/fs" "regexp" "strings" "unicode/utf8" @@ -13,10 +13,10 @@ import ( "github.com/rs/zerolog/log" "github.com/spf13/cobra" "github.com/spf13/viper" - "go.dagger.io/dagger/cmd/dagger/cmd/common" "go.dagger.io/dagger/cmd/dagger/logger" "go.dagger.io/dagger/compiler" "go.dagger.io/dagger/environment" + "go.dagger.io/dagger/stdlib" "golang.org/x/crypto/ssh/terminal" ) @@ -39,19 +39,19 @@ var docCmd = &cobra.Command{ Run: func(cmd *cobra.Command, args []string) { lg := logger.New() ctx := lg.WithContext(cmd.Context()) - workspace := common.CurrentWorkspace(ctx) - st := common.CurrentEnvironmentState(ctx, workspace) format := viper.GetString("output") if format != textFormat && format != markdownFormat { lg.Fatal().Msg("output must be either `txt` or `md`") } - val, err := loadCode(args[0]) + packageName := args[0] + + val, err := loadCode(packageName) if err != nil { lg.Fatal().Err(err).Msg("cannot compile code") } - PrintDoc(ctx, val, format) + PrintDoc(ctx, packageName, val, format) }, } @@ -123,22 +123,70 @@ func terminalTrim(msg string) string { return msg } -func loadCode(path string) (*compiler.Value, error) { - src, err := ioutil.ReadFile(path) +func loadCode(packageName string) (*compiler.Value, error) { + sources := map[string]fs.FS{ + stdlib.Path: stdlib.FS, + } + + src, err := compiler.Build(sources, packageName) if err != nil { return nil, err } - val, err := compiler.Compile("", string(src)) - if err != nil { - return nil, err - } - - return val, nil + return src, nil } -func PrintDoc(ctx context.Context, val *compiler.Value, format string) { +func PrintDoc(ctx context.Context, packageName string, val *compiler.Value, format string) { lg := log.Ctx(ctx) + fields, err := val.Fields(cue.Definitions(true)) + if err != nil { + lg.Fatal().Err(err).Msg("cannot get fields") + } + + // Print title + switch format { + case textFormat: + fmt.Printf("Package:\t%s\n", packageName) + case markdownFormat: + importPath := strings.Split(packageName, "/") + switch { + case len(importPath) == 2: + fmt.Printf("## %s\n", importPath[1]) + case len(importPath) > 2: + fmt.Printf("### %s\n", strings.Join(importPath[2:], "/")) + default: + fmt.Printf("## %s\n", packageName) + } + } + + for _, field := range fields { + if !field.Selector.IsDefinition() { + continue + } + + name := field.Label() + v := field.Value + if v.Cue().IncompleteKind() != cue.StructKind { + continue + } + + switch format { + case textFormat: + comment := extractComment(v.Cue()) + if comment != "" { + comment = fmt.Sprintf(": %s", comment) + } + fmt.Printf("\n=> %s%s\n", name, comment) + case markdownFormat: + comment := extractComment(v.Cue()) + if comment != "" { + comment = fmt.Sprintf("\n\n%s", comment) + } + fmt.Printf("\n#### %s%s\n\n", name, mdEscape(comment)) + fmt.Printf("##### Fields\n\n") + } + } + environment.ScanOutputs(ctx, val) } diff --git a/compiler/value.go b/compiler/value.go index fb772b5f..167f3861 100644 --- a/compiler/value.go +++ b/compiler/value.go @@ -76,8 +76,8 @@ func (f Field) Label() string { // Proxy function to the underlying cue.Value // Field ordering is guaranteed to be stable. -func (v *Value) Fields() ([]Field, error) { - it, err := v.val.Fields() +func (v *Value) Fields(opts ...cue.Option) ([]Field, error) { + it, err := v.val.Fields(opts...) if err != nil { return nil, err } From 92d993f434dcd29539019d48b89139bcce94fe7b Mon Sep 17 00:00:00 2001 From: Sam Alba Date: Thu, 3 Jun 2021 12:15:36 +0200 Subject: [PATCH 4/6] cmd/doc: added support for text format Signed-off-by: Sam Alba --- cmd/dagger/cmd/doc.go | 97 ++++++++++++++++++++++++++++--------------- 1 file changed, 64 insertions(+), 33 deletions(-) diff --git a/cmd/dagger/cmd/doc.go b/cmd/dagger/cmd/doc.go index f3815ef8..8a9f2674 100644 --- a/cmd/dagger/cmd/doc.go +++ b/cmd/dagger/cmd/doc.go @@ -4,8 +4,10 @@ import ( "context" "fmt" "io/fs" + "os" "regexp" "strings" + "text/tabwriter" "unicode/utf8" "cuelang.org/go/cue" @@ -13,22 +15,24 @@ import ( "github.com/rs/zerolog/log" "github.com/spf13/cobra" "github.com/spf13/viper" + "go.dagger.io/dagger/cmd/dagger/cmd/common" "go.dagger.io/dagger/cmd/dagger/logger" "go.dagger.io/dagger/compiler" "go.dagger.io/dagger/environment" "go.dagger.io/dagger/stdlib" - "golang.org/x/crypto/ssh/terminal" + "golang.org/x/term" ) const ( textFormat = "txt" markdownFormat = "md" + textPadding = " " ) var docCmd = &cobra.Command{ Use: "doc [PACKAGE | PATH]", Short: "document a package", - Args: cobra.MaximumNArgs(1), + Args: cobra.ExactArgs(1), PreRun: func(cmd *cobra.Command, args []string) { // Fix Viper bug for duplicate flags: // https://github.com/spf13/viper/issues/233 @@ -63,28 +67,6 @@ func init() { } } -func extractComment(v cue.Value) string { - docs := []string{} - for _, c := range v.Doc() { - docs = append(docs, strings.TrimSpace(c.Text())) - } - doc := strings.Join(docs, " ") - - lines := strings.Split(doc, "\n") - - // Strip out FIXME, TODO, and INTERNAL comments - docs = []string{} - for _, line := range lines { - if strings.HasPrefix(line, "FIXME: ") || - strings.HasPrefix(line, "TODO: ") || - strings.HasPrefix(line, "INTERNAL: ") { - continue - } - docs = append(docs, line) - } - return strings.Join(docs, " ") -} - func extractSpec(v cue.Value) string { node := v.Source() if node == nil { @@ -110,7 +92,7 @@ func mdEscape(s string) string { func terminalTrim(msg string) string { // If we're not running on a terminal, return the whole string - size, _, err := terminal.GetSize(1) + size, _, err := term.GetSize(1) if err != nil { return msg } @@ -123,6 +105,11 @@ func terminalTrim(msg string) string { return msg } +func formatLabel(name string, val *compiler.Value) string { + label := val.Path().String() + return strings.TrimPrefix(label, name+".") +} + func loadCode(packageName string) (*compiler.Value, error) { sources := map[string]fs.FS{ stdlib.Path: stdlib.FS, @@ -144,10 +131,15 @@ func PrintDoc(ctx context.Context, packageName string, val *compiler.Value, form lg.Fatal().Err(err).Msg("cannot get fields") } + comment := common.ValueDocString(val) + // Print title switch format { case textFormat: - fmt.Printf("Package:\t%s\n", packageName) + fmt.Printf("Package %s\n", packageName) + if comment != "" { + fmt.Printf("\n%s\n", comment) + } case markdownFormat: importPath := strings.Split(packageName, "/") switch { @@ -158,6 +150,9 @@ func PrintDoc(ctx context.Context, packageName string, val *compiler.Value, form default: fmt.Printf("## %s\n", packageName) } + if comment != "" { + fmt.Printf("\n%s\n", comment) + } } for _, field := range fields { @@ -171,22 +166,58 @@ func PrintDoc(ctx context.Context, packageName string, val *compiler.Value, form continue } + // Package name + comment + comment := common.ValueDocString(v) switch format { case textFormat: - comment := extractComment(v.Cue()) - if comment != "" { - comment = fmt.Sprintf(": %s", comment) - } - fmt.Printf("\n=> %s%s\n", name, comment) + fmt.Printf("\n%s\n\n%s%s\n", name, textPadding, comment) case markdownFormat: - comment := extractComment(v.Cue()) if comment != "" { comment = fmt.Sprintf("\n\n%s", comment) } fmt.Printf("\n#### %s%s\n\n", name, mdEscape(comment)) fmt.Printf("##### Fields\n\n") } + + // Package inputs + inp := environment.ScanInputs(ctx, v) + switch format { + case textFormat: + if len(inp) == 0 { + fmt.Printf("\n%sInputs: none\n", textPadding) + break + } + w := tabwriter.NewWriter(os.Stdout, 0, 4, len(textPadding), ' ', 0) + fmt.Printf("\n%sInputs:\n", textPadding) + for _, i := range inp { + docStr := terminalTrim(common.ValueDocString(i)) + fmt.Fprintf(w, "\t\t%s\t%s\t%s\n", + formatLabel(name, i), common.FormatValue(i), docStr) + } + w.Flush() + case markdownFormat: + // todo + } + + // Package outputs + out := environment.ScanOutputs(ctx, v) + switch format { + case textFormat: + if len(out) == 0 { + fmt.Printf("\n%sOutputs: none\n", textPadding) + break + } + w := tabwriter.NewWriter(os.Stdout, 0, 4, len(textPadding), ' ', 0) + fmt.Printf("\n%sOutputs:\n", textPadding) + for _, o := range out { + docStr := terminalTrim(common.ValueDocString(o)) + fmt.Fprintf(w, "\t\t%s\t%s\t%s\n", + formatLabel(name, o), common.FormatValue(o), docStr) + } + w.Flush() + case markdownFormat: + // todo + } } - environment.ScanOutputs(ctx, val) } From dd8aae893cf9383d4f4c41ea2eac318abd85e368 Mon Sep 17 00:00:00 2001 From: Sam Alba Date: Thu, 3 Jun 2021 15:34:46 +0200 Subject: [PATCH 5/6] cmd/doc: added support for markdown Signed-off-by: Sam Alba --- cmd/dagger/cmd/doc.go | 116 ++++++++++++++++++++---------------------- 1 file changed, 56 insertions(+), 60 deletions(-) diff --git a/cmd/dagger/cmd/doc.go b/cmd/dagger/cmd/doc.go index 8a9f2674..da5db49f 100644 --- a/cmd/dagger/cmd/doc.go +++ b/cmd/dagger/cmd/doc.go @@ -5,13 +5,11 @@ import ( "fmt" "io/fs" "os" - "regexp" "strings" "text/tabwriter" "unicode/utf8" "cuelang.org/go/cue" - "cuelang.org/go/cue/format" "github.com/rs/zerolog/log" "github.com/spf13/cobra" "github.com/spf13/viper" @@ -67,21 +65,6 @@ func init() { } } -func extractSpec(v cue.Value) string { - node := v.Source() - if node == nil { - return fmt.Sprintf("%v", v) - } - src, err := format.Node(node) - if err != nil { - panic(err) - } - space := regexp.MustCompile(`[\s\n]+`) - return strings.TrimSpace( - space.ReplaceAllString(string(src), " "), - ) -} - func mdEscape(s string) string { escape := []string{"|", "<", ">"} for _, c := range escape { @@ -123,6 +106,33 @@ func loadCode(packageName string) (*compiler.Value, error) { return src, nil } +// printValuesText (text) formats an array of Values on stdout +func printValuesText(libName string, values []*compiler.Value) { + w := tabwriter.NewWriter(os.Stdout, 0, 4, len(textPadding), ' ', 0) + fmt.Printf("\n%sInputs:\n", textPadding) + for _, i := range values { + docStr := terminalTrim(common.ValueDocString(i)) + fmt.Fprintf(w, "\t\t%s\t%s\t%s\n", + formatLabel(libName, i), common.FormatValue(i), docStr) + } + w.Flush() +} + +// printValuesMarkdown (markdown) formats an array of Values on stdout +func printValuesMarkdown(libName string, values []*compiler.Value) { + w := tabwriter.NewWriter(os.Stdout, 0, 4, len(textPadding), ' ', 0) + fmt.Fprintf(w, "| Name\t| Type\t| Description \t|\n") + fmt.Fprintf(w, "| -------------\t|:-------------:\t|:-------------:\t|\n") + for _, i := range values { + fmt.Fprintf(w, "|*%s*\t|``%s``\t|%s\t|\n", + formatLabel(libName, i), + mdEscape(common.FormatValue(i)), + mdEscape(common.ValueDocString(i))) + } + fmt.Fprintln(w) + w.Flush() +} + func PrintDoc(ctx context.Context, packageName string, val *compiler.Value, format string) { lg := log.Ctx(ctx) @@ -131,55 +141,45 @@ func PrintDoc(ctx context.Context, packageName string, val *compiler.Value, form lg.Fatal().Err(err).Msg("cannot get fields") } - comment := common.ValueDocString(val) - // Print title switch format { case textFormat: fmt.Printf("Package %s\n", packageName) - if comment != "" { - fmt.Printf("\n%s\n", comment) - } + fmt.Printf("\n%s\n\n", common.ValueDocString(val)) case markdownFormat: - importPath := strings.Split(packageName, "/") - switch { - case len(importPath) == 2: - fmt.Printf("## %s\n", importPath[1]) - case len(importPath) > 2: - fmt.Printf("### %s\n", strings.Join(importPath[2:], "/")) - default: - fmt.Printf("## %s\n", packageName) - } - if comment != "" { - fmt.Printf("\n%s\n", comment) + fmt.Printf("## Package %s\n", mdEscape(packageName)) + comment := common.ValueDocString(val) + if comment == "-" { + fmt.Println() + break } + fmt.Printf("\n%s\n\n", mdEscape(comment)) } + // Package Fields for _, field := range fields { if !field.Selector.IsDefinition() { + // not a definition, skipping continue } name := field.Label() v := field.Value if v.Cue().IncompleteKind() != cue.StructKind { + // not a struct, skipping continue } - // Package name + comment + // Package Name + Comment comment := common.ValueDocString(v) switch format { case textFormat: - fmt.Printf("\n%s\n\n%s%s\n", name, textPadding, comment) + fmt.Printf("%s\n\n%s%s\n", name, textPadding, comment) case markdownFormat: - if comment != "" { - comment = fmt.Sprintf("\n\n%s", comment) - } - fmt.Printf("\n#### %s%s\n\n", name, mdEscape(comment)) - fmt.Printf("##### Fields\n\n") + fmt.Printf("### %s\n\n%s\n\n", name, mdEscape(comment)) } - // Package inputs + // Inputs inp := environment.ScanInputs(ctx, v) switch format { case textFormat: @@ -187,19 +187,17 @@ func PrintDoc(ctx context.Context, packageName string, val *compiler.Value, form fmt.Printf("\n%sInputs: none\n", textPadding) break } - w := tabwriter.NewWriter(os.Stdout, 0, 4, len(textPadding), ' ', 0) - fmt.Printf("\n%sInputs:\n", textPadding) - for _, i := range inp { - docStr := terminalTrim(common.ValueDocString(i)) - fmt.Fprintf(w, "\t\t%s\t%s\t%s\n", - formatLabel(name, i), common.FormatValue(i), docStr) - } - w.Flush() + printValuesText(name, inp) case markdownFormat: - // todo + fmt.Printf("#### %s Inputs\n\n", mdEscape(name)) + if len(inp) == 0 { + fmt.Printf("_No input._\n\n") + break + } + printValuesMarkdown(name, inp) } - // Package outputs + // Outputs out := environment.ScanOutputs(ctx, v) switch format { case textFormat: @@ -207,16 +205,14 @@ func PrintDoc(ctx context.Context, packageName string, val *compiler.Value, form fmt.Printf("\n%sOutputs: none\n", textPadding) break } - w := tabwriter.NewWriter(os.Stdout, 0, 4, len(textPadding), ' ', 0) - fmt.Printf("\n%sOutputs:\n", textPadding) - for _, o := range out { - docStr := terminalTrim(common.ValueDocString(o)) - fmt.Fprintf(w, "\t\t%s\t%s\t%s\n", - formatLabel(name, o), common.FormatValue(o), docStr) - } - w.Flush() + printValuesText(name, out) case markdownFormat: - // todo + fmt.Printf("#### %s Outputs\n\n", mdEscape(name)) + if len(out) == 0 { + fmt.Printf("_No output._\n\n") + break + } + printValuesMarkdown(name, out) } } From 3b92f70416e56cbedee121c3c3f0bc4d4906347e Mon Sep 17 00:00:00 2001 From: Sam Alba Date: Thu, 3 Jun 2021 17:40:27 +0200 Subject: [PATCH 6/6] cmd/doc: added support for json Signed-off-by: Sam Alba --- cmd/dagger/cmd/doc.go | 84 +++++++++++++++++++++++++++++++++++++++---- 1 file changed, 77 insertions(+), 7 deletions(-) diff --git a/cmd/dagger/cmd/doc.go b/cmd/dagger/cmd/doc.go index da5db49f..859ed897 100644 --- a/cmd/dagger/cmd/doc.go +++ b/cmd/dagger/cmd/doc.go @@ -2,6 +2,7 @@ package cmd import ( "context" + "encoding/json" "fmt" "io/fs" "os" @@ -24,9 +25,31 @@ import ( const ( textFormat = "txt" markdownFormat = "md" + jsonFormat = "json" textPadding = " " ) +// types used for json generation + +type ValueJSON struct { + Name string + Type string + Description string +} + +type FieldJSON struct { + Name string + Description string + Inputs []ValueJSON + Outputs []ValueJSON +} + +type PackageJSON struct { + Name string + Description string + Fields []FieldJSON +} + var docCmd = &cobra.Command{ Use: "doc [PACKAGE | PATH]", Short: "document a package", @@ -43,8 +66,10 @@ var docCmd = &cobra.Command{ ctx := lg.WithContext(cmd.Context()) format := viper.GetString("output") - if format != textFormat && format != markdownFormat { - lg.Fatal().Msg("output must be either `txt` or `md`") + if format != textFormat && + format != markdownFormat && + format != jsonFormat { + lg.Fatal().Msg("output must be either `txt`, `md` or `json`") } packageName := args[0] @@ -133,6 +158,21 @@ func printValuesMarkdown(libName string, values []*compiler.Value) { w.Flush() } +// printValuesJson fills a struct for json output +func valuesToJSON(libName string, values []*compiler.Value) []ValueJSON { + val := []ValueJSON{} + + for _, i := range values { + v := ValueJSON{} + v.Name = formatLabel(libName, i) + v.Type = common.FormatValue(i) + v.Description = common.ValueDocString(i) + val = append(val, v) + } + + return val +} + func PrintDoc(ctx context.Context, packageName string, val *compiler.Value, format string) { lg := log.Ctx(ctx) @@ -141,11 +181,12 @@ func PrintDoc(ctx context.Context, packageName string, val *compiler.Value, form lg.Fatal().Err(err).Msg("cannot get fields") } - // Print title + packageJSON := &PackageJSON{} + // Package Name + Description switch format { case textFormat: fmt.Printf("Package %s\n", packageName) - fmt.Printf("\n%s\n\n", common.ValueDocString(val)) + fmt.Printf("\n%s\n", common.ValueDocString(val)) case markdownFormat: fmt.Printf("## Package %s\n", mdEscape(packageName)) comment := common.ValueDocString(val) @@ -154,10 +195,18 @@ func PrintDoc(ctx context.Context, packageName string, val *compiler.Value, form break } fmt.Printf("\n%s\n\n", mdEscape(comment)) + case jsonFormat: + packageJSON.Name = packageName + comment := common.ValueDocString(val) + if comment != "" { + packageJSON.Description = comment + } } // Package Fields for _, field := range fields { + fieldJSON := FieldJSON{} + if !field.Selector.IsDefinition() { // not a definition, skipping continue @@ -170,13 +219,22 @@ func PrintDoc(ctx context.Context, packageName string, val *compiler.Value, form continue } - // Package Name + Comment + // Field Name + Description comment := common.ValueDocString(v) switch format { case textFormat: - fmt.Printf("%s\n\n%s%s\n", name, textPadding, comment) + fmt.Printf("\n%s\n\n%s%s\n", name, textPadding, comment) case markdownFormat: - fmt.Printf("### %s\n\n%s\n\n", name, mdEscape(comment)) + fmt.Printf("### %s\n\n", name) + if comment != "-" { + fmt.Printf("%s\n\n", mdEscape(comment)) + } + case jsonFormat: + fieldJSON.Name = name + comment := common.ValueDocString(val) + if comment != "" { + fieldJSON.Description = comment + } } // Inputs @@ -195,6 +253,8 @@ func PrintDoc(ctx context.Context, packageName string, val *compiler.Value, form break } printValuesMarkdown(name, inp) + case jsonFormat: + fieldJSON.Inputs = valuesToJSON(name, inp) } // Outputs @@ -213,7 +273,17 @@ func PrintDoc(ctx context.Context, packageName string, val *compiler.Value, form break } printValuesMarkdown(name, out) + case jsonFormat: + fieldJSON.Outputs = valuesToJSON(name, out) + packageJSON.Fields = append(packageJSON.Fields, fieldJSON) } } + if format == jsonFormat { + data, err := json.MarshalIndent(packageJSON, "", " ") + if err != nil { + lg.Fatal().Err(err).Msg("json marshal") + } + fmt.Printf("%s\n", data) + } }