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
}