tap 0.12.4 → 0.17.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (102) hide show
  1. data/History +34 -0
  2. data/README +62 -41
  3. data/bin/tap +36 -40
  4. data/cmd/console.rb +14 -6
  5. data/cmd/manifest.rb +62 -58
  6. data/cmd/run.rb +49 -31
  7. data/doc/API +84 -0
  8. data/doc/Class Reference +83 -115
  9. data/doc/Examples/Command Line +36 -0
  10. data/doc/Examples/Workflow +40 -0
  11. data/lib/tap/app.rb +293 -214
  12. data/lib/tap/app/node.rb +43 -0
  13. data/lib/tap/app/queue.rb +77 -0
  14. data/lib/tap/app/stack.rb +16 -0
  15. data/lib/tap/app/state.rb +22 -0
  16. data/lib/tap/constants.rb +2 -2
  17. data/lib/tap/env.rb +400 -314
  18. data/lib/tap/env/constant.rb +227 -0
  19. data/lib/tap/env/gems.rb +63 -0
  20. data/lib/tap/env/manifest.rb +89 -0
  21. data/lib/tap/env/minimap.rb +292 -0
  22. data/lib/tap/{support → env}/string_ext.rb +2 -2
  23. data/lib/tap/exe.rb +113 -125
  24. data/lib/tap/join.rb +175 -0
  25. data/lib/tap/joins.rb +9 -0
  26. data/lib/tap/joins/switch.rb +44 -0
  27. data/lib/tap/joins/sync.rb +99 -0
  28. data/lib/tap/root.rb +100 -491
  29. data/lib/tap/root/utils.rb +220 -0
  30. data/lib/tap/{support → root}/versions.rb +31 -29
  31. data/lib/tap/schema.rb +248 -0
  32. data/lib/tap/schema/parser.rb +413 -0
  33. data/lib/tap/schema/utils.rb +82 -0
  34. data/lib/tap/support/intern.rb +19 -6
  35. data/lib/tap/support/templater.rb +8 -3
  36. data/lib/tap/task.rb +175 -171
  37. data/lib/tap/tasks/dump.rb +58 -0
  38. data/lib/tap/tasks/load.rb +62 -0
  39. metadata +30 -73
  40. data/cmd/destroy.rb +0 -27
  41. data/cmd/generate.rb +0 -27
  42. data/doc/Command Reference +0 -105
  43. data/doc/Syntax Reference +0 -234
  44. data/doc/Tutorial +0 -348
  45. data/lib/tap/dump.rb +0 -142
  46. data/lib/tap/file_task.rb +0 -384
  47. data/lib/tap/generator/arguments.rb +0 -13
  48. data/lib/tap/generator/base.rb +0 -176
  49. data/lib/tap/generator/destroy.rb +0 -60
  50. data/lib/tap/generator/generate.rb +0 -93
  51. data/lib/tap/generator/generators/command/command_generator.rb +0 -21
  52. data/lib/tap/generator/generators/command/templates/command.erb +0 -32
  53. data/lib/tap/generator/generators/config/config_generator.rb +0 -98
  54. data/lib/tap/generator/generators/generator/generator_generator.rb +0 -37
  55. data/lib/tap/generator/generators/generator/templates/task.erb +0 -27
  56. data/lib/tap/generator/generators/generator/templates/test.erb +0 -26
  57. data/lib/tap/generator/generators/root/root_generator.rb +0 -84
  58. data/lib/tap/generator/generators/root/templates/MIT-LICENSE +0 -22
  59. data/lib/tap/generator/generators/root/templates/README +0 -14
  60. data/lib/tap/generator/generators/root/templates/Rakefile +0 -84
  61. data/lib/tap/generator/generators/root/templates/Rapfile +0 -11
  62. data/lib/tap/generator/generators/root/templates/gemspec +0 -27
  63. data/lib/tap/generator/generators/root/templates/test/tap_test_helper.rb +0 -3
  64. data/lib/tap/generator/generators/task/task_generator.rb +0 -25
  65. data/lib/tap/generator/generators/task/templates/task.erb +0 -14
  66. data/lib/tap/generator/generators/task/templates/test.erb +0 -19
  67. data/lib/tap/generator/manifest.rb +0 -20
  68. data/lib/tap/generator/preview.rb +0 -69
  69. data/lib/tap/load.rb +0 -64
  70. data/lib/tap/spec.rb +0 -41
  71. data/lib/tap/support/aggregator.rb +0 -65
  72. data/lib/tap/support/audit.rb +0 -333
  73. data/lib/tap/support/constant.rb +0 -143
  74. data/lib/tap/support/constant_manifest.rb +0 -126
  75. data/lib/tap/support/dependencies.rb +0 -54
  76. data/lib/tap/support/dependency.rb +0 -44
  77. data/lib/tap/support/executable.rb +0 -198
  78. data/lib/tap/support/executable_queue.rb +0 -125
  79. data/lib/tap/support/gems.rb +0 -43
  80. data/lib/tap/support/join.rb +0 -144
  81. data/lib/tap/support/joins.rb +0 -12
  82. data/lib/tap/support/joins/switch.rb +0 -27
  83. data/lib/tap/support/joins/sync_merge.rb +0 -38
  84. data/lib/tap/support/manifest.rb +0 -171
  85. data/lib/tap/support/minimap.rb +0 -90
  86. data/lib/tap/support/node.rb +0 -176
  87. data/lib/tap/support/parser.rb +0 -450
  88. data/lib/tap/support/schema.rb +0 -385
  89. data/lib/tap/support/shell_utils.rb +0 -67
  90. data/lib/tap/test.rb +0 -77
  91. data/lib/tap/test/assertions.rb +0 -38
  92. data/lib/tap/test/env_vars.rb +0 -29
  93. data/lib/tap/test/extensions.rb +0 -73
  94. data/lib/tap/test/file_test.rb +0 -362
  95. data/lib/tap/test/file_test_class.rb +0 -15
  96. data/lib/tap/test/regexp_escape.rb +0 -87
  97. data/lib/tap/test/script_test.rb +0 -46
  98. data/lib/tap/test/script_tester.rb +0 -115
  99. data/lib/tap/test/subset_test.rb +0 -260
  100. data/lib/tap/test/subset_test_class.rb +0 -99
  101. data/lib/tap/test/tap_test.rb +0 -109
  102. data/lib/tap/test/utils.rb +0 -231
