tap 0.19.0 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. data/History +100 -45
  2. data/MIT-LICENSE +1 -1
  3. data/README +95 -51
  4. data/bin/tap +11 -57
  5. data/bin/tapexe +84 -0
  6. data/doc/API +91 -139
  7. data/doc/Configuration +93 -0
  8. data/doc/Examples/Command Line +10 -42
  9. data/doc/Examples/Tapfile +124 -0
  10. data/doc/Ruby to Ruby +87 -0
  11. data/doc/Workflow Syntax +185 -0
  12. data/lib/tap.rb +74 -5
  13. data/lib/tap/app.rb +217 -310
  14. data/lib/tap/app/api.rb +44 -23
  15. data/lib/tap/app/queue.rb +11 -12
  16. data/lib/tap/app/stack.rb +4 -4
  17. data/lib/tap/declarations.rb +200 -0
  18. data/lib/tap/declarations/context.rb +31 -0
  19. data/lib/tap/declarations/description.rb +33 -0
  20. data/lib/tap/env.rb +133 -779
  21. data/lib/tap/env/cache.rb +87 -0
  22. data/lib/tap/env/constant.rb +94 -39
  23. data/lib/tap/env/path.rb +71 -0
  24. data/lib/tap/join.rb +42 -78
  25. data/lib/tap/joins/gate.rb +85 -0
  26. data/lib/tap/joins/switch.rb +4 -2
  27. data/lib/tap/joins/sync.rb +3 -3
  28. data/lib/tap/middleware.rb +5 -5
  29. data/lib/tap/middlewares/debugger.rb +18 -58
  30. data/lib/tap/parser.rb +115 -183
  31. data/lib/tap/root.rb +162 -239
  32. data/lib/tap/signal.rb +72 -0
  33. data/lib/tap/signals.rb +20 -2
  34. data/lib/tap/signals/class_methods.rb +38 -43
  35. data/lib/tap/signals/configure.rb +19 -0
  36. data/lib/tap/signals/help.rb +5 -7
  37. data/lib/tap/signals/load.rb +49 -0
  38. data/lib/tap/signals/module_methods.rb +1 -0
  39. data/lib/tap/task.rb +46 -275
  40. data/lib/tap/tasks/dump.rb +21 -16
  41. data/lib/tap/tasks/list.rb +184 -0
  42. data/lib/tap/tasks/load.rb +4 -4
  43. data/lib/tap/tasks/prompt.rb +128 -0
  44. data/lib/tap/tasks/signal.rb +42 -0
  45. data/lib/tap/tasks/singleton.rb +35 -0
  46. data/lib/tap/tasks/stream.rb +64 -0
  47. data/lib/tap/utils.rb +83 -0
  48. data/lib/tap/version.rb +2 -2
  49. data/lib/tap/workflow.rb +124 -0
  50. data/tap.yml +0 -0
  51. metadata +59 -24
  52. data/cmd/console.rb +0 -43
  53. data/cmd/manifest.rb +0 -118
  54. data/cmd/run.rb +0 -145
  55. data/doc/Examples/Workflow +0 -40
  56. data/lib/tap/app/node.rb +0 -29
  57. data/lib/tap/env/context.rb +0 -61
  58. data/lib/tap/env/gems.rb +0 -63
  59. data/lib/tap/env/manifest.rb +0 -179
  60. data/lib/tap/env/minimap.rb +0 -308
  61. data/lib/tap/intern.rb +0 -50
  62. data/lib/tap/joins.rb +0 -9
  63. data/lib/tap/prompt.rb +0 -36
  64. data/lib/tap/root/utils.rb +0 -220
  65. data/lib/tap/root/versions.rb +0 -138
  66. data/lib/tap/signals/signal.rb +0 -68
@@ -1,850 +1,204 @@
1
- require 'tap/root'
1
+ require 'tap/signals'
2
+ require 'tap/env/cache'
2
3
  require 'tap/env/constant'
3
- require 'tap/env/context'
4
- require 'tap/env/manifest'
5
- require 'tap/templater'
6
- autoload(:YAML, 'yaml')
7
4
 
5
+ autoload(:YAML, 'yaml')
8
6
  module Tap
