From fcc34a7250563a77f8e02640cd0e04553e1ee306 Mon Sep 17 00:00:00 2001 From: gfanton <8671905+gfanton@users.noreply.github.com> Date: Tue, 25 Jun 2024 14:48:07 +0200 Subject: [PATCH 1/3] feat: add config helper Signed-off-by: gfanton <8671905+gfanton@users.noreply.github.com> --- gno.land/cmd/gnoland/config_get.go | 13 +++ gno.land/cmd/gnoland/config_help.go | 123 ++++++++++++++++++++++++++++ gno.land/cmd/gnoland/config_set.go | 12 +++ gno.land/cmd/gnoland/secrets_get.go | 12 +++ 4 files changed, 160 insertions(+) create mode 100644 gno.land/cmd/gnoland/config_help.go diff --git a/gno.land/cmd/gnoland/config_get.go b/gno.land/cmd/gnoland/config_get.go index 1fd4027ec60..83c77110811 100644 --- a/gno.land/cmd/gnoland/config_get.go +++ b/gno.land/cmd/gnoland/config_get.go @@ -36,6 +36,19 @@ func newConfigGetCmd(io commands.IO) *commands.Command { }, ) + // Add subcommand helpers + helperGen := metadataHelperGenerator{ + MetaUpdate: func(meta *commands.Metadata) { + meta.ShortUsage = "config get " + meta.Name + }, + TagNameSelector: "json", + TreeDisplay: true, + } + subs := generateSubCommandHelper(helperGen, config.Config{}, func(_ context.Context, args []string) error { + return execConfigGet(cfg, io, args) + }) + + cmd.AddSubCommands(subs...) return cmd } diff --git a/gno.land/cmd/gnoland/config_help.go b/gno.land/cmd/gnoland/config_help.go new file mode 100644 index 00000000000..381c7233e4a --- /dev/null +++ b/gno.land/cmd/gnoland/config_help.go @@ -0,0 +1,123 @@ +package main + +import ( + "context" + "fmt" + "reflect" + "strings" + "unicode" + + "github.com/gnolang/gno/tm2/pkg/commands" +) + +type metadataHelperGenerator struct { + // Optional callback to edit metadata + MetaUpdate func(*commands.Metadata) + // Tag to select for name, if empty will use the field Name + TagNameSelector string + // Will display description with tree representation + TreeDisplay bool +} + +func generateSubCommandHelper(gen metadataHelperGenerator, s any, exec commands.ExecMethod) []*commands.Command { + rv := reflect.ValueOf(s) + metas := gen.generateFields(rv, "", 0) + + cmds := make([]*commands.Command, len(metas)) + for i := 0; i < len(metas); i++ { + meta := metas[i] + exec := func(ctx context.Context, args []string) error { + args = append([]string{meta.Name}, args...) + return exec(ctx, args) + } + cmds[i] = commands.NewCommand(meta, nil, exec) + } + + return cmds +} + +func (g *metadataHelperGenerator) generateFields(rv reflect.Value, parent string, depth int) []commands.Metadata { + if parent != "" { + parent += "." + } + + // Unwrap pointer if needed + if rv.Kind() == reflect.Ptr { + if rv.IsNil() { + // Create a new non-nil instance of the original type that was nil + rv = reflect.New(rv.Type().Elem()) + } + rv = rv.Elem() // Dereference to struct value + } + + metas := []commands.Metadata{} + if rv.Kind() != reflect.Struct { + return metas + } + + rt := rv.Type() + for i := 0; i < rv.NumField(); i++ { + field := rt.Field(i) + if !field.IsExported() { + continue + } + + fieldValue := rv.Field(i) + name := field.Name + // Get JSON tag name + if g.TagNameSelector != "" { + name, _, _ = strings.Cut(field.Tag.Get(g.TagNameSelector), ",") + if name == "" || name == "-" { + continue + } + } + + // Recursive call for nested struct + var childs []commands.Metadata + if k := fieldValue.Kind(); k == reflect.Ptr || k == reflect.Struct { + childs = g.generateFields(fieldValue, name, depth+1) + } + + // Generate metadata + var meta commands.Metadata + + // Name + meta.Name = parent + name + + // Create a tree-like display to see nested field + if g.TreeDisplay && depth > 0 { + meta.ShortHelp += strings.Repeat(" ", depth*2) + if i == rv.NumField()-1 { + meta.ShortHelp += "└─" + } else { + meta.ShortHelp += "├─" + } + } + meta.ShortHelp += fmt.Sprintf("<%s>", field.Type) + + // Get Short/Long Help Message from comment tag + comment := field.Tag.Get("comment") + comment = strings.TrimFunc(comment, func(r rune) bool { + return unicode.IsSpace(r) || r == '#' + }) + + if comment != "" { + // Use the first line as short help + meta.ShortHelp += " " + meta.ShortHelp += strings.Split(comment, "\n")[0] + + // Display full comment as Long Help + meta.LongHelp = comment + } + + if g.MetaUpdate != nil { + g.MetaUpdate(&meta) + } + + metas = append(metas, meta) + metas = append(metas, childs...) + + } + + return metas +} diff --git a/gno.land/cmd/gnoland/config_set.go b/gno.land/cmd/gnoland/config_set.go index dd171970bf6..7a8d810aa69 100644 --- a/gno.land/cmd/gnoland/config_set.go +++ b/gno.land/cmd/gnoland/config_set.go @@ -34,6 +34,18 @@ func newConfigSetCmd(io commands.IO) *commands.Command { }, ) + // Add subcommand helpers + helperGen := metadataHelperGenerator{ + MetaUpdate: func(meta *commands.Metadata) { + meta.ShortUsage = fmt.Sprintf("config set %s ", meta.Name) + }, + TagNameSelector: "json", + TreeDisplay: true, + } + cmd.AddSubCommands(generateSubCommandHelper(helperGen, config.Config{}, func(_ context.Context, args []string) error { + return execConfigEdit(cfg, io, args) + })...) + return cmd } diff --git a/gno.land/cmd/gnoland/secrets_get.go b/gno.land/cmd/gnoland/secrets_get.go index 47de7a46283..b8ccd2f6fa4 100644 --- a/gno.land/cmd/gnoland/secrets_get.go +++ b/gno.land/cmd/gnoland/secrets_get.go @@ -41,6 +41,18 @@ func newSecretsGetCmd(io commands.IO) *commands.Command { }, ) + // Add subcommand helpers + helperGen := metadataHelperGenerator{ + MetaUpdate: func(meta *commands.Metadata) { + meta.ShortUsage = "secrets get " + meta.Name + }, + TagNameSelector: "json", + TreeDisplay: false, + } + cmd.AddSubCommands(generateSubCommandHelper(helperGen, secrets{}, func(_ context.Context, args []string) error { + return execSecretsGet(cfg, args, io) + })...) + return cmd } From 50b1434fa4a77f1aa449f6ee19f25a4de2b4d145 Mon Sep 17 00:00:00 2001 From: gfanton <8671905+gfanton@users.noreply.github.com> Date: Tue, 25 Jun 2024 14:50:53 +0200 Subject: [PATCH 2/3] chore: lint Signed-off-by: gfanton <8671905+gfanton@users.noreply.github.com> --- gno.land/cmd/gnoland/config_help.go | 1 - 1 file changed, 1 deletion(-) diff --git a/gno.land/cmd/gnoland/config_help.go b/gno.land/cmd/gnoland/config_help.go index 381c7233e4a..9005fed06b6 100644 --- a/gno.land/cmd/gnoland/config_help.go +++ b/gno.land/cmd/gnoland/config_help.go @@ -116,7 +116,6 @@ func (g *metadataHelperGenerator) generateFields(rv reflect.Value, parent string metas = append(metas, meta) metas = append(metas, childs...) - } return metas From 25b3dc37c2fe2f0142d9dadb9c3f656ee689d1c8 Mon Sep 17 00:00:00 2001 From: gfanton <8671905+gfanton@users.noreply.github.com> Date: Wed, 7 Aug 2024 11:00:40 +0200 Subject: [PATCH 3/3] chore: add comments & type display for getter/setter Signed-off-by: gfanton <8671905+gfanton@users.noreply.github.com> --- gno.land/cmd/gnoland/config_get.go | 4 ++-- gno.land/cmd/gnoland/config_help.go | 9 +++++++-- gno.land/cmd/gnoland/config_set.go | 4 ++-- gno.land/cmd/gnoland/secrets_get.go | 4 ++-- 4 files changed, 13 insertions(+), 8 deletions(-) diff --git a/gno.land/cmd/gnoland/config_get.go b/gno.land/cmd/gnoland/config_get.go index 83c77110811..796ae9da5e9 100644 --- a/gno.land/cmd/gnoland/config_get.go +++ b/gno.land/cmd/gnoland/config_get.go @@ -38,8 +38,8 @@ func newConfigGetCmd(io commands.IO) *commands.Command { // Add subcommand helpers helperGen := metadataHelperGenerator{ - MetaUpdate: func(meta *commands.Metadata) { - meta.ShortUsage = "config get " + meta.Name + MetaUpdate: func(meta *commands.Metadata, inputType string) { + meta.ShortUsage = fmt.Sprintf("config get %s <%s>", meta.Name, inputType) }, TagNameSelector: "json", TreeDisplay: true, diff --git a/gno.land/cmd/gnoland/config_help.go b/gno.land/cmd/gnoland/config_help.go index 9005fed06b6..97d43953bba 100644 --- a/gno.land/cmd/gnoland/config_help.go +++ b/gno.land/cmd/gnoland/config_help.go @@ -12,13 +12,14 @@ import ( type metadataHelperGenerator struct { // Optional callback to edit metadata - MetaUpdate func(*commands.Metadata) + MetaUpdate func(meta *commands.Metadata, inputType string) // Tag to select for name, if empty will use the field Name TagNameSelector string // Will display description with tree representation TreeDisplay bool } +// generateSubCommandHelper generates subcommands based on `s` structure fields and their respective tag descriptions func generateSubCommandHelper(gen metadataHelperGenerator, s any, exec commands.ExecMethod) []*commands.Command { rv := reflect.ValueOf(s) metas := gen.generateFields(rv, "", 0) @@ -108,10 +109,14 @@ func (g *metadataHelperGenerator) generateFields(rv reflect.Value, parent string // Display full comment as Long Help meta.LongHelp = comment + } else { + // If the comment is empty, it mostly means that there is no help. + // Use a blank space to avoid falling back on short help. + meta.LongHelp = " " } if g.MetaUpdate != nil { - g.MetaUpdate(&meta) + g.MetaUpdate(&meta, field.Type.String()) } metas = append(metas, meta) diff --git a/gno.land/cmd/gnoland/config_set.go b/gno.land/cmd/gnoland/config_set.go index 7a8d810aa69..de96aa35c7d 100644 --- a/gno.land/cmd/gnoland/config_set.go +++ b/gno.land/cmd/gnoland/config_set.go @@ -36,8 +36,8 @@ func newConfigSetCmd(io commands.IO) *commands.Command { // Add subcommand helpers helperGen := metadataHelperGenerator{ - MetaUpdate: func(meta *commands.Metadata) { - meta.ShortUsage = fmt.Sprintf("config set %s ", meta.Name) + MetaUpdate: func(meta *commands.Metadata, inputType string) { + meta.ShortUsage = fmt.Sprintf("config set %s <%s>", meta.Name, inputType) }, TagNameSelector: "json", TreeDisplay: true, diff --git a/gno.land/cmd/gnoland/secrets_get.go b/gno.land/cmd/gnoland/secrets_get.go index b8ccd2f6fa4..8d111516816 100644 --- a/gno.land/cmd/gnoland/secrets_get.go +++ b/gno.land/cmd/gnoland/secrets_get.go @@ -43,8 +43,8 @@ func newSecretsGetCmd(io commands.IO) *commands.Command { // Add subcommand helpers helperGen := metadataHelperGenerator{ - MetaUpdate: func(meta *commands.Metadata) { - meta.ShortUsage = "secrets get " + meta.Name + MetaUpdate: func(meta *commands.Metadata, inputType string) { + meta.ShortUsage = fmt.Sprintf("secrets get %s <%s>", meta.Name, inputType) }, TagNameSelector: "json", TreeDisplay: false,