class Toys::Utils::XDG

A class that provides tools for working with the XDG Base Directory Specification.

This class provides utility methods that locate base directories and search paths for application state, configuration, caches, and other data, according to the [XDG Base Directory Spec version 0.8](specifications.freedesktop.org/basedir-spec/0.8/).

Tools can use the `:xdg` mixin for convenient access to this class.

### Example

require "toys/utils/xdg"

xdg = Toys::Utils::XDG.new

# Get config file paths, in order from most to least inportant
config_files = xdg.lookup_config("my-config.toml")
config_files.each { |path| read_my_config(path) }

### Windows operation

The Spec assumes a unix-like environment, and cannot be applied directly to Windows without modification. In general, this class will function on Windows, but with the following caveats:

*   All file paths must use Windows-style absolute paths, beginning with
    the drive letter.
*   Environment variables that can contain multiple paths (`XDG_*_DIRS`)
    use the Windows path delimiter (`;`) rather than the unix path
    delimiter (`:`).
*   Defaults for home directories (`XDG_*_HOME`) will follow unix
    conventions, using subdirectories under the user's profile directory
    rather than the Windows known folder paths.
*   Defaults for search paths (`XDG_*_DIRS`) will be empty and will not
    use the Windows known folder paths.

Public Class Methods

new(env: ::ENV) click to toggle source

Create an instance of XDG.

@param env [Hash{String=>String}] the environment variables. Normally,

you can omit this argument, as it will default to `::ENV`.
# File lib/toys/utils/xdg.rb, line 50
def initialize(env: ::ENV)
  @env = env
end

Public Instance Methods

cache_home() click to toggle source

Returns the absolute path to the single base directory relative to which user-specific non-essential (cached) data should be written. Corresponds to the value of the `$XDG_CACHE_HOME` environment variable and its defaults according to the XDG Base Directory Spec.

@return [String]

# File lib/toys/utils/xdg.rb, line 109
def cache_home
  @cache_home ||= validate_dir_env("XDG_CACHE_HOME") || ::File.join(home_dir, ".cache")
end
config_dirs() click to toggle source

Returns the set of preference ordered base directories relative to which configuration files should be searched, as an array of absolute paths. The array is ordered from most to least important, and does not include the config home directory. Corresponds to the value of the `$XDG_CONFIG_DIRS` environment variable and its defaults according to the XDG Base Directory Spec.

@return [Array<String>]

# File lib/toys/utils/xdg.rb, line 150
def config_dirs
  @config_dirs ||= validate_dirs_env("XDG_CONFIG_DIRS") ||
                   validate_dirs(["/etc/xdg"]) || []
end
config_home() click to toggle source

Returns the absolute path to the single base directory relative to which user-specific configuration files should be written. Corresponds to the value of the `$XDG_CONFIG_HOME` environment variable and its defaults according to the XDG Base Directory Spec.

@return [String]

# File lib/toys/utils/xdg.rb, line 84
def config_home
  @config_home ||= validate_dir_env("XDG_CONFIG_HOME") || ::File.join(home_dir, ".config")
end
data_dirs() click to toggle source

Returns the set of preference ordered base directories relative to which data files should be searched, as an array of absolute paths. The array is ordered from most to least important, and does not include the data home directory. Corresponds to the value of the `$XDG_DATA_DIRS` environment variable and its defaults according to the XDG Base Directory Spec.

@return [Array<String>]

# File lib/toys/utils/xdg.rb, line 135
def data_dirs
  @data_dirs ||= validate_dirs_env("XDG_DATA_DIRS") ||
                 validate_dirs(["/usr/local/share", "/usr/share"]) || []
end
data_home() click to toggle source

Returns the absolute path to the single base directory relative to which user-specific data files should be written. Corresponds to the value of the `$XDG_DATA_HOME` environment variable and its defaults according to the XDG Base Directory Spec.

@return [String]

# File lib/toys/utils/xdg.rb, line 71
def data_home
  @data_home ||=
    validate_dir_env("XDG_DATA_HOME") || ::File.join(home_dir, ".local", "share")
end
ensure_cache_subdir(path) click to toggle source

