diff options
Diffstat (limited to 'internal/xdg')
| -rw-r--r-- | internal/xdg/xdg.go | 60 | ||||
| -rw-r--r-- | internal/xdg/xdg_test.go | 60 |
2 files changed, 120 insertions, 0 deletions
diff --git a/internal/xdg/xdg.go b/internal/xdg/xdg.go new file mode 100644 index 0000000..ed34838 --- /dev/null +++ b/internal/xdg/xdg.go @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +// Package xdg resolves the XDG base directories and expands ~ in paths. +package xdg + +import ( + "os" + "path/filepath" + "strings" +) + +// ConfigHome is $XDG_CONFIG_HOME, or ~/.config. +func ConfigHome() string { return base("XDG_CONFIG_HOME", ".config") } + +// StateHome is $XDG_STATE_HOME, or ~/.local/state. +func StateHome() string { return base("XDG_STATE_HOME", filepath.Join(".local", "state")) } + +// DataHome is $XDG_DATA_HOME, or ~/.local/share. +func DataHome() string { return base("XDG_DATA_HOME", filepath.Join(".local", "share")) } + +// base follows the XDG rule that a relative value is invalid and ignored. +func base(env, fallback string) string { + if v := os.Getenv(env); filepath.IsAbs(v) { + return filepath.Clean(v) + } + return filepath.Join(Home(), fallback) +} + +// Home is the user's home directory, or "/" when it is unknown. +func Home() string { + if h, err := os.UserHomeDir(); err == nil && h != "" { + return filepath.Clean(h) + } + return "/" +} + +// Expand replaces a leading "~" or "~/" with the home directory. +// "~user" is left alone. +func Expand(p string) string { + if p == "~" { + return Home() + } + if strings.HasPrefix(p, "~/") { + return filepath.Join(Home(), p[2:]) + } + return p +} + +// Abbrev replaces a leading home directory with "~", for display and for +// paths written into config files. +func Abbrev(p string) string { + h := Home() + if p == h { + return "~" + } + if h != "/" && strings.HasPrefix(p, h+"/") { + return "~/" + p[len(h)+1:] + } + return p +} diff --git a/internal/xdg/xdg_test.go b/internal/xdg/xdg_test.go new file mode 100644 index 0000000..7c7de87 --- /dev/null +++ b/internal/xdg/xdg_test.go @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +package xdg + +import "testing" + +func TestBaseDirs(t *testing.T) { + t.Setenv("HOME", "/home/u") + t.Setenv("XDG_CONFIG_HOME", "") + t.Setenv("XDG_STATE_HOME", "relative/ignored") + t.Setenv("XDG_DATA_HOME", "/data/") + if got := ConfigHome(); got != "/home/u/.config" { + t.Errorf("ConfigHome() = %q", got) + } + if got := StateHome(); got != "/home/u/.local/state" { + t.Errorf("StateHome() = %q, a relative value must be ignored", got) + } + if got := DataHome(); got != "/data" { + t.Errorf("DataHome() = %q", got) + } +} + +// TestHomeTrailingSlash is item H: Home() must clean its result, or a +// trailing slash from $HOME breaks Abbrev's prefix check. +func TestHomeTrailingSlash(t *testing.T) { + t.Setenv("HOME", "/home/u/") + if got := Abbrev("/home/u/x"); got != "~/x" { + t.Errorf("Abbrev(/home/u/x) = %q, want ~/x", got) + } + if got := Expand("~/x"); got != "/home/u/x" { + t.Errorf("Expand(~/x) = %q, want /home/u/x", got) + } +} + +func TestExpandAbbrev(t *testing.T) { + t.Setenv("HOME", "/home/u") + expand := map[string]string{ + "~": "/home/u", + "~/d/x": "/home/u/d/x", + "~other/x": "~other/x", + "/abs": "/abs", + "rel/x": "rel/x", + } + for in, want := range expand { + if got := Expand(in); got != want { + t.Errorf("Expand(%q) = %q, want %q", in, got, want) + } + } + abbrev := map[string]string{ + "/home/u": "~", + "/home/u/d/x": "~/d/x", + "/home/ux/y": "/home/ux/y", + "/etc": "/etc", + } + for in, want := range abbrev { + if got := Abbrev(in); got != want { + t.Errorf("Abbrev(%q) = %q, want %q", in, got, want) + } + } +} |
