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
@@ -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