diff --git a/LICENSE b/LICENSE index 272e67a..933b6af 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 beatinaniwa +Copyright (c) 2026 beatinaniwa Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 86764a8..f01c1f9 100644 --- a/README.md +++ b/README.md @@ -189,15 +189,14 @@ mf journals create --json '{"..."}' --dry-run ### 設定ファイル -設定ファイルの保存場所は OS の `UserConfigDir` に基づきます: - -| OS | パス | -|----|------| -| macOS | `~/Library/Application Support/mf-cli/` | -| Linux | `~/.config/mf-cli/` | -| Windows | `%AppData%\mf-cli\` | - -`MF_CONFIG_DIR` 環境変数でオーバーライドできます。 +設定ファイルの保存場所は [XDG Base Directory](https://specifications.freedesktop.org/basedir-spec/latest/) に準拠します: + +| 条件 | パス | +|------|------| +| `MF_CONFIG_DIR` 設定時 | `$MF_CONFIG_DIR/` | +| `XDG_CONFIG_HOME` 設定時 | `$XDG_CONFIG_HOME/mf-cli/` | +| デフォルト(macOS/Linux) | `~/.config/mf-cli/` | +| デフォルト(Windows) | `%AppData%\mf-cli\` | #### config.json diff --git a/internal/config/config.go b/internal/config/config.go index bade548..88fab25 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -5,6 +5,7 @@ import ( "fmt" "os" "path/filepath" + "runtime" "strconv" ) @@ -73,9 +74,19 @@ func resolveConfigDir() (string, error) { if v := os.Getenv("MF_CONFIG_DIR"); v != "" { return v, nil } - base, err := os.UserConfigDir() + if runtime.GOOS == "windows" { + base, err := os.UserConfigDir() + if err != nil { + return "", err + } + return filepath.Join(base, "mf-cli"), nil + } + if v := os.Getenv("XDG_CONFIG_HOME"); v != "" && filepath.IsAbs(v) { + return filepath.Join(v, "mf-cli"), nil + } + home, err := os.UserHomeDir() if err != nil { return "", err } - return filepath.Join(base, "mf-cli"), nil + return filepath.Join(home, ".config", "mf-cli"), nil } diff --git a/internal/config/config_test.go b/internal/config/config_test.go index deeed95..cd4d366 100644 --- a/internal/config/config_test.go +++ b/internal/config/config_test.go @@ -4,6 +4,7 @@ import ( "encoding/json" "os" "path/filepath" + "runtime" "testing" ) @@ -141,6 +142,63 @@ func TestLoad_InvalidJSON(t *testing.T) { } } +func TestResolveConfigDir_XDGConfigHome(t *testing.T) { + if runtime.GOOS == "windows" { + t.Skip("Windows does not recognise /custom/xdg as absolute") + } + t.Setenv("MF_CONFIG_DIR", "") + t.Setenv("XDG_CONFIG_HOME", "/custom/xdg") + + dir, err := resolveConfigDir() + if err != nil { + t.Fatalf("resolveConfigDir() returned error: %v", err) + } + want := filepath.Join("/custom/xdg", "mf-cli") + if dir != want { + t.Errorf("resolveConfigDir() = %q, want %q", dir, want) + } +} + +func TestResolveConfigDir_DefaultFallback(t *testing.T) { + if runtime.GOOS == "windows" { + t.Skip("default fallback on Windows uses AppData, not ~/.config") + } + tmpHome := t.TempDir() + t.Setenv("HOME", tmpHome) + t.Setenv("MF_CONFIG_DIR", "") + t.Setenv("XDG_CONFIG_HOME", "") + + dir, err := resolveConfigDir() + if err != nil { + t.Fatalf("resolveConfigDir() returned error: %v", err) + } + + want := filepath.Join(tmpHome, ".config", "mf-cli") + if dir != want { + t.Errorf("resolveConfigDir() = %q, want %q", dir, want) + } +} + +func TestResolveConfigDir_RelativeXDGIgnored(t *testing.T) { + if runtime.GOOS == "windows" { + t.Skip("XDG fallback test not applicable on Windows") + } + tmpHome := t.TempDir() + t.Setenv("HOME", tmpHome) + t.Setenv("MF_CONFIG_DIR", "") + t.Setenv("XDG_CONFIG_HOME", "relative/path") + + dir, err := resolveConfigDir() + if err != nil { + t.Fatalf("resolveConfigDir() returned error: %v", err) + } + + want := filepath.Join(tmpHome, ".config", "mf-cli") + if dir != want { + t.Errorf("resolveConfigDir() = %q, want %q (relative XDG_CONFIG_HOME should be ignored)", dir, want) + } +} + func TestRequireClientID_Empty(t *testing.T) { cfg := &Config{ClientID: ""} if err := cfg.RequireClientID(); err == nil {