255 lines
6.5 KiB
Go
255 lines
6.5 KiB
Go
package cmd
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"reflect"
|
|
"strings"
|
|
|
|
"github.com/BurntSushi/toml"
|
|
"github.com/spf13/cobra"
|
|
)
|
|
|
|
var configCmd = &cobra.Command{
|
|
Use: "config",
|
|
Short: "View and modify configuration",
|
|
}
|
|
|
|
var configListCmd = &cobra.Command{
|
|
Use: "list",
|
|
Aliases: []string{"ls"},
|
|
Short: "List all configuration values",
|
|
Args: cobra.NoArgs,
|
|
SilenceUsage: true,
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
defaults := defaultConfig()
|
|
fields := configFields(&config, &defaults)
|
|
for _, f := range fields {
|
|
fmt.Printf("%s = %v\n", f.Key, f.Value)
|
|
}
|
|
return nil
|
|
},
|
|
}
|
|
|
|
var configGetCmd = &cobra.Command{
|
|
Use: "get <key>",
|
|
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",
|
|
Args: cobra.NoArgs,
|
|
SilenceUsage: true,
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
p, err := configPath()
|
|
if err != nil {
|
|
return fmt.Errorf("cannot determine config path: %w", err)
|
|
}
|
|
fmt.Println(p)
|
|
return nil
|
|
},
|
|
}
|
|
|
|
var configEditCmd = &cobra.Command{
|
|
Use: "edit",
|
|
Short: "Open config file in $EDITOR",
|
|
Args: cobra.NoArgs,
|
|
SilenceUsage: true,
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
editor := os.Getenv("EDITOR")
|
|
if editor == "" {
|
|
return withHint(
|
|
fmt.Errorf("EDITOR not set"),
|
|
"set $EDITOR to your preferred text editor",
|
|
)
|
|
}
|
|
p, err := configPath()
|
|
if err != nil {
|
|
return fmt.Errorf("cannot determine config path: %w", err)
|
|
}
|
|
// Create default config if file doesn't exist
|
|
if _, err := os.Stat(p); os.IsNotExist(err) {
|
|
if err := writeConfigFile(defaultConfig()); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
c := exec.Command(editor, p)
|
|
c.Stdin = os.Stdin
|
|
c.Stdout = os.Stdout
|
|
c.Stderr = os.Stderr
|
|
if err := c.Run(); err != nil {
|
|
return err
|
|
}
|
|
|
|
cfg, undecoded, migrations, err := loadConfig()
|
|
for _, m := range migrations {
|
|
if m.Conflict {
|
|
warnf("both '%s' and '%s' present; using '%s'", m.Old, m.New, m.New)
|
|
} else {
|
|
warnf("config key '%s' is deprecated, use '%s'", m.Old, m.New)
|
|
}
|
|
}
|
|
if err != nil {
|
|
warnf("config has errors: %v", err)
|
|
printHint("re-run 'pda config edit' to fix")
|
|
return nil
|
|
}
|
|
if len(undecoded) > 0 {
|
|
warnf("unrecognised key(s) will be ignored: %s", strings.Join(undecoded, ", "))
|
|
}
|
|
config = cfg
|
|
configUndecodedKeys = undecoded
|
|
configErr = nil
|
|
okf("saved config: %s", p)
|
|
return nil
|
|
},
|
|
}
|
|
|
|
func writeConfigFile(cfg Config) error {
|
|
p, err := configPath()
|
|
if err != nil {
|
|
return fmt.Errorf("cannot determine config path: %w", err)
|
|
}
|
|
if err := os.MkdirAll(filepath.Dir(p), 0o750); err != nil {
|
|
return fmt.Errorf("cannot create config directory: %w", err)
|
|
}
|
|
f, err := os.Create(p)
|
|
if err != nil {
|
|
return fmt.Errorf("cannot write config: %w", err)
|
|
}
|
|
defer f.Close()
|
|
enc := toml.NewEncoder(f)
|
|
return enc.Encode(cfg)
|
|
}
|
|
|
|
var configInitCmd = &cobra.Command{
|
|
Use: "init",
|
|
Short: "Generate default config file",
|
|
Args: cobra.NoArgs,
|
|
SilenceUsage: true,
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
p, err := configPath()
|
|
if err != nil {
|
|
return fmt.Errorf("cannot determine config path: %w", err)
|
|
}
|
|
newFlag, _ := cmd.Flags().GetBool("new")
|
|
updateFlag, _ := cmd.Flags().GetBool("update")
|
|
|
|
if newFlag && updateFlag {
|
|
return fmt.Errorf("--new and --update are mutually exclusive")
|
|
}
|
|
|
|
if updateFlag {
|
|
if _, err := os.Stat(p); os.IsNotExist(err) {
|
|
return withHint(
|
|
fmt.Errorf("no config file to update"),
|
|
"use 'pda config init' to create one",
|
|
)
|
|
}
|
|
cfg, _, migrations, loadErr := loadConfig()
|
|
if loadErr != nil {
|
|
return fmt.Errorf("cannot update config: %w", loadErr)
|
|
}
|
|
if err := writeConfigFile(cfg); err != nil {
|
|
return err
|
|
}
|
|
for _, m := range migrations {
|
|
okf("%s migrated to %s", m.Old, m.New)
|
|
}
|
|
okf("updated config: %s", p)
|
|
return nil
|
|
}
|
|
|
|
if !newFlag {
|
|
if _, err := os.Stat(p); err == nil {
|
|
return withHint(
|
|
fmt.Errorf("config file already exists"),
|
|
"use '--update' to update your config, or '--new' to get a fresh copy",
|
|
)
|
|
}
|
|
}
|
|
okf("generated config: %s", p)
|
|
return writeConfigFile(defaultConfig())
|
|
},
|
|
}
|
|
|
|
var configSetCmd = &cobra.Command{
|
|
Use: "set <key> <value>",
|
|
Short: "Set a configuration value",
|
|
Args: cobra.ExactArgs(2),
|
|
SilenceUsage: true,
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
key, raw := args[0], args[1]
|
|
cfg := config
|
|
defaults := defaultConfig()
|
|
fields := configFields(&cfg, &defaults)
|
|
f := findConfigField(fields, key)
|
|
if f == nil {
|
|
err := fmt.Errorf("unknown config key '%s'", key)
|
|
if suggestions := suggestConfigKey(fields, key); len(suggestions) > 0 {
|
|
return withHint(err, fmt.Sprintf("did you mean '%s'?", strings.Join(suggestions, "', '")))
|
|
}
|
|
return err
|
|
}
|
|
|
|
switch f.Kind {
|
|
case reflect.Bool:
|
|
switch strings.ToLower(raw) {
|
|
case "true":
|
|
f.Field.SetBool(true)
|
|
case "false":
|
|
f.Field.SetBool(false)
|
|
default:
|
|
return fmt.Errorf("cannot set '%s': expected bool (true/false), got '%s'", key, raw)
|
|
}
|
|
case reflect.String:
|
|
f.Field.SetString(raw)
|
|
default:
|
|
return fmt.Errorf("cannot set '%s': unsupported type %s", key, f.Kind)
|
|
}
|
|
|
|
if err := validateConfig(cfg); err != nil {
|
|
return fmt.Errorf("cannot set '%s': %w", key, err)
|
|
}
|
|
|
|
if err := writeConfigFile(cfg); err != nil {
|
|
return err
|
|
}
|
|
|
|
config = cfg
|
|
configUndecodedKeys = nil
|
|
okf("%s set to '%s'", key, raw)
|
|
return nil
|
|
},
|
|
}
|
|
|
|
func init() {
|
|
configInitCmd.Flags().Bool("new", false, "overwrite existing config file")
|
|
configInitCmd.Flags().Bool("update", false, "migrate deprecated keys and fill missing defaults")
|
|
configCmd.AddCommand(configEditCmd)
|
|
configCmd.AddCommand(configGetCmd)
|
|
configCmd.AddCommand(configInitCmd)
|
|
configCmd.AddCommand(configListCmd)
|
|
configCmd.AddCommand(configPathCmd)
|
|
configCmd.AddCommand(configSetCmd)
|
|
rootCmd.AddCommand(configCmd)
|
|
}
|