// 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] }