Returns the absolute path to a directory under {#cache_home}, creating it if it doesn't already exist.

@param path [String] The relative path to the subdir within the base

cache directory.

@return [String] The absolute path to the subdir. @raise [Errno::EEXIST] If a non-directory already exists there

# File lib/toys/utils/xdg.rb, line 253
def ensure_cache_subdir(path)
  ensure_subdir_internal(cache_home, path)
end
ensure_config_subdir(path) click to toggle source

Returns the absolute path to a directory under {#config_home}, creating it if it doesn't already exist.

@param path [String] The relative path to the subdir within the base

config directory.

@return [String] The absolute path to the subdir. @raise [Errno::EEXIST] If a non-directory already exists there

# File lib/toys/utils/xdg.rb, line 227
def ensure_config_subdir(path)
  ensure_subdir_internal(config_home, path)
end
ensure_data_subdir(path) click to toggle source

Returns the absolute path to a directory under {#data_home}, creating it if it doesn't already exist.

@param path [String] The relative path to the subdir within the base

data directory.

@return [String] The absolute path to the subdir. @raise [Errno::EEXIST] If a non-directory already exists there

# File lib/toys/utils/xdg.rb, line 214
def ensure_data_subdir(path)
  ensure_subdir_internal(data_home, path)
end
ensure_state_subdir(path) click to toggle source

Returns the absolute path to a directory under {#state_home}, creating it if it doesn't already exist.

@param path [String] The relative path to the subdir within the base

state directory.

@return [String] The absolute path to the subdir. @raise [Errno::EEXIST] If a non-directory already exists there

# File lib/toys/utils/xdg.rb, line 240
def ensure_state_subdir(path)
  ensure_subdir_internal(state_home, path)
end
executable_home() click to toggle source

Returns the absolute path to the single base directory relative to which user-specific executable files may be written. Returns the value of `$HOME/.local/bin` as specified by the XDG Base Directory Spec.

@return [String]

# File lib/toys/utils/xdg.rb, line 121
def executable_home
  @executable_home ||= ::File.join(home_dir, ".local", "bin")
end
home_dir() click to toggle source

Returns the absolute path to the current user's home directory.

@return [String]

# File lib/toys/utils/xdg.rb, line 59
def home_dir
  @home_dir ||= validate_dir_env("HOME") || ::Dir.home
end
lookup_config(path, type: :file) click to toggle source

Searches the config directories for an object with the given relative path, and returns an array of absolute paths to all objects found in the config directories (i.e. in {#config_dirs} or {#config_home}), in order from most to least important.

@param path [String] Relative path of the object to search for @param type [String,Symbol,Array<String,Symbol>] The type(s) of objects

to find. You can specify any of the types defined by
[File::Stat#ftype](https://ruby-doc.org/core/File/Stat.html#method-i-ftype),
such as `file` or `directory`, or the special type `any`. Types can
be specified as strings or the  corresponding symbols. If this
argument is not provided, the default of `file` is used.

@return [Array<String>]

# File lib/toys/utils/xdg.rb, line 201
def lookup_config(path, type: :file)
  lookup_internal([config_home] + config_dirs, path, type)
end
lookup_data(path, type: :file) click to toggle source

Searches the data directories for an object with the given relative path, and returns an array of absolute paths to all objects found in the data directories (i.e. in {#data_dirs} or {#data_home}), in order from most to least important.

@param path [String] Relative path of the object to search for @param type [String,Symbol,Array<String,Symbol>] The type(s) of objects

to find. You can specify any of the types defined by
[File::Stat#ftype](https://ruby-doc.org/core/File/Stat.html#method-i-ftype),
such as `file` or `directory`, or the special type `any`. Types can
be specified as strings or the  corresponding symbols. If this
argument is not provided, the default of `file` is used.

@return [Array<String>]

# File lib/toys/utils/xdg.rb, line 182
def lookup_data(path, type: :file)
  lookup_internal([data_home] + data_dirs, path, type)
end
runtime_dir() click to toggle source

Returns the absolute path to the single base directory relative to which user-specific runtime files and other file objects should be placed. May return `nil` if no such directory could be determined.

@return [String,nil]

# File lib/toys/utils/xdg.rb, line 162
def runtime_dir
  @runtime_dir = validate_dir_env("XDG_RUNTIME_DIR") unless defined? @runtime_dir
  @runtime_dir
end
state_home() click to toggle source

Returns the absolute path to the single base directory relative to which user-specific state files should be written. Corresponds to the value of the `$XDG_STATE_HOME` environment variable and its defaults according to the XDG Base Directory Spec.

@return [String]

# File lib/toys/utils/xdg.rb, line 96
def state_home
  @state_home ||=
    validate_dir_env("XDG_STATE_HOME") || ::File.join(home_dir, ".local", "state")
end

Private Instance Methods

ensure_subdir_internal(base_dir, path) click to toggle source
# File lib/toys/utils/xdg.rb, line 286
def ensure_subdir_internal(base_dir, path)
  path = ::File.join(base_dir, path)
  ::FileUtils.mkdir_p(path, mode: 0o700)
  path
end
lookup_internal(dirs, path, types) click to toggle source
# File lib/toys/utils/xdg.rb, line 273
def lookup_internal(dirs, path, types)
  results = []
  types = Array(types).map(&:to_s)
  dirs.each do |dir|
    to_check = ::File.join(dir, path)
    stat = ::File.stat(to_check) rescue nil # rubocop:disable Style/RescueModifier
    if stat&.readable? && (types.include?("any") || types.include?(stat.ftype))
      results << to_check
    end
  end
  results
end
validate_dir_env(name) click to toggle source
# File lib/toys/utils/xdg.rb, line 259
def validate_dir_env(name)
  path = @env[name].to_s
  !path.empty? && Compat.absolute_path?(path) ? path : nil
end
validate_dirs(paths) click to toggle source
# File lib/toys/utils/xdg.rb, line 268
def validate_dirs(paths)
  paths = paths.find_all { |path| Compat.absolute_path?(path) }
  paths.empty? ? nil : paths
end
validate_dirs_env(name) click to toggle source
# File lib/toys/utils/xdg.rb, line 264
def validate_dirs_env(name)
  validate_dirs(@env[name].to_s.split(::File::PATH_SEPARATOR))
end