9
-
10
- # == Description
11
- #
12
- # Env provides access to an execution environment spanning many directories,
13
- # such as the working directory plus a series of gem directories. Envs merge
14
- # the files from each directory into an abstract directory that may be
15
- # globbed and accessed as a single unit. For example:
16
- #
17
- # # /one
18
- # # |-- a.rb
19
- # # `-- b.rb
20
- # #
21
- # # /two
22
- # # |-- b.rb
23
- # # `-- c.rb
24
- # env = Env.new('/one')
25
- # env << Env.new('/two')
26
- #
27
- # env.collect {|e| e.root.root}
28
- # # => ["/one", "/two"]
29
- #
30
- # env.glob(:root, "*.rb")
31
- # # => [
32
- # # "/one/a.rb",
33
- # # "/one/b.rb",
34
- # # "/two/c.rb"
35
- # # ]
36
- #
37
- # As illustrated, files in the nested environment are accessible within the
38
- # nesting environment. Envs provide methods for finding files associated
39
- # with a specific class, and allow the generation of manifests that provide
40
- # succinct access to various environment resources.
41
- #
42
- # Usage of Envs is fairly straightforward, but the internals and default
43
- # setup require some study as they have to span numerous functional domains.
44
- # The most common features are detailed below.
45
- #
46
- # ==== Class Paths
47
- #
48
- # Class paths are a kind of inheritance for files associated with a class.
49
- # Say we had the following classes:
50
- #
51
- # class A; end
52
- # class B < A; end
53
- #
54
- # The naturally associated directories are 'a' and 'b'. To look these up:
55
- #
56
- # env.class_path(:root, A) # => "/one/a"
57
- # env.class_path(:root, B) # => "/one/b"
58
- #
59
- # And to look up an associated file:
60
- #
61
- # env.class_path(:root, A, "index.html") # => "/one/a/index.html"
62
- # env.class_path(:root, B, "index.html") # => "/one/b/index.html"
63
- #
64
- # A block may be given to filter paths, for instance to test if a given file
65
- # exists. The class_path method will check each env then roll up the
66
- # inheritance hierarchy until the block returns true.
67
- #
68
- # FileUtils.touch("/two/a/index.html")
69
- #
70
- # visited_paths = []
71
- # env.class_path(:root, B, "index.html) do |path|
72
- # visited_paths << path
73
- # File.exists?(path)
74
- # end # => "/two/a/index.html"
75
- #
76
- # visited_paths
77
- # # => [
78
- # # "/one/b/index.html",
79
- # # "/two/b/index.html",
80
- # # "/one/a/index.html",
81
- # # "/two/a/index.html"
82
- # # ]
83
- #
84
- # This behavior is very useful for associating views with a class.
85
- #
86
- # ==== Manifest
87
- #
88
- # Envs can generate manifests of various resources so they may be identified
89
- # using minipaths (see Minimap for details regarding minipaths). Command
90
- # files used by the tap executable are one example of a resource, and the
91
- # constants used in building a workflow are another.
92
- #
93
- # Manifest are generated by defining a builder, typically a block, that
94
- # receives an env and returns an array of the associated resources.
95
- # Using the same env as above:
96
- #
97
- # manifest = env.manifest {|e| e.root.glob(:root, "*.rb") }
98
- #
99
- # manifest.seek("a") # => "/one/a.rb"
100
- # manifest.seek("b") # => "/one/b.rb"
101
- # manifest.seek("c") # => "/two/c.rb"
102
- #
103
- # As illustrated, seek finds the first entry across all envs that matches the
104
- # input minipath. A minipath for the env may be prepended to only search
105
- # within a specific env.
106
- #
107
- # manifest.seek("one:b") # => "/one/b.rb"
108
- # manifest.seek("two:b") # => "/two/b.rb"
109
- #
110
- # Env caches a manifest of constants identified by {constant attributes}[http://tap.rubyforge.org/lazydoc]
111
- # in files specified by under the Env.const_paths. These constants are used
112
- # when interpreting signals from the command line. Constants may be manually
113
- # registered to the constants manifest and classified by type like this:
114
- #
115
- # class CustomTask
116
- # def call; end
117
- # end
118
- # env.register(CustomTask).register_as(:task, "this is a custom task")
119
- #
120
- # const = env.constants.seek('custom_task')
121
- # const.const_name # => "CustomTask"
122
- # const.types # => {:task => "this is a custom task"}
123
- # const.constantize # => CustomTask
124
- #
125
- # == Setup
126
- #
127
- # Envs may be manually setup in code by individually generating instances
128
- # and nesting them. More commonly envs are defined in configuration files
129
- # and instantiated by specifying where the files are located. The default
130
- # config basename is 'tap.yml'; any env_paths specified in the config file
131
- # will be added.
132
- #
133
- # This type of instantiation is recursive:
134
- #
135
- # # [/one/tap.yml]
136
- # # env_paths: [/two]
137
- # #
138
- # # [/two/tap.yml]
139
- # # env_paths: [/three]
140
- # #
141
- #
142
- # env = Env.new("/one", :basename => 'tap.yml')
143
- # env.collect {|e| e.root.root}
144
- # # => ["/one", "/two", "/three"]
145
- #
146
- # Gem directories are fair game. Env allows specific gems to be specified
147
- # by name (via the 'gems' config), and if a gem has a tap.yml file then it
148
- # will be used to configure the gem env. Alternatively, an env may be set
149
- # to automatically discover and nest gem environments. In this case gems
150
- # are discovered when they have a tap.yml file.
151
- #
152
- # ==== ENV configs
153
- #
154
- # Configurations may be also specified as an ENV variables. This type of
155
- # configuration is very useful on the command line. Config variables
156
- # should be prefixed by TAP_ and named like the capitalized config key
157
- # (ex: TAP_GEMS or TAP_ENV_PATHS). See the
158
- # {Command Line Examples}[link:files/doc/Examples/Command%20Line.html]
159
- # to see ENV configs in action.
160
- #
161
- # These configurations may be accessed from Env#config, and are
162
- # automatically incorporated by Env#setup.
163
- #
164
7
  class Env
