lib/tap/env.rb in tap-0.10.1 vs lib/tap/env.rb in tap-0.11.0

- old
+ new

@@ -1,18 +1,17 @@ -require 'tap/support/manifest' -require 'tap/support/constant' -require 'tap/support/summary' +require 'tap/support/constant_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 + include Support::Configurable + include Support::Minimap class << self # Returns the active instance of Env. def instance @@ -24,17 +23,10 @@ # 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. # @@ -75,81 +67,28 @@ path = File.join(path, DEFAULT_CONFIG_FILE) end File.expand_path(path) end - 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) } + def manifest(name, &block) # :yields: env (returns manifest) + name = name.to_sym + define_method(name) do + self.manifests[name] ||= block.call(self).bind(self, name) end - - manifests[name] = manifest_class 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 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. + # 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_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. - 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 - 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']]. # @@ -163,59 +102,58 @@ instance_variable_set(instance_variable, [*input].compact.collect {|path| root[path]}.uniq) end end 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(Support::Gems.user_home, ".tap.yml") - # The default config file path DEFAULT_CONFIG_FILE = "tap.yml" - # The default task file path - DEFAULT_TASK_FILE = "tapfile.rb" - # 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 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. - #-- - # 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 + specs_by_name = {} @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) end - spec + (specs_by_name[spec.name] ||= []) << spec + spec.name end.uniq + + # this song and dance is to ensure that the latest spec for a + # given gem appears first in the manifest + specs_by_name.each_pair do |name, specs| + specs_by_name[name] = specs.uniq.sort_by {|spec| spec.version }.reverse + end + + @gems.collect! do |name| + specs_by_name[name] + end.flatten! + reset_envs end # Specify configuration files to load as nested Envs. config_attr :env_paths, [] do |input| @@ -233,50 +171,48 @@ path_config :command_paths, ["cmd"] # Designate paths for discovering generators. path_config :generator_paths, ["lib"] - 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 = 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 + manifest(:commands) do |env| + paths = [] + env.command_paths.each do |path_root| + paths.concat env.root.glob(path_root) end + + paths = paths.sort_by {|path| File.basename(path) } + Support::Manifest.new(paths) end - path_manifest(:commands, :command_paths, "**/*.rb") do |command_path, path| - File.file?(path) ? [[path, path]] : nil + manifest(:tasks) do |env| + tasks = Support::ConstantManifest.new('manifest') + env.load_paths.each do |path_root| + tasks.register(path_root, '**/*.rb') + end + # tasks.cache = env.cache[:tasks] + tasks end - - path_manifest(:generators, :generator_paths, '**/*_generator.rb') do |generator_path, path| - dirname = File.dirname(path) - next unless File.file?(path) && "#{File.basename(dirname)}_generator.rb" == File.basename(path) + + manifest(:generators) do |env| + generators = Support::ConstantManifest.intern('generator') do |manifest, const| + const.name.underscore.chomp('_generator') + end - next unless document = Support::Lazydoc.scan_doc(path, 'generator') - document.const_names.collect do |const_name| - if const_name.empty? - 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 + env.generator_paths.each do |path_root| + 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 @envs = [] @active = false @manifests = {} - @manifested = [] - + # initialize these for reset_env @gems = [] @env_paths = [] initialize_config(config) @@ -453,10 +389,21 @@ 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 @@ -494,98 +441,129 @@ # Return true if self has been activated. def active? @active end - # 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. + # Searches each env for the first existing file or directory at + # env.root.filepath(dir, path). Paths are expanded, and search_path + # 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. # - # 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}" + # Returns nil if no file can be found. + def search_path(dir, path) + each do |env| + directory = env.root.filepath(dir) + file = env.root.filepath(dir, path) + + # 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 + end end - manifest.build if build - manifest - end - - # 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 - # 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 + # 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 %> +<% entries.each do |name, const| %> + <%= name.ljust(width) %> +<% end %>} + TEMPLATES[:tasks] = %Q{<% if count > 1 %> +<%= env_name %>: +<% end %> +<% entries.each do |name, const| %> +<% desc = const.document[const.name]['manifest'] %> + <%= name.ljust(width) %><%= desc.empty? ? '' : ' # ' %><%= desc %> +<% end %>} + TEMPLATES[:generators] = %Q{<% if count > 1 %> +<%= env_name %>: +<% end %> +<% entries.each do |name, const| %> +<% desc = const.document[const.name]['generator'] %> + <%= name.ljust(width) %><%= desc.empty? ? '' : ' # ' %><%= desc %> +<% end %>} + + def summarize(name, template=TEMPLATES[name]) + count = 0 + width = 10 - envs = case pattern - when /^(.*):([^:]+)$/ - env_pattern = $1 - pattern = $2 - find(:envs, env_pattern) - else manifest(:envs).values + env_names = {} + minimap.each do |env_name, env| + env_names[env] = env_name end - envs.each do |env| - if result = env.find(name, pattern, value_only) - return result + inspect(template) do |templater, share| + env = templater.env + entries = env.send(name).minimap + next(false) if entries.empty? + + templater.env_name = env_names[env] + templater.entries = entries + + count += 1 + entries.each do |entry_name, entry| + width = entry_name.length if width < entry_name.length end - end if envs - - nil + + share[:count] = count + share[:width] = width + true + end 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 + def inspect(template=nil) # :yields: templater, attrs + return "#<#{self.class}:#{object_id} root='#{root.root}'>" if template == nil + + attrs = {} + collect do |env| + templater = Support::Templater.new(template, :env => env) + block_given? ? (yield(templater, attrs) ? templater : nil) : templater + end.compact.collect do |templater| + templater.build(attrs) + end.join end - def summary(name) - summary = Support::Summary.new - manifest(:envs, true).minimize.each do |(key, env)| - summary.add(key, env, env.manifest(name, true).minimize) + 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| + templater = Support::Templater.new(template, :env => env) + next_args = block_given? ? yield(templater, attrs, *argv) : argv + templaters << templater if next_args + + next_args end - summary + + templaters.collect do |templater| + templater.build(attrs) + end.join end - def summarize(name, &block) - lines = summary(name).lines(&block) - lines << "=== no #{name} found" if lines.empty? - lines.join("\n") - end - - def inspect(brief=false) - brief ? "#<#{self.class}:#{object_id} root='#{root.root}'>" : super() - end + protected - def to_s - inspect(true) - end + # A hash of the manifests for self. + attr_reader :manifests - protected + 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? \ No newline at end of file