tap 0.19.0 → 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -0,0 +1,87 @@
1
+ require 'tap/env/path'
2
+
3
+ autoload(:Gem, 'rubygems')
4
+ autoload(:RbConfig, 'rbconfig')
5
+ module Tap
6
+ class Env
7
+ class Cache
8
+ attr_reader :cache_home
9
+
10
+ def initialize(dir=Dir.pwd, debug=false)
11
+ @cache_home = File.expand_path("#{RbConfig::CONFIG['RUBY_INSTALL_NAME']}/#{RUBY_VERSION}", dir)
12
+ @debug = debug
13
+ end
14
+
15
+ def select(dependencies)
16
+ if dependencies.kind_of?(String)
17
+ dependencies = dependencies.split(':')
18
+ end
19
+
20
+ paths = []
21
+ dependencies.collect! do |dep|
22
+ dep.kind_of?(String) ? dep.split(',', 2) : dep
23
+ end.each do |(pattern, version_requirements)|
24
+ pattern = Regexp.new(pattern) if pattern.kind_of?(String)
25
+ paths.concat search(pattern, version_requirements)
26
+ end
27
+
28
+ paths.uniq!
29
+ paths
30
+ end
31
+
32
+ def search(pattern, version_requirements)
33
+ dependency = Gem::Dependency.new(pattern, version_requirements)
34
+
35
+ sources = {}
36
+ Gem.source_index.search(dependency).sort_by do |spec|
37
+ spec.version
38
+ end.reverse_each do |spec|
39
+ sources[spec.name] ||= spec
40
+ end
41
+
42
+ paths = []
43
+ sources.values.sort_by do |spec|
44
+ spec.name
45
+ end.each do |spec|
46
+ unless File.exists? File.expand_path(Path::FILE, spec.full_gem_path)
47
+ next
48
+ end
49
+
50
+ path = File.join(cache_home, spec.full_name)
51
+ gem_path = spec.full_gem_path
52
+
53
+ unless FileUtils.uptodate?(path, [gem_path, __FILE__])
54
+ unless File.exists?(cache_home)
55
+ FileUtils.mkdir_p(cache_home)
56
+ end
57
+
58
+ if @debug
59
+ $stderr.puts(App::LOG_FORMAT % [' ', nil, :generate, spec.full_name])
60
+ end
61
+
62
+ File.open(path, 'w') do |io|
63
+ io << generate(spec)
64
+ end
65
+ end
66
+
67
+ paths << path
68
+ end
69
+
70
+ paths
71
+ end
72
+
73
+ def generate(spec)
74
+ lines = Env.generate(
75
+ :dir => spec.full_gem_path,
76
+ :pathfile => File.expand_path(Path::FILE, spec.full_gem_path),
77
+ :load_paths => false)
78
+
79
+ lines.unshift "# Generated for #{spec.full_name} on #{Time.now}. Do not edit."
80
+ lines << "activate #{spec.name} #{spec.version}"
81
+ lines.uniq!
82
+ lines.sort!
83
+ lines.join("\n")
84
+ end
85
+ end
86
+ end
87
+ end
@@ -1,4 +1,5 @@
1
1
  require 'tap/env/string_ext'
2
+ require 'tap/root'
2
3
 
3
4
  module Tap
4
5
  class Env
@@ -8,11 +9,11 @@ module Tap
8
9
  # it doesn't exist, constantize requires require_path and tries again.
9
10
  #
10
11
  # Object.const_defined?(:Net) # => false
11
- # $".include?('net/http') # => false
12
+ # $".grep(/net\/http.rb$/).empty? # => true
12
13
  #
13
14
  # http = Constant.new('Net::HTTP', 'net/http.rb')
14
15
  # http.constantize # => Net::HTTP
15
- # $".include?('net/http.rb') # => true
16
+ # $".grep(/net\/http.rb$/).empty? # => false
16
17
  #
17
18
  # === Unloading
18
19
  #
@@ -84,33 +85,39 @@ module Tap
84
85
  const
85
86
  end
86
87
 
