lib/tap/env.rb in tap-0.11.1 vs lib/tap/env.rb in tap-0.12.0
- old
+ new
@@ -1,16 +1,25 @@
require 'tap/support/constant_manifest'
-require 'tap/support/gems'
module Tap
-
+ module Support
+ autoload(:Templater, 'tap/support/templater')
+ autoload(:Gems, 'tap/support/gems')
+ end
+
+ # Envs are locations on the filesystem that have resources associated with
+ # them (commands, tasks, generators, etc). Envs may point to files, but it's
+ # more commonly environments are set to a directory and resources are various
+ # files within the directory.
+ #
+ #
#--
# Note that gems and env_paths reset envs -- custom modifications to envs will be lost
# whenever these configs are reset.
class Env
include Enumerable
- include Support::Configurable
+ include Configurable
include Support::Minimap
class << self
# Returns the active instance of Env.
@@ -36,108 +45,97 @@
# 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, &block)
- path = path_or_root.kind_of?(Root) ? path_or_root.root : path_or_root
- path = pathify(path)
+ # The Env is initialized using configurations read from the env config
+ # file. An instance will be initialized regardless of whether the config
+ # file or directory exists.
+ def instantiate(path_or_root)
+ path = config_path(path_or_root.kind_of?(Root) ? path_or_root.root : path_or_root)
+ return instances[path] if instances.has_key?(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] = new({}, root, logger)).reconfigure(config, &block)
- rescue(Exception)
- raise Env::ConfigError.new($!, path)
- end
+ config = load_config(path)
+ root = path_or_root.kind_of?(Root) ? path_or_root : File.dirname(path)
+
+ # note the assignment of env to instances MUST occur
+ # before reconfigure to prevent infinite looping
+ (instances[path] = new(root)).reconfigure(config)
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 manifest(name, &block) # :yields: env (returns manifest)
+ def manifest(name, &block) # :yields: env (and should return a manifest)
name = name.to_sym
define_method(name) do
self.manifests[name] ||= block.call(self).bind(self, name)
end
end
- # Returns the gemspecs for all installed gems with a 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_CONFIG_FILE))
+ private
+
+ def config_path(path) # :nodoc:
+ if File.directory?(path) || (!File.exists?(path) && File.extname(path) == "")
+ path = File.join(path, DEFAULT_CONFIG_FILE)
end
+
+ File.expand_path(path)
end
- protected
-
- # 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)
+ # helper to load path as YAML. load_file returns a hash if the path
+ # loads to nil or false (as happens for empty files)
+ def load_config(path) # :nodoc:
+ begin
+ Root.trivial?(path) ? {} : (YAML.load_file(path) || {})
+ rescue(Exception)
+ raise Env::ConfigError.new($!, path)
end
end
end
@@instance = nil
@@instances = {}
# The default config file path
DEFAULT_CONFIG_FILE = "tap.yml"
+ # An array of nested Envs, by default comprised of the env_path
+ # + gem environments (in that order). Nested environments are
+ # activated/deactivated with self.
+ attr_reader :envs
+
# The Root directory structure for self.
- attr_reader :root
+ nest(:root, Tap::Root) do |config|
+ case config
+ when Root then config
+ when String then Root.new(config)
+ else Root.new.reconfigure(config)
+ end
+ end
- # Gets or sets the logger for self
- attr_accessor :logger
-
- # Specify files to require when self is activated.
- config :requires, [], &c.array_or_nil
-
- # Specify files to load when self is activated.
- config :loads, [], &c.array_or_nil
-
# 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.
config_attr :gems, [] do |input|
- check_configurable
specs_by_name = {}
+
+ input = YAML.load(input) if input.kind_of?(String)
+ input = case input
+ when :latest, :all
+ Support::Gems.select_gems(input == :latest) do |spec|
+ env_config = File.join(spec.full_gem_path, Tap::Env::DEFAULT_CONFIG_FILE)
+ File.exists?(env_config)
+ end
+ else input
+ end
+
@gems = [*input].compact.collect do |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)
+ else Env.instantiate(spec.full_gem_path)
end
(specs_by_name[spec.name] ||= []) << spec
spec.name
end.uniq
@@ -155,25 +153,32 @@
reset_envs
end
# Specify configuration files to load as nested Envs.
config_attr :env_paths, [] do |input|
- check_configurable
+ input = YAML.load(input) if input.kind_of?(String)
@env_paths = [*input].compact.collect do |path|
- Env.instance_for(root[path]).env_path
+ Env.instantiate(root[path]).env_path
end.uniq
reset_envs
end
# Designate load paths.
- path_config :load_paths, ["lib"]
+ config_attr :load_paths, ["lib"] do |paths|
+ raise "load_paths cannot be modified once active" if active?
+ @load_paths = resolve_paths(paths)
+ end
# Designate paths for discovering and executing commands.
- path_config :command_paths, ["cmd"]
+ config_attr :command_paths, ["cmd"] do |paths|
+ @command_paths = resolve_paths(paths)
+ end
# Designate paths for discovering generators.
- path_config :generator_paths, ["lib"]
+ config_attr :generator_paths, ["lib"] do |paths|
+ @generator_paths = resolve_paths(paths)
+ end
manifest(:commands) do |env|
paths = []
env.command_paths.each do |path_root|
paths.concat env.root.glob(path_root)
@@ -201,43 +206,45 @@
generators.register(path_root, '**/*_generator.rb')
end
# generators.cache = env.cache[:generators]
generators
end
-
- def initialize(config={}, root=Tap::Root.new, logger=nil)
- @root = root
- @logger = logger
+
+ def initialize(path_root_or_config=Dir.pwd)
@envs = []
@active = false
@manifests = {}
# initialize these for reset_env
@gems = []
@env_paths = []
- initialize_config(config)
+ initialize_config case path_root_or_config
+ when String, Root then {:root => path_root_or_config}
+ else path_root_or_config
+ end
end
- # Sets envs removing duplicates and instances of self.
- def envs=(envs)
- @envs = envs.uniq.delete_if {|e| e == self }
- @envs.freeze
- @flat_envs = nil
+ # Clears manifests so they may be regenerated.
+ def reset
+ @manifests.clear
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.
- #
- # Returns a flattened array of the unique nested envs
- # when flat == true.
- def envs(flat=false)
- flat ? (@flat_envs ||= self.flatten_envs.freeze) : @envs
+ # Returns the key for self in Env.instances.
+ def env_path
+ Env.instances.each_pair {|path, env| return path if env == self }
+ nil
end
- # Unshifts env onto envs, removing duplicates.
+ # 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 {|e| e == self }
+ 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
@@ -254,31 +261,28 @@
self
end
# Passes each nested env to the block in order, starting with self.
def each
- envs(true).each {|e| yield(e) }
+ visit_envs.each {|e| yield(e) }
end
# Passes each nested env to the block in reverse order, ending with self.
def reverse_each
- envs(true).reverse_each {|e| yield(e) }
+ visit_envs.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.
+ # Recursively injects the memo to each env of self. Each env in envs
+ # receives the same memo from the parent.
#
- # e0, e1, e2, e3, e4 = ('a'..'e').collect {|name| Tap::Env.new(:name => name) }
+ # a,b,c,d,e = ('a'..'e').collect {|name| Tap::Env.new(:name => name) }
#
- # e0.push(e1).push(e2)
- # e1.push(e3).push(e4)
+ # a.push(b).push(c)
+ # b.push(d).push(e)
#
# lines = []
- # e0.recursive_each(0) do |env, nesting_depth|
+ # a.recursive_inject(0) do |nesting_depth, env|
# lines << "\n#{'..' * nesting_depth}#{env.config[:name]} (#{nesting_depth})"
# nesting_depth + 1
# end
#
# lines.join
@@ -287,195 +291,112 @@
# # ..b (1)
# # ....d (2)
# # ....e (2)
# # ..c (1)}
#
- def recursive_each(*args, &block) # :yields: env, *parent_args
- each_nested_env(self, [], args, &block)
+ def recursive_inject(memo, &block) # :yields: memo, env
+ inject_envs(memo, &block)
end
- # Returns the total number of unique envs nested in self (including self).
- def count
- envs(true).length
- end
-
- # Processes and resets the input configurations for both root
- # and self. Reconfiguration consists of the following steps:
+ # Activates self by doing the following, in order:
#
- # * 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)
+ # * sets Env.instance to self (unless already set)
+ # * activate nested environments
+ # * unshift load_paths to $LOAD_PATH
#
- # 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
-
- # partiton config into its parts
- env_configs = {}
- root_configs = {}
- other_configs = {}
-
- env_configurations = self.class.configurations
- root_configurations = root.class.configurations
- overrides.each_pair do |key, value|
- key = key.to_sym
-
- 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)
-
- # reconfigure self
- super(env_configs)
-
- # 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
-
- 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.
+ # Once active, the current envs and load_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
@@instance = self if @@instance == nil
- # freeze array configs like load_paths
- config.each_pair do |key, value|
- case value
- when Array then value.freeze
- end
- end
+ # freeze envs and load paths
+ @envs.freeze
+ @load_paths.freeze
# activate nested envs
envs.reverse_each do |env|
env.activate
end
-
+
# add load paths
load_paths.reverse_each do |path|
$LOAD_PATH.unshift(path)
end
-
+
$LOAD_PATH.uniq!
- # perform requires
- requires.each do |path|
- require path
- end
-
- # perform loads
- loads.each do |path|
- load path
- end
-
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
- # and the configurations are unfrozen (using duplication).
+ # Deactivates self by doing the following in order:
#
+ # * deactivates nested environments
+ # * removes load_paths from $LOAD_PATH
+ # * sets Env.instance to nil (if set to self)
+ # * clears cached manifest data
+ #
+ # Once deactivated, envs and load_paths are unfrozen and may be modified.
# Returns true if deactivate succeeded, or false if self is not active.
def deactivate
return false unless active?
+ @active = false
+ # dectivate nested envs
+ envs.reverse_each do |env|
+ env.deactivate
+ 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)
- end
- end
- @active = false
- @manifests.clear
+ # unfreeze envs and load paths
+ @envs = @envs.dup
+ @load_paths = @load_paths.dup
+
+ # clear cached data
@@instance = nil if @@instance == self
+ @manifests.clear
- # dectivate nested envs
- envs.reverse_each do |env|
- env.deactivate
- end
-
true
end
# Return true if self has been activated.
def active?
@active
end
# Searches each env for the first existing file or directory at
- # env.root.filepath(dir, path). Paths are expanded, and search_path
+ # env.root.filepath(dir, path). Paths are expanded, and search
# checks to make sure the file is, in fact, relative to env.root[dir].
# An optional block may be used to check the file; the file will only
# be returned if the block returns true.
#
# Returns nil if no file can be found.
- def search_path(dir, path)
+ def search(dir, path, strict=true)
each do |env|
directory = env.root.filepath(dir)
file = env.root.filepath(dir, path)
+ next unless File.exists?(file)
- # check the file is relative to the
- # directory, and that the file exists.
- if file.rindex(directory, 0) == 0 &&
- File.exists?(file) &&
- (!block_given? || yield(file))
- return file
+ # check the file is relative to directory
+ if strict && file.rindex(directory, 0) != 0
+ raise "not relative to search dir: #{file} (#{directory})"
end
+
+ # filter
+ return file if !block_given? || yield(file)
end
nil
end
- # def reset(name, &block)
- # each do |env|
- # env.manifests[name].each(&block)
- # env.manifests[name] = nil
- # end
- # end
-
#
TEMPLATES = {}
TEMPLATES[:commands] = %Q{<% if count > 1 %>
<%= env_name %>:
<% end %>
@@ -540,11 +461,11 @@
def recursive_inspect(template=nil, *args) # :yields: templater, attrs
return "#<#{self.class}:#{object_id} root='#{root.root}'>" if template == nil
attrs = {}
templaters = []
- recursive_each(*args) do |env, *argv|
+ recursive_inject(args) do |argv, env|
templater = Support::Templater.new(template, :env => env)
next_args = block_given? ? yield(templater, attrs, *argv) : argv
templaters << templater if next_args
next_args
@@ -562,49 +483,54 @@
def minikey(env)
env.root.root
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
-
# Resets envs using the current env_paths and gems.
def reset_envs
self.envs = env_paths.collect do |path|
- Env.instance_for(path)
+ Env.instantiate(path)
end + gems.collect do |spec|
- Env.instance_for(spec.full_gem_path)
+ Env.instantiate(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
+ # Arrayifies, compacts, and resolves input paths using root, and
+ # 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
+
+ # Recursively iterates through envs, starting with self, and
+ # collects the visited envs in order.
+ def visit_envs(visited=[], &block) # :nodoc:
+ unless visited.include?(self)
+ visited << self
+ yield(self) if block_given?
+
envs.each do |env|
- env.flatten_envs(target)
+ env.visit_envs(visited, &block)
end
end
- target
+ visited
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)
+ # 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
# Raised when there is a Env-level configuration error.
class ConfigError < StandardError
attr_reader :original_error, :env_path
\ No newline at end of file