@@ -0,0 +1,43 @@
1
+ module Tap
2
+ class App
3
+
4
+ # Node adds the node API[link:files/doc/API.html] to objects responding
5
+ # to call. Additional helper methods are added to simplify the
6
+ # construction of workflows; they are not required by the API.
7
+ module Node
8
+
9
+ # The joins called when call completes
10
+ attr_accessor :joins
11
+
12
+ # An array of node dependencies
13
+ attr_reader :dependencies
14
+
15
+ # Interns a new node by extending the block with Node.
16
+ def self.intern(&block)
17
+ block.extend self
18
+ end
19
+
20
+ # Sets up required variables for extended objects.
21
+ def self.extended(obj) # :nodoc:
22
+ obj.instance_variable_set(:@joins, [])
23
+ obj.instance_variable_set(:@dependencies, [])
24
+ end
25
+
26
+ # Sets the block as a join for self.
27
+ def on_complete(&block) # :yields: result
28
+ self.joins << block if block
29
+ self
30
+ end
31
+
32
+ # Adds the dependency to self. Dependencies are resolved by an app
33
+ # during App#dispatch and must be valid nodes.
34
+ def depends_on(dependency)
35
+ raise "cannot depend on self" if dependency == self
36
+ unless dependencies.include?(dependency)
37
+ dependencies << dependency
38
+ end
39
+ self
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,77 @@
1
+ require 'monitor'
2
+
3
+ module Tap
4
+ class App
5
+
6
+ # Queue allows thread-safe enqueing and dequeing of nodes and inputs for
7
+ # execution.
8
+ class Queue < Monitor
9
+
10
+ attr_reader :queue
11
+
12
+ # Creates a new Queue
13
+ def initialize
14
+ super
15
+ @queue = []
16
+ end
17
+
18
+ # Clears self and returns an array of the enqueued methods and inputs,
19
+ # organized by round.
20
+ def clear
21
+ synchronize do
22
+ current = queue
23
+ @queue = []
24
+ current
25
+ end
26
+ end
27
+
28
+ # Returns the number of enqueued methods
29
+ def size
30
+ synchronize do
31
+ queue.size
32
+ end
33
+ end
34
+
35
+ # True if no methods are enqueued
36
+ def empty?
37
+ synchronize { size == 0 }
38
+ end
39
+
40
+ # Enqueues the method and inputs.
41
+ def enq(method, inputs)
42
+ synchronize do
43
+ queue.push [method, inputs]
44
+ end
45
+ end
46
+
47
+ # Enqueues the method and inputs, but to the top of the queue.
48
+ def unshift(method, inputs)
49
+ synchronize do
50
+ queue.unshift [method, inputs]
51
+ end
52
+ end
53
+
54
+ # Concats an array of [method, input] entries to self.
55
+ def concat(entries)
56
+ synchronize do
57
+ entries.each do |method, inputs|
58
+ enq(method, inputs)
59
+ end
60
+ end
61
+ end
62
+
63
+ # Dequeues the next method and inputs as an array like
64
+ # [method, inputs]. Returns nil if the queue is empty.
65
+ def deq
66
+ synchronize { queue.shift }
67
+ end
68
+
69
+ # Converts self to an array.
70
+ def to_a
71
+ synchronize do
72
+ queue.dup
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,16 @@
1
+ module Tap
2
+ class App
3
+
4
+ # The base of the application call stack.
5
+ class Stack
6
+
7
+ # Calls the node with the inputs:
8
+ #
9
+ # node.call(*inputs)
10
+ #
11
+ def call(node, inputs)
12
+ node.call(*inputs)
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,22 @@
1
+ module Tap
2
+ class App
3
+ # The constants defining the possible App states.
4
+ module State
5
+ READY = 0
6
+ RUN = 1
7
+ STOP = 2
8
+ TERMINATE = 3
9
+
10
+ module_function
11
+
12
+ # Returns a string corresponding to the input state value.
13
+ # Returns nil for unknown states.
14
+ #
15
+ # State.state_str(0) # => 'READY'
16
+ # State.state_str(12) # => nil
17
+ def state_str(state)
18
+ constants.inject(nil) {|str, s| const_get(s) == state ? s.to_s : str}
19
+ end
20
+ end
21
+ end
22
+ end
data/lib/tap/constants.rb CHANGED
@@ -1,7 +1,7 @@
1
1
  module Tap
2
2
  MAJOR = 0
3
- MINOR = 12
4
- TINY = 4
3
+ MINOR = 17
4
+ TINY = 0
5
5
 
6
6
  VERSION="#{MAJOR}.#{MINOR}.#{TINY}"
7
7
  WEBSITE="http://tap.rubyforge.org"
