Go 179 lines
package main
import (
"fmt"
"io"
"path/filepath"
"strings"
"github.com/ajhahnde/eeco/internal/config"
)
const configUsage = `usage:
eeco config list show every config key, its effective value, and origin
eeco config get <key> print the effective value of one key
eeco config set <key> <value> set a key in this workspace (<repo>/<user>/.eeco/config.local)
eeco config set --global <key> <value> set a key in the user-global layer (~/.config/eeco/config.local)
eeco config import [--force] <path> copy config.local, cockpit.json, and workflows from another eeco project
Resolution order: built-in defaults → user-global → workspace (later wins).
The global layer lets every project inherit your settings; set it once with --global.
Exit 0 clean, 1 on a failure, 2 on usage error.`
// runConfig dispatches `eeco config` subcommands. Config has three
// layers — defaults, the user-global file, and the per-workspace
// config.local — and this group inspects and edits the two file layers.
func runConfig(args []string, stdout, stderr io.Writer) int {
if len(args) == 0 {
fmt.Fprintln(stderr, configUsage)
return 2
}
switch args[0] {
case "list":
return runConfigList(args[1:], stdout, stderr)
case "get":
return runConfigGet(args[1:], stdout, stderr)
case "set":
return runConfigSet(args[1:], stdout, stderr)
case "import":
return runConfigImport(args[1:], stdout, stderr)
default:
fmt.Fprintln(stderr, configUsage)
return 2
}
}
// runConfigImport copies portable settings from another eeco project's
// workspace into this one. Without --force, existing local files and keys are
// preserved; with --force, the source wins.
func runConfigImport(args []string, stdout, stderr io.Writer) int {
force, rest := popBoolFlag(args, "force")
if len(rest) != 1 {
fmt.Fprintln(stderr, "eeco config: import needs exactly one <path>")
return 2
}
dst, code := loadInitedConfig(stderr, "eeco config")
if code != 0 {
return code
}
src, err := resolveSourceWorkspace(rest[0])
if err != nil {
fmt.Fprintln(stderr, "eeco config:", err)
return 1
}
if sameWorkspace(src.Workspace, dst.Workspace) {
fmt.Fprintln(stderr, "eeco config: source and destination are the same workspace")
return 1
}
rep, err := importSettings(src, dst, force)
if err != nil {
fmt.Fprintln(stderr, "eeco config:", err)
return 1
}
printImportReport(stdout, rest[0], rep)
return 0
}
// runConfigList prints every known key with its effective value and the
// layer that set it (default | global | local), git-config-style.
func runConfigList(args []string, stdout, stderr io.Writer) int {
if len(args) != 0 {
fmt.Fprintln(stderr, configUsage)
return 2
}
cfg, code := loadRepoConfig(stderr, "eeco config")
if code != 0 {
return code
}
globalKeys, err := config.DeclaredKeys(config.GlobalConfigLocalPath())
if err != nil {
fmt.Fprintln(stderr, "eeco config:", err)
return 1
}
localKeys, err := config.DeclaredKeys(filepath.Join(cfg.Workspace, config.LocalFilename))
if err != nil {
fmt.Fprintln(stderr, "eeco config:", err)
return 1
}
keys := config.KnownKeys()
width := 0
for _, k := range keys {
if len(k) > width {
width = len(k)
}
}
for _, k := range keys {
val, _ := config.EffectiveValue(cfg, k)
origin := "default"
switch {
case localKeys[k]:
origin = "local"
case globalKeys[k]:
origin = "global"
}
fmt.Fprintf(stdout, "%-*s = %s (%s)\n", width, k, val, origin)
}
return 0
}
// runConfigGet prints the effective value of one key (bare, for scripts).
func runConfigGet(args []string, stdout, stderr io.Writer) int {
if len(args) != 1 {
fmt.Fprintln(stderr, "eeco config: get needs exactly one <key>")
return 2
}
key := args[0]
cfg, code := loadRepoConfig(stderr, "eeco config")
if code != 0 {
return code
}
val, ok := config.EffectiveValue(cfg, key)
if !ok {
fmt.Fprintf(stderr, "eeco config: unknown config key %q (run `eeco config list`)\n", key)
return 1
}
fmt.Fprintln(stdout, val)
return 0
}
// runConfigSet writes one key into the workspace config.local, or into
// the user-global config.local with --global. The value is validated
// against the same rules Load applies before anything is written.
func runConfigSet(args []string, stdout, stderr io.Writer) int {
global, rest := popBoolFlag(args, "global")
var key, val string
switch {
case len(rest) == 2:
key, val = rest[0], rest[1]
case len(rest) == 1 && strings.Contains(rest[0], "="):
key, val, _ = strings.Cut(rest[0], "=")
default:
fmt.Fprintln(stderr, "eeco config: set needs <key> <value> (or key=value)")
return 2
}
if err := config.ValidateSetValue(key, val); err != nil {
fmt.Fprintln(stderr, "eeco config:", err)
return 1
}
if global {
if err := config.WriteGlobalKeys(map[string]string{key: val}); err != nil {
fmt.Fprintln(stderr, "eeco config:", err)
return 1
}
fmt.Fprintf(stdout, "eeco config: set %s=%s (global: %s)\n", key, val, config.GlobalConfigLocalPath())
return 0
}
cfg, code := loadInitedConfig(stderr, "eeco config")
if code != 0 {
return code
}
if err := config.WriteLocalKeys(cfg, map[string]string{key: val}); err != nil {
fmt.Fprintln(stderr, "eeco config:", err)
return 1
}
fmt.Fprintf(stdout, "eeco config: set %s=%s (workspace)\n", key, val)
return 0
}