From 6bba227654a9b756d128c576a9cbaf0e474032be Mon Sep 17 00:00:00 2001 From: lew Date: Wed, 11 Feb 2026 23:37:52 +0000 Subject: [PATCH] feat(config): add config get subcommand with suggestions --- cmd/config_cmd.go | 23 +++++++++++++++++++++++ cmd/config_fields.go | 31 ++++++++++++++++++++++++++++++- testdata/config-get.ct | 13 +++++++++++++ 3 files changed, 66 insertions(+), 1 deletion(-) create mode 100644 testdata/config-get.ct diff --git a/cmd/config_cmd.go b/cmd/config_cmd.go index 2e1423c..5ab24d8 100644 --- a/cmd/config_cmd.go +++ b/cmd/config_cmd.go @@ -2,6 +2,7 @@ package cmd import ( "fmt" + "strings" "github.com/spf13/cobra" ) @@ -27,6 +28,27 @@ var configListCmd = &cobra.Command{ }, } +var configGetCmd = &cobra.Command{ + Use: "get ", + Short: "Print a configuration value", + Args: cobra.ExactArgs(1), + SilenceUsage: true, + RunE: func(cmd *cobra.Command, args []string) error { + defaults := defaultConfig() + fields := configFields(&config, &defaults) + f := findConfigField(fields, args[0]) + if f == nil { + err := fmt.Errorf("unknown config key '%s'", args[0]) + if suggestions := suggestConfigKey(fields, args[0]); len(suggestions) > 0 { + return withHint(err, fmt.Sprintf("did you mean '%s'?", strings.Join(suggestions, "', '"))) + } + return err + } + fmt.Printf("%v\n", f.Value) + return nil + }, +} + var configPathCmd = &cobra.Command{ Use: "path", Short: "Print config file path", @@ -43,6 +65,7 @@ var configPathCmd = &cobra.Command{ } func init() { + configCmd.AddCommand(configGetCmd) configCmd.AddCommand(configListCmd) configCmd.AddCommand(configPathCmd) rootCmd.AddCommand(configCmd) diff --git a/cmd/config_fields.go b/cmd/config_fields.go index 6627cec..5b94f79 100644 --- a/cmd/config_fields.go +++ b/cmd/config_fields.go @@ -1,6 +1,10 @@ package cmd -import "reflect" +import ( + "reflect" + + "github.com/agnivade/levenshtein" +) // ConfigField represents a single leaf field in the Config struct, // mapped to its dotted TOML key path. @@ -53,3 +57,28 @@ func walk(cv, dv reflect.Value, prefix string, out *[]ConfigField) { }) } } + +// findConfigField returns the ConfigField matching the given dotted key, +// or nil if not found. +func findConfigField(fields []ConfigField, key string) *ConfigField { + for i := range fields { + if fields[i].Key == key { + return &fields[i] + } + } + return nil +} + +// suggestConfigKey returns Levenshtein-based suggestions for a mistyped config key. +func suggestConfigKey(fields []ConfigField, target string) []string { + minThreshold := 1 + maxThreshold := 4 + threshold := min(max(len(target)/3, minThreshold), maxThreshold) + var suggestions []string + for _, f := range fields { + if levenshtein.ComputeDistance(target, f.Key) <= threshold { + suggestions = append(suggestions, f.Key) + } + } + return suggestions +} diff --git a/testdata/config-get.ct b/testdata/config-get.ct new file mode 100644 index 0000000..a4e7f4e --- /dev/null +++ b/testdata/config-get.ct @@ -0,0 +1,13 @@ +$ pda config get display_ascii_art +true + +$ pda config get store.default_store_name +default + +$ pda config get git.auto_commit +false + +# Unknown key with suggestion +$ pda config get git.auto_comit --> FAIL +FAIL unknown config key 'git.auto_comit' +hint did you mean 'git.auto_commit'?