lib/tap/env.rb in tap-0.10.0 vs lib/tap/env.rb in tap-0.10.1
- old
+ new
@@ -1,36 +1,40 @@
-require 'tap/root'
+require 'tap/support/manifest'
require 'tap/support/constant'
require 'tap/support/summary'
-require 'tap/support/manifest'
+require 'tap/support/gems'
module Tap
#--
# 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
- @@instance = nil
- @@instances = {}
- @@manifests = {}
-
- class << self
+ 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
+ # A hash of predefined manifest classes that can be initialized
+ # from an env. These classes are instantiated by instances
+ # of Env, as needed.
+ def manifests
+ @@manifests
+ end
+
# 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.
#
@@ -43,72 +47,94 @@
# # 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)
+ def instantiate(path_or_root, default_config={}, logger=nil, &block)
path = path_or_root.kind_of?(Root) ? path_or_root.root : path_or_root
path = pathify(path)
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
+ (instances[path] = new({}, root, logger)).reconfigure(config, &block)
rescue(Exception)
raise Env::ConfigError.new($!, path)
end
end
+ def instance_for(path)
+ path = pathify(path)
+ instances.has_key?(path) ? instances[path] : instantiate(path)
+ end
+
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
- def instance_for(path)
- path = pathify(path)
- instances.has_key?(path) ? instances[path] : instantiate(path)
- 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)
+ def manifest(name, pattern, default_paths=[], &block) # :yields: search_path
+ manifest_class = Class.new(Support::Manifest)
+ manifest_class.send(:define_method, :entries_for, &block) if block_given?
+ manifest_class.send(:attr_reader, :env)
+ manifest_class.send(:define_method, :initialize) do |env|
+ @env = env
+ search_paths = default_paths.collect {|path| env.root[path] }
+ search_paths += env.root.glob(:root, pattern)
+ super search_paths.sort_by {|p| File.basename(p) }
+ end
- # 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]
+ manifests[name] = manifest_class
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
+ #--
+ # 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 path_manifest(name, paths_key, pattern, default_paths=[], &block) # :yields: search_path_root, search_path
+ manifest_class = Class.new(Support::Manifest)
+ manifest_class.send(:define_method, :entries_for, &block) if block_given?
+ manifest_class.send(:attr_reader, :env)
+ manifest_class.send(:define_method, :initialize) do |env|
+ @env = env
+ search_paths = default_paths.collect do |path|
+ [env.root.root, env.root[path]]
+ end
+
+ env.send(paths_key).each do |search_path_root|
+ env.root.glob(search_path_root, pattern).each do |search_path|
+ search_paths << [search_path_root, search_path]
+ end
+ end
+
+ super search_paths.sort_by {|pr, p| File.basename(p) }
+ end
+ manifests[name] = manifest_class
end
+ # Returns the gemspecs for all installed gems with a DEFAULT_TASK_FILE
+ # or DEFAULT_CONFIG_FILE. If latest==true, then only the specs for the
+ # most current gems will be returned.
+ def gemspecs(latest=true)
+ Support::Gems.select_gems(latest) do |spec|
+ File.exists?(File.join(spec.full_gem_path, DEFAULT_TASK_FILE)) ||
+ File.exists?(File.join(spec.full_gem_path, DEFAULT_CONFIG_FILE))
+ end
+ 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.
@@ -135,50 +161,25 @@
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
+
+ class Manifest < Support::Manifest
+ def initialize(env)
+ super([])
+ @entries = env.collect {|e| [e.root.root, e] }
end
end
+ @@instance = nil
+ @@instances = {}
+ @@manifests = {:envs => Manifest}
+
# The global config file path
- GLOBAL_CONFIG_FILE = File.join(Gem.user_home, ".tap.yml")
+ GLOBAL_CONFIG_FILE = File.join(Support::Gems.user_home, ".tap.yml")
# The default config file path
DEFAULT_CONFIG_FILE = "tap.yml"
# The default task file path
@@ -202,11 +203,11 @@
# 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)
+ spec = Support::Gems.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
@@ -222,45 +223,45 @@
@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.
+
+ # Designate load paths.
path_config :load_paths, ["lib"]
# Designate paths for discovering and executing commands.
path_config :command_paths, ["cmd"]
# 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')
+ path_manifest(:tasks, :load_paths, "**/*.rb", [DEFAULT_TASK_FILE]) do |load_path, path|
+ next unless File.file?(path) && 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 = env.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
- manifest(:commands, :command_paths, "**/*.rb")
+ path_manifest(:commands, :command_paths, "**/*.rb") do |command_path, path|
+ File.file?(path) ? [[path, path]] : nil
+ end
- manifest(:generators, :generator_paths, '**/*_generator.rb') do |load_path, path|
+ path_manifest(:generators, :generator_paths, '**/*_generator.rb') do |generator_path, path|
dirname = File.dirname(path)
- next unless "#{File.basename(dirname)}_generator.rb" == File.basename(path)
+ next unless File.file?(path) && "#{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 = env.root.relative_filepath(generator_path, dirname)
[key, Support::Constant.new((key + '_generator').camelize, path)]
else
[const_name.underscore, Support::Constant.new(const_name, path)]
end
end
@@ -302,21 +303,21 @@
# Self cannot be unshifted onto self.
def unshift(env)
unless env == self || envs[0] == env
self.envs = envs.dup.unshift(env)
end
- envs
+ 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
- envs
+ self
end
# Passes each nested env to the block in order, starting with self.
def each
envs(true).each {|e| yield(e) }
@@ -325,22 +326,44 @@
# Passes each nested env to the block in reverse order, ending with self.
def reverse_each
envs(true).reverse_each {|e| yield(e) }
end
+ # Visits each nested env in order, starting with self, and passing
+ # to the block the env and any arguments generated by the parent of
+ # the env. The initial arguments are set when recursive_each is
+ # first called; subsequent arguements are the return values of the
+ # block.
+ #
+ # e0, e1, e2, e3, e4 = ('a'..'e').collect {|name| Tap::Env.new(:name => name) }
+ #
+ # e0.push(e1).push(e2)
+ # e1.push(e3).push(e4)
+ #
+ # lines = []
+ # e0.recursive_each(0) do |env, nesting_depth|
+ # 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_each(*args, &block) # :yields: env, *parent_args
+ each_nested_env(self, [], args, &block)
+ end
+
# 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:
#
# * partition overrides into env, root, and other configs
# * reconfigure root with the root configs
@@ -410,11 +433,11 @@
# is already active.
def activate
return false if active?
@active = true
- @@instance = self unless @@instance
+ @@instance = self if @@instance == nil
# freeze array configs like load_paths
config.each_pair do |key, value|
case value
when Array then value.freeze
@@ -424,19 +447,16 @@
# 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!
+ # add load paths
+ load_paths.reverse_each do |path|
+ $LOAD_PATH.unshift(path)
end
+ $LOAD_PATH.uniq!
true
end
# Deactivates self by clearing manifests and deleting load_paths for self
# from the load_path_targets. Env.instance will no longer reference self
@@ -444,17 +464,15 @@
#
# Returns true if deactivate succeeded, or false if self is not active.
def deactivate
return false unless active?
- # remove load paths from load_path_targets
- load_path_targets.each do |target|
- load_paths.each do |path|
- target.delete(path)
- end
+ # remove load paths
+ load_paths.each do |path|
+ $LOAD_PATH.delete(path)
end
-
+
# 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)
@@ -476,61 +494,80 @@
# Return true if self has been activated.
def active?
@active
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?
-
+ # Returns the manifest in manifests by the specified name. Yields
+ # each entry in the manifest to the block, if given, or simply
+ # builds and returns the manifest.
+ #
+ # If the specified manifest does not exists, the manifest class
+ # in self.class.manifests will be instatiated with self to make
+ # the manifest. Raises an error if no manifest could be found
+ # or instantiated.
+ def manifest(name, build=false)
+ manifest = manifests[name] ||= case
+ when manifests_class = self.class.manifests[name]
+ manifests_class.new(self)
+ else
+ raise "unknown manifest: #{name}"
+ end
+
+ manifest.build if build
manifest
end
- def find(name, pattern)
- manifest(name) do |key, path|
- return path if Root.minimal_match?(key, pattern)
+ # Returns the first value in the specified manifest where the key
+ # mini-matches the input pattern. See Tap::Root.minimal_match?
+ # for details on mini-matching.
+ def find(name, pattern, value_only=true)
+ manifest(name).each do |key, value|
+ return(value_only ? value : [key, value]) if Root.minimal_match?(key, pattern)
end
nil
end
- def search(name, pattern)
- return find(name, pattern) if name == :envs
+ # Like find, but searches across all envs for the matching value.
+ # An env pattern can be provided in pattern, to select a single
+ # env to search.
+ #
+ # The :envs manifest cannot be searched; use find instead.
+ def search(name, pattern, value_only=true)
+ if name == :envs
+ raise ArgumentError, "cannot search the :envs manifest; use find instead"
+ end
envs = case pattern
when /^(.*):([^:]+)$/
env_pattern = $1
pattern = $2
- find(:envs, env_pattern) or raise(ArgumentError, "could not find env: #{env_pattern}")
+ find(:envs, env_pattern)
else manifest(:envs).values
end
envs.each do |env|
- if result = env.find(name, pattern)
+ if result = env.find(name, pattern, value_only)
return result
end
- end
+ end if envs
nil
end
+ def constantize(name, *patterns)
+ patterns.collect do |pattern|
+ case const = search(name, pattern)
+ when Support::Constant then const.constantize
+ else raise "could not constantize: #{pattern} (#{name})"
+ end
+ end
+ 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)
+ manifest(:envs, true).minimize.each do |(key, env)|
+ summary.add(key, env, env.manifest(name, true).minimize)
end
summary
end
def summarize(name, &block)
@@ -544,55 +581,13 @@
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
- # 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
-
# 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
@@ -617,10 +612,23 @@
end
end
target
end
-
+
+ private
+
+ def each_nested_env(env, visited, args, &block)
+ return if visited.include?(env)
+
+ visited << env
+ next_args = yield(env, *args)
+ next_args = [] if next_args == nil
+ env.envs.each do |nested_env|
+ each_nested_env(nested_env, visited, next_args, &block)
+ end
+ 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)
\ No newline at end of file