87
- # Scans the directory and pattern for constants and adds them to the
88
- # constants hash by name.
89
- def scan(dir, pattern="**/*.rb", constants={})
90
- if pattern.include?("..")
91
- raise "patterns cannot include relative paths: #{pattern.inspect}"
92
- end
88
+ # Scans the directory and pattern for constants.
89
+ def scan(dir, pattern="**/*.rb")
90
+ constants = {}
93
91
 
94
- # note changing dir here makes require paths relative to load_path,
95
- # hence they can be directly converted into a default_const_name
96
- # rather than first performing Root.relative_path
97
- Dir.chdir(dir) do
98
- Dir.glob(pattern).each do |path|
99
- default_const_name = path.chomp(File.extname(path)).camelize
100
-
101
- # scan for constants
102
- Lazydoc::Document.scan(File.read(path)) do |const_name, type, summary|
103
- const_name = default_const_name if const_name.empty?
104
-
105
- constant = (constants[const_name] ||= Constant.new(const_name))
106
- constant.register_as(type, summary)
107
- constant.require_paths << path
92
+ root = Root.new(dir)
93
+ root.glob(pattern).each do |path|
94
+ Lazydoc::Document.scan(File.read(path)) do |const_name, type, summary|
95
+ require_path = root.relative_path(path)
96
+
97
+ if const_name.empty?
98
+ extname = File.extname(path)
99
+ const_name = require_path.chomp(extname).camelize
108
100
  end
101
+
102
+ constant = (constants[const_name] ||= new(const_name))
103
+ constant.register_as(type, summary)
104
+ constant.require_paths << require_path
109
105
  end
110
106
  end
111
-
107
+
108
+ constants = constants.values
109
+ constants.each {|constant| constant.require_paths.uniq! }
112
110
  constants
113
111
  end
112
+
113
+ def cast(obj)
114
+ case obj
115
+ when String then new(obj)
116
+ when Module then new(obj.to_s)
117
+ when Constant then obj
118
+ else raise ArgumentError, "not a constant or constant name: #{obj.inspect}"
119
+ end
120
+ end
114
121
 
115
122
  private
116
123
 
@@ -129,9 +136,12 @@ module Tap
129
136
  end
130
137
  end
131
138
 
132
- # Matches a valid constant
139
+ # Matches a valid constant. After the match:
140
+ #
141
+ # $1:: The unqualified constant (ex 'Const' for '::Const')
142
+ #
133
143
  CONST_REGEXP = /\A(?:::)?([A-Z]\w*(?:::[A-Z]\w*)*)\z/
134
-
144
+
135
145
  # The full constant name
136
146
  attr_reader :const_name
137
147
 
@@ -142,21 +152,24 @@ module Tap
142
152
  # A hash of (type, summary) pairs used to classify self.
143
153
  attr_reader :types
144
154
 
145
- # Initializes a new Constant with the specified constant name,
146
- # require_path, and comment. The const_name should be a valid
147
- # constant name.
155
+ # Initializes a new Constant with the specified constant name, and
156
+ # require_paths. Raises an error if const_name is not valid.
148
157
  def initialize(const_name, *require_paths)
149
- @const_name = const_name
150
- @require_paths = require_paths
151
158
  @types = {}
159
+ @const_name = normalize(const_name)
160
+ @require_paths = require_paths
161
+ end
162
+
163
+ def relative_path
164
+ @relative_path ||= const_name.underscore
152
165
  end
153
166
 
154
167
  # Returns the underscored const_name.
155
168
  #
156
- # Constant.new("Const::Name").path # => 'const/name'
169
+ # Constant.new("Const::Name").path # => '/const/name'
157
170
  #
158
171
  def path
159
- @path ||= const_name.underscore
172
+ @path ||= "/#{relative_path}"
160
173
  end
161
174
 
162
175
  # Returns the basename of path.
@@ -169,10 +182,10 @@ module Tap
169
182
 
170
183
  # Returns the path, minus the basename of path.
171
184
  #
172
- # Constant.new("Const::Name").dirname # => 'const'
185
+ # Constant.new("Const::Name").dirname # => '/const'
173
186
  #
174
187
  def dirname
175
- @dirname ||= (dirname = File.dirname(path)) == "." ? "" : dirname
188
+ @dirname ||= File.dirname(path)
176
189
  end
177
190
 
