feat(config): some additional config options, and config migration from deprecated keys

This commit is contained in:
Lewis Wynne 2026-02-12 19:31:24 +00:00
parent 629358a81b
commit 4e78cefd56
16 changed files with 363 additions and 51 deletions

View file

@ -23,6 +23,7 @@ THE SOFTWARE.
package cmd
import (
"bytes"
"fmt"
"os"
"path/filepath"
@ -43,6 +44,7 @@ type KeyConfig struct {
AlwaysPromptDelete bool `toml:"always_prompt_delete"`
AlwaysPromptGlobDelete bool `toml:"always_prompt_glob_delete"`
AlwaysPromptOverwrite bool `toml:"always_prompt_overwrite"`
AlwaysEncrypt bool `toml:"always_encrypt"`
}
type StoreConfig struct {
@ -52,14 +54,18 @@ type StoreConfig struct {
}
type ListConfig struct {
ListAllStores bool `toml:"list_all_stores"`
DefaultListFormat string `toml:"default_list_format"`
AlwaysShowAllStores bool `toml:"always_show_all_stores"`
DefaultListFormat string `toml:"default_list_format"`
AlwaysShowFullValues bool `toml:"always_show_full_values"`
AlwaysHideHeader bool `toml:"always_hide_header"`
DefaultColumns string `toml:"default_columns"`
}
type GitConfig struct {
AutoFetch bool `toml:"auto_fetch"`
AutoCommit bool `toml:"auto_commit"`
AutoPush bool `toml:"auto_push"`
AutoFetch bool `toml:"auto_fetch"`
AutoCommit bool `toml:"auto_commit"`
AutoPush bool `toml:"auto_push"`
DefaultCommitMessage string `toml:"default_commit_message"`
}
var (
@ -78,7 +84,15 @@ var (
)
func init() {
config, configUndecodedKeys, configErr = loadConfig()
var migrations []migration
config, configUndecodedKeys, migrations, configErr = 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)
}
}
}
func defaultConfig() Config {
@ -95,35 +109,56 @@ func defaultConfig() Config {
AlwaysPromptOverwrite: true,
},
List: ListConfig{
ListAllStores: true,
DefaultListFormat: "table",
AlwaysShowAllStores: true,
DefaultListFormat: "table",
DefaultColumns: "key,store,value,ttl",
},
Git: GitConfig{
AutoFetch: false,
AutoCommit: false,
AutoPush: false,
AutoFetch: false,
AutoCommit: false,
AutoPush: false,
DefaultCommitMessage: "sync: {{.Time}}",
},
}
}
func loadConfig() (Config, []string, error) {
// loadConfig returns (config, undecodedKeys, migrations, error).
// Migrations are returned but NOT printed — callers decide.
func loadConfig() (Config, []string, []migration, error) {
cfg := defaultConfig()
path, err := configPath()
if err != nil {
return cfg, nil, err
return cfg, nil, nil, err
}
if _, err := os.Stat(path); err != nil {
if os.IsNotExist(err) {
return cfg, nil, nil
}
return cfg, nil, err
}
meta, err := toml.DecodeFile(path, &cfg)
data, err := os.ReadFile(path)
if err != nil {
return cfg, nil, fmt.Errorf("parse %s: %w", path, err)
if os.IsNotExist(err) {
return cfg, nil, nil, nil
}
return cfg, nil, nil, err
}
// Decode into a raw map so we can run deprecation migrations before
// the struct decode sees the keys.
var raw map[string]any
if _, err := toml.Decode(string(data), &raw); err != nil {
return cfg, nil, nil, fmt.Errorf("parse %s: %w", path, err)
}
warnings := migrateRawConfig(raw)
// Re-encode the migrated map and decode into the typed struct so
// defaults fill any missing fields.
var buf bytes.Buffer
if err := toml.NewEncoder(&buf).Encode(raw); err != nil {
return cfg, nil, nil, fmt.Errorf("parse %s: %w", path, err)
}
meta, err := toml.Decode(buf.String(), &cfg)
if err != nil {
return cfg, nil, nil, fmt.Errorf("parse %s: %w", path, err)
}
var undecoded []string
@ -139,15 +174,29 @@ func loadConfig() (Config, []string, error) {
cfg.List.DefaultListFormat = defaultConfig().List.DefaultListFormat
}
if err := validListFormat(cfg.List.DefaultListFormat); err != nil {
return cfg, undecoded, fmt.Errorf("parse %s: list.default_list_format: %w", path, err)
return cfg, undecoded, warnings, fmt.Errorf("parse %s: list.default_list_format: %w", path, err)
}
return cfg, undecoded, nil
if cfg.List.DefaultColumns == "" {
cfg.List.DefaultColumns = defaultConfig().List.DefaultColumns
}
if err := validListColumns(cfg.List.DefaultColumns); err != nil {
return cfg, undecoded, warnings, fmt.Errorf("parse %s: list.default_columns: %w", path, err)
}
if cfg.Git.DefaultCommitMessage == "" {
cfg.Git.DefaultCommitMessage = defaultConfig().Git.DefaultCommitMessage
}
return cfg, undecoded, warnings, nil
}
// validateConfig checks invariants on a Config value before it is persisted.
func validateConfig(cfg Config) error {
return validListFormat(cfg.List.DefaultListFormat)
if err := validListFormat(cfg.List.DefaultListFormat); err != nil {
return err
}
return validListColumns(cfg.List.DefaultColumns)
}
func configPath() (string, error) {