165
- autoload(:Gems, 'tap/env/gems')
166
-
167
8
  class << self
168
-
169
- # Returns the Env configs specified in ENV. Config variables are
170
- # prefixed by TAP_ and named like the capitalized config key
171
- # (ex: TAP_GEMS or TAP_ENV_PATHS).
172
- def config(env_vars=ENV)
173
- config = {}
174
- env_vars.each_pair do |key, value|
175
- if key =~ /\ATAP_(.*)\z/
176
- config[$1.downcase] = value
177
- end
178
- end
179
- config
180
- end
181
-
182
- # Initializes and activates an env as described in the config file under
183
- # dir. The config file should be a relative path and will be used for
184
- # determining configuration files under each env_path.
185
- #
186
- # The env configuration is determined by merging the following in order:
187
- # * defaults {root => dir, gems => all}
188
- # * ENV configs
189
- # * config_file configs
190
- #
191
- # The HOME directory for Tap will be added as an additonal environment
192
- # if not already added somewhere in the env hierarchy. By default all
193
- # gems will be included in the Env.
194
- def setup(dir=Dir.pwd, config_file=CONFIG_FILE)
195
- # setup configurations
196
- config = {'root' => dir, 'gems' => :all}
197
-
198
- user_config_file = config_file ? File.join(dir, config_file) : nil
199
- user = load_config(user_config_file)
200
-
201
- config.merge!(self.config)
202
- config.merge!(user)
203
-
204
- # keys must be symbolized as they are immediately
205
- # used to initialize the Env configs
206
- config = config.inject({}) do |options, (key, value)|
207
- options[key.to_sym || key] = value
208
- options
209
- end
9
+ def generate(options={})
10
+ options = {
11
+ :register => true,
12
+ :load_paths => true,
13
+ :set => true
14
+ }.merge(options)
210
15
 
211
- # instantiate
212
- context = Context.new(:basename => config_file)
213
- env = new(config, context)
16
+ dir = File.expand_path(options[:dir] || Dir.pwd)
17
+ pathfile = options[:pathfile] || File.expand_path(Path::FILE, dir)
18
+ map = options[:map] || Path.load(pathfile)
19
+ lib = options[:lib] || 'lib'
20
+ pattern = options[:pattern] || '**/*.rb'
214
21
 
215
- # add the tap env if necessary
216
- unless env.any? {|e| e.root.root == HOME }
217
- env.push new(HOME, context)
218
- end
22
+ register = options[:register]
23
+ load_paths = options[:load_paths]
24
+ set = options[:set]
219
25
 
220
- env.activate
221
- env
222
- end
223
-
224
- # Generates an Env for the specified gem or Gem::Specification. The
225
- # gemspec for the gem is used to determine the env configuration in
226
- # the following way:
227
- #
228
- # root: the gem path
229
- # gems: all gem dependencies with a config_file
230
- # const_paths: the gem require paths
231
- # set_const_paths: false (because RubyGems sets them for you)
232
- #
233
- # Configurations specified in the gem config_file override these
234
- # defaults.
235
- def setup_gem(gem_name, context=Context.new)
236
- spec = Gems.gemspec(gem_name)
237
- path = spec.full_gem_path
26
+ lines = []
27
+ lines << "register #{Path.escape(dir)}" if register
238
28
 
239
- # determine gem dependencies that have a config_file;
240
- # these will be set as the gems for the new Env
241
- dependencies = []
242
- spec.dependencies.each do |dependency|
243
- unless dependency.type == :runtime
244
- next
245
- end
246
-
247
- unless gemspec = Gems.gemspec(dependency)
248
- # this error may result when a dependency has
249
- # been uninstalled for a particular gem
250
- warn "missing gem dependency: #{dependency.to_s} (#{spec.full_name})"
251
- next
252
- end
29
+ path = Path.new(dir, map)
30
+ path[lib].each do |lib_dir|
31
+ lines << "loadpath #{Path.escape(lib_dir)}" if load_paths
253
32
 
254
- if config_file = context.config_file(gemspec.full_gem_path)
255
- next unless File.exists?(config_file)
256
- end
257
-
258
- dependencies << gemspec
259
- end
260
-
261
- config = {
262
- 'root' => path,
263
- 'gems' => dependencies,
264
- 'const_paths' => spec.require_paths,
265
- 'set_const_paths' => false
266
- }
267
-
268
- # override the default configs with whatever configs
269
- # are specified in the gem config file
270
- if config_file = context.config_file(path)
271
- config.merge!(load_config(config_file))
33
+ Constant.scan(lib_dir, pattern).each do |constant|
34
+ require_paths = Path.join(constant.require_paths)
35
+ types = constant.types.to_a.collect {|type| Path.escape(Path.join(type)) }
36
+ lines << "set #{constant.const_name} #{Path.escape require_paths} #{types.join(' ')}"
37
+ end if set
272
38
  end