178
191
  # Returns the name of the constant, minus nesting.
@@ -206,12 +219,17 @@ module Tap
206
219
  another.const_name == self.const_name &&
207
220
  another.require_paths == self.require_paths
208
221
  end
222
+
223
+ # Peforms comparison of the const_name of self vs another.
224
+ def <=>(another)
225
+ const_name <=> another.const_name
226
+ end
209
227
 
210
228
  # Registers the type and summary with self. Raises an error if self is
211
229
  # already registerd as the type and override is false.
212
230
  def register_as(type, summary=nil, override=false)
213
- if types.include?(type) && !override
214
- raise "already registered as a #{type.inspect}"
231
+ if types.include?(type) && types[type] != summary && !override
232
+ raise "already registered as a #{type.inspect} (#{const_name})"
215
233
  end
216
234
 
217
235
  types[type] = summary
@@ -250,8 +268,10 @@ module Tap
250
268
 
251
269
  if const.const_defined?(name)
252
270
  require_paths.each do |require_path|
253
- path = File.extname(require_path).empty? ? "#{require_path}.rb" : require_path
254
- $".delete(path)
271
+ require_path = File.extname(require_path).empty? ? "#{require_path}.rb" : require_path
272
+ regexp = /#{require_path}$/
273
+
274
+ $".delete_if {|path| path =~ regexp }
255
275
  end if unrequire
256
276
 
257
277
  return const.send(:remove_const, name)
@@ -259,7 +279,19 @@ module Tap
259
279
 
260
280
  nil
261
281
  end
262
-
282
+
283
+ def path_match?(head, tail=nil)
284
+ (head.nil? || head.empty? || head_match(head)) && (tail.nil? || tail.empty? || tail_match(tail))
285
+ end
286
+
287
+ def type_match?(type)
288
+ case type
289
+ when nil then true
290
+ when Array then type.any? {|t| types.has_key?(t) }
291
+ else types.has_key?(type)
292
+ end
293
+ end
294
+
263
295
  # Returns a string like:
264
296
  #
265
297
  # "#<Tap::Env::Constant:object_id Const::Name (require_path)>"
@@ -272,6 +304,29 @@ module Tap
272
304
  def to_s
273
305
  const_name
274
306
  end
307
+
308
+ private
309
+
310
+ def normalize(const_name) # :nodoc:
311
+ case const_name
312
+ when Module then const_name.to_s
313
+ when CONST_REGEXP then $1
314
+ else raise NameError, "#{const_name.inspect} is not a valid constant name!"
315
+ end
316
+ end
317
+
318
+ def head_match(head) # :nodoc:
319
+ index = path.index(head)
320
+ index == (head[0] == ?/ ? 0 : 1) && begin
321
+ match_end = index + head.length
322
+ (match_end == path.length || path[match_end] == ?/)
323
+ end
324
+ end
325
+
326
+ def tail_match(tail) # :nodoc:
327
+ index = path.rindex(tail)
328
+ index && (index + tail.length) == path.length && (index == 0 || path[index-1] == ?/)
329
+ end
275
330
  end
276
331
  end
277
332
  end