data/lib/tap/env.rb CHANGED
@@ -1,257 +1,246 @@
1
- require 'tap/support/constant_manifest'
1
+ require 'tap/root'
2
+ require 'tap/env/manifest'
3
+ require 'tap/support/templater'
2
4
  autoload(:YAML, 'yaml')
3
5
 
4
6
  module Tap
5
- module Support
6
- autoload(:Templater, 'tap/support/templater')
7
- autoload(:Gems, 'tap/support/gems')
8
- end
9
-
10
- # Envs are locations on the filesystem that have resources associated with
11
- # them (commands, tasks, generators, etc). Envs may point to files, but it's
12
- # more commonly environments are set to a directory and resources are various
13
- # files within the directory.
14
- #
15
- #
16
- #--
17
- # Note that gems and env_paths reset envs -- custom modifications to envs will be lost
18
- # whenever these configs are reset.
7
+ # Env abstracts an execution environment that spans many directories.
19
8
  class Env
20
- include Enumerable
21
- include Configurable
22
- include Support::Minimap
23
-
9
+ autoload(:Gems, 'tap/env/gems')
10
+
24
11
  class << self
12
+ attr_writer :instance
25
13
 
26
- # Returns the active instance of Env.
27
- def instance
28
- @@instance
29
- end
30
-
31
- # A hash of (path, Env instance) pairs, generated by Env#instantiate. Used
32
- # to prevent infinite loops of Env dependencies by assigning a single Env
33
- # to a given path.
34
- def instances
35
- @@instances
14
+ def instance(auto_initialize=true)
15
+ @instance ||= (auto_initialize ? new : nil)
36
16
  end
37
17
 
38
- # Creates a new Env for the specified path and adds it to Env#instances, or
39
- # returns the existing instance for the path. Paths can point to an env config
40
- # file, or to a directory. If a directory is provided, instantiate treats
41
- # path as the DEFAULT_CONFIG_FILE in that directory. All paths are expanded.
42
- #
43
- # e1 = Env.instantiate("./path/to/config.yml")
44
- # e2 = Env.instantiate("./path/to/dir")
45
- #
46
- # Env.instances
47
- # # => {
48
- # # File.expand_path("./path/to/config.yml") => e1,
49
- # # File.expand_path("./path/to/dir/#{Tap::Env::DEFAULT_CONFIG_FILE}") => e2 }
50
- #
51
- # The Env is initialized using configurations read from the env config
52
- # file. An instance will be initialized regardless of whether the config
53
- # file or directory exists.
54
- def instantiate(path_or_root)
55
- path = config_path(path_or_root.kind_of?(Root) ? path_or_root.root : path_or_root)
56
- return instances[path] if instances.has_key?(path)
18
+ def from_gemspec(spec, context={})
19
+ path = spec.full_gem_path
20
+ basename = context[:basename]
57
21
 
58
- config = load_config(path)
59
- root = path_or_root.kind_of?(Root) ? path_or_root : File.dirname(path)
60
-
61
- # note the assignment of env to instances MUST occur
62
- # before reconfigure to prevent infinite looping
63
- (instances[path] = new(root)).reconfigure(config)
64
- end
65
-
66
- def manifest(name, &block) # :yields: env (and should return a manifest)
67
- name = name.to_sym
68
- define_method(name) do
69
- self.manifests[name] ||= block.call(self).bind(self, name)
22
+ dependencies = []
23
+ spec.dependencies.each do |dependency|
24
+ unless dependency.type == :runtime
25
+ next
26
+ end
27
+
28
+ unless gemspec = Gems.gemspec(dependency)
29
+ # this error may result when a dependency has
30
+ # been uninstalled for a particular gem
31
+ warn "missing gem dependency: #{dependency.to_s} (#{spec.full_name})"
32
+ next
33
+ end
34
+
35
+ if basename && !File.exists?(File.join(gemspec.full_gem_path, basename))
36
+ next
37
+ end
38
+
39
+ dependencies << gemspec
70
40
  end
71
- end
72
-
73
- private
74
-
75
- def config_path(path) # :nodoc:
76
- if File.directory?(path) || (!File.exists?(path) && File.extname(path) == "")
77
- path = File.join(path, DEFAULT_CONFIG_FILE)
41
+
42
+ config = {
43
+ 'root' => path,
44
+ 'gems' => dependencies,
45
+ 'load_paths' => spec.require_paths,
46
+ 'set_load_paths' => false
47
+ }
48
+
49
+ if context[:basename]
50
+ config.merge!(Env.load_config(File.join(path, context[:basename])))
78
51
  end
79
52
 
80
- File.expand_path(path)
53
+ new(config, context)
81
54
  end
82
55
 
83
- # helper to load path as YAML. load_file returns a hash if the path
84
- # loads to nil or false (as happens for empty files)
85
- def load_config(path) # :nodoc:
56
+ # Loads configurations from path as YAML. Returns an empty hash if the path
57
+ # loads to nil or false (as happens for empty files), or doesn't exist.
58
+ def load_config(path)
59
+ return {} unless path
60
+
86
61
  begin
87
- Root.trivial?(path) ? {} : (YAML.load_file(path) || {})
62
+ Root::Utils.trivial?(path) ? {} : (YAML.load_file(path) || {})
88
63
  rescue(Exception)
