From b15e78fd4d469fc72157299a2268b54830d25ca5 Mon Sep 17 00:00:00 2001 From: Fendy Date: Thu, 13 Aug 2026 00:37:15 +0800 Subject: [PATCH] feat(cmd/ry): add simplified single-provider config --- cmd/ry/internal/config/config.go | 53 ++++++++++++++++ cmd/ry/internal/config/config_test.go | 91 +++++++++++++++++++++++++++ 2 files changed, 144 insertions(+) create mode 100644 cmd/ry/internal/config/config.go create mode 100644 cmd/ry/internal/config/config_test.go diff --git a/cmd/ry/internal/config/config.go b/cmd/ry/internal/config/config.go new file mode 100644 index 0000000..6155a49 --- /dev/null +++ b/cmd/ry/internal/config/config.go @@ -0,0 +1,53 @@ +package config + +import ( + "os" + "path/filepath" + + "github.com/BurntSushi/toml" +) + +type Config struct { + APIKey string `toml:"api_key,omitempty"` + Output string `toml:"output"` +} + +func Load(path string) (*Config, error) { + cfg := &Config{Output: "table"} + data, err := os.ReadFile(path) + if err != nil { + if os.IsNotExist(err) { + return cfg, nil + } + return nil, err + } + if err := toml.Unmarshal(data, cfg); err != nil { + return nil, err + } + if cfg.Output == "" { + cfg.Output = "table" + } + return cfg, nil +} + +func Save(path string, cfg *Config) error { + dir := filepath.Dir(path) + if err := os.MkdirAll(dir, 0700); err != nil { + return err + } + f, err := os.Create(path) + if err != nil { + return err + } + defer f.Close() + enc := toml.NewEncoder(f) + return enc.Encode(cfg) +} + +func DefaultPath() string { + home, err := os.UserHomeDir() + if err != nil { + return "config.toml" + } + return filepath.Join(home, ".config", "ry", "config.toml") +} diff --git a/cmd/ry/internal/config/config_test.go b/cmd/ry/internal/config/config_test.go new file mode 100644 index 0000000..be97eb5 --- /dev/null +++ b/cmd/ry/internal/config/config_test.go @@ -0,0 +1,91 @@ +package config + +import ( + "os" + "path/filepath" + "testing" +) + +func TestLoadTOML(t *testing.T) { + dir := t.TempDir() + path := filepath.Join(dir, "config.toml") + content := ` +api_key = "sk-test123" +output = "json" +` + if err := os.WriteFile(path, []byte(content), 0600); err != nil { + t.Fatal(err) + } + cfg, err := Load(path) + if err != nil { + t.Fatalf("Load() error: %v", err) + } + if cfg.APIKey != "sk-test123" { + t.Errorf("APIKey = %q, want %q", cfg.APIKey, "sk-test123") + } + if cfg.Output != "json" { + t.Errorf("Output = %q, want %q", cfg.Output, "json") + } +} + +func TestLoadDefaults(t *testing.T) { + dir := t.TempDir() + path := filepath.Join(dir, "empty.toml") + if err := os.WriteFile(path, []byte(""), 0600); err != nil { + t.Fatal(err) + } + cfg, err := Load(path) + if err != nil { + t.Fatalf("Load() error: %v", err) + } + if cfg.Output != "table" { + t.Errorf("default Output = %q, want %q", cfg.Output, "table") + } +} + +func TestLoadMissingFile(t *testing.T) { + dir := t.TempDir() + path := filepath.Join(dir, "missing.toml") + cfg, err := Load(path) + if err != nil { + t.Fatalf("Load() missing file error: %v", err) + } + if cfg.Output != "table" { + t.Errorf("default Output = %q, want %q", cfg.Output, "table") + } +} + +func TestSave(t *testing.T) { + dir := t.TempDir() + path := filepath.Join(dir, "out.toml") + cfg := &Config{APIKey: "secret", Output: "json"} + if err := Save(path, cfg); err != nil { + t.Fatalf("Save() error: %v", err) + } + loaded, err := Load(path) + if err != nil { + t.Fatalf("re-Load() error: %v", err) + } + if loaded.APIKey != "secret" { + t.Errorf("roundtrip APIKey = %q", loaded.APIKey) + } + if loaded.Output != "json" { + t.Errorf("roundtrip Output = %q", loaded.Output) + } +} + +func TestDefaultPath(t *testing.T) { + path := DefaultPath() + if path == "" { + t.Error("DefaultPath() returned empty") + } +} + +func TestEnsureDir(t *testing.T) { + dir := filepath.Join(t.TempDir(), "sub", "dir") + path := filepath.Join(dir, "config.toml") + cfg := &Config{Output: "table"} + if err := Save(path, cfg); err != nil { + t.Fatalf("Save() to deep dir error: %v", err) + } +}