From 2d33aab91fa42c4a68abe37b66a48c5c003bc6aa Mon Sep 17 00:00:00 2001 From: Sasha Koshka Date: Wed, 24 Apr 2024 16:22:18 -0400 Subject: [PATCH] Add basedir implementation --- assets/icon.svg | 302 +++++++++++++++++++++++++++++++++++++++++++++ basedir/basedir.go | 209 +++++++++++++++++++++++++++++++ go.mod | 3 + 3 files changed, 514 insertions(+) create mode 100644 assets/icon.svg create mode 100644 basedir/basedir.go create mode 100644 go.mod diff --git a/assets/icon.svg b/assets/icon.svg new file mode 100644 index 0000000..b5a8436 --- /dev/null +++ b/assets/icon.svg @@ -0,0 +1,302 @@ + + + +FREEDESKTOP diff --git a/basedir/basedir.go b/basedir/basedir.go new file mode 100644 index 0000000..f2db41a --- /dev/null +++ b/basedir/basedir.go @@ -0,0 +1,209 @@ +// Package basedir implements https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html +package basedir + +import "os" +import "errors" +import "path/filepath" + +// Var is an XDG environment variable. +type Var string; const ( + VarDataHome Var = "XDG_DATA_HOME" + VarConfigHome Var = "XDG_CONFIG_HOME" + VarStateHome Var = "XDG_STATE_HOME" + VarDataDirs Var = "XDG_DATA_DIRS" + VarConfigDirs Var = "XDG_CONFIG_DIRS" + VarCacheHome Var = "XDG_CACHE_HOME" + VarRuntimeDir Var = "XDG_RUNTIME_DIR" +) + +// Value returns the value of the environment variable. +func (v Var) Value () string { + return os.Getenv(string(v)) +} + +// ErrEmpty indicaties a path is empty. +var ErrEmpty = errors.New("path is not absolute") +// ErrNotAbs indicates a path is not absolute. +var ErrNotAbs = errors.New("path is not absolute") + +// Valid returns an error if the path is not absolute (beginning from '/') or if +// it is otherwise invalid. +func Valid (path string) error { + if path == "" { return ErrEmpty } + if !filepath.IsAbs(path) { return ErrNotAbs } + return nil +} + +// DataHome returns the single base directory relative to which user-specific +// data files should be written. +// +// $XDG_DATA_HOME defines the base directory relative to which user-specific +// data files should be stored. If $XDG_DATA_HOME is either not set or empty, +// a default equal to $HOME/.local/share is used. +func DataHome () (string, error) { + return homeDefault(VarDataHome.Value(), ".local/share") +} + +// ConfigHome returns the single base directory relative to which user-specific +// configuration files should be written. +// +// $XDG_CONFIG_HOME defines the base directory relative to which +// user-specific configuration files should be stored. If $XDG_CONFIG_HOME is +// either not set or empty, a default equal to $HOME/.config is used. +func ConfigHome () (string, error) { + return homeDefault(VarConfigHome.Value(), ".config") +} + +// StateHome returns the single base directory relative to which user-specific +// state data should be written. +// +// The $XDG_STATE_HOME contains state data that should persist between +// (application) restarts, but that is not important or portable enough to the +// user that it should be stored in $XDG_DATA_HOME. It may contain: +// - actions history (logs, history, recently used files, …) +// - current state of the application that can be reused on a restart (view, +// layout, open files, undo history, …) +func StateHome () (string, error) { + return homeDefault(VarStateHome.Value(), ".local/state") +} + +// ExecutableHome returns the directory in which user-specific executables may +// be stored, which is $HOME/.local/bin. +func ExecutableHome () (string, error) { + home, err := os.UserHomeDir() + if err != nil { return "", err } + return filepath.Join(home, ".local/bin"), nil +} + +// DataDirs returns the set of preference ordered base directories relative to +// which data files should be searched. +// +// $XDG_DATA_DIRS defines the preference-ordered set of base directories to +// search for data files in addition to the $XDG_DATA_HOME base directory. The +// directories in $XDG_DATA_DIRS should be seperated with a colon ':'. If +// $XDG_DATA_DIRS is either not set or empty, a value equal to +// /usr/local/share/:/usr/share/ is used. +// +// It is reccomended to call AllDataDirs instead of DataDirs for most use cases. +func DataDirs () ([]string, error) { + return listDefault(VarDataDirs.Value(), "/usr/local/share", "/usr/share"), nil +} + +// AllDataDirs returns the result of DataHome in front of DataDirs. +func AllDataDirs () ([]string, error) { + dataHome, err := DataHome() + if err != nil { return nil, err } + dataDirs, err := DataDirs() + if err != nil { return nil, err } + + if dataHome == "" { + return dataDirs, nil + } else { + return append([]string{ dataHome }, dataDirs...), nil + } +} + +// ConfigDirs returns set of preference ordered base directories relative to +// which configuration files should be searched. +// +// $XDG_CONFIG_DIRS defines the preference-ordered set of base directories to +// search for configuration files in addition to the $XDG_CONFIG_HOME base +// directory. The directories in $XDG_CONFIG_DIRS should be seperated with a +// colon ':'. If $XDG_CONFIG_DIRS is either not set or empty, a value equal to +// /etc/xdg is used. +// +// It is reccomended to call AllConfigDirs instead of ConfigDirs for most use +// cases. +func ConfigDirs () ([]string, error) { + return listDefault(VarConfigDirs.Value(), "/etc/xdg"), nil +} + +// AllConfigDirs returns the result of ConfigHome in front of ConfigDirs. +func AllConfigDirs () ([]string, error) { + configHome, err := ConfigHome() + if err != nil { return nil, err } + configDirs, err := ConfigDirs() + if err != nil { return nil, err } + + if configHome == "" { + return configDirs, nil + } else { + return append([]string{ configHome }, configDirs...), nil + } +} + +// CacheHome returns the single base directory relative to which user-specific +// non-essential (cached) data should be written. +// +// $XDG_CACHE_HOME defines the base directory relative to which user-specific +// non-essential data files should be stored. If $XDG_CACHE_HOME is either not +// set or empty, a default equal to $HOME/.cache is used. +func CacheHome () (string, error) { + return homeDefault(VarCacheHome.Value(), ".cache") +} + +// RuntimeDir returns the single base directory relative to which user-specific +// runtime files and other file objects should be placed. +// +// $XDG_RUNTIME_DIR defines the base directory relative to which user-specific +// non-essential runtime files and other file objects (such as sockets, named +// pipes, ...) should be stored. The directory MUST be owned by the user, and +// they MUST be the only one having read and write access to it. Its Unix access +// mode MUST be 0700. +// +// The lifetime of the directory MUST be bound to the user being logged in. It +// MUST be created when the user first logs in and if the user fully logs out +// the directory MUST be removed. If the user logs in more than once they should +// get pointed to the same directory, and it is mandatory that the directory +// continues to exist from their first login to their last logout on the system, +// and not removed in between. Files in the directory MUST not survive reboot or +// a full logout/login cycle. +// +// The directory MUST be on a local file system and not shared with any other +// system. The directory MUST by fully-featured by the standards of the +// operating system. More specifically, on Unix-like operating systems AF_UNIX +// sockets, symbolic links, hard links, proper permissions, file locking, sparse +// files, memory mapping, file change notifications, a reliable hard link count +// must be supported, and no restrictions on the file name character set should +// be imposed. Files in this directory MAY be subjected to periodic clean-up. To +// ensure that your files are not removed, they should have their access time +// timestamp modified at least once every 6 hours of monotonic time or the +// 'sticky' bit should be set on the file. +// +// If this function returns an error, applications should fall back to a +// replacement directory with similar capabilities and print a warning message. +// Applications should use this directory for communication and synchronization +// purposes and should not place larger files in it, since it might reside in +// runtime memory and cannot necessarily be swapped out to disk. +func RuntimeDir () (string, error) { + value := VarRuntimeDir.Value() + err := Valid(value) + if err != nil { return "", err } + return value, nil +} + +func homeDefault (value, defaul string) (string, error) { + if Valid(value) == nil { + return value, nil + } else { + home, err := os.UserHomeDir() + if err != nil { return "", err } + return filepath.Join(home, defaul), nil + } +} + +func listDefault (list string, defaul ...string) []string { + if list == "" { return defaul } + + envDirs := filepath.SplitList(list) + dirs := make([]string, len(envDirs)) + + index := 0 + for _, dir := range envDirs { + if Valid(dir) != nil { + dirs[index] = dir + index ++ + } + } + return dirs[:index] +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..2afbc2e --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module git.tebibyte.media/tomo/xdg + +go 1.19