89
- raise Env::ConfigError.new($!, path)
64
+ raise ConfigError.new($!, path)
65
+ end
66
+ end
67
+
68
+ def scan(load_path, pattern='**/*.rb')
69
+ Dir.chdir(load_path) do
70
+ Dir.glob(pattern).each do |require_path|
71
+ next unless File.file?(require_path)
72
+
73
+ default_const_name = require_path.chomp('.rb').camelize
74
+
75
+ # note: the default const name has to be set here to allow for implicit
76
+ # constant attributes (because a dir is needed to figure the relative path).
77
+ # A conflict could arise if the same path is globed from two different
78
+ # dirs... no surefire solution.
79
+ document = Lazydoc[require_path]
80
+ case document.default_const_name
81
+ when nil then document.default_const_name = default_const_name
82
+ when default_const_name
83
+ else raise "found a conflicting default const name"
84
+ end
85
+
86
+ # scan for constants
87
+ Lazydoc::Document.scan(File.read(require_path)) do |const_name, type, comment|
88
+ const_name = default_const_name if const_name.empty?
89
+ constant = Constant.new(const_name, require_path, comment)
90
+ yield(type, constant)
91
+
92
+ ###############################################################
93
+ # [depreciated] manifest as a task key will be removed at 1.0
94
+ if type == 'manifest'
95
+ warn "depreciation: ::task should be used instead of ::manifest as a resource key (#{require_path})"
96
+ yield('task', constant)
97
+ end
98
+ ###############################################################
99
+ end
100
+ end
90
101
  end
91
102
  end
92
103
  end
104
+ self.instance = nil
93
105
 
94
- @@instance = nil
95
- @@instances = {}
96
-
97
- # The default config file path
98
- DEFAULT_CONFIG_FILE = "tap.yml"
106
+ include Enumerable
107
+ include Configurable
108
+ include Minimap
99
109
 
110
+ # Matches a compound registry search key. After the match, if the key is
111
+ # compound then:
112
+ #
113
+ # $1:: env_key
114
+ # $2:: key
115
+ #
116
+ # If the key is not compound, $2 is nil and $1 is the key.
117
+ COMPOUND_KEY = /^((?:[A-z]:(?:\/|\\))?.*?)(?::(.*))?$/
118
+
100
119
  # An array of nested Envs, by default comprised of the env_path
101
- # + gem environments (in that order). Nested environments are
102
- # activated/deactivated with self.
120
+ # + gem environments (in that order).
103
121
  attr_reader :envs
104
122
 
105
- # The Root directory structure for self.
106
- nest(:root, Tap::Root, :set_default => false)
123
+ attr_reader :context
124
+
125
+ attr_reader :manifests
107
126
 
108
- # Specify gems to load as nested Envs. Gems may be specified
127
+ # The Root directory structure for self.
128
+ nest(:root, Root, :set_default => false)
129
+
130
+ # Specify gems to add as nested Envs. Gems may be specified
109
131
  # by name and/or version, like 'gemname >= 1.2'; by default the
110
- # latest version of the gem is selected.
111
- #
112
- # Gems are immediately loaded (via gem) through this method.
132
+ # latest version of the gem is selected. Gems are not activated
133
+ # by Env.
113
134
  config_attr :gems, [] do |input|
114
- specs_by_name = {}
115
-
116
- input = YAML.load(input) if input.kind_of?(String)
117
- input = case input
135
+ input = yaml_load(input) if input.kind_of?(String)
136
+
137
+ @gems = case input
138
+ when false, nil, :NONE, :none
139
+ []
140
+ when :LATEST, :ALL
141
+ # latest and all, no filter
142
+ Gems.select_gems(input == :LATEST)
118
143
  when :latest, :all
119
- Support::Gems.select_gems(input == :latest) do |spec|
120
- env_config = File.join(spec.full_gem_path, Tap::Env::DEFAULT_CONFIG_FILE)
121
- File.exists?(env_config)
122
- end
123
- else input
124
- end
125
-
126
- @gems = [*input].compact.collect do |gem_name|
127
- spec = Support::Gems.gemspec(gem_name)
128
-
129
- case spec
130
- when nil then log(:warn, "unknown gem: #{gem_name}", Logger::WARN)
131
- else Env.instantiate(spec.full_gem_path)
144
+ # latest and all, filtering by basename
145
+ Gems.select_gems(input == :latest) do |spec|
146
+ basename == nil || File.exists?(File.join(spec.full_gem_path, basename))
132
147
  end
133
-
134
- (specs_by_name[spec.name] ||= []) << spec
135
- spec.name
136
- end.uniq
137
-
138
- # this song and dance is to ensure that the latest spec for a
139
- # given gem appears first in the manifest
140
- specs_by_name.each_pair do |name, specs|
141
- specs_by_name[name] = specs.uniq.sort_by {|spec| spec.version }.reverse
148
+ else
149
+ # resolve gem names manually
150
+ [*input].collect do |name|
151
+ Gems.gemspec(name)
152
+ end.compact
142
153
  end
143
-
144
- @gems.collect! do |name|
145
- specs_by_name[name]
146
- end.flatten!
147
-
154
+
148
155
  reset_envs
149
156
  end
150
157
 
151
- # Specify configuration files to load as nested Envs.
158
+ # Specify directories to load as nested Envs.
152
159
  config_attr :env_paths, [] do |input|
153
- input = YAML.load(input) if input.kind_of?(String)
154
- @env_paths = [*input].compact.collect do |path|
155
- Env.instantiate(root[path]).env_path
156
- end.uniq
160
+ @env_paths = resolve_paths(input)
157
161
  reset_envs
158
162
  end
159
163
 