273
39
 
274
- new(config, context)
275
- end
276
-
277
- # Loads configurations from path as YAML. Returns an empty hash if the path
278
- # loads to nil or false (as happens for empty files), or doesn't exist.
279
- #
280
- # Raises a ConfigError if the configurations do not load properly.
281
- def load_config(path)
282
- return {} unless path
283
-
284
- begin
285
- Root::Utils.trivial?(path) ? {} : (YAML.load_file(path) || {})
286
- rescue(Exception)
287
- raise ConfigError.new($!, path)
288
- end
40
+ lines.uniq!
41
+ lines.sort!
42
+ lines
289
43
  end
290
44
  end
291
45
 
292
- include Configurable
293
- include Enumerable
294
- include Minimap
295
-
296
- # The config file path
297
- CONFIG_FILE = "tap.yml"
46
+ include Signals
298
47
 
299
- # The home directory for Tap
300
- HOME = File.expand_path("#{File.dirname(__FILE__)}/../..")
301
-
302
- # An array of nested Envs, by default comprised of the env_path
303
- # + gem environments (in that order). Envs can be manually set
304
- # to override these defaults.
305
- attr_reader :envs
306
-
307
- # A Context tracking information shared among a set of envs.
308
- attr_reader :context
309
-
310
- # The Root directory structure for self.
311
- nest(:root, Root, :init => false)
312
-
313
- # Specify gems to add as nested Envs. Gems may be specified by name
314
- # and/or version, like 'gemname >= 1.2'; by default the latest version
315
- # of the gem is selected.
316
- #
317
- # Several special values also exist:
48
+ # Matches an inline type. After the match:
318
49
  #
319
- # :NONE, :none indicates no gems (same as nil, false)
320
- # :LATEST the latest version of all gems
321
- # :ALL all gems
322
- # :latest the latest version of all gems with a config file
323
- # :all all gems with a config file
50
+ # $1:: The prefix string (ex 'Const' for '::Const::type')
51
+ # $2:: The inline type (ex 'type' for '::Const::type')
324
52
  #
325
- # Gems are not activated by Env.
326
- config_attr :gems, [] do |input|
327
- input = yaml_load(input) if input.kind_of?(String)
328
-
329
- @gems = case input
330
- when false, nil, :NONE, :none
331
- []
332
- when :LATEST, :ALL
333
- # latest and all, no filter
334
- Gems.select_gems(input == :LATEST)
335
- when :latest, :all
336
- # latest and all, filtering by the existence of a
337
- # config file; all gems are selected if no config
338
- # file can be determined.
339
- Gems.select_gems(input == :latest) do |spec|
340
- config_file = context.config_file(spec.full_gem_path)
341
- config_file == nil || File.exists?(config_file)
342
- end
343
- else
344
- # resolve gem names manually
345
- [*input].collect do |name|
346
- Gems.gemspec(name)
347
- end.compact
348
- end
53
+ INLINE_TYPE = /(.*)::([a-z_]*)\z/
349
54
 
350
- reset_envs
351
- end
352
-
353
- # Specify directories to load as nested Envs. Configurations for the
354
- # env are loaded from the config file under dir, if it exists.
355
- config_attr :env_paths, [] do |input|
356
- @env_paths = resolve_paths(input)
357
- reset_envs
358
- end
55
+ attr_reader :paths
56
+ attr_reader :constants
359
57
 
360
- # Designates directories searched for constants. The const_paths are
361
- # added to $LOAD_PATH on activation if set_const_paths is specified.
362
- config_attr :const_paths, [:lib] do |input|
363
- raise "const_paths cannot be modified once active" if active?
364
- @const_paths = resolve_paths(input)
365
- end
58
+ signal_hash :auto, # auto-scan resources from a dir
59
+ :signature => [:dir, :pathfile, :lib, :pattern]
366
60
 
367
- # If set to true const_paths are added to $LOAD_PATH on activation.
368
- config_attr :set_const_paths, true do |input|
369
- raise "set_const_paths cannot be modified once active" if active?
370
- @set_const_paths = Configurable::Validation.boolean[input]
371
- end
61
+ signal :activate, :signature => [:name, :version]
372
62
 