@@ -0,0 +1,71 @@
1
+ module Tap
2
+ class Env
3
+ class Path
4
+ class << self
5
+
6
+ # Splits the path string along ':' boundaries and expands each
7
+ # resulting fragments relative to dir. Duplicate paths are removed.
8
+ # Returns the resulting paths.
9
+ #
10
+ # An array of pre-split paths may also be provided as an input.
11
+ def split(str, dir=Dir.pwd)
12
+ paths = str.kind_of?(String) ? str.split(':') : str
13
+ paths.collect! {|path| File.expand_path(path, dir) } if dir
14
+ paths.uniq!
15
+ paths
16
+ end
17
+
18
+ def join(paths)
19
+ paths.join(':')
20
+ end
21
+
22
+ def load(path_file)
23
+ Root.trivial?(path_file) ? {} : (YAML.load_file(path_file) || {})
24
+ end
25
+
26
+ def escape(str)
27
+ "'#{str.gsub("'", "\\\\'")}'"
28
+ end
29
+ end
30
+
31
+ FILE = 'tap.yml'
32
+
33
+ # The path base.
34
+ attr_reader :base
35
+
36
+ # A mapping of types to paths.
37
+ attr_reader :map
38
+
39
+ # Creates a new Path relative to the base.
40
+ def initialize(base, map={})
41
+ @base = File.expand_path(base)
42
+ @map = {}
43
+
44
+ map.each_pair {|type, paths| self[type] = paths }
45
+ end
46
+
47
+ # Returns an array of expanded paths associated with the type; by
48
+ # default the type expanded under base.
49
+ def [](type)
50
+ map[type] ||= [File.expand_path(type.to_s, base)]
51
+ end
52
+
53
+ # Sets the path for the type. Paths are split and expanded relative to
54
+ # base (see Path.split).
55
+ def []=(type, paths)
56
+ map[type] = Path.split(paths, base)
57
+ end
58
+
59
+ def ==(another)
60
+ another.kind_of?(Path) &&
61
+ base == another.base &&
62
+ map == another.map
63
+ end
64
+
65
+ # Returns the base path.
66
+ def to_s
67
+ base
68
+ end
69
+ end
70
+ end
71
+ end
@@ -1,15 +1,7 @@
1
- require 'tap/app'
2
- require 'tap/intern'
1
+ require 'tap/app/api'
3
2
 
4
3
  module Tap
5
- class App
6
- # Generates a join between the inputs and outputs.
7
- def join(inputs, outputs, config={}, klass=Join, &block)
8
- klass.new(config, self).join(inputs, outputs, &block)
9
- end
10
- end
11
-
12
- # :startdoc::join an unsyncrhonized, multi-way join
4
+ # :startdoc::join unsyncrhonized multi-way join
13
5
  #
14
6
  # Join defines an unsynchronized, multi-way join where n inputs send their
15
7
  # results to m outputs. Flags can augment how the results are passed, in
@@ -17,34 +9,7 @@ module Tap
17
9
  #
18
10
  class Join < App::Api
19
11
  class << self
20
-
21
- # Instantiates a new join with the input arguments and overrides
22
- # call with the block. The block will be called with the join
23
- # instance and result.
24
- #
25
- # Simply instantiates a new join if no block is given.
26
- def intern(config={}, app=Tap::App.instance, &block) # :yields: join, result
27
- instance = new(config, app)
28
- if block_given?
29
- instance.extend Intern(:call)
30
- instance.call_block = block
31
- end
32
- instance
33
- end
34
-
35
- def parse!(argv=ARGV, app=Tap::App.instance)
36
- parser = self.parser
37
- argv = parser.parse!(argv, :add_defaults => false)
38
- instance = build({
39
- 'config' => parser.nested_config,
40
- 'inputs' => argv.shift,
41
- 'outputs' => argv.shift
42
- }, app)
43
-
44
- [instance, argv]
45
- end
46
-
47
- def build(spec={}, app=Tap::App.instance)
12
+ def build(spec={}, app=Tap::App.current)
48
13
  inputs = resolve(spec['inputs']) do |var|
49
14
  app.get(var) or raise "missing join input: #{var}"
50
15
  end
@@ -58,6 +23,14 @@ module Tap
58
23
 
59
24
  protected
60
25
 
26
+ def convert_to_spec(parser, args)
27
+ {
28
+ 'config' => parser.nested_config,
29
+ 'inputs' => args.shift,
30
+ 'outputs' => args.shift
31
+ }
32
+ end
33
+
61
34
  def resolve(refs) # :nodoc:
62
35
  refs = case refs
63
36
  when String then parse_indicies(refs)
@@ -83,34 +56,23 @@ module Tap
83
56
  end
84
57
 
85
58
  # Causes the outputs to be enqued rather than executed immediately.
86
- config :enq, false, :short => 'q', &c.flag # Enque output nodes
59
+ config :enq, false, :short => 'q', &c.flag # Enque output tasks
87
60
 
88
- # Splats the result to the outputs, allowing a many-to-one join
89
- # from the perspective of the results.
90
- #
91
- # # results: [1,2,3]
92
- # # outputs: call(*inputs)
93
- # app.execute(output, *result)
94
- #
95
- config :splat, false, :short => 's', &c.flag # Splat results to outputs
61
+ # Converts each result into a one-member array before being passed onto
62
+ # outputs. Arrayify occurs before iterate and combined the two flags
63
+ # cancel.
64
+ config :arrayify, false, :short => 'a', &c.flag # Arrayify results
96
65
 
