Class: Tap::Env

Inherits:
Object
  • Object
show all
Includes:
Configurable, Enumerable, Minimap
Defined in:
lib/tap/env.rb,
lib/tap/env/gems.rb,
lib/tap/env/minimap.rb,
lib/tap/env/constant.rb,
lib/tap/env/manifest.rb,
lib/tap/env/string_ext.rb

Overview

Env abstracts an execution environment that spans many directories.

Defined Under Namespace

Modules: Gems, Minimap, StringExt Classes: ConfigError, Constant, Manifest

Constant Summary collapse

COMPOUND_KEY =

Matches a compound registry search key. After the match, if the key is compound then:

$1:: env_key
$2:: key

If the key is not compound, $2 is nil and $1 is the key.

/^((?:[A-z]:(?:\/|\\))?.*?)(?::(.*))?$/

Class Attribute Summary collapse

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

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. A config file basename may be specified to load configurations from ‘dir/basename’ as YAML. If a basename is specified, the same basename will be used to load configurations for nested envs.

Configurations may be manually provided in the place of dir. In that case, the same rules apply for loading configurations for nested envs, but no configurations will be loaded for the current instance.

The cache is used internally to prevent infinite loops of nested envs, and to optimize the generation of manifests.



188
189
190
191
192
193
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 188

def initialize(config_or_dir=Dir.pwd, context={})
  @active = false
  @manifests = {}
  @context = context
  
  # setup root
  config = nil
  @root = case config_or_dir
  when Root   then config_or_dir
  when String then 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
  
  if basename && !config
    config = Env.load_config(File.join(@root.root, basename))
  end
  
  if instance(@root.root)
    raise "context already has an env for: #{@root.root}"
  end
  instances << self
  
  # set these for reset_env
  @gems = nil
  @env_paths = nil
  initialize_config(config || {})
end

Class Attribute Details

.instance(auto_initialize = true) ⇒ Object



14
15
16
# File 'lib/tap/env.rb', line 14

def instance(auto_initialize=true)
  @instance ||= (auto_initialize ? new : nil)
end

Instance Attribute Details

#contextObject (readonly)

Returns the value of attribute context.



123
124
125
# File 'lib/tap/env.rb', line 123

def context
  @context
end

#envsObject

An array of nested Envs, by default comprised of the env_path + gem environments (in that order).



121
122
123
# File 'lib/tap/env.rb', line 121

def envs
  @envs
end

#manifestsObject (readonly)

Returns the value of attribute manifests.



125
126
127
# File 'lib/tap/env.rb', line 125

def manifests
  @manifests
end

Class Method Details

.from_gemspec(spec, context = {}) ⇒ Object



18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
# File 'lib/tap/env.rb', line 18

def from_gemspec(spec, context={})
  path = spec.full_gem_path
  basename = context[:basename]
  
  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 basename && !File.exists?(File.join(gemspec.full_gem_path, basename))
      next
    end
    
    dependencies << gemspec
  end
  
  config = {
    'root' => path,
    'gems' => dependencies,
    'load_paths' => spec.require_paths,
    'set_load_paths' => false
  }
  
  if context[:basename]
    config.merge!(Env.load_config(File.join(path, context[:basename])))
  end
  
  new(config, context)
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.



58
59
60
61
62
63
64
65
66
# File 'lib/tap/env.rb', line 58

def load_config(path)
  return {} unless path
  
  begin
    Root::Utils.trivial?(path) ? {} : (YAML.load_file(path) || {})
  rescue(Exception)
    raise ConfigError.new($!, path)
  end
end

.scan(load_path, pattern = '**/*.rb') ⇒ Object



68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
# File 'lib/tap/env.rb', line 68

def scan(load_path, pattern='**/*.rb')
  Dir.chdir(load_path) do 
    Dir.glob(pattern).each do |require_path|
      next unless File.file?(require_path)

      default_const_name = require_path.chomp('.rb').camelize
      
      # note: the default const name has to be set here to allow for implicit
      # constant attributes (because a dir is needed to figure the relative path).
      # A conflict could arise if the same path is globed from two different
      # dirs... no surefire solution.
      document = Lazydoc[require_path]
      case document.default_const_name
      when nil then document.default_const_name = default_const_name
      when default_const_name
      else raise "found a conflicting default const name"
      end
      
      # scan for constants
      Lazydoc::Document.scan(File.read(require_path)) do |const_name, type, comment|
        const_name = default_const_name if const_name.empty?
        constant = Constant.new(const_name, require_path, comment)
        yield(type, constant)
        
        ###############################################################
        # [depreciated] manifest as a task key will be removed at 1.0
        if type == 'manifest'
          warn "depreciation: ::task should be used instead of ::manifest as a resource key (#{require_path})"
          yield('task', constant)
        end
        ###############################################################
      end
    end
  end
end

Instance Method Details

#[](type) ⇒ Object



482
483
484
# File 'lib/tap/env.rb', line 482

def [](type)
  manifest(type)
end

#activateObject

Activates self by doing the following, in order:

  • sets Env.instance to self (unless already set)

  • activate nested environments

  • unshift load_paths to $LOAD_PATH (if set_load_paths is true)

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.



301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
# File 'lib/tap/env.rb', line 301

def activate
  return false if active?
  
  @active = true
  unless self.class.instance(false)
    self.class.instance = self
  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
  if set_load_paths
    load_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.

Returns:

  • (Boolean)