373
- # Initializes a new Env linked to the specified directory. Configurations
374
- # for the env will be loaded from the config file (as determined by the
375
- # context) if it exists.
376
- #
377
- # A configuration hash may be manually provided in the place of dir. In
378
- # that case, no configurations will be loaded, even if the config file
379
- # exists.
380
- #
381
- # Context can be specified as a Context, or a Hash used to initialize a
382
- # Context.
383
- def initialize(config_or_dir=Dir.pwd, context={})
384
-
385
- # setup root
386
- config = nil
387
- @root = case config_or_dir
388
- when Root
389
- config_or_dir
390
- when String
391
- Root.new(config_or_dir)
392
- else
393
- config = config_or_dir
394
-
395
- if config.has_key?(:root) && config.has_key?('root')
396
- raise "multiple values mapped to :root"
397
- end
398
-
399
- root = config.delete(:root) || config.delete('root') || Dir.pwd
400
- root.kind_of?(Root) ? root : Root.new(root)
401
- end
402
-
403
- # note registration requires root.root, and so the
404
- # setup of context must follow the setup of root.
405
- @context = case context
406
- when Context
407
- context
408
- when Hash
409
- Context.new(context)
410
- else raise "cannot convert #{context.inspect} to Tap::Env::Context"
411
- end
412
- @context.register(self)
413
-
414
- # these need to be set for reset_env
415
- @active = false
416
- @gems = nil
417
- @env_paths = nil
418
-
419
- # only load configurations if configs were not provided
420
- config ||= Env.load_config(@context.config_file(@root.root))
421
- initialize_config(config)
422
-
423
- # set the invert flag
424
- @invert = false
425
- end
426
-
427
- # Sets envs removing duplicates and instances of self. Setting envs
428
- # overrides any environments specified by env_path and gem.
429
- def envs=(envs)
430
- raise "envs cannot be modified once active" if active?
431
- @envs = envs.uniq.delete_if {|env| env == self }
432
- end
433
-
434
- # Unshifts env onto envs. Self cannot be unshifted onto self.
435
- def unshift(env)
436
- unless env == self || envs[0] == env
437
- self.envs = envs.dup.unshift(env)
438
- end
439
- self
440
- end
441
-
442
- # Pushes env onto envs, removing duplicates.
443
- # Self cannot be pushed onto self.
444
- def push(env)
445
- unless env == self || envs[-1] == env
446
- envs = self.envs.reject {|e| e == env }
447
- self.envs = envs.push(env)
448
- end
449
- self
450
- end
451
- alias_method :<<, :push
452
-
453
- # Passes each nested env to the block in order, starting with self.
454
- def each
455
- visit_envs.each {|e| yield(e) }
456
- end
457
-
458
- # Passes each nested env to the block in reverse order, ending with self.
459
- def reverse_each
460
- visit_envs.reverse_each {|e| yield(e) }
461
- end
462
-
463
- # Recursively injects the memo to each env of self. Each env in envs
464
- # receives the same memo from the parent. This is different from the
465
- # inject provided via Enumerable, where each subsequent env receives
466
- # the memo from the previous, not the parent, env.
467
- #
468
- # a,b,c,d,e = ('a'..'e').collect {|name| Env.new(:name => name) }
469
- #
470
- # a.push(b).push(c)
471
- # b.push(d).push(e)
472
- #
473
- # lines = []
474
- # a.recursive_inject(0) do |nesting_depth, env|
475
- # lines << "\n#{'..' * nesting_depth}#{env.config[:name]} (#{nesting_depth})"
476
- # nesting_depth + 1
477
- # end
478
- #
479
- # lines.join
480
- # # => %Q{
481
- # # a (0)
482
- # # ..b (1)
483
- # # ....d (2)
484
- # # ....e (2)
485
- # # ..c (1)}
486
- #
487
- def recursive_inject(memo, &block) # :yields: memo, env
488
- inject_envs(memo, &block)
489
- end
490
-
491
- # Activates self by doing the following, in order:
492
- #
493
- # * activate nested environments
494
- # * unshift const_paths to $LOAD_PATH (if set_const_paths is true)
495
- #
496
- # Once active, the current envs and const_paths are frozen and cannot be
497
- # modified until deactivated. Returns true if activate succeeded, or
498
- # false if self is already active.
499
- def activate
500
- return false if active?
501
-
502
- @active = true
503
-
504
- # freeze envs and const paths
505
- @envs.freeze
506
- @const_paths.freeze
507
-
508
- # activate nested envs
509
- envs.reverse_each do |env|
510
- env.activate
511
- end
512
-
513
- # add const paths
514
- if set_const_paths
515
- const_paths.reverse_each do |path|
516
- $LOAD_PATH.unshift(path)
517
- end
518
-
519
- $LOAD_PATH.uniq!
520
- end
521
-
522
- true
523
- end
63
+ signal :register # add a resource path
64
+ signal :loadpath # add a load path
65
+ signal :set # add a constant
524
66
 
