Class: Tap::Env
- Inherits:
-
Object
- Object
- Tap::Env
- Includes:
- Configurable, Enumerable, Minimap
- Defined in:
- lib/tap/env.rb,
lib/tap/env/gems.rb,
lib/tap/env/context.rb,
lib/tap/env/minimap.rb,
lib/tap/env/constant.rb,
lib/tap/env/manifest.rb,
lib/tap/env/string_ext.rb
Overview
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 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 to see ENV configs in action.
These configurations may be accessed from Env#config, and are automatically incorporated by Env#setup.
Defined Under Namespace
Modules: Gems, Minimap, StringExt Classes: ConfigError, Constant, Context, Manifest
Constant Summary collapse
- CONFIG_FILE =
The config file path
"tap.yml"
- HOME =
The home directory for Tap
File.("#{File.dirname(__FILE__)}/../..")
Instance Attribute Summary collapse
-
#context ⇒ Object
readonly
A Context tracking information shared among a set of envs.
-
#envs ⇒ Object
An array of nested Envs, by default comprised of the env_path + gem environments (in that order).
Class Method Summary collapse
-
.config(env_vars = ENV) ⇒ Object
Returns the Env configs specified in ENV.
-
.load_config(path) ⇒ Object
Loads configurations from path as YAML.
-
.setup(dir = Dir.pwd, config_file = CONFIG_FILE) ⇒ Object
Initializes and activates an env as described in the config file under dir.
-
.setup_gem(gem_name, context = Context.new) ⇒ Object
Generates an Env for the specified gem or Gem::Specification.
Instance Method Summary collapse
-
#[](key, invert = invert?) ) ⇒ Object
Seeks a constant for the key, constantizing if necessary.
-
#activate ⇒ Object
Activates self by doing the following, in order:.
-
#active? ⇒ Boolean
Return true if self has been activated.
-
#class_path(dir, obj, *paths, &block) ⇒ Object
Returns the module_path traversing the inheritance hierarchy for the class of obj (or obj if obj is a Class).
-
#constants ⇒ Object
Returns a manifest of Constants located in .rb files under each of the const_paths.
-
#deactivate ⇒ Object
Deactivates self by doing the following in order:.
-
#each ⇒ Object
Passes each nested env to the block in order, starting with self.
-
#glob(dir, pattern = "**/*") ⇒ Object
Globs the abstract directory for files in the specified directory alias, matching the pattern.
-
#hlob(dir, pattern = "**/*") ⇒ Object
Same as glob but returns results as a hash of (relative_path, path) pairs.
-
#initialize(config_or_dir = Dir.pwd, context = {}) ⇒ Env
constructor
Initializes a new Env linked to the specified directory.
-
#inspect(template = nil, globals = {}, filename = nil) ⇒ Object
When no template is specified, inspect generates a fairly standard inspection string.
-
#invert ⇒ Object
Returns a duplicate of self with inverted AGET lookup.
-
#invert! ⇒ Object
Inverts the AGET lookup for self.
-
#invert? ⇒ Boolean
Indicates AGET looks up constants from keys (false), or keys from constants (true).
-
#manifest(type = nil, &block) ⇒ Object
Generates a Manifest for self using the block as a builder.
-
#module_path(dir, modules, *paths, &block) ⇒ Object
Retrieves a path associated with the inheritance hierarchy of an object.
-
#path(dir = :root, *paths) ⇒ Object
Returns the path to the specified file, as determined by root.
-
#push(env) ⇒ Object
(also: #<<)
Pushes env onto envs, removing duplicates.
-
#recursive_inject(memo, &block) ⇒ Object
Recursively injects the memo to each env of self.
-
#register(constant) ⇒ Object
Registers a constant with self.
-
#reverse_each ⇒ Object
Passes each nested env to the block in reverse order, ending with self.
-
#scan(dir, pattern = "**/*.rb") ⇒ Object
Scans the files matched under the directory and pattern for constants and adds them to the existing constants for self.
-
#unshift(env) ⇒ Object
Unshifts env onto envs.
Methods included from Minimap
#minihash, #minimap, #minimatch
Constructor Details
#initialize(config_or_dir = Dir.pwd, context = {}) ⇒ Env
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.
383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 |
# File 'lib/tap/env.rb', line 383 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 |
Instance Attribute Details
#context ⇒ Object (readonly)
A Context tracking information shared among a set of envs.
308 309 310 |
# File 'lib/tap/env.rb', line 308 def context @context end |
#envs ⇒ Object
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.
305 306 307 |
# File 'lib/tap/env.rb', line 305 def envs @envs end |
Class Method Details
.config(env_vars = ENV) ⇒ Object
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).
172 173 174 175 176 177 178 179 180 |
# File 'lib/tap/env.rb', line 172 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 |
.load_config(path) ⇒ Object
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.
281 282 283 284 285 286 287 288 289 |
# File 'lib/tap/env.rb', line 281 def load_config(path) return {} unless path begin Root::Utils.trivial?(path) ? {} : (YAML.load_file(path) || {}) rescue(Exception) raise ConfigError.new($!, path) end end |
.setup(dir = Dir.pwd, config_file = CONFIG_FILE) ⇒ Object
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 => 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.
194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 |
# File 'lib/tap/env.rb', line 194 def setup(dir=Dir.pwd, config_file=CONFIG_FILE) # setup configurations config = {'root' => dir, 'gems' => :all} user_config_file = config_file ? File.join(dir, config_file) : nil user = load_config(user_config_file) config.merge!(self.config) config.merge!(user) # keys must be symbolized as they are immediately # used to initialize the Env configs config = config.inject({}) do |, (key, value)| [key.to_sym || key] = value end # 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 |
.setup_gem(gem_name, context = Context.new) ⇒ Object
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.
235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 |
# File 'lib/tap/env.rb', line 235 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 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 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) end |
Instance Method Details
#[](key, invert = invert?) ) ⇒ Object
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.
674 675 676 677 678 679 680 681 682 683 684 685 686 687 |
# File 'lib/tap/env.rb', line 674 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 end |
#activate ⇒ Object
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.
499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 |
# File 'lib/tap/env.rb', line 499 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 end |
#active? ⇒ Boolean
Return true if self has been activated.
563 564 565 |
# File 'lib/tap/env.rb', line 563 def active? @active end |
#class_path(dir, obj, *paths, &block) ⇒ Object
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.
633 634 635 636 637 |
# File 'lib/tap/env.rb', line 633 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 |
#constants ⇒ Object
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).
656 657 658 659 660 661 662 663 664 665 666 667 668 669 |
# File 'lib/tap/env.rb', line 656 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 end end |
#deactivate ⇒ Object
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.
541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 |
# File 'lib/tap/env.rb', line 541 def deactivate return false unless active? @active = false # dectivate nested envs envs.reverse_each do |env| env.deactivate 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 end |
#each ⇒ Object
Passes each nested env to the block in order, starting with self.
454 455 456 |
# File 'lib/tap/env.rb', line 454 def each visit_envs.each {|e| yield(e) } end |
#glob(dir, pattern = "**/*") ⇒ Object
Globs the abstract directory for files in the specified directory alias, matching the pattern. The expanded path of each matching file is returned.
570 571 572 |
# File 'lib/tap/env.rb', line 570 def glob(dir, pattern="**/*") hlob(dir, pattern).values.sort! end |
#hlob(dir, pattern = "**/*") ⇒ Object
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.
577 578 579 580 581 582 583 584 585 586 587 |
# File 'lib/tap/env.rb', line 577 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 end results end |
#inspect(template = nil, globals = {}, filename = nil) ⇒ Object
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).
752 753 754 755 756 757 758 759 760 761 762 763 764 765 |
# File 'lib/tap/env.rb', line 752 def inspect(template=nil, globals={}, filename=nil) # :yields: templater, globals if template == nil return "#<#{self.class}:#{object_id} root='#{root.root}'>" 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 end |
#invert ⇒ Object
Returns a duplicate of self with inverted AGET lookup.
702 703 704 |
# File 'lib/tap/env.rb', line 702 def invert dup.invert! end |
#invert! ⇒ Object
Inverts the AGET lookup for self.
696 697 698 699 |
# File 'lib/tap/env.rb', line 696 def invert! @invert = !@invert self end |
#invert? ⇒ Boolean
Indicates AGET looks up constants from keys (false), or keys from constants (true).
691 692 693 |
# File 'lib/tap/env.rb', line 691 def invert? @invert end |
#manifest(type = nil, &block) ⇒ Object
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.
647 648 649 650 |
# File 'lib/tap/env.rb', line 647 def manifest(type=nil, &block) # :yields: env cache = type ? (context.cache[type] ||= {}) : {} Manifest.new(self, block, cache) end |
#module_path(dir, modules, *paths, &block) ⇒ Object
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.
613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 |
# File 'lib/tap/env.rb', line 613 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 end |
#path(dir = :root, *paths) ⇒ Object
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.
593 594 595 596 597 598 599 |
# File 'lib/tap/env.rb', line 593 def path(dir = :root, *paths) each do |env| path = env.root.path(dir, *paths) return path if !block_given? || yield(path) end nil end |
#push(env) ⇒ Object Also known as: <<
Pushes env onto envs, removing duplicates.
Self cannot be pushed onto self.
444 445 446 447 448 449 450 |
# File 'lib/tap/env.rb', line 444 def push(env) unless env == self || envs[-1] == env envs = self.envs.reject {|e| e == env } self.envs = envs.push(env) end self end |
#recursive_inject(memo, &block) ⇒ Object
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)}
487 488 489 |
# File 'lib/tap/env.rb', line 487 def recursive_inject(memo, &block) # :yields: memo, env inject_envs(memo, &block) end |
#register(constant) ⇒ Object
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.
724 725 726 727 728 729 730 731 732 733 734 735 736 |
# File 'lib/tap/env.rb', line 724 def register(constant) const_name = constant.to_s entries = constants.entries(self) # try to find the existing Constant before making a new constant unless constant = entries.find {|const| const.const_name == const_name} constant = Constant.new(const_name) entries << constant entries.replace entries.sort_by {|const| const.const_name } end constant end |
#reverse_each ⇒ Object
Passes each nested env to the block in reverse order, ending with self.
459 460 461 |
# File 'lib/tap/env.rb', line 459 def reverse_each visit_envs.reverse_each {|e| yield(e) } end |
#scan(dir, pattern = "**/*.rb") ⇒ Object
Scans the files matched under the directory and pattern for constants and adds them to the existing constants for self.
708 709 710 711 712 713 714 715 716 717 718 |
# File 'lib/tap/env.rb', line 708 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 end |
#unshift(env) ⇒ Object
Unshifts env onto envs. Self cannot be unshifted onto self.
435 436 437 438 439 440 |
# File 'lib/tap/env.rb', line 435 def unshift(env) unless env == self || envs[0] == env self.envs = envs.dup.unshift(env) end self end |