diff --git a/README.md b/README.md index 7d66215..2f1a67b 100644 --- a/README.md +++ b/README.md @@ -41,8 +41,9 @@ Provides an implementation of the [XDG Base Directory Specification](https://spe The specification defines a set of standard paths for storing application files, including data and configuration files. For portability and flexibility reasons, applications should use the XDG defined locations instead of hardcoding paths. -The package also includes the locations of well known [user directories](https://wiki.archlinux.org/index.php/XDG_user_directories), as well as -other common directories such as fonts and applications. + +The package also includes the locations of well known [user directories](https://wiki.archlinux.org/index.php/XDG_user_directories), +support for the non-standard `XDG_BIN_HOME` directory, as well as other common directories such as fonts and applications. The current implementation supports **most flavors of Unix**, **Windows**, **macOS** and **Plan 9**. On Windows, where XDG environment variables are not usually set, the package uses [Known Folders](https://docs.microsoft.com/en-us/windows/win32/shell/known-folders) @@ -79,6 +80,7 @@ Sensible fallback locations are used for the folders which are not set. | XDG_STATE_HOME | ~/.local/state | ~/Library/Application Support | $home/lib/state | | XDG_CACHE_HOME | ~/.cache | ~/Library/Caches | $home/lib/cache | | XDG_RUNTIME_DIR | /run/user/UID | ~/Library/Application Support | /tmp | +| XDG_BIN_HOME | ~/.local/bin | ~/.local/bin | $home/bin | @@ -95,6 +97,7 @@ Sensible fallback locations are used for the folders which are not set. | XDG_STATE_HOME | LocalAppData | %LOCALAPPDATA% | | XDG_CACHE_HOME | LocalAppData\cache | %LOCALAPPDATA%\cache | | XDG_RUNTIME_DIR | LocalAppData | %LOCALAPPDATA% | +| XDG_BIN_HOME | UserProgramFiles | %LOCALAPPDATA%/Programs | @@ -163,11 +166,11 @@ as shown in the following tables. Microsoft Windows
-| |

Known Folder(s)

|

Fallback(s)

| -| :-----------------------------------------------------------: | :--------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------: | -| Home | Profile | %USERPROFILE% | -| Applications | Programs
CommonPrograms | %APPDATA%\Microsoft\Windows\Start Menu\Programs
%ProgramData%\Microsoft\Windows\Start Menu\Programs | -| Fonts | Fonts | %SystemRoot%\Fonts
%LOCALAPPDATA%\Microsoft\Windows\Fonts | +| |

Known Folder(s)

|

Fallback(s)

| +| :-----------------------------------------------------------: | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| Home | Profile | %USERPROFILE% | +| Applications | Programs
CommonPrograms
ProgramFiles
ProgramFilesCommon
UserProgramFiles
UserProgramFilesCommon | %APPDATA%\Microsoft\Windows\Start Menu\Programs
%ProgramData%\Microsoft\Windows\Start Menu\Programs
%ProgramFiles%
%ProgramFiles%\Common Files
%LOCALAPPDATA%\Programs
%LOCALAPPDATA%\Programs\Common| +| Fonts | Fonts | %SystemRoot%\Fonts
%LOCALAPPDATA%\Microsoft\Windows\Fonts | @@ -193,6 +196,7 @@ func main() { log.Println("Home state directory:", xdg.StateHome) log.Println("Cache directory:", xdg.CacheHome) log.Println("Runtime directory:", xdg.RuntimeDir) + log.Println("Home binaries directory:", xdg.BinHome) // Other common directories. log.Println("Home directory:", xdg.Home) diff --git a/base_dirs.go b/base_dirs.go index a8a3fd5..fc482e2 100644 --- a/base_dirs.go +++ b/base_dirs.go @@ -11,6 +11,9 @@ const ( envStateHome = "XDG_STATE_HOME" envCacheHome = "XDG_CACHE_HOME" envRuntimeDir = "XDG_RUNTIME_DIR" + + // Non-standard. + envBinHome = "XDG_BIN_HOME" ) type baseDirectories struct { @@ -22,7 +25,8 @@ type baseDirectories struct { cacheHome string runtime string - // Non-standard directories. + // Non-standard. + binHome string fonts []string applications []string } diff --git a/doc.go b/doc.go index 7747b18..ddbaae4 100644 --- a/doc.go +++ b/doc.go @@ -16,9 +16,10 @@ The current implementation supports most flavors of Unix, Windows, Mac OS and Pl For more information regarding the Windows Known Folders see: https://docs.microsoft.com/en-us/windows/win32/shell/known-folders -Usage +# Usage XDG Base Directory + package main import ( @@ -36,6 +37,7 @@ XDG Base Directory log.Println("Home state directory:", xdg.StateHome) log.Println("Cache directory:", xdg.CacheHome) log.Println("Runtime directory:", xdg.RuntimeDir) + log.Println("Home binaries directory:", xdg.BinHome) // Other common directories. log.Println("Home directory:", xdg.Home) @@ -76,6 +78,7 @@ XDG Base Directory } XDG user directories + package main import ( diff --git a/paths_darwin.go b/paths_darwin.go index ccf903b..2d9d60c 100644 --- a/paths_darwin.go +++ b/paths_darwin.go @@ -30,6 +30,8 @@ func initBaseDirs(home string) { baseDirs.runtime = pathutil.EnvPath(envRuntimeDir, homeAppSupport) // Initialize non-standard directories. + baseDirs.binHome = pathutil.EnvPath(envBinHome, filepath.Join(home, ".local", "bin")) + baseDirs.applications = []string{ "/Applications", } diff --git a/paths_darwin_test.go b/paths_darwin_test.go index 13af1bd..95b1222 100644 --- a/paths_darwin_test.go +++ b/paths_darwin_test.go @@ -56,6 +56,11 @@ func TestDefaultBaseDirs(t *testing.T) { expected: homeAppSupport, actual: &xdg.RuntimeDir, }, + &envSample{ + name: "XDG_BIN_HOME", + expected: filepath.Join(home, ".local", "bin"), + actual: &xdg.BinHome, + }, &envSample{ name: "XDG_APPLICATION_DIRS", expected: []string{ @@ -87,10 +92,13 @@ func TestCustomBaseDirs(t *testing.T) { actual: &xdg.DataHome, }, &envSample{ - name: "XDG_DATA_DIRS", - value: "~/Library/data:/Library/Application Support", - expected: []string{filepath.Join(home, "Library/data"), "/Library/Application Support"}, - actual: &xdg.DataDirs, + name: "XDG_DATA_DIRS", + value: "~/Library/data:/Library/Application Support", + expected: []string{ + filepath.Join(home, "Library/data"), + "/Library/Application Support", + }, + actual: &xdg.DataDirs, }, &envSample{ name: "XDG_CONFIG_HOME", @@ -99,10 +107,13 @@ func TestCustomBaseDirs(t *testing.T) { actual: &xdg.ConfigHome, }, &envSample{ - name: "XDG_CONFIG_DIRS", - value: "~/Library/config:/Library/Preferences", - expected: []string{filepath.Join(home, "Library/config"), "/Library/Preferences"}, - actual: &xdg.ConfigDirs, + name: "XDG_CONFIG_DIRS", + value: "~/Library/config:/Library/Preferences", + expected: []string{ + filepath.Join(home, "Library/config"), + "/Library/Preferences", + }, + actual: &xdg.ConfigDirs, }, &envSample{ name: "XDG_STATE_HOME", @@ -122,6 +133,12 @@ func TestCustomBaseDirs(t *testing.T) { expected: filepath.Join(home, "Library/runtime"), actual: &xdg.RuntimeDir, }, + &envSample{ + name: "XDG_BIN_HOME", + value: "~/Library/bin", + expected: filepath.Join(home, "Library/bin"), + actual: &xdg.BinHome, + }, ) } diff --git a/paths_plan9.go b/paths_plan9.go index 0cab83c..d0c36ba 100644 --- a/paths_plan9.go +++ b/paths_plan9.go @@ -26,6 +26,8 @@ func initBaseDirs(home string) { baseDirs.runtime = pathutil.EnvPath(envRuntimeDir, "/tmp") // Initialize non-standard directories. + baseDirs.binHome = pathutil.EnvPath(envBinHome, filepath.Join(home, "bin")) + baseDirs.applications = []string{ filepath.Join(home, "bin"), "/bin", diff --git a/paths_plan9_test.go b/paths_plan9_test.go index 9fff760..7ee3700 100644 --- a/paths_plan9_test.go +++ b/paths_plan9_test.go @@ -50,6 +50,11 @@ func TestDefaultBaseDirs(t *testing.T) { expected: "/tmp", actual: &xdg.RuntimeDir, }, + &envSample{ + name: "XDG_BIN_HOME", + expected: filepath.Join(home, "bin"), + actual: &xdg.BinHome, + }, &envSample{ name: "XDG_APPLICATION_DIRS", expected: []string{ @@ -115,6 +120,12 @@ func TestCustomBaseDirs(t *testing.T) { expected: filepath.Join(homeLib, "runtime"), actual: &xdg.RuntimeDir, }, + &envSample{ + name: "XDG_BIN_HOME", + value: filepath.Join(homeLib, "bin"), + expected: filepath.Join(homeLib, "bin"), + actual: &xdg.BinHome, + }, ) } diff --git a/paths_unix.go b/paths_unix.go index dd98839..8a07809 100644 --- a/paths_unix.go +++ b/paths_unix.go @@ -27,6 +27,8 @@ func initBaseDirs(home string) { baseDirs.runtime = pathutil.EnvPath(envRuntimeDir, filepath.Join("/run/user", strconv.Itoa(os.Getuid()))) // Initialize non-standard directories. + baseDirs.binHome = pathutil.EnvPath(envBinHome, filepath.Join(home, ".local", "bin")) + appDirs := []string{ filepath.Join(baseDirs.dataHome, "applications"), filepath.Join(home, ".local/share/applications"), diff --git a/paths_unix_test.go b/paths_unix_test.go index f0d21ad..38c44b1 100644 --- a/paths_unix_test.go +++ b/paths_unix_test.go @@ -52,6 +52,11 @@ func TestDefaultBaseDirs(t *testing.T) { expected: filepath.Join("/run/user", strconv.Itoa(os.Getuid())), actual: &xdg.RuntimeDir, }, + &envSample{ + name: "XDG_BIN_HOME", + expected: filepath.Join(home, ".local", "bin"), + actual: &xdg.BinHome, + }, &envSample{ name: "XDG_APPLICATION_DIRS", expected: []string{ @@ -85,10 +90,13 @@ func TestCustomBaseDirs(t *testing.T) { actual: &xdg.DataHome, }, &envSample{ - name: "XDG_DATA_DIRS", - value: "~/.local/data:/usr/share", - expected: []string{filepath.Join(home, ".local/data"), "/usr/share"}, - actual: &xdg.DataDirs, + name: "XDG_DATA_DIRS", + value: "~/.local/data:/usr/share", + expected: []string{ + filepath.Join(home, ".local/data"), + "/usr/share", + }, + actual: &xdg.DataDirs, }, &envSample{ name: "XDG_CONFIG_HOME", @@ -97,10 +105,13 @@ func TestCustomBaseDirs(t *testing.T) { actual: &xdg.ConfigHome, }, &envSample{ - name: "XDG_CONFIG_DIRS", - value: "~/.local/config:/etc/xdg", - expected: []string{filepath.Join(home, ".local/config"), "/etc/xdg"}, - actual: &xdg.ConfigDirs, + name: "XDG_CONFIG_DIRS", + value: "~/.local/config:/etc/xdg", + expected: []string{ + filepath.Join(home, ".local/config"), + "/etc/xdg", + }, + actual: &xdg.ConfigDirs, }, &envSample{ name: "XDG_STATE_HOME", @@ -120,6 +131,12 @@ func TestCustomBaseDirs(t *testing.T) { expected: filepath.Join(home, ".local/runtime"), actual: &xdg.RuntimeDir, }, + &envSample{ + name: "XDG_BIN_HOME", + value: "~/bin", + expected: filepath.Join(home, "bin"), + actual: &xdg.BinHome, + }, ) } diff --git a/paths_windows.go b/paths_windows.go index eaac2d3..bb80819 100644 --- a/paths_windows.go +++ b/paths_windows.go @@ -25,10 +25,17 @@ func initBaseDirs(home string, kf *knownFolders) { baseDirs.runtime = pathutil.EnvPath(envRuntimeDir, kf.localAppData) // Initialize non-standard directories. + baseDirs.binHome = pathutil.EnvPath(envBinHome, kf.userProgramFiles) + baseDirs.applications = []string{ kf.programs, kf.commonPrograms, + kf.programFiles, + kf.programFilesCommon, + kf.userProgramFiles, + kf.userProgramFilesCommon, } + baseDirs.fonts = []string{ kf.fonts, filepath.Join(kf.localAppData, "Microsoft", "Windows", "Fonts"), @@ -47,24 +54,28 @@ func initUserDirs(home string, kf *knownFolders) { } type knownFolders struct { - systemDrive string - systemRoot string - programData string - userProfile string - userProfiles string - roamingAppData string - localAppData string - desktop string - downloads string - documents string - music string - pictures string - videos string - templates string - public string - fonts string - programs string - commonPrograms string + systemDrive string + systemRoot string + programData string + userProfile string + userProfiles string + roamingAppData string + localAppData string + desktop string + downloads string + documents string + music string + pictures string + videos string + templates string + public string + fonts string + programs string + commonPrograms string + programFiles string + programFilesCommon string + userProgramFiles string + userProgramFilesCommon string } func initKnownFolders(home string) *knownFolders { @@ -156,6 +167,30 @@ func initKnownFolders(home string) *knownFolders { nil, []string{filepath.Join(kf.programData, "Microsoft", "Windows", "Start Menu", "Programs")}, ) + kf.programFiles = pathutil.KnownFolder( + windows.FOLDERID_ProgramFiles, + []string{"ProgramFiles"}, + []string{filepath.Join(kf.systemDrive, "Program Files")}, + ) + kf.programFilesCommon = pathutil.KnownFolder( + windows.FOLDERID_ProgramFilesCommon, + nil, + []string{filepath.Join(kf.programFiles, "Common Files")}, + ) + kf.userProgramFiles = pathutil.KnownFolder( + windows.FOLDERID_UserProgramFiles, + nil, + []string{ + filepath.Join(kf.localAppData, "Programs"), + }, + ) + kf.userProgramFilesCommon = pathutil.KnownFolder( + windows.FOLDERID_UserProgramFilesCommon, + nil, + []string{ + filepath.Join(kf.userProgramFiles, "Common"), + }, + ) return kf } diff --git a/paths_windows_test.go b/paths_windows_test.go index 8407686..9c64455 100644 --- a/paths_windows_test.go +++ b/paths_windows_test.go @@ -19,6 +19,7 @@ func TestDefaultBaseDirs(t *testing.T) { localAppData := filepath.Join(home, "AppData", "Local") systemRoot := filepath.Join(systemDrive, "Windows") programData := filepath.Join(systemDrive, "ProgramData") + programFiles := filepath.Join(systemDrive, "Program Files") envSamples := []*envSample{ { @@ -56,11 +57,20 @@ func TestDefaultBaseDirs(t *testing.T) { expected: localAppData, actual: &xdg.RuntimeDir, }, + { + name: "XDG_BIN_HOME", + expected: filepath.Join(localAppData, "Programs"), + actual: &xdg.BinHome, + }, { name: "XDG_APPLICATION_DIRS", expected: []string{ filepath.Join(roamingAppData, "Microsoft", "Windows", "Start Menu", "Programs"), filepath.Join(programData, "Microsoft", "Windows", "Start Menu", "Programs"), + programFiles, + filepath.Join(programFiles, "Common Files"), + filepath.Join(localAppData, "Programs"), + filepath.Join(localAppData, "Programs", "Common"), }, actual: &xdg.ApplicationDirs, }, @@ -105,10 +115,13 @@ func TestCustomBaseDirs(t *testing.T) { actual: &xdg.DataHome, }, &envSample{ - name: "XDG_DATA_DIRS", - value: fmt.Sprintf("%s;%s", filepath.Join(localAppData, "Data"), filepath.Join(roamingAppData, "Data")), - expected: []string{filepath.Join(localAppData, "Data"), filepath.Join(roamingAppData, "Data")}, - actual: &xdg.DataDirs, + name: "XDG_DATA_DIRS", + value: fmt.Sprintf("%s;%s", filepath.Join(localAppData, "Data"), filepath.Join(roamingAppData, "Data")), + expected: []string{ + filepath.Join(localAppData, "Data"), + filepath.Join(roamingAppData, "Data"), + }, + actual: &xdg.DataDirs, }, &envSample{ name: "XDG_CONFIG_HOME", @@ -117,10 +130,13 @@ func TestCustomBaseDirs(t *testing.T) { actual: &xdg.ConfigHome, }, &envSample{ - name: "XDG_CONFIG_DIRS", - value: fmt.Sprintf("%s;%s", filepath.Join(localAppData, "Config"), filepath.Join(roamingAppData, "Config")), - expected: []string{filepath.Join(localAppData, "Config"), filepath.Join(roamingAppData, "Config")}, - actual: &xdg.ConfigDirs, + name: "XDG_CONFIG_DIRS", + value: fmt.Sprintf("%s;%s", filepath.Join(localAppData, "Config"), filepath.Join(roamingAppData, "Config")), + expected: []string{ + filepath.Join(localAppData, "Config"), + filepath.Join(roamingAppData, "Config"), + }, + actual: &xdg.ConfigDirs, }, &envSample{ name: "XDG_STATE_HOME", @@ -140,6 +156,12 @@ func TestCustomBaseDirs(t *testing.T) { expected: filepath.Join(programData, "Runtime"), actual: &xdg.RuntimeDir, }, + &envSample{ + name: "XDG_BIN_HOME", + value: filepath.Join(programData, "Programs"), + expected: filepath.Join(programData, "Programs"), + actual: &xdg.BinHome, + }, ) require.NoError(t, os.Setenv("APPDATA", envRoamingAppData)) diff --git a/xdg.go b/xdg.go index 32574ce..654d1d7 100644 --- a/xdg.go +++ b/xdg.go @@ -67,6 +67,12 @@ var ( // swapped out to disk. RuntimeDir string + // BinHome defines the base directory relative to which user-specific + // binary files should be written. This directory is defined by + // the non-standard $XDG_BIN_HOME environment variable. If the variable is + // not set, a default equal to $HOME/.local/bin should be used. + BinHome string + // UserDirs defines the locations of well known user directories. UserDirs UserDirectories @@ -104,6 +110,7 @@ func Reload() { RuntimeDir = baseDirs.runtime // Set non-standard directories. + BinHome = baseDirs.binHome FontDirs = baseDirs.fonts ApplicationDirs = baseDirs.applications }