525
- # Deactivates self by doing the following in order:
526
- #
527
- # * deactivates nested environments
528
- # * removes const_paths from $LOAD_PATH (if set_const_paths is true)
529
- #
530
- # Once deactivated, envs and const_paths are unfrozen and may be modified.
531
- # Returns true if deactivate succeeded, or false if self is not active.
532
- #
533
- # ==== Note
534
- #
535
- # Deactivation does not necessarily leave $LOAD_PATH in the same condition
536
- # as before activation. A pre-existing $LOAD_PATH entry can go missing if
537
- # it is also registered as an env load_path (deactivation doesn't know to
538
- # leave such paths alone).
539
- #
540
- # Deactivation, like constant unloading should be done with caution.
541
- def deactivate
542
- return false unless active?
543
- @active = false
544
-
545
- # dectivate nested envs
546
- envs.reverse_each do |env|
547
- env.deactivate
548
- end
549
-
550
- # remove const paths
551
- const_paths.each do |path|
552
- $LOAD_PATH.delete(path)
553
- end if set_const_paths
554
-
555
- # unfreeze envs and const paths
556
- @envs = @envs.dup
557
- @const_paths = @const_paths.dup
558
-
559
- true
560
- end
67
+ signal :unregister # remove a resource path
68
+ signal :unloadpath # remove a load path
69
+ signal :unset # remove a constant
561
70
 
562
- # Return true if self has been activated.
563
- def active?
564
- @active
565
- end
71
+ define_signal :load, Load # load a tapenv file
72
+ define_signal :help, Help # signals help
566
73
 
567
- # Globs the abstract directory for files in the specified directory alias,
568
- # matching the pattern. The expanded path of each matching file is
569
- # returned.
570
- def glob(dir, pattern="**/*")
571
- hlob(dir, pattern).values.sort!
74
+ def initialize(options={})
75
+ @paths = options[:paths] || []
76
+ @paths.collect! {|path| path.kind_of?(Path) ? path : Path.new(*path) }
77
+ @constants = options[:constants] || []
78
+ @constants.collect! {|constant| constant.kind_of?(Constant) ? constant : Constant.new(constant) }
572
79
  end
573
80
 
574
- # Same as glob but returns results as a hash of (relative_path, path)
575
- # pairs. In short the hash defines matching files in the abstract
576
- # directory, linked to the actual path for these files.
577
- def hlob(dir, pattern="**/*")
578
- results = {}
579
- each do |env|
580
- root = env.root
581
- root.glob(dir, pattern).each do |path|
582
- relative_path = root.relative_path(dir, path)
583
- results[relative_path] ||= path
584
- end
81
+ def path(type)
82
+ result = []
83
+ paths.each do |path|
84
+ result.concat path[type]
585
85
  end
586
- results
86
+ result
587
87
  end
588
88
 
589
- # Returns the path to the specified file, as determined by root.
590
- #
591
- # If a block is given, a path for each env will be yielded until the block
592
- # returns a true value. Returns nil if the block never returns true.
593
- def path(dir = :root, *paths)
594
- each do |env|
595
- path = env.root.path(dir, *paths)
596
- return path if !block_given? || yield(path)
597
- end
598
- nil
89
+ def match(const_str, type=nil)
90
+ const_str = const_str.to_s
91
+ const_str =~ Constant::CONST_REGEXP ? constants_by_const_name($1) : constants_by_path(const_str, type)
599
92
  end
600
93
 
601
- # Retrieves a path associated with the inheritance hierarchy of an object.
602
- # An array of modules (which naturally can include classes) are provided
603
- # and module_path traverses each, forming paths like:
604
- #
605
- # path(dir, module_path, *paths)
606
- #
607
- # By default 'module_path' is 'module.to_s.underscore' but modules can
608
- # specify an alternative by providing a module_path method.
609
- #
610
- # Paths are yielded to the block until the block returns true, at which
611
- # point the current the path is returned. If no block is given, the
612
- # first path is returned. Returns nil if the block never returns true.
613
- def module_path(dir, modules, *paths, &block)
614
- paths.compact!
615
- while current = modules.shift
616
- module_path = if current.respond_to?(:module_path)
617
- current.module_path
618
- else
619
- current.to_s.underscore
620
- end
621
-
622
- if path = self.path(dir, module_path, *paths, &block)
623
- return path
624
- end
94
+ def resolve(const_str, type=nil)
95
+ matches = match(const_str, type)
96
+ case matches.length
97
+ when 0 then raise "unresolvable constant: #{const_str.inspect}"
98
+ when 1 then matches.at(0)
99
+ else raise "multiple matching constants: #{const_str.inspect} (#{matches.join(', ')})"
625
100
  end
626
-
627
- nil
628
- end
629
-
630
- # Returns the module_path traversing the inheritance hierarchy for the
631
- # class of obj (or obj if obj is a Class). Included modules are not
632
- # visited, only the superclasses.
633
- def class_path(dir, obj, *paths, &block)
634
- klass = obj.kind_of?(Class) ? obj : obj.class
635
- superclasses = klass.ancestors - klass.included_modules
636
- module_path(dir, superclasses, *paths, &block)
637
101
  end
638
102
 
