-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
OS-specific directory building implementations
The main package will abstract over these, but they are exposed as public helpers for applications with unusual needs that our main OS-agnostic abstraction cannot meet.
- Loading branch information
1 parent
fb3d831
commit e020e2c
Showing
13 changed files
with
306 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,5 @@ | ||
module github.com/apparentlymart/go-userdirs | ||
|
||
go 1.12 | ||
|
||
require golang.org/x/sys v0.0.0-20190509141414-a5b02f93d862 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
golang.org/x/sys v0.0.0-20190509141414-a5b02f93d862 h1:rM0ROo5vb9AdYJi1110yjWGMej9ITfKddS89P3Fkhug= | ||
golang.org/x/sys v0.0.0-20190509141414-a5b02f93d862/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
package unix | ||
|
||
import ( | ||
"os" | ||
"os/user" | ||
"path/filepath" | ||
) | ||
|
||
// Home returns the home directory for the current process, with the following | ||
// preference order: | ||
// | ||
// - The value of the HOME environment variable, if it is set and contains | ||
// an absolute path. | ||
// - The home directory indicated in the return value of the "Current" | ||
// function in the os/user standard library package, which has | ||
// platform-specific behavior, if it contains an absolute path. | ||
// - If neither of the above yields an absolute path, the string "/". | ||
// | ||
// In practice, POSIX requires the HOME environment variable to be set, so on | ||
// any reasonable system it is that which will be selected. The other | ||
// permutations are fallback behavior for less reasonable systems. | ||
// | ||
// XDG does not permit applications to write directly into the home directory. | ||
// Instead, the paths returned by other functions in this package are | ||
// potentially derived from the home path, if their explicit environment | ||
// variables are not set. | ||
func Home() string { | ||
if homeDir := os.Getenv("HOME"); homeDir != "" { | ||
if filepath.IsAbs(homeDir) { | ||
return homeDir | ||
} | ||
} | ||
|
||
user, err := user.Current() | ||
if err != nil { | ||
if homeDir := user.HomeDir; homeDir != "" { | ||
if filepath.IsAbs(homeDir) { | ||
return homeDir | ||
} | ||
} | ||
} | ||
|
||
// Fallback behavior mimics common choice in other software. | ||
return "/" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
// Package macosbase contains helper functions that construct base paths | ||
// conforming to the Mac OS user-specific file layout guidelines as | ||
// documented in https://developer.apple.com/library/archive/documentation/FileManagement/Conceptual/FileSystemProgrammingGuide/FileSystemOverview/FileSystemOverview.html#//apple_ref/doc/uid/TP40010672-CH2-SW6 . | ||
// | ||
// This package only does path construction, and doesn't depend on any Mac OS | ||
// system APIs, so in principle it can run on other platforms but the results | ||
// it produces in that case are undefined and unlikely to be useful. | ||
package macosbase |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
package macosbase | ||
|
||
import ( | ||
"github.com/apparentlymart/go-userdirs/internal/unix" | ||
) | ||
|
||
func home() string { | ||
return unix.Home() | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
package macosbase | ||
|
||
import ( | ||
"path/filepath" | ||
) | ||
|
||
// ApplicationSupportDir returns the path to the current user's | ||
// "Application Support" library directory. | ||
func ApplicationSupportDir() string { | ||
return filepath.Join(home(), "Library", "Application Support") | ||
} | ||
|
||
// CachesDir returns the path to the current user's "Caches" library directory. | ||
func CachesDir() string { | ||
return filepath.Join(home(), "Library", "Caches") | ||
} | ||
|
||
// FrameworksDir returns the path to the current user's "Frameworks" library directory. | ||
func FrameworksDir() string { | ||
return filepath.Join(home(), "Library", "Frameworks") | ||
} | ||
|
||
// PreferencesDir returns the path to the current user's "Preferences" library directory. | ||
func PreferencesDir() string { | ||
return filepath.Join(home(), "Library", "Preferences") | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
// Package windowsbase contains helper functionality for accessing the | ||
// "Known Folders" shell API functions on Windows systems. | ||
// | ||
// This package calls into Windows system DLLs, so it cannot be used on any | ||
// other platform. | ||
package windowsbase |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
// +build windows | ||
|
||
package windowsbase | ||
|
||
import ( | ||
"syscall" | ||
"unsafe" | ||
|
||
"golang.org/x/sys/windows" | ||
) | ||
|
||
var ( | ||
shell32 = windows.NewLazyDLL("Shell32.dll") | ||
ole32 = windows.NewLazyDLL("Ole32.dll") | ||
procSHGetKnownFolderPath = modShell32.NewProc("SHGetKnownFolderPath") | ||
procCoTaskMemFree = modOle32.NewProc("CoTaskMemFree") | ||
) | ||
|
||
func knownFolderDir(fid *FolderID) (string, error) { | ||
var path uintptr | ||
err := SHGetKnownFolderPath(fid, 0, 0, &path) | ||
if err != nil { | ||
return "", err | ||
} | ||
defer CoTaskMemFree(path) | ||
dir := syscall.UTF16ToString((*[1 << 16]uint16)(unsafe.Pointer(path))[:]) | ||
return dir, nil | ||
} | ||
|
||
func shGetKnownFolderPath(fid *FolderID, dwFlags uint32, hToken syscall.Handle, pszPath *uintptr) (retval error) { | ||
r0, _, _ := procSHGetKnownFolderPath.Call(uintptr(unsafe.Pointer(rfid)), uintptr(dwFlags), uintptr(hToken), uintptr(unsafe.Pointer(pszPath)), 0, 0) | ||
if r0 != 0 { | ||
return syscall.Errno(r0) | ||
} | ||
return nil | ||
} | ||
|
||
func coTaskMemFree(pv uintptr) { | ||
procCoTaskMemFree.Call(uintptr(pv), 0, 0) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
// +build !windows | ||
|
||
package windowsbase | ||
|
||
import ( | ||
"errors" | ||
) | ||
|
||
func knownFolderDir(id *FolderID) (string, error) { | ||
return "", errors.New("cannot use Windows known folders on a non-Windows platform") | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
package windowsbase | ||
|
||
// FolderID is a representation of a known folder id UUID | ||
type FolderID struct { | ||
a uint32 | ||
b uint16 | ||
c uint16 | ||
d [8]byte | ||
} | ||
|
||
var ( | ||
// RoamingAppDataID is the FolderID for the roaming application data folder | ||
RoamingAppDataID = &FolderID{0x3EB685DB, 0x65F9, 0x4CF6, [...]byte{0xA0, 0x3A, 0xE3, 0xEF, 0x65, 0x72, 0x9F, 0x3D}} | ||
|
||
// LocalAppDataID is the FolderID for the local application data folder | ||
LocalAppDataID = &FolderID{0xF1B32785, 0x6FBA, 0x4FCF, [...]byte{0x9D, 0x55, 0x7B, 0x8E, 0x7F, 0x15, 0x70, 0x91}} | ||
) | ||
|
||
// KnownFolderDir returns the absolute path for the given known folder id, or | ||
// returns an error if that is not possible. | ||
func KnownFolderDir(id *FolderID) (string, error) { | ||
return knownFolderDir(id) | ||
} | ||
|
||
// RoamingAppDataDir returns the absolute path for the current user's roaming | ||
// application data directory. | ||
func RoamingAppDataDir() (string, error) { | ||
return KnownFolderDir(RoamingAppDataID) | ||
} | ||
|
||
// LocalAppDataDir returns the absolute path for the current user's local | ||
// application data directory. | ||
func LocalAppDataDir() (string, error) { | ||
return KnownFolderDir(LocalAppDataID) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
// Package xdgbase is an implementation of the XDG Basedir Specification | ||
// version 0.8, as published at https://specifications.freedesktop.org/basedir-spec/basedir-spec-0.8.html . | ||
// | ||
// This package has no checks for the host operating system, so it can in | ||
// principle function on any operating system but in practice XDG conventions | ||
// are followed only on some Unix-like systems, so using this library elsewhere | ||
// would not be very useful and will produce undefined results. | ||
package xdgbase |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
package xdgbase | ||
|
||
import ( | ||
"github.com/apparentlymart/go-userdirs/internal/unix" | ||
) | ||
|
||
func home() string { | ||
return unix.Home() | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,105 @@ | ||
package xdgbase | ||
|
||
import ( | ||
"os" | ||
"path/filepath" | ||
) | ||
|
||
// DataHome returns the value of XDG_DATA_HOME, or the specification-defined | ||
// fallback value of $HOME/.local/share. | ||
func DataHome() string { | ||
return envSingle("XDG_DATA_HOME", func() string { | ||
return filepath.Join(home(), ".local", "share") | ||
}) | ||
} | ||
|
||
// OtherDataDirs returns the values from XDG_DATA_DIRS, or the specification-defined | ||
// fallback values "/usr/local/share/" and "/usr/share/". | ||
func OtherDataDirs() []string { | ||
return envMulti("XDG_DATA_DIRS", func() []string { | ||
return []string{"/usr/local/share/", "/usr/share/"} | ||
}) | ||
} | ||
|
||
// DataDirs returns the combination of DataHome and OtherDataDirs, giving the | ||
// full set of data directories to search, in preference order. | ||
func DataDirs() []string { | ||
ret := make([]string, 0, 3) // default OtherDataDirs has two elements | ||
ret = append(ret, DataHome()) | ||
ret = append(ret, OtherDataDirs()...) | ||
return ret[:len(ret):len(ret)] | ||
} | ||
|
||
// ConfigHome returns the value of XDG_CONFIG_HOME, or the specification-defined | ||
// fallback value of $HOME/.config. | ||
func ConfigHome() string { | ||
return envSingle("XDG_CONFIG_HOME", func() string { | ||
return filepath.Join(home(), ".config") | ||
}) | ||
} | ||
|
||
// OtherConfigDirs returns the values from XDG_CONFIG_DIRS, or the | ||
// specification-defined fallback value "/etc/xdg". | ||
func OtherConfigDirs() []string { | ||
return envMulti("XDG_CONFIG_DIRS", func() []string { | ||
return []string{"/etc/xdg"} | ||
}) | ||
} | ||
|
||
// ConfigDirs returns the combination of ConfigHome and OtherConfigDirs, giving the | ||
// full set of config directories to search, in preference order. | ||
func ConfigDirs() []string { | ||
ret := make([]string, 0, 2) // default OtherConfigDirs has one element | ||
ret = append(ret, ConfigHome()) | ||
ret = append(ret, OtherConfigDirs()...) | ||
return ret[:len(ret):len(ret)] | ||
} | ||
|
||
// CacheHome returns the value of XDG_CACHE_HOME, or the specification-defined | ||
// fallback value of $HOME/.cache. | ||
func CacheHome() string { | ||
return envSingle("XDG_CACHE_HOME", func() string { | ||
return filepath.Join(home(), ".cache") | ||
}) | ||
} | ||
|
||
// MaybeRuntimeDir returns the value of XDG_RUNTIME_DIR, or an empty string if | ||
// it is not set. | ||
// | ||
// Calling applications MUST check that the return value is non-empty before | ||
// using it, because there is no reasonable default behavior when no runtime | ||
// directory is defined. | ||
func MaybeRuntimeDir() string { | ||
return envSingle("XDG_RUNTIME_DIR", func() string { | ||
return "" | ||
}) | ||
} | ||
|
||
func envSingle(name string, fallback func() string) string { | ||
if p := os.Getenv(name); p != "" { | ||
if filepath.IsAbs(p) { | ||
return p | ||
} | ||
} | ||
|
||
return fallback() | ||
} | ||
|
||
func envMulti(name string, fallback func() []string) []string { | ||
if p := os.Getenv(name); p != "" { | ||
parts := filepath.SplitList(p) | ||
// Make sure all of the paths are absolute | ||
for i := len(parts) - 1; i >= 0; i-- { | ||
if !filepath.IsAbs(parts[i]) { | ||
// We'll shift everything after this point in the list | ||
// down so that this element is no longer present. | ||
copy(parts[i:], parts[i+1:]) | ||
parts = parts[:len(parts)-1] | ||
} | ||
} | ||
parts = parts[:len(parts):len(parts)] // hide any extra capacity from the caller | ||
return parts | ||
} | ||
|
||
return fallback() | ||
} |