From da89cf78f9ea6f93fa602c8c63863f2e19fbcbd1 Mon Sep 17 00:00:00 2001 From: "Calvin A. Allen" Date: Sun, 25 Jan 2026 16:50:34 -0500 Subject: [PATCH] feat(config): support XDG_DATA_HOME on Windows and macOS When XDG_DATA_HOME is explicitly set, dtvem will now use $XDG_DATA_HOME/dtvem as the root directory on Windows and macOS, matching the existing Linux behavior. This is opt-in only - when XDG_DATA_HOME is not set, the default ~/.dtvem is still used. Closes #202 --- src/internal/config/paths.go | 15 +++++++--- src/internal/config/paths_test.go | 46 ++++++++++++++++++++++++++++--- 2 files changed, 53 insertions(+), 8 deletions(-) diff --git a/src/internal/config/paths.go b/src/internal/config/paths.go index b6c7ff6..cdac977 100644 --- a/src/internal/config/paths.go +++ b/src/internal/config/paths.go @@ -54,16 +54,18 @@ func initPaths() *Paths { // - Otherwise uses ~/.local/share/dtvem (XDG default) // - This is the standard location for user-specific data files on Linux // -// macOS: Uses ~/.dtvem +// macOS: Uses ~/.dtvem by default // - macOS has its own conventions (~/Library/Application Support) but many CLI tools // use dotfiles in home directory for better discoverability and Unix compatibility // - ~/.dtvem is more familiar to users coming from tools like nvm, pyenv, rbenv +// - Optionally respects $XDG_DATA_HOME if set (for users who prefer XDG consistency) // -// Windows: Uses %USERPROFILE%\.dtvem +// Windows: Uses %USERPROFILE%\.dtvem by default // - Alternatives considered: %LOCALAPPDATA% (C:\Users\\AppData\Local) // - Chose home directory for consistency with macOS/Linux and better visibility // - Users expect CLI tool configs in their home directory // - Easier to locate and backup than buried in AppData +// - Optionally respects $XDG_DATA_HOME if set (for users who prefer XDG consistency) // // Override: DTVEM_ROOT environment variable overrides all platform defaults func getRootDir() string { @@ -79,12 +81,17 @@ func getRootDir() string { return ".dtvem" } - // On Linux, respect XDG Base Directory specification + // On Linux, always respect XDG Base Directory specification if runtime.GOOS == constants.OSLinux { return getXDGDataPath(home) } - // On macOS and Windows, use ~/.dtvem + // On macOS and Windows, use XDG_DATA_HOME if explicitly set (opt-in) + if xdgDataHome := os.Getenv("XDG_DATA_HOME"); xdgDataHome != "" { + return filepath.Join(xdgDataHome, "dtvem") + } + + // Default for macOS and Windows: ~/.dtvem return filepath.Join(home, ".dtvem") } diff --git a/src/internal/config/paths_test.go b/src/internal/config/paths_test.go index 79d6bdc..3b3086a 100644 --- a/src/internal/config/paths_test.go +++ b/src/internal/config/paths_test.go @@ -384,8 +384,8 @@ func TestGetRootDir_XDGOnLinux(t *testing.T) { } } -func TestGetRootDir_NonLinux(t *testing.T) { - // On non-Linux platforms, verify that ~/.dtvem is used regardless of XDG +func TestGetRootDir_NonLinux_WithXDG(t *testing.T) { + // On non-Linux platforms, verify that XDG_DATA_HOME is respected when set if runtime.GOOS == constants.OSLinux { t.Skip("This test only runs on non-Linux platforms") } @@ -409,7 +409,45 @@ func TestGetRootDir_NonLinux(t *testing.T) { // Clear DTVEM_ROOT and set XDG_DATA_HOME _ = os.Unsetenv("DTVEM_ROOT") - _ = os.Setenv("XDG_DATA_HOME", "/should/be/ignored") + customXDG := "/custom/xdg/data" + _ = os.Setenv("XDG_DATA_HOME", customXDG) + resetPathsForTesting() + + result := getRootDir() + expected := filepath.Join(customXDG, "dtvem") + + if result != expected { + t.Errorf("getRootDir() on %s should use XDG_DATA_HOME when set, got %q, want %q", + runtime.GOOS, result, expected) + } +} + +func TestGetRootDir_NonLinux_WithoutXDG(t *testing.T) { + // On non-Linux platforms, verify that ~/.dtvem is used when XDG_DATA_HOME is not set + if runtime.GOOS == constants.OSLinux { + t.Skip("This test only runs on non-Linux platforms") + } + + // Save original environment + originalRoot := os.Getenv("DTVEM_ROOT") + originalXDG := os.Getenv("XDG_DATA_HOME") + defer func() { + if originalRoot != "" { + _ = os.Setenv("DTVEM_ROOT", originalRoot) + } else { + _ = os.Unsetenv("DTVEM_ROOT") + } + if originalXDG != "" { + _ = os.Setenv("XDG_DATA_HOME", originalXDG) + } else { + _ = os.Unsetenv("XDG_DATA_HOME") + } + resetPathsForTesting() + }() + + // Clear both DTVEM_ROOT and XDG_DATA_HOME + _ = os.Unsetenv("DTVEM_ROOT") + _ = os.Unsetenv("XDG_DATA_HOME") resetPathsForTesting() result := getRootDir() @@ -417,7 +455,7 @@ func TestGetRootDir_NonLinux(t *testing.T) { expected := filepath.Join(home, ".dtvem") if result != expected { - t.Errorf("getRootDir() on %s should ignore XDG_DATA_HOME, got %q, want %q", + t.Errorf("getRootDir() on %s without XDG_DATA_HOME should use ~/.dtvem, got %q, want %q", runtime.GOOS, result, expected) } }