639
- # Generates a Manifest for self using the block as a builder. The builder
640
- # receives an env and should return an array of resources, each of which
641
- # can be minimappped. Minimapping requires that the resource is either
642
- # a path string, or provides a 'path' method that returns a path string.
643
- # Alternatively, a Minimap may be returned.
644
- #
645
- # If a type is specified, then the manifest cache will be linked to the
646
- # context cache.
647
- def manifest(type=nil, &block) # :yields: env
648
- cache = type ? (context.cache[type] ||= {}) : {}
649
- Manifest.new(self, block, cache)
103
+ def constant(const_str, type=nil)
104
+ const_str.kind_of?(Module) ? const_str : resolve(const_str, type).constantize
650
105
  end
651
106
 
652
- # Returns a manifest of Constants located in .rb files under each of the
653
- # const_paths. Constants are identified using Lazydoc constant attributes;
654
- # all attributes are registered to the constants for classification
655
- # (for example as a task, join, etc).
656
- def constants
657
- @constants ||= manifest(:constants) do |env|
658
- constants = {}
659
-
660
- env.const_paths.each do |load_path|
661
- next unless File.directory?(load_path)
662
- Constant.scan(load_path, "**/*.rb", constants)
663
- end
664
-
665
- constants.keys.sort!.collect! do |key|
666
- constants[key]
667
- end
668
- end
107
+ # Registers the directory and path mappings as a Path, into paths. The
108
+ # path is unshifted to paths to provide similar functionality as loadpath.
109
+ # Returns the new path.
110
+ def register(dir, map={})
111
+ new_path = Path.new(dir, map)
112
+ paths.unshift new_path
113
+ new_path
669
114
  end
670
115
 
671
- # Seeks a constant for the key, constantizing if necessary. If invert is
672
- # true, this method seeks and returns a key for the input constant. In
673
- # both cases AGET returns nil if no key-constant pair can be found.
674
- def [](key, invert=invert?)
675
- if invert
676
- const_name = key.to_s
677
- constants.unseek(true) do |const|
678
- const_name == const.const_name
679
- end
680
- else
681
- if constant = constants.seek(key)
682
- constant.constantize
683
- else
684
- nil
685
- end
116
+ def auto(options, log=nil)
117
+ Env.generate(options).each do |line|
118
+ sig, *args = Utils.shellsplit(line)
119
+ signal(sig).call(args)
686
120
  end
121
+ self
687
122
  end
688
123
 
689
- # Indicates AGET looks up constants from keys (false), or keys from
690
- # constants (true).
691
- def invert?
692
- @invert
124
+ def activate(name, version)
125
+ Gem.activate(name, version)
693
126
  end
694
127
 
695
- # Inverts the AGET lookup for self.
696
- def invert!
697
- @invert = !@invert
128
+ def unregister(*dirs)
129
+ dirs.collect! {|dir| File.expand_path(dir) }
130
+ paths.delete_if {|path| dirs.include?(path.base) }
698
131
  self
699
132
  end
700
133
 
701
- # Returns a duplicate of self with inverted AGET lookup.
702
- def invert
703
- dup.invert!
134
+ # Expands and prepends the specified paths to $LOAD_PATH, removing any
135
+ # duplicates. Returns $LOAD_PATH.
136
+ def loadpath(*paths)
137
+ paths.reverse_each do |path|
138
+ $LOAD_PATH.unshift File.expand_path(path)
139
+ end
140
+
141
+ $LOAD_PATH.uniq!
142
+ $LOAD_PATH
704
143
  end
705
144
 
706
- # Scans the files matched under the directory and pattern for constants
707
- # and adds them to the existing constants for self.
708
- def scan(dir, pattern="**/*.rb")
709
- new_entries = {}
710
- entries = constants.entries(self)
711
- entries.each {|const| new_entries[const.const_name] = const }
712
-
713
- Constant.scan(root[dir], pattern, new_entries)
714
-
715
- entries.replace(new_entries.keys)
716
- entries.sort!.collect! {|key| new_entries[key] }
717
- entries
145
+ # Expands and removes the specified paths from $LOAD_PATH. Returns
146
+ # $LOAD_PATH.
147
+ def unloadpath(*paths)
148
+ paths.each {|path| $LOAD_PATH.delete File.expand_path(path) }
149
+ $LOAD_PATH
718
150
  end
719
151
 
720
- # Registers a constant with self. The constant is stored as a new
721
- # Constant in the constants manifest. Returns the new Constant.
722
- # If the constant is already registered, the existing Constant is
723
- # returned.
724
- def register(constant)
725
- const_name = constant.to_s
726
- entries = constants.entries(self)
152
+ def set(const_name, require_path=nil, *types)
153
+ if const_name =~ INLINE_TYPE
154
+ const_name = $1
155
+ types << $2
156
+ end
157
+
158
+ constant = constants.find {|c| c.const_name == const_name }
727
159
 
728
- # try to find the existing Constant before making a new constant
729
- unless constant = entries.find {|const| const.const_name == const_name}
160
+ unless constant
730
161
  constant = Constant.new(const_name)
731
- entries << constant
732
- entries.replace entries.sort_by {|const| const.const_name }
162
+ constants << constant
733
163
  end
734
164
 