97
- # Iterates the results to the outputs, allowing a many-to-one join
98
- # from the perspective of the results. Non-array results are converted
99
- # to arrays using to_ary:
66
+ # Iterates the results to the outputs. Non-array results are converted to
67
+ # arrays using to_ary:
100
68
  #
101
69
  # # results: [1,2,3]
102
70
  # # outputs: call(input)
103
- # result.to_ary.each {|r| app.execute(output, r) }
104
- #
105
- # Iterate may be combined with splat:
106
- #
107
- # # results: [[1,2],3]
108
- # # outputs: call(*inputs)
109
- # result.to_ary.each {|r| app.execute(output, *r) }
71
+ # result.to_ary.each {|r| app.exe(output, r) }
110
72
  #
111
73
  config :iterate, false, :short => 'i', &c.flag # Iterate results to outputs
112
74
 
113
- signal :join do |sig, (inputs, outputs)|
75
+ signal :join do |sig, (inputs, outputs)| # join app objects
114
76
  app = sig.obj.app
115
77
 
116
78
  inputs = resolve(inputs) do |var|
@@ -124,14 +86,14 @@ module Tap
124
86
  [inputs, outputs]
125
87
  end
126
88
 
127
- # An array of input nodes, or nil if the join has not been set.
89
+ # An array of input tasks, or nil if the join has not been set.
128
90
  attr_reader :inputs
129
91
 
130
- # An array of output nodes, or nil if the join has not been set.
92
+ # An array of output tasks, or nil if the join has not been set.
131
93
  attr_reader :outputs
132
94
 
133
95
  # Initializes a new join with the specified configuration.
134
- def initialize(config={}, app=Tap::App.instance)
96
+ def initialize(config={}, app=Tap::App.current)
135
97
  @inputs = nil
136
98
  @outputs = nil
137
99
  super
@@ -143,6 +105,12 @@ module Tap
143
105
  input.joins.delete(self)
144
106
  end if @inputs
145
107
 
108
+ inputs.each do |input|
109
+ unless input.respond_to?(:joins)
110
+ raise "input does not support joins: #{input.inspect}"
111
+ end
112
+ end
113
+
146
114
  @inputs = inputs
147
115
 
148
116
  inputs.each do |input|
@@ -157,7 +125,7 @@ module Tap
157
125
  # each output.
158
126
  def call(result)
159
127
  outputs.each do |output|
160
- dispatch(output, result)
128
+ exe(output, result)
161
129
  end
162
130
  end
163
131
 
@@ -167,29 +135,25 @@ module Tap
167
135
 
168
136
  def to_spec
169
137
  spec = super
170
- spec['inputs'] = inputs.collect {|node| app.var(node) }
171
- spec['outputs'] = outputs.collect {|node| app.var(node) }
138
+ spec['inputs'] = inputs.collect {|task| app.var(task) }
139
+ spec['outputs'] = outputs.collect {|task| app.var(task) }
172
140
  spec
173
141
  end
174
142
 
175
143
  protected
176
144
 
177
- # Dispatches the results to the node.
178
- def dispatch(node, result) # :nodoc:
179
- mode = enq ? :enq : :execute
180
- if iterate
181
- result.to_ary.each {|r| execute(mode, node, r) }
182
- else
183
- execute(mode, node, result)
145
+ # Executes the task with the input results.
146
+ def exe(task, result) # :nodoc:
147
+ mode = enq ? :enq : :exe
148
+
149
+ if arrayify
150
+ result = [result]
184
151
  end
185
- end
186
-
187
- # Executes the node with the input results.
188
- def execute(mode, node, result) # :nodoc:
189
- if splat
190
- app.send(mode, node, *result)
152
+
153
+ if iterate
154
+ result.to_ary.each {|item| app.send(mode, task, item) }
191
155
  else
192
- app.send(mode, node, result)
156
+ app.send(mode, task, result)
193
157
  end
194
158
  end
195
159
  end