lib/tap/env.rb in tap-0.19.0 vs lib/tap/env.rb in tap-1.3.0

- old
+ new

@@ -1,851 +1,205 @@ -require 'tap/root' +require 'tap/signals' +require 'tap/env/cache' require 'tap/env/constant' -require 'tap/env/context' -require 'tap/env/manifest' -require 'tap/templater' -autoload(:YAML, 'yaml') +autoload(:YAML, 'yaml') module Tap - - # == Description - # - # Env provides access to an execution environment spanning many directories, - # such as the working directory plus a series of gem directories. Envs merge - # the files from each directory into an abstract directory that may be - # globbed and accessed as a single unit. For example: - # - # # /one - # # |-- a.rb - # # `-- b.rb - # # - # # /two - # # |-- b.rb - # # `-- c.rb - # env = Env.new('/one') - # env << Env.new('/two') - # - # env.collect {|e| e.root.root} - # # => ["/one", "/two"] - # - # env.glob(:root, "*.rb") - # # => [ - # # "/one/a.rb", - # # "/one/b.rb", - # # "/two/c.rb" - # # ] - # - # As illustrated, files in the nested environment are accessible within the - # nesting environment. Envs provide methods for finding files associated - # with a specific class, and allow the generation of manifests that provide - # succinct access to various environment resources. - # - # Usage of Envs is fairly straightforward, but the internals and default - # setup require some study as they have to span numerous functional domains. - # The most common features are detailed below. - # - # ==== Class Paths - # - # Class paths are a kind of inheritance for files associated with a class. - # Say we had the following classes: - # - # class A; end - # class B < A; end - # - # The naturally associated directories are 'a' and 'b'. To look these up: - # - # env.class_path(:root, A) # => "/one/a" - # env.class_path(:root, B) # => "/one/b" - # - # And to look up an associated file: - # - # env.class_path(:root, A, "index.html") # => "/one/a/index.html" - # env.class_path(:root, B, "index.html") # => "/one/b/index.html" - # - # A block may be given to filter paths, for instance to test if a given file - # exists. The class_path method will check each env then roll up the - # inheritance hierarchy until the block returns true. - # - # FileUtils.touch("/two/a/index.html") - # - # visited_paths = [] - # env.class_path(:root, B, "index.html) do |path| - # visited_paths << path - # File.exists?(path) - # end # => "/two/a/index.html" - # - # visited_paths - # # => [ - # # "/one/b/index.html", - # # "/two/b/index.html", - # # "/one/a/index.html", - # # "/two/a/index.html" - # # ] - # - # This behavior is very useful for associating views with a class. - # - # ==== Manifest - # - # Envs can generate manifests of various resources so they may be identified - # using minipaths (see Minimap for details regarding minipaths). Command - # files used by the tap executable are one example of a resource, and the - # constants used in building a workflow are another. - # - # Manifest are generated by defining a builder, typically a block, that - # receives an env and returns an array of the associated resources. - # Using the same env as above: - # - # manifest = env.manifest {|e| e.root.glob(:root, "*.rb") } - # - # manifest.seek("a") # => "/one/a.rb" - # manifest.seek("b") # => "/one/b.rb" - # manifest.seek("c") # => "/two/c.rb" - # - # As illustrated, seek finds the first entry across all envs that matches the - # input minipath. A minipath for the env may be prepended to only search - # within a specific env. - # - # manifest.seek("one:b") # => "/one/b.rb" - # manifest.seek("two:b") # => "/two/b.rb" - # - # Env caches a manifest of constants identified by {constant attributes}[http://tap.rubyforge.org/lazydoc] - # in files specified by under the Env.const_paths. These constants are used - # when interpreting signals from the command line. Constants may be manually - # registered to the constants manifest and classified by type like this: - # - # class CustomTask - # def call; end - # end - # env.register(CustomTask).register_as(:task, "this is a custom task") - # - # const = env.constants.seek('custom_task') - # const.const_name # => "CustomTask" - # const.types # => {:task => "this is a custom task"} - # const.constantize # => CustomTask - # - # == Setup - # - # Envs may be manually setup in code by individually generating instances - # and nesting them. More commonly envs are defined in configuration files - # and instantiated by specifying where the files are located. The default - # config basename is 'tap.yml'; any env_paths specified in the config file - # will be added. - # - # This type of instantiation is recursive: - # - # # [/one/tap.yml] - # # env_paths: [/two] - # # - # # [/two/tap.yml] - # # env_paths: [/three] - # # - # - # env = Env.new("/one", :basename => 'tap.yml') - # env.collect {|e| e.root.root} - # # => ["/one", "/two", "/three"] - # - # Gem directories are fair game. Env allows specific gems to be specified - # by name (via the 'gems' config), and if a gem has a tap.yml file then it - # will be used to configure the gem env. Alternatively, an env may be set - # to automatically discover and nest gem environments. In this case gems - # are discovered when they have a tap.yml file. - # - # ==== ENV configs - # - # Configurations may be also specified as an ENV variables. This type of - # configuration is very useful on the command line. Config variables - # should be prefixed by TAP_ and named like the capitalized config key - # (ex: TAP_GEMS or TAP_ENV_PATHS). See the - # {Command Line Examples}[link:files/doc/Examples/Command%20Line.html] - # to see ENV configs in action. - # - # These configurations may be accessed from Env#config, and are - # automatically incorporated by Env#setup. - # class Env - autoload(:Gems, 'tap/env/gems') - class << self - - # Returns the Env configs specified in ENV. Config variables are - # prefixed by TAP_ and named like the capitalized config key - # (ex: TAP_GEMS or TAP_ENV_PATHS). - def config(env_vars=ENV) - config = {} - env_vars.each_pair do |key, value| - if key =~ /\ATAP_(.*)\z/ - config[$1.downcase] = value - end - end - config - end - - # Initializes and activates an env as described in the config file under - # dir. The config file should be a relative path and will be used for - # determining configuration files under each env_path. - # - # The env configuration is determined by merging the following in order: - # * defaults {root => dir, gems => all} - # * ENV configs - # * config_file configs - # - # The HOME directory for Tap will be added as an additonal environment - # if not already added somewhere in the env hierarchy. By default all - # gems will be included in the Env. - def setup(dir=Dir.pwd, config_file=CONFIG_FILE) - # setup configurations - config = {'root' => dir, 'gems' => :all} + def generate(options={}) + options = { + :register => true, + :load_paths => true, + :set => true + }.merge(options) - user_config_file = config_file ? File.join(dir, config_file) : nil - user = load_config(user_config_file) + dir = File.expand_path(options[:dir] || Dir.pwd) + pathfile = options[:pathfile] || File.expand_path(Path::FILE, dir) + map = options[:map] || Path.load(pathfile) + lib = options[:lib] || 'lib' + pattern = options[:pattern] || '**/*.rb' - config.merge!(self.config) - config.merge!(user) + register = options[:register] + load_paths = options[:load_paths] + set = options[:set] - # keys must be symbolized as they are immediately - # used to initialize the Env configs - config = config.inject({}) do |options, (key, value)| - options[key.to_sym || key] = value - options - end + lines = [] + lines << "register #{Path.escape(dir)}" if register - # instantiate - context = Context.new(:basename => config_file) - env = new(config, context) - - # add the tap env if necessary - unless env.any? {|e| e.root.root == HOME } - env.push new(HOME, context) - end - - env.activate - env - end - - # Generates an Env for the specified gem or Gem::Specification. The - # gemspec for the gem is used to determine the env configuration in - # the following way: - # - # root: the gem path - # gems: all gem dependencies with a config_file - # const_paths: the gem require paths - # set_const_paths: false (because RubyGems sets them for you) - # - # Configurations specified in the gem config_file override these - # defaults. - def setup_gem(gem_name, context=Context.new) - spec = Gems.gemspec(gem_name) - path = spec.full_gem_path - - # determine gem dependencies that have a config_file; - # these will be set as the gems for the new Env - dependencies = [] - spec.dependencies.each do |dependency| - unless dependency.type == :runtime - next - end + path = Path.new(dir, map) + path[lib].each do |lib_dir| + lines << "loadpath #{Path.escape(lib_dir)}" if load_paths - unless gemspec = Gems.gemspec(dependency) - # this error may result when a dependency has - # been uninstalled for a particular gem - warn "missing gem dependency: #{dependency.to_s} (#{spec.full_name})" - next - end - - if config_file = context.config_file(gemspec.full_gem_path) - next unless File.exists?(config_file) - end - - dependencies << gemspec + Constant.scan(lib_dir, pattern).each do |constant| + require_paths = Path.join(constant.require_paths) + types = constant.types.to_a.collect {|type| Path.escape(Path.join(type)) } + lines << "set #{constant.const_name} #{Path.escape require_paths} #{types.join(' ')}" + end if set end - config = { - 'root' => path, - 'gems' => dependencies, - 'const_paths' => spec.require_paths, - 'set_const_paths' => false - } - - # override the default configs with whatever configs - # are specified in the gem config file - if config_file = context.config_file(path) - config.merge!(load_config(config_file)) - end - - new(config, context) + lines.uniq! + lines.sort! + lines end - - # Loads configurations from path as YAML. Returns an empty hash if the path - # loads to nil or false (as happens for empty files), or doesn't exist. - # - # Raises a ConfigError if the configurations do not load properly. - def load_config(path) - return {} unless path - - begin - Root::Utils.trivial?(path) ? {} : (YAML.load_file(path) || {}) - rescue(Exception) - raise ConfigError.new($!, path) - end - end end - include Configurable - include Enumerable - include Minimap + include Signals - # The config file path - CONFIG_FILE = "tap.yml" - - # The home directory for Tap - HOME = File.expand_path("#{File.dirname(__FILE__)}/../..") - - # An array of nested Envs, by default comprised of the env_path - # + gem environments (in that order). Envs can be manually set - # to override these defaults. - attr_reader :envs - - # A Context tracking information shared among a set of envs. - attr_reader :context - - # The Root directory structure for self. - nest(:root, Root, :init => false) - - # Specify gems to add as nested Envs. Gems may be specified by name - # and/or version, like 'gemname >= 1.2'; by default the latest version - # of the gem is selected. + # Matches an inline type. After the match: # - # Several special values also exist: + # $1:: The prefix string (ex 'Const' for '::Const::type') + # $2:: The inline type (ex 'type' for '::Const::type') # - # :NONE, :none indicates no gems (same as nil, false) - # :LATEST the latest version of all gems - # :ALL all gems - # :latest the latest version of all gems with a config file - # :all all gems with a config file - # - # Gems are not activated by Env. - config_attr :gems, [] do |input| - input = yaml_load(input) if input.kind_of?(String) - - @gems = case input - when false, nil, :NONE, :none - [] - when :LATEST, :ALL - # latest and all, no filter - Gems.select_gems(input == :LATEST) - when :latest, :all - # latest and all, filtering by the existence of a - # config file; all gems are selected if no config - # file can be determined. - Gems.select_gems(input == :latest) do |spec| - config_file = context.config_file(spec.full_gem_path) - config_file == nil || File.exists?(config_file) - end - else - # resolve gem names manually - [*input].collect do |name| - Gems.gemspec(name) - end.compact - end + INLINE_TYPE = /(.*)::([a-z_]*)\z/ - reset_envs - end - - # Specify directories to load as nested Envs. Configurations for the - # env are loaded from the config file under dir, if it exists. - config_attr :env_paths, [] do |input| - @env_paths = resolve_paths(input) - reset_envs - end + attr_reader :paths + attr_reader :constants - # Designates directories searched for constants. The const_paths are - # added to $LOAD_PATH on activation if set_const_paths is specified. - config_attr :const_paths, [:lib] do |input| - raise "const_paths cannot be modified once active" if active? - @const_paths = resolve_paths(input) - end + signal_hash :auto, # auto-scan resources from a dir + :signature => [:dir, :pathfile, :lib, :pattern] - # If set to true const_paths are added to $LOAD_PATH on activation. - config_attr :set_const_paths, true do |input| - raise "set_const_paths cannot be modified once active" if active? - @set_const_paths = Configurable::Validation.boolean[input] - end + signal :activate, :signature => [:name, :version] - # Initializes a new Env linked to the specified directory. Configurations - # for the env will be loaded from the config file (as determined by the - # context) if it exists. - # - # A configuration hash may be manually provided in the place of dir. In - # that case, no configurations will be loaded, even if the config file - # exists. - # - # Context can be specified as a Context, or a Hash used to initialize a - # Context. - def initialize(config_or_dir=Dir.pwd, context={}) - - # setup root - config = nil - @root = case config_or_dir - when Root - config_or_dir - when String - Root.new(config_or_dir) - else - config = config_or_dir - - if config.has_key?(:root) && config.has_key?('root') - raise "multiple values mapped to :root" - end - - root = config.delete(:root) || config.delete('root') || Dir.pwd - root.kind_of?(Root) ? root : Root.new(root) - end - - # note registration requires root.root, and so the - # setup of context must follow the setup of root. - @context = case context - when Context - context - when Hash - Context.new(context) - else raise "cannot convert #{context.inspect} to Tap::Env::Context" - end - @context.register(self) - - # these need to be set for reset_env - @active = false - @gems = nil - @env_paths = nil - - # only load configurations if configs were not provided - config ||= Env.load_config(@context.config_file(@root.root)) - initialize_config(config) - - # set the invert flag - @invert = false - end + signal :register # add a resource path + signal :loadpath # add a load path + signal :set # add a constant - # Sets envs removing duplicates and instances of self. Setting envs - # overrides any environments specified by env_path and gem. - def envs=(envs) - raise "envs cannot be modified once active" if active? - @envs = envs.uniq.delete_if {|env| env == self } - end - - # Unshifts env onto envs. Self cannot be unshifted onto self. - def unshift(env) - unless env == self || envs[0] == env - self.envs = envs.dup.unshift(env) - end - self - end - - # Pushes env onto envs, removing duplicates. - # Self cannot be pushed onto self. - def push(env) - unless env == self || envs[-1] == env - envs = self.envs.reject {|e| e == env } - self.envs = envs.push(env) - end - self - end - alias_method :<<, :push - - # Passes each nested env to the block in order, starting with self. - def each - visit_envs.each {|e| yield(e) } - end - - # Passes each nested env to the block in reverse order, ending with self. - def reverse_each - visit_envs.reverse_each {|e| yield(e) } - end + signal :unregister # remove a resource path + signal :unloadpath # remove a load path + signal :unset # remove a constant - # Recursively injects the memo to each env of self. Each env in envs - # receives the same memo from the parent. This is different from the - # inject provided via Enumerable, where each subsequent env receives - # the memo from the previous, not the parent, env. - # - # a,b,c,d,e = ('a'..'e').collect {|name| Env.new(:name => name) } - # - # a.push(b).push(c) - # b.push(d).push(e) - # - # lines = [] - # a.recursive_inject(0) do |nesting_depth, env| - # lines << "\n#{'..' * nesting_depth}#{env.config[:name]} (#{nesting_depth})" - # nesting_depth + 1 - # end - # - # lines.join - # # => %Q{ - # # a (0) - # # ..b (1) - # # ....d (2) - # # ....e (2) - # # ..c (1)} - # - def recursive_inject(memo, &block) # :yields: memo, env - inject_envs(memo, &block) - end + define_signal :load, Load # load a tapenv file + define_signal :help, Help # signals help - # Activates self by doing the following, in order: - # - # * activate nested environments - # * unshift const_paths to $LOAD_PATH (if set_const_paths is true) - # - # Once active, the current envs and const_paths are frozen and cannot be - # modified until deactivated. Returns true if activate succeeded, or - # false if self is already active. - def activate - return false if active? - - @active = true - - # freeze envs and const paths - @envs.freeze - @const_paths.freeze - - # activate nested envs - envs.reverse_each do |env| - env.activate - end - - # add const paths - if set_const_paths - const_paths.reverse_each do |path| - $LOAD_PATH.unshift(path) - end - - $LOAD_PATH.uniq! - end - - true + def initialize(options={}) + @paths = options[:paths] || [] + @paths.collect! {|path| path.kind_of?(Path) ? path : Path.new(*path) } + @constants = options[:constants] || [] + @constants.collect! {|constant| constant.kind_of?(Constant) ? constant : Constant.new(constant) } end - # Deactivates self by doing the following in order: - # - # * deactivates nested environments - # * removes const_paths from $LOAD_PATH (if set_const_paths is true) - # - # Once deactivated, envs and const_paths are unfrozen and may be modified. - # Returns true if deactivate succeeded, or false if self is not active. - # - # ==== Note - # - # Deactivation does not necessarily leave $LOAD_PATH in the same condition - # as before activation. A pre-existing $LOAD_PATH entry can go missing if - # it is also registered as an env load_path (deactivation doesn't know to - # leave such paths alone). - # - # Deactivation, like constant unloading should be done with caution. - def deactivate - return false unless active? - @active = false - - # dectivate nested envs - envs.reverse_each do |env| - env.deactivate + def path(type) + result = [] + paths.each do |path| + result.concat path[type] end - - # remove const paths - const_paths.each do |path| - $LOAD_PATH.delete(path) - end if set_const_paths - - # unfreeze envs and const paths - @envs = @envs.dup - @const_paths = @const_paths.dup - - true + result end - # Return true if self has been activated. - def active? - @active + def match(const_str, type=nil) + const_str = const_str.to_s + const_str =~ Constant::CONST_REGEXP ? constants_by_const_name($1) : constants_by_path(const_str, type) end - # Globs the abstract directory for files in the specified directory alias, - # matching the pattern. The expanded path of each matching file is - # returned. - def glob(dir, pattern="**/*") - hlob(dir, pattern).values.sort! - end - - # Same as glob but returns results as a hash of (relative_path, path) - # pairs. In short the hash defines matching files in the abstract - # directory, linked to the actual path for these files. - def hlob(dir, pattern="**/*") - results = {} - each do |env| - root = env.root - root.glob(dir, pattern).each do |path| - relative_path = root.relative_path(dir, path) - results[relative_path] ||= path - end + def resolve(const_str, type=nil) + matches = match(const_str, type) + case matches.length + when 0 then raise "unresolvable constant: #{const_str.inspect}" + when 1 then matches.at(0) + else raise "multiple matching constants: #{const_str.inspect} (#{matches.join(', ')})" end - results end - # Returns the path to the specified file, as determined by root. - # - # If a block is given, a path for each env will be yielded until the block - # returns a true value. Returns nil if the block never returns true. - def path(dir = :root, *paths) - each do |env| - path = env.root.path(dir, *paths) - return path if !block_given? || yield(path) - end - nil + def constant(const_str, type=nil) + const_str.kind_of?(Module) ? const_str : resolve(const_str, type).constantize end - # Retrieves a path associated with the inheritance hierarchy of an object. - # An array of modules (which naturally can include classes) are provided - # and module_path traverses each, forming paths like: - # - # path(dir, module_path, *paths) - # - # By default 'module_path' is 'module.to_s.underscore' but modules can - # specify an alternative by providing a module_path method. - # - # Paths are yielded to the block until the block returns true, at which - # point the current the path is returned. If no block is given, the - # first path is returned. Returns nil if the block never returns true. - def module_path(dir, modules, *paths, &block) - paths.compact! - while current = modules.shift - module_path = if current.respond_to?(:module_path) - current.module_path - else - current.to_s.underscore - end - - if path = self.path(dir, module_path, *paths, &block) - return path - end - end - - nil + # Registers the directory and path mappings as a Path, into paths. The + # path is unshifted to paths to provide similar functionality as loadpath. + # Returns the new path. + def register(dir, map={}) + new_path = Path.new(dir, map) + paths.unshift new_path + new_path end - # Returns the module_path traversing the inheritance hierarchy for the - # class of obj (or obj if obj is a Class). Included modules are not - # visited, only the superclasses. - def class_path(dir, obj, *paths, &block) - klass = obj.kind_of?(Class) ? obj : obj.class - superclasses = klass.ancestors - klass.included_modules - module_path(dir, superclasses, *paths, &block) - end - - # Generates a Manifest for self using the block as a builder. The builder - # receives an env and should return an array of resources, each of which - # can be minimappped. Minimapping requires that the resource is either - # a path string, or provides a 'path' method that returns a path string. - # Alternatively, a Minimap may be returned. - # - # If a type is specified, then the manifest cache will be linked to the - # context cache. - def manifest(type=nil, &block) # :yields: env - cache = type ? (context.cache[type] ||= {}) : {} - Manifest.new(self, block, cache) - end - - # Returns a manifest of Constants located in .rb files under each of the - # const_paths. Constants are identified using Lazydoc constant attributes; - # all attributes are registered to the constants for classification - # (for example as a task, join, etc). - def constants - @constants ||= manifest(:constants) do |env| - constants = {} - - env.const_paths.each do |load_path| - next unless File.directory?(load_path) - Constant.scan(load_path, "**/*.rb", constants) - end - - constants.keys.sort!.collect! do |key| - constants[key] - end + def auto(options, log=nil) + Env.generate(options).each do |line| + sig, *args = Utils.shellsplit(line) + signal(sig).call(args) end + self end - # Seeks a constant for the key, constantizing if necessary. If invert is - # true, this method seeks and returns a key for the input constant. In - # both cases AGET returns nil if no key-constant pair can be found. - def [](key, invert=invert?) - if invert - const_name = key.to_s - constants.unseek(true) do |const| - const_name == const.const_name - end - else - if constant = constants.seek(key) - constant.constantize - else - nil - end - end + def activate(name, version) + Gem.activate(name, version) end - # Indicates AGET looks up constants from keys (false), or keys from - # constants (true). - def invert? - @invert - end - - # Inverts the AGET lookup for self. - def invert! - @invert = !@invert + def unregister(*dirs) + dirs.collect! {|dir| File.expand_path(dir) } + paths.delete_if {|path| dirs.include?(path.base) } self end - # Returns a duplicate of self with inverted AGET lookup. - def invert - dup.invert! + # Expands and prepends the specified paths to $LOAD_PATH, removing any + # duplicates. Returns $LOAD_PATH. + def loadpath(*paths) + paths.reverse_each do |path| + $LOAD_PATH.unshift File.expand_path(path) + end + + $LOAD_PATH.uniq! + $LOAD_PATH end - # Scans the files matched under the directory and pattern for constants - # and adds them to the existing constants for self. - def scan(dir, pattern="**/*.rb") - new_entries = {} - entries = constants.entries(self) - entries.each {|const| new_entries[const.const_name] = const } - - Constant.scan(root[dir], pattern, new_entries) - - entries.replace(new_entries.keys) - entries.sort!.collect! {|key| new_entries[key] } - entries + # Expands and removes the specified paths from $LOAD_PATH. Returns + # $LOAD_PATH. + def unloadpath(*paths) + paths.each {|path| $LOAD_PATH.delete File.expand_path(path) } + $LOAD_PATH end - # Registers a constant with self. The constant is stored as a new - # Constant in the constants manifest. Returns the new Constant. - # If the constant is already registered, the existing Constant is - # returned. - def register(constant) - const_name = constant.to_s - entries = constants.entries(self) + def set(const_name, require_path=nil, *types) + if const_name =~ INLINE_TYPE + const_name = $1 + types << $2 + end - # try to find the existing Constant before making a new constant - unless constant = entries.find {|const| const.const_name == const_name} + constant = constants.find {|c| c.const_name == const_name } + + unless constant constant = Constant.new(const_name) - entries << constant - entries.replace entries.sort_by {|const| const.const_name } + constants << constant end - constant - end - - # When no template is specified, inspect generates a fairly standard - # inspection string. When a template is provided, inspect builds a - # Templater for each env with the following local variables: - # - # variable value - # env the current env - # env_keys a minihash for all envs - # - # If a block is given, the globals and templater are yielded before - # any templater is built; this allows each env to add env-specific - # variables. After this preparation, each templater is built with - # the globals and the results concatenated. - # - # The template is built with filename, if specified (for debugging). - def inspect(template=nil, globals={}, filename=nil) # :yields: templater, globals - if template == nil - return "#<#{self.class}:#{object_id} root='#{root.root}'>" + require_paths = require_path ? Path.split(require_path, nil) : [] + if require_paths.empty? && const_name.kind_of?(String) + require_paths << const_name.underscore end - env_keys = minihash(true) - collect do |env| - templater = Templater.new(template, :env => env, :env_key => env_keys[env]) - yield(templater, globals) if block_given? - templater - end.collect! do |templater| - templater.build(globals, filename) - end.join + constant.require_paths.concat(require_paths).uniq! + types.each {|type| constant.register_as(*Path.split(type, nil)) } + + constant end - - protected - # helper for Minimap; note that specifying env.root.root via path - # is not possible because path is required for other purposes. - def entry_to_path(env) # :nodoc: - env.root.root - end - - # resets envs using the current env_paths and gems. does nothing - # until both env_paths and gems are set. - def reset_envs # :nodoc: - if env_paths && gems - self.envs = env_paths.collect do |path| - context.instance(path) || Env.new(path, context) - end + gems.collect do |spec| - context.instance(spec.full_gem_path) || Env.setup_gem(spec, context) + def unset(*const_names) + const_names.each do |const_name| + constants.delete_if do |constant| + constant.const_name == const_name end end + self end - - # arrayifies, compacts, and resolves input paths using root. - # also removes duplicates. in short: - # - # resolve_paths ['lib', nil, 'lib', 'alt] # => [root['lib'], root['alt']] - # - def resolve_paths(paths) # :nodoc: - paths = yaml_load(paths) if paths.kind_of?(String) - [*paths].compact.collect {|path| root[path] }.uniq - end - - # helper to recursively iterate through envs, starting with self. - # visited envs are collected in order and are used to ensure a - # given env will only be visited once. - def visit_envs(visited=[], &block) # :nodoc: - unless visited.include?(self) - visited << self - yield(self) if block_given? - - envs.each do |env| - env.visit_envs(visited, &block) - end - end - visited - end - - # helper to recursively inject a memo to the children of env - def inject_envs(memo, visited=[], &block) # :nodoc: - unless visited.include?(self) - visited << self - next_memo = yield(memo, self) - envs.each do |env| - env.inject_envs(next_memo, visited, &block) - end - end - - visited - end - private - # A 'quick' yaml load where empty strings will not cause YAML to autoload. - # This is a silly song and dance, but provides for optimal launch times. - def yaml_load(str) # :nodoc: - str.empty? ? false : YAML.load(str) + def constants_by_const_name(const_str) # :nodoc: + constants.select do |constant| + constant.const_name == const_str + end end - - # Raised when there is a configuration error from Env.load_config. - class ConfigError < StandardError # :nodoc: - attr_reader :original_error, :env_path + + def constants_by_path(const_str, type) # :nodoc: + const_str, inline_type = const_str.split('::', 2) + type = inline_type if inline_type - def initialize(original_error, env_path) - @original_error = original_error - @env_path = env_path - super() - end + head, tail = const_str.split(':', 2) + head, tail = nil, head unless tail - def message - "Configuration error: #{original_error.message}\n" + - ($DEBUG ? "#{original_error.backtrace}\n" : "") + - "Check '#{env_path}' configurations" + constants.select do |constant| + constant.type_match?(type) && constant.path_match?(head, tail) end end end end \ No newline at end of file