Merge pull request #547 from samalba/cmd-doc
doc cmd: generate stdlib doc
This commit is contained in:
commit
92ceae9ce6
@ -110,7 +110,7 @@ func FormatValue(val *compiler.Value) string {
|
|||||||
return "dagger.#Secret"
|
return "dagger.#Secret"
|
||||||
}
|
}
|
||||||
if val.IsConcreteR() != nil {
|
if val.IsConcreteR() != nil {
|
||||||
return val.Cue().IncompleteKind().String()
|
return val.IncompleteKindString()
|
||||||
}
|
}
|
||||||
// value representation in Cue
|
// value representation in Cue
|
||||||
valStr := fmt.Sprintf("%v", val.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
|
// ValueDocString returns the value doc from the comment lines
|
||||||
func ValueDocString(val *compiler.Value) string {
|
func ValueDocString(val *compiler.Value) string {
|
||||||
docs := []string{}
|
docs := []string{}
|
||||||
for _, c := range val.Cue().Doc() {
|
for _, c := range val.Doc() {
|
||||||
docs = append(docs, strings.TrimSpace(c.Text()))
|
docs = append(docs, strings.TrimSpace(c.Text()))
|
||||||
}
|
}
|
||||||
doc := strings.Join(docs, " ")
|
doc := strings.Join(docs, " ")
|
||||||
|
289
cmd/dagger/cmd/doc.go
Normal file
289
cmd/dagger/cmd/doc.go
Normal file
@ -0,0 +1,289 @@
|
|||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io/fs"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"text/tabwriter"
|
||||||
|
"unicode/utf8"
|
||||||
|
|
||||||
|
"cuelang.org/go/cue"
|
||||||
|
"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/term"
|
||||||
|
)
|
||||||
|
|
||||||
|
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",
|
||||||
|
Args: cobra.ExactArgs(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())
|
||||||
|
|
||||||
|
format := viper.GetString("output")
|
||||||
|
if format != textFormat &&
|
||||||
|
format != markdownFormat &&
|
||||||
|
format != jsonFormat {
|
||||||
|
lg.Fatal().Msg("output must be either `txt`, `md` or `json`")
|
||||||
|
}
|
||||||
|
|
||||||
|
packageName := args[0]
|
||||||
|
|
||||||
|
val, err := loadCode(packageName)
|
||||||
|
if err != nil {
|
||||||
|
lg.Fatal().Err(err).Msg("cannot compile code")
|
||||||
|
}
|
||||||
|
PrintDoc(ctx, packageName, 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 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 := term.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 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,
|
||||||
|
}
|
||||||
|
|
||||||
|
src, err := compiler.Build(sources, packageName)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
|
||||||
|
fields, err := val.Fields(cue.Definitions(true))
|
||||||
|
if err != nil {
|
||||||
|
lg.Fatal().Err(err).Msg("cannot get fields")
|
||||||
|
}
|
||||||
|
|
||||||
|
packageJSON := &PackageJSON{}
|
||||||
|
// Package Name + Description
|
||||||
|
switch format {
|
||||||
|
case textFormat:
|
||||||
|
fmt.Printf("Package %s\n", packageName)
|
||||||
|
fmt.Printf("\n%s\n", common.ValueDocString(val))
|
||||||
|
case markdownFormat:
|
||||||
|
fmt.Printf("## Package %s\n", mdEscape(packageName))
|
||||||
|
comment := common.ValueDocString(val)
|
||||||
|
if comment == "-" {
|
||||||
|
fmt.Println()
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
name := field.Label()
|
||||||
|
v := field.Value
|
||||||
|
if v.Cue().IncompleteKind() != cue.StructKind {
|
||||||
|
// not a struct, skipping
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Field Name + Description
|
||||||
|
comment := common.ValueDocString(v)
|
||||||
|
switch format {
|
||||||
|
case textFormat:
|
||||||
|
fmt.Printf("\n%s\n\n%s%s\n", name, textPadding, comment)
|
||||||
|
case markdownFormat:
|
||||||
|
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
|
||||||
|
inp := environment.ScanInputs(ctx, v)
|
||||||
|
switch format {
|
||||||
|
case textFormat:
|
||||||
|
if len(inp) == 0 {
|
||||||
|
fmt.Printf("\n%sInputs: none\n", textPadding)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
printValuesText(name, inp)
|
||||||
|
case markdownFormat:
|
||||||
|
fmt.Printf("#### %s Inputs\n\n", mdEscape(name))
|
||||||
|
if len(inp) == 0 {
|
||||||
|
fmt.Printf("_No input._\n\n")
|
||||||
|
break
|
||||||
|
}
|
||||||
|
printValuesMarkdown(name, inp)
|
||||||
|
case jsonFormat:
|
||||||
|
fieldJSON.Inputs = valuesToJSON(name, inp)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Outputs
|
||||||
|
out := environment.ScanOutputs(ctx, v)
|
||||||
|
switch format {
|
||||||
|
case textFormat:
|
||||||
|
if len(out) == 0 {
|
||||||
|
fmt.Printf("\n%sOutputs: none\n", textPadding)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
printValuesText(name, out)
|
||||||
|
case markdownFormat:
|
||||||
|
fmt.Printf("#### %s Outputs\n\n", mdEscape(name))
|
||||||
|
if len(out) == 0 {
|
||||||
|
fmt.Printf("_No output._\n\n")
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
@ -57,15 +57,6 @@ var listCmd = &cobra.Command{
|
|||||||
for _, inp := range inputs {
|
for _, inp := range inputs {
|
||||||
isConcrete := (inp.IsConcreteR() == nil)
|
isConcrete := (inp.IsConcreteR() == nil)
|
||||||
_, hasDefault := inp.Default()
|
_, 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") {
|
if !viper.GetBool("all") {
|
||||||
// skip input that is not overridable
|
// skip input that is not overridable
|
||||||
|
@ -46,6 +46,7 @@ func init() {
|
|||||||
input.Cmd,
|
input.Cmd,
|
||||||
output.Cmd,
|
output.Cmd,
|
||||||
versionCmd,
|
versionCmd,
|
||||||
|
docCmd,
|
||||||
)
|
)
|
||||||
|
|
||||||
if err := viper.BindPFlags(rootCmd.PersistentFlags()); err != nil {
|
if err := viper.BindPFlags(rootCmd.PersistentFlags()); err != nil {
|
||||||
|
@ -5,6 +5,7 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"cuelang.org/go/cue"
|
"cuelang.org/go/cue"
|
||||||
|
"cuelang.org/go/cue/ast"
|
||||||
cueformat "cuelang.org/go/cue/format"
|
cueformat "cuelang.org/go/cue/format"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -75,8 +76,8 @@ func (f Field) Label() string {
|
|||||||
|
|
||||||
// Proxy function to the underlying cue.Value
|
// Proxy function to the underlying cue.Value
|
||||||
// Field ordering is guaranteed to be stable.
|
// Field ordering is guaranteed to be stable.
|
||||||
func (v *Value) Fields() ([]Field, error) {
|
func (v *Value) Fields(opts ...cue.Option) ([]Field, error) {
|
||||||
it, err := v.val.Fields()
|
it, err := v.val.Fields(opts...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -270,3 +271,11 @@ func (v *Value) Default() (*Value, bool) {
|
|||||||
val, hasDef := v.val.Default()
|
val, hasDef := v.val.Default()
|
||||||
return v.cc.Wrap(val), hasDef
|
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()
|
||||||
|
}
|
||||||
|
@ -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) {
|
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
|
||||||
}
|
}
|
||||||
|
@ -42,7 +42,7 @@ func isReference(val cue.Value) bool {
|
|||||||
return isRef(val)
|
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)
|
lg := log.Ctx(ctx)
|
||||||
inputs := []*compiler.Value{}
|
inputs := []*compiler.Value{}
|
||||||
|
|
||||||
@ -67,7 +67,7 @@ func scanInputs(ctx context.Context, value *compiler.Value) []*compiler.Value {
|
|||||||
return inputs
|
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)
|
lg := log.Ctx(ctx)
|
||||||
inputs := []*compiler.Value{}
|
inputs := []*compiler.Value{}
|
||||||
|
|
||||||
|
1
go.mod
1
go.mod
@ -30,6 +30,7 @@ require (
|
|||||||
github.com/uber/jaeger-lib v2.4.1+incompatible // indirect
|
github.com/uber/jaeger-lib v2.4.1+incompatible // indirect
|
||||||
go.mozilla.org/sops/v3 v3.7.1
|
go.mozilla.org/sops/v3 v3.7.1
|
||||||
go.uber.org/atomic v1.7.0 // indirect
|
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/net v0.0.0-20210331212208-0fccb6fa2b5c // indirect
|
||||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9
|
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1
|
||||||
|
Reference in New Issue
Block a user