lib/tap/env.rb in tap-0.9.1 vs lib/tap/env.rb in tap-0.10.0
- old
+ new
@@ -1,335 +1,640 @@
require 'tap/root'
-require 'singleton'
-autoload(:PP, "pp")
+require 'tap/support/constant'
+require 'tap/support/summary'
+require 'tap/support/manifest'
module Tap
- # == Under Construction
- #
- # Env manages configuration of the Tap execution environment, including the
- # specification of gems that should be available through the tap command.
+ #--
+ # Note that gems and env_paths reset envs -- custom modifications to envs will be lost
+ # whenever these configs are reset.
class Env
+ include Support::Configurable
+ include Enumerable
- # A variety of configuration loading/handling methods for use in
- # conjuction with Tap::Env, to aid in configuring the running
- # environment for Tap.
- module Configuration
- module_function
+ @@instance = nil
+ @@instances = {}
+ @@manifests = {}
+
+ class << self
+ # Returns the active instance of Env.
+ def instance
+ @@instance
+ end
+
+ # A hash of (path, Env instance) pairs, generated by Env#instantiate. Used
+ # to prevent infinite loops of Env dependencies by assigning a single Env
+ # to a given path.
+ def instances
+ @@instances
+ end
- # Templates the input filepath using ERB then loads it as YAML.
- # Returns an empty hash if the file doesn't exist, or loads to
- # nil or false (as for an empty file). Raises an error if the
- # filepath doesn't load to a hash.
- def read_config(filepath)
- return {} if !File.exists?(filepath) || File.directory?(filepath)
+ # Creates a new Env for the specified path and adds it to Env#instances, or
+ # returns the existing instance for the path. Paths can point to an env config
+ # file, or to a directory. If a directory is provided, instantiate treats
+ # path as the DEFAULT_CONFIG_FILE in that directory. All paths are expanded.
+ #
+ # e1 = Env.instantiate("./path/to/config.yml")
+ # e2 = Env.instantiate("./path/to/dir")
+ #
+ # Env.instances
+ # # => {
+ # # File.expand_path("./path/to/config.yml") => e1,
+ # # File.expand_path("./path/to/dir/#{Tap::Env::DEFAULT_CONFIG_FILE}") => e2 }
+ #
+ # The Env is initialized using configurations read from the env config file using
+ # load_config, and a Root initialized to the config file directory. An instance
+ # will be initialized regardless of whether the config file or directory exists.
+ def instantiate(path_or_root, default_config={}, logger=nil)
+ path = path_or_root.kind_of?(Root) ? path_or_root.root : path_or_root
+ path = pathify(path)
- input = ERB.new(File.read(filepath)).result
- config = YAML.load(input)
+ begin
+ root = path_or_root.kind_of?(Root) ? path_or_root : Root.new(File.dirname(path))
+ config = default_config.merge(load_config(path))
+
+ # note the assignment of env to instances MUST occur before
+ # reconfigure to prevent infinite looping
+ (instances[path] = Env.new({}, root, logger)).reconfigure(config) do |unhandled_configs|
+ yield(unhandled_configs) if block_given?
+ end
+ rescue(Exception)
+ raise Env::ConfigError.new($!, path)
+ end
+ end
- case config
- when Hash then config
- when nil, false then {}
- else
- raise "expected hash from config file: #{filepath}"
+ def pathify(path)
+ if File.directory?(path) || (!File.exists?(path) && File.extname(path) == "")
+ path = File.join(path, DEFAULT_CONFIG_FILE)
end
+ File.expand_path(path)
end
- # Partitions a configuration hash into environment, execution,
- # and application configurations, as determined by ENV_CONFIG_KEYS
- # and EXE_CONFIG_KEYS. All non-env, non-exe configurations are
- # considered application configurations.
- def partition_configs(hash, *sets)
- partitions = Array.new(sets.length + 1) { Hash.new }
+ def instance_for(path)
+ path = pathify(path)
+ instances.has_key?(path) ? instances[path] : instantiate(path)
+ end
- hash.each_pair do |key, value|
- index = 0
- sets.each do |keys|
- break if keys.include?(key)
- index += 1
- end
-
- partitions[index][key] = value
- end
+ # Returns the gemspec for the specified gem. A gem version
+ # can be specified in the name, like 'gem >= 1.2'. The gem
+ # will be activated using +gem+ if necessary.
+ def gemspec(gem_name)
+ return gem_name if gem_name.kind_of?(Gem::Specification)
- partitions
+ # figure the version of the gem, by default >= 0.0.0
+ gem_name.to_s =~ /^([^<=>]*)(.*)$/
+ name, version = $1.strip, $2
+ version = ">= 0.0.0" if version.empty?
+
+ return nil if name.empty?
+
+ # load the gem and get the spec
+ gem(name, version)
+ Gem.loaded_specs[name]
end
- # Joins the input configuration hashes, concatenating
- # values for matching keys. Values will be made into
- # arrays if they are not so already; duplicate values
- # are removed from the result on a key-per-key basis.
- def join_configs(*configs)
- merge = {}
- configs.each do |hash|
- hash.each_pair do |key, values|
- values = [values] unless values.kind_of?(Array)
- (merge[key] ||= []).concat(values)
- end
+ # Returns the gem name for all installed gems with a DEFAULT_CONFIG_FILE.
+ # If latest==true, then only the names for the most current gem specs
+ # will be returned.
+ def known_gems(latest=true)
+ index = latest ?
+ Gem.source_index.latest_specs :
+ Gem.source_index.gems.collect {|(name, spec)| spec }
+
+ index.select do |spec|
+ File.exists?(File.join(spec.full_gem_path, DEFAULT_CONFIG_FILE)) ||
+ File.exists?(File.join(spec.full_gem_path, DEFAULT_TASK_FILE))
+ end.sort
+ end
+
+ protected
+
+ # Defines a config that raises an error if set when the
+ # instance is active. static_config MUST take a block
+ # and raises an error if a block is not given.
+ def static_config(key, value=nil, &block)
+ raise ArgumentError.new("active config requires block") unless block_given?
+
+ instance_variable = "@#{key}".to_sym
+ config_attr(key, value) do |input|
+ check_configurable
+ instance_variable_set(instance_variable, block.call(input))
end
- merge.values.each {|values| values.uniq! }
- merge
end
+
+ # Defines a config that collects the input into a unique,
+ # compact array where each member has been resolved using
+ # root[]. In short, ['lib', nil, 'lib', 'alt] becomes
+ # [root['lib'], root['alt']].
+ #
+ # Single and nil arguments are allowed; they are arrayified
+ # and handled as above. Path configs raise an error if
+ # modified when the instance is active.
+ def path_config(key, value=[])
+ instance_variable = "@#{key}".to_sym
+ config_attr(key, value) do |input|
+ check_configurable
+ instance_variable_set(instance_variable, [*input].compact.collect {|path| root[path]}.uniq)
+ end
+ end
+
+ #--
+ # To manifest simply requires an glob_<name> method which
+ # yields each (key, path) pair for the manifested object in
+ # a predictable order.
+ #
+ #--
+ # Alternate implementation would create the manifest for each individual
+ # env, then merge the manifests. On the plus side, each env would then
+ # carry it's own slice of the manifest without having to recalculate.
+ # On the down side, the merging would have to occur in some separate
+ # method that cannot be defined here.
+ def manifest(name, paths_key, pattern, &block)
+ return manifest(name, paths_key, pattern) do |context, path|
+ [[path.chomp(File.extname(path)), path]]
+ end unless block_given?
+
+ glob_method = Support::Manifest.glob_method(name)
+ module_eval %Q{
+ def #{glob_method}
+ paths = []
+ self.#{paths_key}.each do |manifest_path|
+ root.glob(manifest_path, "#{pattern}").each do |path|
+ next if File.directory?(path)
+ paths << [manifest_path, path]
+ end
+ end
+ paths.sort_by {|mp, p| File.basename(p)}
+ end
+ }
+
+ map_method = Support::Manifest.map_method(name)
+ define_method(map_method, &block)
+
+ protected glob_method, map_method
+ end
end
- include Configuration
- include Singleton
+ # The global config file path
+ GLOBAL_CONFIG_FILE = File.join(Gem.user_home, ".tap.yml")
+ # The default config file path
DEFAULT_CONFIG_FILE = "tap.yml"
- # Currently these are ALWAYS included.
- DEFAULT_CONFIG = {
- "load_paths" => ["lib"],
- "load_once_paths" => [],
- "config_paths" => [],
- "command_paths" => ["cmd"],
- "gems" => [],
- "generator_paths" => ["lib/generators"]
- }
+ # The default task file path
+ DEFAULT_TASK_FILE = "tapfile.rb"
- attr_reader :config
+ # The Root directory structure for self.
+ attr_reader :root
+
+ # Gets or sets the logger for self
attr_accessor :logger
+
+ # A hash of the manifests for self.
+ attr_reader :manifests
+
+ # Specify gems to load 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.
+ #
+ # Gems are immediately loaded (via gem) through this method.
+ #--
+ # Note that the gems are resolved to gemspecs using Env.gemspec,
+ # so self.gems returns an array of gemspecs.
+ config_attr :gems, [] do |input|
+ check_configurable
+ @gems = [*input].compact.collect do |gem_name|
+ spec = Env.gemspec(gem_name)
+
+ case spec
+ when nil then log(:warn, "unknown gem: #{gem_name}", Logger::WARN)
+ else Env.instance_for(spec.full_gem_path)
+ end
+
+ spec
+ end.uniq
+ reset_envs
+ end
- def initialize
- @config = nil
- @logger = nil
- reset
+ # Specify configuration files to load as nested Envs.
+ config_attr :env_paths, [] do |input|
+ check_configurable
+ @env_paths = [*input].compact.collect do |path|
+ Env.instance_for(root[path]).env_path
+ end.uniq
+ reset_envs
end
+
+ # Designate load paths. If use_dependencies == true, then
+ # load_paths will be used for automatic loading of modules
+ # through the active_support Dependencies module.
+ path_config :load_paths, ["lib"]
- def debug_setup
- $DEBUG = true
- logger.level = Logger::DEBUG
- end
+ # Designate paths for discovering and executing commands.
+ path_config :command_paths, ["cmd"]
- def rails_setup(app=Tap::App.instance)
- Object.const_set('RAILS_ROOT', app.root)
- Object.const_set('RAILS_DEFAULT_LOGGER', app.logger)
- Dependencies.log_activity = app.debug?
+ # Designate paths for discovering generators.
+ path_config :generator_paths, ["lib"]
+
+ manifest(:tasks, :load_paths, "**/*.rb") do |load_path, path|
+ next unless document = Support::Lazydoc.scan_doc(path, 'manifest')
+
+ document.const_names.collect do |const_name|
+ if const_name.empty?
+ key = root.relative_filepath(load_path, path).chomp('.rb')
+ [key, Support::Constant.new(key.camelize, path)]
+ else
+ [const_name.underscore, Support::Constant.new(const_name, path)]
+ end
+ end
end
- def rake_setup(argv=ARGV, app=Tap::App.instance)
- Tap::Support.autoload(:Rake, 'tap/support/rake')
-
- # setup
- app.extend Tap::Support::Rake
- rake = Rake.application
- options = rake.options
+ manifest(:commands, :command_paths, "**/*.rb")
- # merge options down from app
- app.options.marshal_dump.each_pair do |key, value|
- options.send("#{key}=", value)
+ manifest(:generators, :generator_paths, '**/*_generator.rb') do |load_path, path|
+ dirname = File.dirname(path)
+ next unless "#{File.basename(dirname)}_generator.rb" == File.basename(path)
+
+ next unless document = Support::Lazydoc.scan_doc(path, 'generator')
+ document.const_names.collect do |const_name|
+ if const_name.empty?
+ key = root.relative_filepath(load_path, dirname)
+ [key, Support::Constant.new((key + '_generator').camelize, path)]
+ else
+ [const_name.underscore, Support::Constant.new(const_name, path)]
+ end
end
- options.silent = true
+ end
- # run as if from command line using argv
- current_argv = ARGV.dup
- begin
- ARGV.concat(argv)
-
- # now follow the same protocol as
- # in run, handling options
- rake.init
- rake.load_rakefile
- ensure
- ARGV.clear
- ARGV.concat(current_argv)
- end
+ def initialize(config={}, root=Tap::Root.new, logger=nil)
+ @root = root
+ @logger = logger
+ @envs = []
+ @active = false
+ @manifests = {}
+ @manifested = []
- rake
+ # initialize these for reset_env
+ @gems = []
+ @env_paths = []
+
+ initialize_config(config)
end
- # Resets Env. Load paths (load_paths and load_once_paths) are
- # not reset unless dependencies==true; in which case Dependencies
- # are cleared before load paths are cleared. The load paths added
- # to $LOAD_PATH are not cleared.
+ # Sets envs removing duplicates and instances of self.
+ def envs=(envs)
+ @envs = envs.uniq.delete_if {|e| e == self }
+ @envs.freeze
+ @flat_envs = nil
+ end
+
+ # An array of nested Envs, by default comprised of the
+ # env_path + gem environments (in that order). These
+ # nested Envs are activated/deactivated with self.
#
- # Generally not recommended.
- def reset
- unless @config == nil
- $LOAD_PATH.delete_if {|path| config['load_paths'].include?(path) }
-
- Dependencies.clear
- Dependencies.load_paths.delete_if {|path| config['load_paths'].include?(path) }
- Dependencies.load_once_paths.delete_if {|path| config['load_once_paths'].include?(path) }
+ # Returns a flattened array of the unique nested envs
+ # when flat == true.
+ def envs(flat=false)
+ flat ? (@flat_envs ||= self.flatten_envs.freeze) : @envs
+ end
+
+ # Unshifts env onto envs, removing duplicates.
+ # Self cannot be unshifted onto self.
+ def unshift(env)
+ unless env == self || envs[0] == env
+ self.envs = envs.dup.unshift(env)
end
-
- @config = {}
- DEFAULT_CONFIG.keys.each do |key|
- @config[key] = []
+ envs
+ 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
+ envs
end
-
- # Logs the action and message at the input level (default INFO).
- # Logging is suppressed if no logger is set.
- def log(action, msg="", level=Logger::INFO)
- logger.add(level, msg, action.to_s) if logger
+
+ # Passes each nested env to the block in order, starting with self.
+ def each
+ envs(true).each {|e| yield(e) }
end
- # Configures the specified App using the configurations in config_file.
- # Loading of environement configurations occcurs via load_env_config;
- # all environment paths are resolved using the app, after the app has
- # been configured.
+ # Passes each nested env to the block in reverse order, ending with self.
+ def reverse_each
+ envs(true).reverse_each {|e| yield(e) }
+ end
- # Loads environment configurations from the specified path. If a directory
- # is given as path, then the DEFAULT_CONFIG_FILE relative to that location
- # will be loaded. The loading cycle recurses as specified by the configurations.
+ # Returns the total number of unique envs nested in self (including self).
+ def count
+ envs(true).length
+ end
+
+ # Returns a list of arrays that receive load_paths on activate,
+ # by default [$LOAD_PATH]. If use_dependencies == true, then
+ # Dependencies.load_paths will also be included.
+ def load_path_targets
+ [$LOAD_PATH]
+ end
+
+ # Processes and resets the input configurations for both root
+ # and self. Reconfiguration consists of the following steps:
#
- # Configuration paths are expanded relative to the parent directory
- # of the loaded file. Raises an error if non-env configuration are
- # found (as determined by Tap::Env::Configurtion::ENV_CONFIG_KEYS).
- def load_config(path, root=Tap::Root.new, &block) # :yields: non_env_configs
- path = File.join(path, DEFAULT_CONFIG_FILE) if File.directory?(path)
- path = File.expand_path(path)
-
- # prevent infinite looping
- config_paths = config['config_paths']
- return false if config_paths.include?(path)
+ # * partition overrides into env, root, and other configs
+ # * reconfigure root with the root configs
+ # * reconfigure self with the env configs
+ # * yield other configs to the block (if given)
+ #
+ # Reconfigure will always yields to the block, even if there
+ # are no non-root, non-env configurations. Unspecified
+ # configurations are NOT reconfigured. (Note this means
+ # that existing path configurations like load_paths will
+ # not automatically be reset using reconfigured root.)
+ def reconfigure(overrides={})
+ check_configurable
- # load config
- log(:load_config, path, Logger::DEBUG)
- config_paths << path
+ # partiton config into its parts
+ env_configs = {}
+ root_configs = {}
+ other_configs = {}
- config = read_config(path)
- config['root'] = File.dirname(path) unless config['root']
-
- configure(config, root, &block)
- end
+ env_configurations = self.class.configurations
+ root_configurations = root.class.configurations
+ overrides.each_pair do |key, value|
+ key = key.to_sym
- #--
- # Note: always yields to the block, even if non_env_configs is empty
- def configure(config, root=Tap::Root.new, &block) # :yields: non_env_configs
- root_configs, env_configs, other_configs = partition_configs(config, ['root', 'directories', 'absolute_paths'], DEFAULT_CONFIG.keys)
- env_configs = join_configs(DEFAULT_CONFIG, env_configs)
+ partition = case
+ when env_configurations.key?(key) then env_configs
+ when root_configurations.key?(key) then root_configs
+ else other_configs
+ end
+
+ partition[key] = value
+ end
+
+ # reconfigure root so it can resolve path_configs
+ root.reconfigure(root_configs)
- # assign root configs
- root.send(:assign_paths,
- root_configs['root'] || root.root,
- root_configs['directories'] || root.directories,
- root_configs['absolute_paths'] || root.absolute_paths)
+ # reconfigure self
+ super(env_configs)
- # handle unknown configs (handle before setting
- # env configs in case the configs modify root)
+ # handle other configs
case
when block_given?
yield(other_configs)
when !other_configs.empty?
log(:warn, "ignoring non-env configs: #{other_configs.keys.join(',')}", Logger::DEBUG)
end
- # load gems and configurations
- gem_paths = env_configs.delete('gems').collect do |gem_name|
- full_gem_path(gem_name)
- end
- config_paths = env_configs.delete('config_paths') + gem_paths
- config_paths.each {|path| load_config(root[path]) }
+ self
+ end
+
+ # Returns the path for self in Env.instances.
+ def env_path
+ Env.instances.each_pair {|path, env| return path if env == self }
+ nil
+ end
+
+ # Logs the action and message at the input level (default INFO).
+ # Logging is suppressed if no logger is set.
+ def log(action, msg="", level=Logger::INFO)
+ logger.add(level, msg, action.to_s) if logger
+ end
+
+ # Activates self by unshifting load_paths for self to the load_path_targets.
+ # Once active, self can be referenced from Env.instance and the current
+ # configurations are frozen. Env.instance is deactivated, if set, before
+ # self is activated. Returns true if activate succeeded, or false if self
+ # is already active.
+ def activate
+ return false if active?
- # assign env configs
- env_configs.each_pair do |key, value|
- case key
- when 'load_paths'
- assign_paths(root, value, self.config[key], $LOAD_PATH, Dependencies.load_paths)
- when 'load_once_paths'
- assign_paths(root, value, self.config[key], Dependencies.load_once_paths)
- when /_paths$/
- assign_paths(root, value, self.config[key])
- else
- handle_unknown_env_config(root, key, value)
+ @active = true
+ @@instance = self unless @@instance
+
+ # freeze array configs like load_paths
+ config.each_pair do |key, value|
+ case value
+ when Array then value.freeze
end
end
+
+ # activate nested envs
+ envs.reverse_each do |env|
+ env.activate
+ end
+ # add load paths to load_path_targets
+ load_path_targets.each do |target|
+ load_paths.reverse_each do |path|
+ target.unshift(path)
+ end
+
+ target.uniq!
+ end
+
true
end
- # Loads env configurations from a gem, specifically from
- # gemspec.full_gem_path. A gem version can be specified
- # in the name, like 'gem >= 1.2'.
- def full_gem_path(gem_name)
- # figure the version of the gem, by default >= 0.0.0
- gem_name =~ /^([^<=>]*)(.*)$/
- name, version = $1, $2
- version = ">= 0.0.0" if version.empty?
-
- # load the gem and get the spec
- gem(name, version)
- spec = Gem.loaded_specs[name]
+ # Deactivates self by clearing manifests and deleting load_paths for self
+ # from the load_path_targets. Env.instance will no longer reference self
+ # and the configurations are unfrozen (using duplication).
+ #
+ # Returns true if deactivate succeeded, or false if self is not active.
+ def deactivate
+ return false unless active?
- if spec == nil
- log(:warn, "unknown gem: #{gem_name}", Logger::WARN)
+ # remove load paths from load_path_targets
+ load_path_targets.each do |target|
+ load_paths.each do |path|
+ target.delete(path)
+ end
end
- spec.full_gem_path
+ # unfreeze array configs by duplicating
+ self.config.class_config.each_pair do |key, value|
+ value = send(key)
+ case value
+ when Array then instance_variable_set("@#{key}", value.dup)
+ end
+ end
+
+ @active = false
+ @manifests.clear
+ @@instance = nil if @@instance == self
+
+ # dectivate nested envs
+ envs.reverse_each do |env|
+ env.deactivate
+ end
+
+ true
end
- # Loads the config for the specified gem. A gem version can be
- # specified in the name, see full_gem_path.
- def load_gem(gem_name)
- load_config(full_gem_path(gem_name))
+ # Return true if self has been activated.
+ def active?
+ @active
end
- # Returns the path to all DEFAULT_CONFIG_FILEs for installed gems.
- # If latest==true, then only the config files for the latest gem
- # specs will be returned (ie for the most current version of a
- # gem).
- def gem_config_files(latest=true)
- if latest
- Gem.source_index.latest_specs.collect do |spec|
- config_file = File.join(spec.full_gem_path, DEFAULT_CONFIG_FILE)
- File.exists?(config_file) ? config_file : nil
- end.compact
- else
- Gem.path.collect do |dir|
- Dir.glob( File.join(dir, "gems/*", DEFAULT_CONFIG_FILE) )
- end.flatten.uniq
- end
+ # Cycles through all items yielded by the iterate_<name> method and
+ # adds each to the manifests[name] hash. Freezes the hash when complete.
+ # Simply returns the manifests[name] hash if frozen.
+ def manifest(name)
+ manifest = manifests[name] ||= Support::Manifest.new(name, self)
+
+ manifest.entries.each do |key, path|
+ yield(key, path)
+ end if block_given?
+
+ manifest.each_path do |context, path|
+ next unless keys = send(manifest.map_method, context, path)
+
+ keys.each {|entry| manifest.store(entry) }
+ keys.each {|key, value| yield(key, value) } if block_given?
+ end unless manifest.complete?
+
+ manifest
end
- # Loads the config files discovered by gem_config_files(true).
- def discover_gems
- gem_config_files.collect do |config_file|
- load_config(config_file)
+ def find(name, pattern)
+ manifest(name) do |key, path|
+ return path if Root.minimal_match?(key, pattern)
end
+ nil
end
- # Searches for and returns all .rb files under each of the command_paths
- # as well as the default tap commands. Commands with conflicting names
- # raise an error; however, user commands are allowed to override the
- # default tap commands and will NOT raise an error.
- def commands
- commands = {}
- config['command_paths'].each do |path|
- pattern = File.join(path, "**/*.rb")
-
- Dir.glob(pattern).each do |file|
- cmd = Tap::App.relative_filepath(path, file).chomp(".rb")
- raise "command name confict: #{cmd}" if commands.include?(cmd)
- commands[cmd] = file
+ def search(name, pattern)
+ return find(name, pattern) if name == :envs
+
+ envs = case pattern
+ when /^(.*):([^:]+)$/
+ env_pattern = $1
+ pattern = $2
+ find(:envs, env_pattern) or raise(ArgumentError, "could not find env: #{env_pattern}")
+ else manifest(:envs).values
+ end
+
+ envs.each do |env|
+ if result = env.find(name, pattern)
+ return result
end
end
-
- # allow all other scripts to override default scripts
- # (hence do this second)
- tap_command_dir = File.expand_path(File.join( File.dirname(__FILE__), "cmd"))
- Dir.glob( tap_command_dir + "/**/*.rb" ).each do |file|
- cmd = Tap::App.relative_filepath(tap_command_dir, file).chomp(".rb")
- commands[cmd] = file unless commands.include?(cmd)
+
+ nil
+ end
+
+ def summary(name)
+ summary = Support::Summary.new
+ manifest(:envs).mini_map.each do |(key, env)|
+ summary.add(key, env, env.manifest(name).mini_map)
end
+ summary
+ end
+
+ def summarize(name, &block)
+ lines = summary(name).lines(&block)
+ lines << "=== no #{name} found" if lines.empty?
+ lines.join("\n")
+ end
- commands
+ def inspect(brief=false)
+ brief ? "#<#{self.class}:#{object_id} root='#{root.root}'>" : super()
end
+ def to_s
+ inspect(true)
+ end
+
+ #--
+ # Under construction
+ #++
+
+ def handle_error(err)
+ case
+ when $DEBUG
+ puts err.message
+ puts
+ puts err.backtrace
+ else puts err.message
+ end
+ end
+
protected
- def assign_paths(root, paths, *targets)
- paths = paths.collect {|path| root[path]}
- targets.each do |array|
- paths.reverse_each do |path|
- array.unshift(path)
- end
- array.uniq!
+ # Iterates over each nested env, yielding the root path and env.
+ # This is the manifest method for envs.
+ def manifest_glob_envs
+ collect {|env| [env.root.root, env] }.sort_by {|root, env| File.basename(root) }
+ end
+
+ def manifest_map(context, path)
+ [[context, path]]
+ end
+
+ alias default_manifest_glob_tasks manifest_glob_tasks
+
+ def manifest_glob_tasks
+ paths = default_manifest_glob_tasks
+
+ # very odd behaviors --
+ # * OS X is case-insensitive, apparently. Tapfile.rb and tapfile.rb are the same.
+ # * require 'tapfile' does not work
+ # * require 'tapfile.rb' works
+ # * load 'tapfile' works
+ #
+ root.glob(:root, DEFAULT_TASK_FILE).each do |path|
+ next if File.directory?(path)
+ paths.unshift [root.root, path]
end
+ paths
end
- def handle_unknown_env_config(key, value)
- raise "unknown env config: #{key}"
+ # Raises an error if self is already active (and hence, configurations
+ # should not be modified)
+ def check_configurable
+ raise "path configurations are disabled when active" if active?
+ end
+
+ # Resets envs using the current env_paths and gems.
+ def reset_envs
+ self.envs = env_paths.collect do |path|
+ Env.instance_for(path)
+ end + gems.collect do |spec|
+ Env.instance_for(spec.full_gem_path)
+ end
+ end
+
+ # Recursively iterates through envs collecting all envs into
+ # the target. The result is a unique array of all nested
+ # envs, in order, beginning with self.
+ def flatten_envs(target=[])
+ unless target.include?(self)
+ target << self
+ envs.each do |env|
+ env.flatten_envs(target)
+ end
+ end
+
+ target
+ end
+
+ # Raised when there is a Env-level configuration error.
+ class ConfigError < StandardError
+ attr_reader :original_error, :env_path
+
+ def initialize(original_error, env_path)
+ @original_error = original_error
+ @env_path = env_path
+ super()
+ end
+
+ def message
+ "Configuration error: #{original_error.message}\n" +
+ ($DEBUG ? "#{original_error.backtrace}\n" : "") +
+ "Check '#{env_path}' configurations"
+ end
end
end
end
\ No newline at end of file