735
- constant
736
- end
737
-
738
- # When no template is specified, inspect generates a fairly standard
739
- # inspection string. When a template is provided, inspect builds a
740
- # Templater for each env with the following local variables:
741
- #
742
- # variable value
743
- # env the current env
744
- # env_keys a minihash for all envs
745
- #
746
- # If a block is given, the globals and templater are yielded before
747
- # any templater is built; this allows each env to add env-specific
748
- # variables. After this preparation, each templater is built with
749
- # the globals and the results concatenated.
750
- #
751
- # The template is built with filename, if specified (for debugging).
752
- def inspect(template=nil, globals={}, filename=nil) # :yields: templater, globals
753
- if template == nil
754
- return "#<#{self.class}:#{object_id} root='#{root.root}'>"
165
+ require_paths = require_path ? Path.split(require_path, nil) : []
166
+ if require_paths.empty? && const_name.kind_of?(String)
167
+ require_paths << const_name.underscore
755
168
  end
756
169
 
757
- env_keys = minihash(true)
758
- collect do |env|
759
- templater = Templater.new(template, :env => env, :env_key => env_keys[env])
760
- yield(templater, globals) if block_given?
761
- templater
762
- end.collect! do |templater|
763
- templater.build(globals, filename)
764
- end.join
765
- end
766
-
767
- protected
768
-
769
- # helper for Minimap; note that specifying env.root.root via path
770
- # is not possible because path is required for other purposes.
771
- def entry_to_path(env) # :nodoc:
772
- env.root.root
773
- end
774
-
775
- # resets envs using the current env_paths and gems. does nothing
776
- # until both env_paths and gems are set.
777
- def reset_envs # :nodoc:
778
- if env_paths && gems
779
- self.envs = env_paths.collect do |path|
780
- context.instance(path) || Env.new(path, context)
781
- end + gems.collect do |spec|
782
- context.instance(spec.full_gem_path) || Env.setup_gem(spec, context)
783
- end
784
- end
785
- end
786
-
787
- # arrayifies, compacts, and resolves input paths using root.
788
- # also removes duplicates. in short:
789
- #
790
- # resolve_paths ['lib', nil, 'lib', 'alt] # => [root['lib'], root['alt']]
791
- #
792
- def resolve_paths(paths) # :nodoc:
793
- paths = yaml_load(paths) if paths.kind_of?(String)
794
- [*paths].compact.collect {|path| root[path] }.uniq
795
- end
796
-
797
- # helper to recursively iterate through envs, starting with self.
798
- # visited envs are collected in order and are used to ensure a
799
- # given env will only be visited once.
800
- def visit_envs(visited=[], &block) # :nodoc:
801
- unless visited.include?(self)
802
- visited << self
803
- yield(self) if block_given?
170
+ constant.require_paths.concat(require_paths).uniq!
171
+ types.each {|type| constant.register_as(*Path.split(type, nil)) }
804
172
 
805
- envs.each do |env|
806
- env.visit_envs(visited, &block)
807
- end
808
- end
809
-
810
- visited
173
+ constant
811
174
  end
812
-
813
- # helper to recursively inject a memo to the children of env
814
- def inject_envs(memo, visited=[], &block) # :nodoc:
815
- unless visited.include?(self)
816
- visited << self
817
- next_memo = yield(memo, self)
818
- envs.each do |env|
819
- env.inject_envs(next_memo, visited, &block)
175
+
176
+ def unset(*const_names)
177
+ const_names.each do |const_name|
178
+ constants.delete_if do |constant|
179
+ constant.const_name == const_name
820
180
  end
821
181
  end
822
-
823
- visited
182
+ self
824
183
  end
825
184
 
826
185
  private
827
186
 
828
- # A 'quick' yaml load where empty strings will not cause YAML to autoload.
829
- # This is a silly song and dance, but provides for optimal launch times.
830
- def yaml_load(str) # :nodoc:
831
- str.empty? ? false : YAML.load(str)
187
+ def constants_by_const_name(const_str) # :nodoc:
188
+ constants.select do |constant|
189
+ constant.const_name == const_str
190
+ end
832
191
  end
833
-
834
- # Raised when there is a configuration error from Env.load_config.
835
- class ConfigError < StandardError # :nodoc:
836
- attr_reader :original_error, :env_path
192
+
193
+ def constants_by_path(const_str, type) # :nodoc:
194
+ const_str, inline_type = const_str.split('::', 2)
195
+ type = inline_type if inline_type
837
196
 
838
- def initialize(original_error, env_path)
839
- @original_error = original_error
840
- @env_path = env_path
841
- super()
842
- end
197
+ head, tail = const_str.split(':', 2)
198
+ head, tail = nil, head unless tail
843
199
 
844
- def message
845
- "Configuration error: #{original_error.message}\n" +
846
- ($DEBUG ? "#{original_error.backtrace}\n" : "") +
847
- "Check '#{env_path}' configurations"
200
+ constants.select do |constant|
201
+ constant.type_match?(type) && constant.path_match?(head, tail)
848
202
  end
849
203
  end
850
204
  end