11
This commit is contained in:
@@ -0,0 +1,114 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/BurntSushi/toml"
|
||||
)
|
||||
|
||||
const DefaultWebhookPort = 17421
|
||||
|
||||
type Settings struct {
|
||||
WebhookPort int `toml:"webhook_port"`
|
||||
BrainstormPrompt string `toml:"brainstorm_prompt"`
|
||||
BrainstormContinuePrompt string `toml:"brainstorm_continue_prompt"`
|
||||
ImplementPlanPrompt string `toml:"implement_plan_prompt"`
|
||||
ReviewPrompt string `toml:"review_prompt"`
|
||||
AutoReview bool `toml:"auto_review"`
|
||||
DevBranch string `toml:"dev_branch"`
|
||||
AutoBuildBranch string `toml:"auto_build_branch"`
|
||||
WorktreePostCreateScript string `toml:"worktree_post_create_script"`
|
||||
WorktreePreRemoveScript string `toml:"worktree_pre_remove_script"`
|
||||
ImplTabPreCreateScript string `toml:"impl_tab_pre_create_script"`
|
||||
ImplTabPostCloseScript string `toml:"impl_tab_post_close_script"`
|
||||
ProfilesDir string `toml:"profiles_dir"`
|
||||
SystemPromptCommand string `toml:"system_prompt_command"`
|
||||
}
|
||||
|
||||
func DefaultSettings() *Settings {
|
||||
return &Settings{
|
||||
WebhookPort: DefaultWebhookPort,
|
||||
AutoReview: true,
|
||||
DevBranch: "main",
|
||||
}
|
||||
}
|
||||
|
||||
// configFilePath returns the path to the config file. It's a variable so tests can override it.
|
||||
var configFilePath = func() (string, error) {
|
||||
home, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("resolve home dir: %w", err)
|
||||
}
|
||||
return filepath.Join(home, ".config", "superwork", "config.toml"), nil
|
||||
}
|
||||
|
||||
func Load() (*Settings, error) {
|
||||
return LoadFrom("")
|
||||
}
|
||||
|
||||
func LoadFrom(path string) (*Settings, error) {
|
||||
if path == "" {
|
||||
var err error
|
||||
path, err = configFilePath()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
s := DefaultSettings()
|
||||
_, err := toml.DecodeFile(path, s)
|
||||
if errors.Is(err, fs.ErrNotExist) {
|
||||
return s, nil
|
||||
}
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("decode config %s: %w", path, err)
|
||||
}
|
||||
return s, nil
|
||||
}
|
||||
|
||||
func Save(s *Settings) error {
|
||||
return SaveTo("", s)
|
||||
}
|
||||
|
||||
func SaveTo(path string, s *Settings) error {
|
||||
if path == "" {
|
||||
var err error
|
||||
path, err = configFilePath()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
|
||||
return fmt.Errorf("create config dir: %w", err)
|
||||
}
|
||||
f, err := os.Create(path)
|
||||
if err != nil {
|
||||
return fmt.Errorf("create config file: %w", err)
|
||||
}
|
||||
defer f.Close()
|
||||
if err := toml.NewEncoder(f).Encode(s); err != nil {
|
||||
return fmt.Errorf("encode config: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func WorkspaceRoot() (string, error) {
|
||||
out, err := exec.Command("git", "rev-parse", "--show-toplevel").Output()
|
||||
if err != nil {
|
||||
cwd, cerr := os.Getwd()
|
||||
if cerr != nil {
|
||||
return "", fmt.Errorf("get cwd: %w", cerr)
|
||||
}
|
||||
return cwd, nil
|
||||
}
|
||||
return strings.TrimSpace(string(out)), nil
|
||||
}
|
||||
|
||||
func SpxDir(workspaceRoot string) string {
|
||||
return filepath.Join(workspaceRoot, ".spx")
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
package config_test
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"superwork-tui/internal/config"
|
||||
)
|
||||
|
||||
func TestDefaultSettings(t *testing.T) {
|
||||
s := config.DefaultSettings()
|
||||
if s.WebhookPort != 17421 {
|
||||
t.Errorf("WebhookPort: got %d, want 17421", s.WebhookPort)
|
||||
}
|
||||
if !s.AutoReview {
|
||||
t.Error("AutoReview: want true")
|
||||
}
|
||||
if s.DevBranch != "main" {
|
||||
t.Errorf("DevBranch: got %q, want main", s.DevBranch)
|
||||
}
|
||||
if s.ProfilesDir != "" {
|
||||
t.Errorf("ProfilesDir: want empty, got %q", s.ProfilesDir)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSaveLoadRoundtrip(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
path := filepath.Join(dir, "config.toml")
|
||||
|
||||
original := &config.Settings{
|
||||
WebhookPort: 12345,
|
||||
AutoReview: false,
|
||||
DevBranch: "develop",
|
||||
AutoBuildBranch: "ci",
|
||||
ProfilesDir: "/some/path",
|
||||
}
|
||||
if err := config.SaveTo(path, original); err != nil {
|
||||
t.Fatalf("SaveTo: %v", err)
|
||||
}
|
||||
loaded, err := config.LoadFrom(path)
|
||||
if err != nil {
|
||||
t.Fatalf("LoadFrom: %v", err)
|
||||
}
|
||||
if loaded.WebhookPort != original.WebhookPort {
|
||||
t.Errorf("WebhookPort: got %d, want %d", loaded.WebhookPort, original.WebhookPort)
|
||||
}
|
||||
if loaded.AutoReview != original.AutoReview {
|
||||
t.Errorf("AutoReview: got %v, want %v", loaded.AutoReview, original.AutoReview)
|
||||
}
|
||||
if loaded.DevBranch != original.DevBranch {
|
||||
t.Errorf("DevBranch: got %q, want %q", loaded.DevBranch, original.DevBranch)
|
||||
}
|
||||
if loaded.ProfilesDir != original.ProfilesDir {
|
||||
t.Errorf("ProfilesDir: got %q, want %q", loaded.ProfilesDir, original.ProfilesDir)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSystemPromptCommandDefault(t *testing.T) {
|
||||
s := config.DefaultSettings()
|
||||
if s.SystemPromptCommand != "" {
|
||||
t.Errorf("SystemPromptCommand: want empty, got %q", s.SystemPromptCommand)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSystemPromptCommandRoundtrip(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
path := filepath.Join(dir, "config.toml")
|
||||
|
||||
original := &config.Settings{
|
||||
WebhookPort: 12345,
|
||||
SystemPromptCommand: "echo hello",
|
||||
}
|
||||
if err := config.SaveTo(path, original); err != nil {
|
||||
t.Fatalf("SaveTo: %v", err)
|
||||
}
|
||||
loaded, err := config.LoadFrom(path)
|
||||
if err != nil {
|
||||
t.Fatalf("LoadFrom: %v", err)
|
||||
}
|
||||
if loaded.SystemPromptCommand != original.SystemPromptCommand {
|
||||
t.Errorf("SystemPromptCommand: got %q, want %q", loaded.SystemPromptCommand, original.SystemPromptCommand)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadMissingFile_ReturnsDefaults(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
path := filepath.Join(dir, "nonexistent.toml")
|
||||
s, err := config.LoadFrom(path)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if s.WebhookPort != 17421 {
|
||||
t.Errorf("WebhookPort: got %d, want 17421", s.WebhookPort)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user