160
- # Designate load paths.
161
- config_attr :load_paths, ["lib"] do |paths|
164
+ # Designates paths added to $LOAD_PATH on activation (see set_load_paths).
165
+ # These paths are also the default directories searched for resources.
166
+ config_attr :load_paths, [:lib] do |input|
162
167
  raise "load_paths cannot be modified once active" if active?
163
- @load_paths = resolve_paths(paths)
164
- end
165
-
166
- # Designate paths for discovering and executing commands.
167
- config_attr :command_paths, ["cmd"] do |paths|
168
- @command_paths = resolve_paths(paths)
169
- end
170
-
171
- # Designate paths for discovering generators.
172
- config_attr :generator_paths, ["lib"] do |paths|
173
- @generator_paths = resolve_paths(paths)
174
- end
175
-
176
- manifest(:commands) do |env|
177
- paths = []
178
- env.command_paths.each do |path_root|
179
- paths.concat env.root.glob(path_root)
180
- end
181
-
182
- paths = paths.sort_by {|path| File.basename(path) }
183
- Support::Manifest.new(paths)
168
+ @load_paths = resolve_paths(input)
184
169
  end
185
170
 
186
- manifest(:tasks) do |env|
187
- tasks = Support::ConstantManifest.new('manifest')
188
- env.load_paths.each do |path_root|
189
- tasks.register(path_root, '**/*.rb')
190
- end
191
- # tasks.cache = env.cache[:tasks]
192
- tasks
193
- end
194
-
195
- manifest(:generators) do |env|
196
- generators = Support::ConstantManifest.intern('generator') do |manifest, const|
197
- const.name.underscore.chomp('_generator')
198
- end
199
-
200
- env.generator_paths.each do |path_root|
201
- generators.register(path_root, '**/*_generator.rb')
202
- end
203
- # generators.cache = env.cache[:generators]
204
- generators
171
+ # If set to true load_paths are added to $LOAD_PATH on activation.
172
+ config_attr :set_load_paths, true do |input|
173
+ raise "set_load_paths cannot be modified once active" if active?
174
+ @set_load_paths = Configurable::Validation.boolean[input]
205
175
  end
206
176
 
207
- def initialize(path_root_or_config=Dir.pwd)
208
- @envs = []
177
+ # Initializes a new Env linked to the specified directory. A config file
178
+ # basename may be specified to load configurations from 'dir/basename' as
179
+ # YAML. If a basename is specified, the same basename will be used to
180
+ # load configurations for nested envs.
181
+ #
182
+ # Configurations may be manually provided in the place of dir. In that
183
+ # case, the same rules apply for loading configurations for nested envs,
184
+ # but no configurations will be loaded for the current instance.
185
+ #
186
+ # The cache is used internally to prevent infinite loops of nested envs,
187
+ # and to optimize the generation of manifests.
188
+ def initialize(config_or_dir=Dir.pwd, context={})
209
189
  @active = false
210
190
  @manifests = {}
211
-
212
- # initialize these for reset_env
213
- @gems = []
214
- @env_paths = []
191
+ @context = context
192
+
193
+ # setup root
194
+ config = nil
195
+ @root = case config_or_dir
196
+ when Root then config_or_dir
197
+ when String then Root.new(config_or_dir)
198
+ else
199
+ config = config_or_dir
200
+
201
+ if config.has_key?(:root) && config.has_key?('root')
202
+ raise "multiple values mapped to :root"
203
+ end
204
+
205
+ root = config.delete(:root) || config.delete('root') || Dir.pwd
206
+ root.kind_of?(Root) ? root : Root.new(root)
207
+ end
215
208
 
216
- @root = case path_root_or_config
217
- when Root then path_root_or_config
218
- when String then Root.new(path_root_or_config)
219
- else Root.new
209
+ if basename && !config
210
+ config = Env.load_config(File.join(@root.root, basename))
220
211
  end
221
212
 
222
- unless path_root_or_config.kind_of?(Hash)
223
- path_root_or_config = {}
213
+ if instance(@root.root)
214
+ raise "context already has an env for: #{@root.root}"
224
215
  end
225
- initialize_config(path_root_or_config)
226
- end
227
-
228
- # Clears manifests so they may be regenerated.
229
- def reset
230
- @manifests.clear
216
+ instances << self
217
+
218
+ # set these for reset_env
219
+ @gems = nil
220
+ @env_paths = nil
221
+ initialize_config(config || {})
231
222
  end
232
223
 
233
- # Returns the key for self in Env.instances.
234
- def env_path
235
- Env.instances.each_pair {|path, env| return path if env == self }
236
- nil
224
+ # The minikey for self (root.root).
225
+ def minikey
226
+ root.root
237
227
  end
238
228
 
239
229
  # Sets envs removing duplicates and instances of self. Setting envs
240
230
  # overrides any environments specified by env_path and gem.
241
231
  def envs=(envs)
242
232
  raise "envs cannot be modified once active" if active?
243
- @envs = envs.uniq.delete_if {|e| e == self }
233
+ @envs = envs.uniq.delete_if {|env| env == self }
244
234
  end
245
-
246
- # Unshifts env onto envs, removing duplicates.
247
- # Self cannot be unshifted onto self.
235
+
236
+ # Unshifts env onto envs. Self cannot be unshifted onto self.
248
237
  def unshift(env)
249
238
  unless env == self || envs[0] == env
250
239
  self.envs = envs.dup.unshift(env)
251
240
  end
252
241
  self
253
242
  end
254
-
243
+
255
244
  # Pushes env onto envs, removing duplicates.
256
245
  # Self cannot be pushed onto self.
257
246
  def push(env)
