Merge pull request #76 from charmbracelet/feature/finder-config

Kujtim Hoxha created

add finders for windows/unix/local

Change summary

cspell.json               | 40 ++++++++++++++++++++++++
internal/config/config.go | 64 +++++++++++++++++++++++++++++++++-------
2 files changed, 92 insertions(+), 12 deletions(-)

Detailed changes

cspell.json 🔗

@@ -1 +1,39 @@
-{"flagWords":[],"words":["crush","charmbracelet","lipgloss","bubbletea","textinput","Focusable","lsps","Sourcegraph","filepicker","imageorient","rasterx","oksvg","termenv","trashhalo","lucasb","nfnt","srwiley","Lanczos","fsext","GROQ","alecthomas","Preproc","Emph","charmtone","Charple","Guac","diffview","Strikethrough","Unticked","uniseg","rivo"],"version":"0.2","language":"en"}
+{
+    "flagWords": [],
+    "words": [
+        "afero",
+        "alecthomas",
+        "bubbletea",
+        "charmbracelet",
+        "charmtone",
+        "Charple",
+        "crush",
+        "diffview",
+        "Emph",
+        "filepicker",
+        "Focusable",
+        "fsext",
+        "GROQ",
+        "Guac",
+        "imageorient",
+        "Lanczos",
+        "lipgloss",
+        "lsps",
+        "lucasb",
+        "nfnt",
+        "oksvg",
+        "Preproc",
+        "rasterx",
+        "rivo",
+        "Sourcegraph",
+        "srwiley",
+        "Strikethrough",
+        "termenv",
+        "textinput",
+        "trashhalo",
+        "uniseg",
+        "Unticked"
+    ],
+    "version": "0.2",
+    "language": "en"
+}

internal/config/config.go 🔗

@@ -11,6 +11,7 @@ import (
 
 	"github.com/charmbracelet/crush/internal/llm/models"
 	"github.com/charmbracelet/crush/internal/logging"
+	"github.com/spf13/afero"
 	"github.com/spf13/viper"
 )
 
@@ -201,21 +202,62 @@ func Load(workingDir string, debug bool) (*Config, error) {
 	return cfg, nil
 }
 
+type configFinder struct {
+	appName   string
+	dotPrefix bool
+	paths     []string
+}
+
+func (f configFinder) Find(fsys afero.Fs) ([]string, error) {
+	var configFiles []string
+	configName := fmt.Sprintf("%s.json", f.appName)
+	if f.dotPrefix {
+		configName = fmt.Sprintf(".%s.json", f.appName)
+	}
+	paths := []string{}
+	for _, p := range f.paths {
+		if p == "" {
+			continue
+		}
+		paths = append(paths, os.ExpandEnv(p))
+	}
+
+	for _, path := range paths {
+		if path == "" {
+			continue
+		}
+
+		configPath := filepath.Join(path, configName)
+		if exists, err := afero.Exists(fsys, configPath); err == nil && exists {
+			configFiles = append(configFiles, configPath)
+		}
+	}
+	return configFiles, nil
+}
+
 // configureViper sets up viper's configuration paths and environment variables.
 func configureViper() {
-	viper.SetConfigName(fmt.Sprintf(".%s", appName))
 	viper.SetConfigType("json")
 
-	// Unix-style paths
-	viper.AddConfigPath("$HOME")
-	viper.AddConfigPath(fmt.Sprintf("$XDG_CONFIG_HOME/%s", appName))
-	viper.AddConfigPath(fmt.Sprintf("$HOME/.config/%s", appName))
-
-	// Windows-style paths
-	viper.AddConfigPath(fmt.Sprintf("$USERPROFILE"))
-	viper.AddConfigPath(fmt.Sprintf("$APPDATA/%s", appName))
-	viper.AddConfigPath(fmt.Sprintf("$LOCALAPPDATA/%s", appName))
-
+	// Create the three finders
+	windowsFinder := configFinder{appName: appName, dotPrefix: false, paths: []string{
+		"$USERPROFILE",
+		fmt.Sprintf("$APPDATA/%s", appName),
+		fmt.Sprintf("$LOCALAPPDATA/%s", appName),
+	}}
+
+	unixFinder := configFinder{appName: appName, dotPrefix: false, paths: []string{
+		"$HOME",
+		fmt.Sprintf("$XDG_CONFIG_HOME/%s", appName),
+		fmt.Sprintf("$HOME/.config/%s", appName),
+	}}
+
+	localFinder := configFinder{appName: appName, dotPrefix: true, paths: []string{
+		".",
+	}}
+
+	// Use all finders with viper
+	viper.SetOptions(viper.WithFinder(viper.Finders(windowsFinder, unixFinder, localFinder)))
 	viper.SetEnvPrefix(strings.ToUpper(appName))
 	viper.AutomaticEnv()
 }