module XDG # Base Directory Standard class BaseDir # Try to get information from Ruby's install configuration. require 'rbconfig' sysconfdir = ::RbConfig::CONFIG['sysconfdir'] || '/etc' datadir = ::RbConfig::CONFIG['datadir'] || '/usr/share' # Standard defaults for locations. DEFAULTS = { 'XDG_DATA_HOME' => ['~/.local/share'], 'XDG_DATA_DIRS' => ['/usr/local/share', datadir], 'XDG_CONFIG_HOME' => ['~/.config'], 'XDG_CONFIG_DIRS' => [File.join(sysconfdir,'xdg'), sysconfdir], 'XDG_CACHE_HOME' => ['~/.cache'], 'XDG_CACHE_DIRS' => ['/tmp'] } # BaseDir iterates over directory paths. include Enumerable # Shortcut for `BaseDir.new`. def self.[](*env) new(*env) end # Initialize new instance of BaseDir class. def initialize(*env) @environment_variables = [] env.each do |v| v = v.to_s.upcase if v !~ /_/ @environment_variables << 'XDG_' + v + '_HOME' @environment_variables << 'XDG_' + v + '_DIRS' else @environment_variables << 'XDG_' + v end end end # The environment variables being referenced. # # @return [Array] list of XDG environment variable names def environment_variables @environment_variables end # The environment setting, or it's equivalent when the BaseDir # is a combination of environment variables. # # @return [String] evnironment vsetting def environment environment_variables.map{ |v| ENV[v] }.join(':') end # This is same as #environment, but also includes default values. # # @return [String] envinronment value. def environment_with_defaults environment_variables.map{ |v| ENV[v] || DEFAULTS[v] }.join(':') end # Shortcut for #environment_with_defaults. alias_method :env, :environment_with_defaults # Returns a complete list of expanded directories. # # @return [Array<String>] expanded directory list def to_a environment_variables.map do |v| if paths = ENV[v] dirs = paths.split(/[:;]/) else dirs = DEFAULTS[v] end dirs.map{ |path| expand(path) } end.flatten end # BaseDir is essentially an array. alias_method :to_ary, :to_a # Number of directory paths. def size to_a.size end # Iterate of each directory. def each(&block) to_a.each(&block) end # Returns an *unexpanded* list of directories. # # @return [Array<String>] unexpanded directory list def list environment_variables.map do |v| if paths = ENV[v] dirs = paths.split(/[:;]/) else dirs = DEFAULTS[v] end if subdirectory dirs.map{ |path| File.join(path, subdirectory) } else dirs end end.flatten end # List of directories as Pathanme objects. # # @return [Array<Pathname>] list of directories as Pathname objects def paths map{ |dir| Pathname.new(dir) } end # Returns the first directory expanded. Since a environment settings # like `*_HOME` will only have one directory entry, this definition # of #to_s makes utilizing those more convenient. # # @return [String] directory def to_s to_a.first end # The first directory converted to a Pathname object. # # @return [Pathname] pathname of first directory def to_path Pathname.new(to_a.first) end # The common subdirectory. attr :subdirectory # Set subdirectory to be applied to all paths. def subdirectory=(path) @subdirectory = path.to_s end # Set subdirectory to be applied to all paths and return `self`. # # @return [BaseDir] self def with_subdirectory(path) @subdirectory = path if path self end # Return array of matching files or directories # in any of the resource locations, starting with # the home directory and searching outward into # system directories. # # Unlike #select, this doesn't take a block and each # additional glob argument is treated as a logical-or. # # XDG[:DATA].glob("stick/*.rb", "stick/*.yaml") # def glob(*glob_and_flags) glob, flags = *parse_arguments(*glob_and_flags) find = [] to_a.each do |dir| glob.each do |pattern| find.concat(Dir.glob(File.join(dir, pattern), flags)) end end find.uniq end # Return array of matching files or directories # in any of the resource locations, starting with # the home directory and searching outward into # system directories. # # String parameters are joined into a pathname # while Integers and Symbols treated as flags. # # For example, the following are equivalent: # # XDG::BaseDir[:DATA,:HOME].select('stick/units', File::FNM_CASEFOLD) # # XDG::BaseDir[:DATA,:HOME].select('stick', 'units', :casefold) # def select(*glob_and_flags, &block) glob, flag = *parse_arguments(*glob_and_flags) find = [] to_a.each do |dir| path = File.join(dir, *glob) hits = Dir.glob(path, flag) hits = hits.select(&block) if block_given? find.concat(hits) end find.uniq end # Find a file or directory. This works just like #select # except that it returns the first match found. # # TODO: It would be more efficient to traverse the dirs and use #fnmatch. def find(*glob_and_flags, &block) glob, flag = *parse_arguments(*glob_and_flags) find = nil to_a.each do |dir| path = File.join(dir, *glob) hits = Dir.glob(path, flag) hits = hits.select(&block) if block_given? find = hits.first break if find end find end private def parse_arguments(*glob_and_flags) glob, flags = *glob_and_flags.partition{ |e| String===e } glob = ['**/*'] if glob.empty? flag = flags.inject(0) do |m, f| if Symbol === f m + File::const_get("FNM_#{f.to_s.upcase}") else m + f.to_i end end return glob, flag end # def expand(path) if subdirectory File.expand_path(File.join(path, subdirectory)) else File.expand_path(path) end end # If Pathname is referenced the library is automatically loaded. def self.const_missing(const) if const == :Pathname require 'pathname' ::Pathname else super(const) end end end end # Copyright (c) 2008 Rubyworks