@@ -261,21 +250,23 @@ module Tap
261
250
  end
262
251
  self
263
252
  end
264
-
253
+
265
254
  # Passes each nested env to the block in order, starting with self.
266
255
  def each
267
256
  visit_envs.each {|e| yield(e) }
268
257
  end
269
-
258
+
270
259
  # Passes each nested env to the block in reverse order, ending with self.
271
260
  def reverse_each
272
261
  visit_envs.reverse_each {|e| yield(e) }
273
262
  end
274
263
 
275
264
  # Recursively injects the memo to each env of self. Each env in envs
276
- # receives the same memo from the parent.
265
+ # receives the same memo from the parent. This is different from the
266
+ # inject provided via Enumerable, where each subsequent env receives
267
+ # the memo from the previous, not the parent, env.
277
268
  #
278
- # a,b,c,d,e = ('a'..'e').collect {|name| Tap::Env.new(:name => name) }
269
+ # a,b,c,d,e = ('a'..'e').collect {|name| Env.new(:name => name) }
279
270
  #
280
271
  # a.push(b).push(c)
281
272
  # b.push(d).push(e)
@@ -302,7 +293,7 @@ module Tap
302
293
  #
303
294
  # * sets Env.instance to self (unless already set)
304
295
  # * activate nested environments
305
- # * unshift load_paths to $LOAD_PATH
296
+ # * unshift load_paths to $LOAD_PATH (if set_load_paths is true)
306
297
  #
307
298
  # Once active, the current envs and load_paths are frozen and cannot be
308
299
  # modified until deactivated. Returns true if activate succeeded, or
@@ -311,7 +302,9 @@ module Tap
311
302
  return false if active?
312
303
 
313
304
  @active = true
314
- @@instance = self if @@instance == nil
305
+ unless self.class.instance(false)
306
+ self.class.instance = self
307
+ end
315
308
 
316
309
  # freeze envs and load paths
317
310
  @envs.freeze
@@ -323,11 +316,13 @@ module Tap
323
316
  end
324
317
 
325
318
  # add load paths
326
- load_paths.reverse_each do |path|
327
- $LOAD_PATH.unshift(path)
328
- end
319
+ if set_load_paths
320
+ load_paths.reverse_each do |path|
321
+ $LOAD_PATH.unshift(path)
322
+ end
329
323
 
330
- $LOAD_PATH.uniq!
324
+ $LOAD_PATH.uniq!
325
+ end
331
326
 
332
327
  true
333
328
  end
@@ -335,7 +330,7 @@ module Tap
335
330
  # Deactivates self by doing the following in order:
336
331
  #
337
332
  # * deactivates nested environments
338
- # * removes load_paths from $LOAD_PATH
333
+ # * removes load_paths from $LOAD_PATH (if set_load_paths is true)
339
334
  # * sets Env.instance to nil (if set to self)
340
335
  # * clears cached manifest data
341
336
  #
@@ -353,15 +348,17 @@ module Tap
353
348
  # remove load paths
354
349
  load_paths.each do |path|
355
350
  $LOAD_PATH.delete(path)
356
- end
351
+ end if set_load_paths
357
352
 
358
353
  # unfreeze envs and load paths
359
354
  @envs = @envs.dup
360
355
  @load_paths = @load_paths.dup
361
356
 
362
357
  # clear cached data
363
- @@instance = nil if @@instance == self
364
- @manifests.clear
358
+ klass = self.class
359
+ if klass.instance(false) == self
360
+ klass.instance = nil
361
+ end
365
362
 
366
363
  true
367
364
  end
@@ -371,157 +368,238 @@ module Tap
371
368
  @active
372
369
  end
373
370
 
374
- # Searches each env for the first existing file or directory at
375
- # env.root.filepath(dir, path). Paths are expanded, and search
376
- # checks to make sure the file is, in fact, relative to env.root[dir].
377
- # An optional block may be used to check the file; the file will only
378
- # be returned if the block returns true.
379
- #
380
- # Returns nil if no file can be found.
381
- def search(dir, path, strict=true)
371
+ def hlob(dir, pattern="**/*")
372
+ results = {}
373
+ each do |env|
374
+ root = env.root
375
+ root.glob(dir, pattern).each do |path|
376
+ relative_path = root.relative_path(dir, path)
377
+ results[relative_path] ||= path
378
+ end
379
+ end
380
+ results
381
+ end
382
+
383
+ def glob(dir, pattern="**/*")
384
+ hlob(dir, pattern).values.sort!
385
+ end
386
+
387
+ def path(dir, *paths)
382
388
  each do |env|
383
- directory = env.root.filepath(dir)
384
- file = env.root.filepath(dir, path)
385
- next unless File.exists?(file)
389
+ path = env.root.path(dir, *paths)
390
+ return path if !block_given? || yield(path)
391
+ end
392
+ nil
393
+ end
394
+
395
+ # Retrieves a path associated with the inheritance hierarchy of an object.
396
+ # An array of modules (which naturally can include classes) are provided
397
+ # and module_path traverses each, forming paths like:
398
+ #
399
+ # path(dir, module_path, *paths)
400
+ #
401
+ # By default, 'module_path' is 'module.to_s.underscore', but modules can
402
+ # specify an alternative by providing a module_path method.
403
+ #
404
+ # The paths are yielded to the block and when the block returns true,
405
+ # the path will be returned. If no block is given, the first module path
406
+ # is returned. Returns nil if the block never returns true.
407
+ #
408
+ def module_path(dir, modules, *paths, &block)
409
+ paths.compact!
410
+ while current = modules.shift
411
+ module_path = if current.respond_to?(:module_path)
412
+ current.module_path
413
+ else
414
+ current.to_s.underscore
415
+ end
386
416
 
