Skip to content

Commit 315dcfb

Browse files
authored
feat: use XDG_CONFIG_HOME (#80)
1 parent 0f0f08e commit 315dcfb

3 files changed

Lines changed: 61 additions & 6 deletions

File tree

‎README.md‎

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
# andcli
22

3-
[![Go Report Card](https://goreportcard.com/badge/github.com/tjblackheart/andcli)](https://goreportcard.com/report/github.com/tjblackheart/andcli) ![Build](https://github.com/tjblackheart/andcli/actions/workflows/build.yaml/badge.svg) [![AI free project](https://img.shields.io/badge/AI-is_a_lie-cc0000?logo=githubcopilot&logoColor=white)](https://deplet.ing/the-copilot-delusion)
4-
53
andcli lets you work with 2FA tokens directly in your shell, using encrypted backups exported out of your favourite 2FA apps. All the data is held in memory only and will never leave your machine.
64

75
andcli can handle input from the following providers (if they provide more than 2FA support, only the OTP related entries will be consumed):
@@ -49,7 +47,8 @@ By default andcli will choose the first system clipboard tool found. For Linux,
4947

5048
## Config file
5149

52-
The configuration will get persisted in the default user home config directory. For Linux, this is `$HOME/.config/andcli`. For MacOS, it's `$HOME/Library/Application Support/andcli` and for Windows it should be in `C:\Users\$USER\AppData\Roaming\andcli`.
50+
The configuration is persisted in the default user home config directory. For Linux, this is `$XDG_CONFIG_HOME/andcli`. For MacOS, it's `$HOME/Library/Application Support/andcli`. For Windows it's `C:\Users\$USER\AppData\Roaming\andcli`.
51+
If `$XDG_CONFIG_HOME` is set it will be used as the preferred config home dir on all OSes.
5352

5453
## Theming
5554

‎internal/config/config.go‎

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ import (
1414
"github.com/tjblackheart/andcli/v2/internal/vaults"
1515
)
1616

17+
const cfgFileName = "config.yaml"
18+
1719
type (
1820
Config struct {
1921
File string `yaml:"file"`
@@ -44,7 +46,7 @@ func Create() (*Config, error) {
4446
if err != nil {
4547
return nil, fmt.Errorf("unable to read user directory: %s", err)
4648
}
47-
return create(dir)
49+
return create(resolve(dir))
4850
}
4951

5052
func create(dir string) (*Config, error) {
@@ -54,7 +56,7 @@ func create(dir string) (*Config, error) {
5456
}
5557

5658
cfg := &Config{
57-
path: filepath.Join(path, "config.yaml"),
59+
path: filepath.Join(path, cfgFileName),
5860
Options: &Opts{
5961
ShowUsernames: true,
6062
ShowTokens: false,
@@ -213,3 +215,13 @@ func (cfg *Config) validate() error {
213215

214216
return nil
215217
}
218+
219+
func resolve(dir string) string {
220+
path := filepath.Join(dir, buildinfo.AppName, cfgFileName)
221+
if _, err := os.Stat(path); os.IsNotExist(err) {
222+
if xdg := os.Getenv("XDG_CONFIG_HOME"); xdg != "" {
223+
return xdg
224+
}
225+
}
226+
return dir
227+
}

‎internal/config/config_test.go‎

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -310,7 +310,7 @@ func Test_create(t *testing.T) {
310310
ShowTokens: false,
311311
},
312312
Theme: &DefaultTheme,
313-
path: filepath.Join(cfgDir, buildinfo.AppName, "config.yaml"),
313+
path: filepath.Join(cfgDir, buildinfo.AppName, cfgFileName),
314314
dirty: true,
315315
timeout: 5,
316316
}
@@ -320,6 +320,50 @@ func Test_create(t *testing.T) {
320320
}
321321
}
322322

323+
func Test_resolve(t *testing.T) {
324+
writeCfg := func(dir string) {
325+
t.Helper()
326+
path := filepath.Join(dir, buildinfo.AppName, cfgFileName)
327+
if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
328+
t.Fatal(err)
329+
}
330+
if err := os.WriteFile(path, nil, 0o600); err != nil {
331+
t.Fatal(err)
332+
}
333+
}
334+
335+
tests := []struct {
336+
name string
337+
xdg string
338+
hasCfg bool
339+
}{
340+
{"xdg unset uses default dir", "", false},
341+
{"xdg unset keeps default dir with existing config", "", true},
342+
{"prefers xdg when no config exists", "/tmp/xdg-test", false},
343+
{"keeps existing config over xdg", "/tmp/xdg-test", true},
344+
}
345+
346+
for _, tt := range tests {
347+
t.Run(tt.name, func(t *testing.T) {
348+
t.Setenv("XDG_CONFIG_HOME", tt.xdg)
349+
350+
dir := t.TempDir()
351+
if tt.hasCfg {
352+
writeCfg(dir)
353+
}
354+
355+
want := dir
356+
if !tt.hasCfg && tt.xdg != "" {
357+
want = tt.xdg
358+
}
359+
360+
if got := resolve(dir); got != want {
361+
t.Errorf("resolve() = %q, want %q", got, want)
362+
}
363+
})
364+
}
365+
}
366+
323367
func TestConfig_Flags(t *testing.T) {
324368
args := os.Args
325369
defer func() { os.Args = args }()

0 commit comments

Comments
 (0)