367
368
369
# File 'lib/tap/env.rb', line 367

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.



428
429
430
431
432
# File 'lib/tap/env.rb', line 428

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

#deactivateObject

Deactivates self by doing the following in order:

  • deactivates nested environments

  • removes load_paths from $LOAD_PATH (if set_load_paths is true)

  • 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.



339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
# File 'lib/tap/env.rb', line 339

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 if set_load_paths
  
  # unfreeze envs and load paths
  @envs = @envs.dup
  @load_paths = @load_paths.dup
  
  # clear cached data
  klass = self.class
  if klass.instance(false) == self
    klass.instance = nil
  end
  
  true
end

#eachObject

Passes each nested env to the block in order, starting with self.



255
256
257
# File 'lib/tap/env.rb', line 255

def each
  visit_envs.each {|e| yield(e) }
end

#eeek(type, key) ⇒ Object

– Environment-seek



493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
# File 'lib/tap/env.rb', line 493

def eeek(type, key)
  key =~ COMPOUND_KEY
  envs = if $2
    # compound key, match for env
    key = $2
    [minimatch($1)].compact
  else
    # not a compound key, search all envs by iterating self
    self
  end

  # traverse envs looking for the first
  # manifest entry matching key
  envs.each do |env|
    if result = env.manifest(type).minimatch(key)
      return [env, result]
    end
  end

  nil
end

#glob(dir, pattern = "**/*") ⇒ Object



383
384
385
# File 'lib/tap/env.rb', line 383

def glob(dir, pattern="**/*")
  hlob(dir, pattern).values.sort!
end

#hlob(dir, pattern = "**/*") ⇒ Object



371
372
373
374
375
376
377
378
379
380
381
# File 'lib/tap/env.rb', line 371

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

All templaters are yielded to the block before any are built. This allows globals to be determined for all environments.



526
527
528
529
530
531
532
533
534
535
536
537
538
539
# File 'lib/tap/env.rb', line 526

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 = Support::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

#manifest(type) ⇒ Object

:yields: env



471
472
473
474
475
476
477
478
479
480
# File 'lib/tap/env.rb', line 471

def manifest(type) # :yields: env
  type = type.to_sym
  
  registry[type] ||= begin
    builder = builders[type]
    builder ? builder.call(self) : []
  end
  
  manifests[type] ||= Manifest.new(self, type)
end

#minikeyObject

The minikey for self (root.root).



225
226
227
# File 'lib/tap/env.rb', line 225

def minikey
  root.root
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.

The paths are yielded to the block and when the block returns true, the path will be returned. If no block is given, the first module path is returned. Returns nil if the block never returns true.



408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
# File 'lib/tap/env.rb', line 408

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, *paths) ⇒ Object



387
388
389
390
391
392
393
# File 'lib/tap/env.rb', line 387

def path(dir, *paths)
  each do |env|
    path = env.root.path(dir, *paths)
    return path if !block_given? || yield(path)
  end
  nil
end

#push(env) ⇒ Object

Pushes env onto envs, removing duplicates.

Self cannot be pushed onto self.



246
247
248
249
250
251
252
# File 'lib/tap/env.rb', line 246

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)}


288
289
290
# File 'lib/tap/env.rb', line 288

def recursive_inject(memo, &block) # :yields: memo, env
  inject_envs(memo, &block)
end

#register(type, override = false, &block) ⇒ Object



454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
# File 'lib/tap/env.rb', line 454

def register(type, override=false, &block)
  type = type.to_sym
  
  # error for existing, or overwrite
  case
  when override
    builders.delete(type)
    registries.each {|root, registry| registry.delete(type) }
  when builders.has_key?(type)
    raise "a builder is already registered for: #{type.inspect}"
  when registries.any? {|root, registry| registry.has_key?(type) }
    raise "entries are already registered for: #{type.inspect}"
  end
  
  builders[type] = block
end

#registry(build = false) ⇒ Object



434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
# File 'lib/tap/env.rb', line 434

def registry(build=false)
  builders.each_pair do |type, builder|
    registry[type] ||= builder.call(self)
  end if build
  
  registries[minikey] ||= begin
    registry = {}
    load_paths.each do |load_path|
      next unless File.directory?(load_path)
      
      Env.scan(load_path) do |type, constant|
        entries = registry[type.to_sym] ||= []
        entries << constant
      end
    end
    
    registry
  end
end

#resetObject



486
487
488
489
# File 'lib/tap/env.rb', line 486

def reset
  manifests.clear
  registries.clear
end

#reverse_eachObject

Passes each nested env to the block in reverse order, ending with self.



260
261
262
# File 'lib/tap/env.rb', line 260

def reverse_each
  visit_envs.reverse_each {|e| yield(e) }
end

#seek(type, key, &block) ⇒ Object

Searches across each for the first registered object minimatching key. A single env can be specified by using a compound key like ‘env_key:key’.

Returns nil if no matching object is found.



519
520
521
522
# File 'lib/tap/env.rb', line 519

def seek(type, key, &block) # :yields: env, key
  env, result = eeek(type, key, &block)
  result
end

#unshift(env) ⇒ Object

Unshifts env onto envs. Self cannot be unshifted onto self.



237
238
239
240
241
242
# File 'lib/tap/env.rb', line 237

def unshift(env)
  unless env == self || envs[0] == env
    self.envs = envs.dup.unshift(env)
  end
  self
end