387
- # check the file is relative to directory
388
- if strict && file.rindex(directory, 0) != 0
389
- raise "not relative to search dir: #{file} (#{directory})"
417
+ if path = self.path(dir, module_path, *paths, &block)
418
+ return path
419
+ end
420
+ end
421
+
422
+ nil
423
+ end
424
+
425
+ # Returns the module_path traversing the inheritance hierarchy for the
426
+ # class of obj (or obj if obj is a Class). Included modules are not
427
+ # visited, only the superclasses.
428
+ def class_path(dir, obj, *paths, &block)
429
+ klass = obj.kind_of?(Class) ? obj : obj.class
430
+ superclasses = klass.ancestors - klass.included_modules
431
+ module_path(dir, superclasses, *paths, &block)
432
+ end
433
+
434
+ def registry(build=false)
435
+ builders.each_pair do |type, builder|
436
+ registry[type] ||= builder.call(self)
437
+ end if build
438
+
439
+ registries[minikey] ||= begin
440
+ registry = {}
441
+ load_paths.each do |load_path|
442
+ next unless File.directory?(load_path)
443
+
444
+ Env.scan(load_path) do |type, constant|
445
+ entries = registry[type.to_sym] ||= []
446
+ entries << constant
447
+ end
390
448
  end
391
449
 
392
- # filter
393
- return file if !block_given? || yield(file)
450
+ registry
451
+ end
452
+ end
453
+
454
+ def register(type, override=false, &block)
455
+ type = type.to_sym
456
+
457
+ # error for existing, or overwrite
458
+ case
459
+ when override
460
+ builders.delete(type)
461
+ registries.each {|root, registry| registry.delete(type) }
462
+ when builders.has_key?(type)
463
+ raise "a builder is already registered for: #{type.inspect}"
464
+ when registries.any? {|root, registry| registry.has_key?(type) }
465
+ raise "entries are already registered for: #{type.inspect}"
394
466
  end
395
467
 
396
- nil
468
+ builders[type] = block
397
469
  end
398
470
 
399
- #
400
- TEMPLATES = {}
401
- TEMPLATES[:commands] = %Q{<% if count > 1 %>
402
- <%= env_name %>:
403
- <% end %>
404
- <% entries.each do |name, const| %>
405
- <%= name.ljust(width) %>
406
- <% end %>}
407
- TEMPLATES[:tasks] = %Q{<% if count > 1 %>
408
- <%= env_name %>:
409
- <% end %>
410
- <% entries.each do |name, const| %>
411
- <% desc = const.document[const.name]['manifest'] %>
412
- <%= name.ljust(width) %><%= desc.empty? ? '' : ' # ' %><%= desc %>
413
- <% end %>}
414
- TEMPLATES[:generators] = %Q{<% if count > 1 %>
415
- <%= env_name %>:
416
- <% end %>
417
- <% entries.each do |name, const| %>
418
- <% desc = const.document[const.name]['generator'] %>
419
- <%= name.ljust(width) %><%= desc.empty? ? '' : ' # ' %><%= desc %>
420
- <% end %>}
421
-
422
- def summarize(name, template=TEMPLATES[name])
423
- count = 0
424
- width = 10
471
+ def manifest(type) # :yields: env
472
+ type = type.to_sym
425
473
 
426
- env_names = {}
427
- minimap.each do |env_name, env|
428
- env_names[env] = env_name
474
+ registry[type] ||= begin
475
+ builder = builders[type]
476
+ builder ? builder.call(self) : []
429
477
  end
430
478
 
431
- inspect(template) do |templater, share|
432
- env = templater.env
433
- entries = env.send(name).minimap
434
- next(false) if entries.empty?
435
-
436
- templater.env_name = env_names[env]
437
- templater.entries = entries
438
-
439
- count += 1
440
- entries.each do |entry_name, entry|
441
- width = entry_name.length if width < entry_name.length
479
+ manifests[type] ||= Manifest.new(self, type)
480
+ end
481
+
482
+ def [](type)
483
+ manifest(type)
484
+ end
485
+
486
+ def reset
487
+ manifests.clear
488
+ registries.clear
489
+ end
490
+
491
+ #--
492
+ # Environment-seek
493
+ def eeek(type, key)
494
+ key =~ COMPOUND_KEY
495
+ envs = if $2
496
+ # compound key, match for env
497
+ key = $2
498
+ [minimatch($1)].compact
499
+ else
500
+ # not a compound key, search all envs by iterating self
501
+ self
502
+ end
503
+
504
+ # traverse envs looking for the first
505
+ # manifest entry matching key
506
+ envs.each do |env|
507
+ if result = env.manifest(type).minimatch(key)
508
+ return [env, result]
442
509
  end
443
-
444
- share[:count] = count
445
- share[:width] = width
446
- true
447
510
  end
511
+
512
+ nil
448
513
  end
449
514
 
450
- def inspect(template=nil) # :yields: templater, attrs
451
- return "#<#{self.class}:#{object_id} root='#{root.root}'>" if template == nil
452
-
453
- attrs = {}
454
- collect do |env|
455
- templater = Support::Templater.new(template, :env => env)
456
- block_given? ? (yield(templater, attrs) ? templater : nil) : templater
457
- end.compact.collect do |templater|
458
- templater.build(attrs)
459
- end.join
515
+ # Searches across each for the first registered object minimatching key. A
516
+ # single env can be specified by using a compound key like 'env_key:key'.
517
+ #
518
+ # Returns nil if no matching object is found.
519
+ def seek(type, key, &block) # :yields: env, key
520
+ env, result = eeek(type, key, &block)
521
+ result
460
522
  end
461
523
 
462
- def recursive_inspect(template=nil, *args) # :yields: templater, attrs
463
- return "#<#{self.class}:#{object_id} root='#{root.root}'>" if template == nil
464
-
465
- attrs = {}
466
- templaters = []
467
- recursive_inject(args) do |argv, env|
468
- templater = Support::Templater.new(template, :env => env)
469
- next_args = block_given? ? yield(templater, attrs, *argv) : argv
470
- templaters << templater if next_args
471
-
472
- next_args
524
+ # All templaters are yielded to the block before any are built. This
525
+ # allows globals to be determined for all environments.
526
+ def inspect(template=nil, globals={}, filename=nil) # :yields: templater, globals
527
+ if template == nil
528
+ return "#<#{self.class}:#{object_id} root='#{root.root}'>"
473
529
  end
474
530
 
475
- templaters.collect do |templater|
476
- templater.build(attrs)
531
+ env_keys = minihash(true)
532
+ collect do |env|
533
+ templater = Support::Templater.new(template, :env => env, :env_key => env_keys[env])
534
+ yield(templater, globals) if block_given?
535
+ templater
536
+ end.collect! do |templater|
537
+ templater.build(globals, filename)
477
538
  end.join
478
539
  end
479
540
 
480
541
  protected
481
542
 
482
- # A hash of the manifests for self.
483
- attr_reader :manifests
543
+ def registries # :nodoc:
544
+ context[:registries] ||= {}
545
+ end
484
546
 
485
- def minikey(env)
486
- env.root.root
547
+ def basename # :nodoc:
548
+ context[:basename]
487
549
  end
488
550
 
489
- # Resets envs using the current env_paths and gems.
490
- def reset_envs
491
- self.envs = env_paths.collect do |path|
492
- Env.instantiate(path)
493
- end + gems.collect do |spec|
494
- Env.instantiate(spec.full_gem_path)
495
- end
551
+ def builders # :nodoc:
552
+ context[:builders] ||= {}
553
+ end
554
+
555
+ def instances # :nodoc:
556
+ context[:instances] ||= []
496
557
  end
497
558
 
498
- # Arrayifies, compacts, and resolves input paths using root, and
499
- # removes duplicates. In short
559
+ def instance(path) # :nodoc:
560
+ instances.find {|env| env.root.root == path }
561
+ end
562
+
563
+ # resets envs using the current env_paths and gems. does nothing
564
+ # until both env_paths and gems are set.
565
+ def reset_envs # :nodoc:
566
+ if env_paths && gems
567
+ self.envs = env_paths.collect do |path|
568
+ instance(path) || Env.new(path, context)
569
+ end + gems.collect do |spec|
570
+ instance(spec.full_gem_path) || Env.from_gemspec(spec, context)
571
+ end
572
+ end
573
+ end
574
+
575
+ # arrayifies, compacts, and resolves input paths using root, and
576
+ # removes duplicates. in short:
500
577
  #
501
578
  # resolve_paths ['lib', nil, 'lib', 'alt] # => [root['lib'], root['alt']]
502
579
  #
503
580
  def resolve_paths(paths) # :nodoc:
504
- paths = YAML.load(paths) if paths.kind_of?(String)
505
- [*paths].compact.collect {|path| root[path]}.uniq
581
+ paths = yaml_load(paths) if paths.kind_of?(String)
582
+ [*paths].compact.collect {|path| root[path] }.uniq
506
583
  end
507
-
508
- # Recursively iterates through envs, starting with self, and
509
- # collects the visited envs in order.
584
+
585
+ # helper to recursively iterate through envs, starting with self.
586
+ # visited envs are collected in order and are used to ensure a
587
+ # given env will only be visited once.
510
588
  def visit_envs(visited=[], &block) # :nodoc:
511
589
  unless visited.include?(self)
512
590
  visited << self
513
591
  yield(self) if block_given?
514
-
592
+
515
593
  envs.each do |env|
516
594
  env.visit_envs(visited, &block)
517
595
  end
518
596
  end
519
-
597
+
520
598
  visited
521
599
  end
522
-
600
+
523
601
  # helper to recursively inject a memo to the children of env
524
- def inject_envs(memo, visited=[], &block) # :nodoc:
602
+ def inject_envs(memo, visited=[], &block) # :nodoc:
525
603
  unless visited.include?(self)
526
604
  visited << self
527
605
  next_memo = yield(memo, self)
@@ -529,12 +607,20 @@ module Tap
529
607
  env.inject_envs(next_memo, visited, &block)
530
608
  end
531
609
  end
532
-
610
+
533
611
  visited
534
612
  end
535
613
 
536
- # Raised when there is a Env-level configuration error.
537
- class ConfigError < StandardError
614
+ private
615
+
616
+ # A 'quick' yaml load where empty strings will not cause YAML to autoload.
617
+ # This is a silly song and dance, but provides for optimal launch times.
618
+ def yaml_load(str) # :nodoc:
619
+ str.empty? ? false : YAML.load(str)
620
+ end
621
+
622
+ # Raised when there is a configuration error from Env.load_config.
623
+ class ConfigError < StandardError # :nodoc:
538
624
  attr_reader :original_error, :env_path
539
625
 
540
626
  def initialize(original_error, env_path)