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,5 +1,4 @@
1
- require 'configurable'
2
- require 'tap/signals/help'
1
+ require 'tap/app'
3
2
 
4
3
  module Tap
5
4
  class App
@@ -30,7 +29,7 @@ module Tap
30
29
  #
31
30
  # The parse method uses parser by default, so subclasses can simply
32
31
  # modify parser and ensure parse still works correctly.
33
- def parser
32
+ def parser(app)
34
33
  opts = ConfigParser.new
35
34
 
36
35
  unless configurations.empty?
@@ -42,34 +41,44 @@ module Tap
42
41
  opts.separator "options:"
43
42
 
44
43
  # add option to print help
45
- opts.on("-h", "--help", "Print this help") do
46
- puts "#{self}#{desc.empty? ? '' : ' -- '}#{desc.to_s}"
47
- puts help
48
- puts opts
49
- exit
44
+ opts.on("--help", "Print this help") do
45
+ lines = ["#{self}#{desc.empty? ? '' : ' -- '}#{desc.to_s}"]
46
+ lines << help
47
+ lines << "usage: tap #{to_s.underscore} #{respond_to?(:args) ? args : nil}"
48
+ lines << nil
49
+ lines << opts
50
+ raise lines.join("\n")
50
51
  end
51
52
 
52
53
  opts
53
54
  end
54
55
 
56
+ def parse(argv=ARGV, app=Tap::App.current, &block)
57
+ parse!(argv.dup, app, &block)
58
+ end
59
+
55
60
  # Parses the argv into an instance of self. Internally parse parses
56
61
  # an argh then calls build, but there is no requirement that this
57
62
  # occurs in subclasses.
58
- def parse(argv=ARGV, app=Tap::App.instance)
59
- parse!(argv.dup, app)
60
- end
61
-
62
- # Same as parse, but removes arguments destructively.
63
- def parse!(argv=ARGV, app=Tap::App.instance)
64
- parser = self.parser
65
- argv = parser.parse!(argv, :add_defaults => false)
63
+ #
64
+ # Returns the instance. If a block is given, the instance and any
65
+ # remaining arguments will be yielded to it.
66
+ def parse!(argv=ARGV, app=Tap::App.current)
67
+ parser = self.parser(app)
68
+ args = parser.parse!(argv, :add_defaults => false)
69
+ obj = build(convert_to_spec(parser, args), app)
66
70
 
67
- [build({'config' => parser.nested_config}, app), argv]
71
+ if block_given?
72
+ yield(obj, args)
73
+ else
74
+ parser.warn_ignored_args(args)
75
+ obj
76
+ end
68
77
  end
69
-
78
+
70
79
  # Returns an instance of self. By default build calls new with the
71
80
  # configurations specified by spec['config'], and app.
72
- def build(spec={}, app=Tap::App.instance)
81
+ def build(spec={}, app=Tap::App.current)
73
82
  new(spec['config'] || {}, app)
74
83
  end
75
84
 
@@ -85,17 +94,23 @@ module Tap
85
94
 
86
95
  lines.join("\n")
87
96
  end
97
+
98
+ protected
99
+
100
+ def convert_to_spec(parser, args)
101
+ {'config' => parser.nested_config}
102
+ end
88
103
  end
89
104
 
90
105
  include Configurable
91
106
  include Signals
92
107
 
93
- signal :help, :class => Help, :bind => nil # signals help
108
+ define_signal :help, Help # signals help
94
109
 
95
- # The App receiving self during enq
110
+ # The app for self
96
111
  attr_reader :app
97
112
 
98
- def initialize(config={}, app=Tap::App.instance)
113
+ def initialize(config={}, app=Tap::App.current)
99
114
  @app = app
100
115
  initialize_config(config)
101
116
  end
@@ -108,7 +123,13 @@ module Tap
108
123
  # config is a stringified representation of the configurations for self.
109
124
  def to_spec
110
125
  config = self.config.to_hash {|hash, key, value| hash[key.to_s] = value }
111
- {'config' => config}
126
+ config.empty? ? {} : {'config' => config}
127
+ end
128
+
129
+ # Provides an abbreviated version of the default inspect, with only the
130
+ # class, object_id, and configurations listed.
131
+ def inspect
132
+ "#<#{self.class.to_s}:#{object_id} #{config.to_hash.inspect} >"
112
133
  end
113
134
  end
114
135
  end
@@ -3,16 +3,16 @@ require 'monitor'
3
3
  module Tap
4
4
  class App
5
5
 
6
- # Queue allows thread-safe enqueing and dequeing of nodes and inputs for
6
+ # Queue allows thread-safe enqueing and dequeing of tasks and inputs for
7
7
  # execution.
8
8
  #
9
9
  # === API
10
10
  #
11
11
  # The following methods are required in alternative implementations of an
12
- # applicaton queue, where a job is a [node, inputs] array:
12
+ # applicaton queue, where a job is a [task, input] array:
13
13
  #
14
- # enq(node, inputs) # pushes the job onto the queue
15
- # unshift(node, inputs) # unshifts the job onto the queue
14
+ # enq(task, input) # pushes the job onto the queue
15
+ # unshift(task, input) # unshifts the job onto the queue
16
16
  # deq # shifts a job off the queue
17
17
  # size # returns the number of jobs in the queue
18
18
  # clear # clears the queue, returns current jobs
@@ -29,22 +29,21 @@ module Tap
29
29
  @queue = []
30
30
  end
31
31
 
32
- # Enqueues the node and inputs as a job.
33
- def enq(node, inputs)
32
+ # Enqueues the task and input.
33
+ def enq(task, input)
34
34
  synchronize do
35
- @queue.push [node, inputs]
35
+ @queue.push [task, input]
36
36
  end
37
37
  end
38
38
 
39
- # Enqueues the node and inputs, but to the top of the queue.
40
- def unshift(node, inputs)
39
+ # Enqueues the task and input, but to the top of the queue.
40
+ def unshift(task, input)
41
41
  synchronize do
42
- @queue.unshift [node, inputs]
42
+ @queue.unshift [task, input]
43
43
  end
44
44
  end
45
45
 
46
- # Dequeues the next job as an array like [node, inputs]. Returns nil if
47
- # the queue is empty.
46
+ # Dequeues the next job. Returns nil if the queue is empty.
48
47
  def deq
49
48
  synchronize { @queue.shift }
50
49
  end
@@ -11,13 +11,13 @@ module Tap
11
11
  @app = app
12
12
  end
13
13
 
14
- # Checks app for termination and then calls the node with the inputs:
14
+ # Checks app for termination and then calls the task with the input:
15
15
  #
16
- # node.call(*inputs)
16
+ # task.call(input)
17
17
  #
18
- def call(node, inputs)
18
+ def call(task, input)
19
19
  app.check_terminate
20
- node.call(*inputs)
20
+ task.call(input)
21
21
  end
22
22
  end
23
23
  end
@@ -0,0 +1,200 @@
1
+ require 'tap/join'
2
+ require 'tap/workflow'
3
+ require 'tap/declarations/description'
4
+ require 'tap/declarations/context'
5
+ require 'tap/parser'
6
+ require 'tap/tasks/singleton'
7
+
8
+ module Tap
9
+ module Declarations
10
+ def env
11
+ app.env
12
+ end
13
+
14
+ # Returns a new node that executes block on call.
15
+ def node(var=nil, &node) # :yields: *args
16
+ def node.joins; @joins ||= []; end
17
+ app.set(var, node) if var
18
+ node
19
+ end
20
+
21
+ # Generates a join between the inputs and outputs. Join resolves the
22
+ # class using env and initializes a new instance with the configs and
23
+ # self.
24
+ def join(inputs, outputs, config={}, clas=Tap::Join, &block)
25
+ inputs = [inputs] unless inputs.kind_of?(Array)
26
+ outputs = [outputs] unless outputs.kind_of?(Array)
27
+
28
+ obj = app.init(clas, config, app)
29
+ obj.join(inputs, outputs, &block)
30
+ obj
31
+ end
32
+
33
+ # Sets the description for use by the next task declaration.
34
+ def desc(str)
35
+ @desc = Lazydoc.register_caller(Description)
36
+ @desc.desc = str
37
+ @desc
38
+ end
39
+
40
+ def singleton(&block)
41
+ baseclass(Tap::Tasks::Singleton, &block)
42
+ end
43
+
44
+ def baseclass(baseclass=Tap::Task)
45
+ current = @baseclass
46
+ begin
47
+ @baseclass = env.constant(baseclass) unless baseclass.nil?
48
+ yield if block_given?
49
+ ensure
50
+ @baseclass = current if block_given?
51
+ end
52
+ end
53
+
54
+ # Nests tasks within the named module for the duration of the block.
55
+ # Namespaces may be nested.
56
+ def namespace(namespace)
57
+ current = @namespace
58
+ begin
59
+ unless namespace.nil? || namespace.kind_of?(Module)
60
+ const_name = namespace.to_s.camelize
61
+ unless current.const_defined?(const_name)
62
+ current.const_set(const_name, Module.new)
63
+ end
64
+ namespace = current.const_get(const_name)
65
+ end
66
+
67
+ @namespace = namespace unless namespace.nil?
68
+ yield if block_given?
69
+ ensure
70
+ @namespace = current if block_given?
71
+ end
72
+ end
73
+
74
+ def declare(baseclass, const_name, configs={}, &block)
75
+ const_name = const_name.to_s.camelize
76
+ subclass = Class.new(env.constant(baseclass))
77
+ @namespace.const_set(const_name, subclass)
78
+
79
+ # define configs
80
+ configs.each_pair do |key, value|
81
+ # specifying a desc prevents lazydoc registration of these lines
82
+ opts = {:desc => ""}
83
+ opts[:short] = key if key.to_s.length == 1
84
+ config_block = Configurable::Validation.guess(value)
85
+ subclass.send(:config, key, value, opts, &config_block)
86
+ end
87
+
88
+ # define process
89
+ if block
90
+ # determine arity, correcting for the self arg
91
+ arity = block.arity
92
+ arity -= arity > 0 ? 1 : -1
93
+ signature = Array.new(arity < 0 ? arity.abs - 1 : arity, 'arg')
94
+ signature << '*args' if arity < 0
95
+
96
+ # prevents assessment of process args by lazydoc
97
+ subclass.const_attrs[:process] = signature.join(' ')
98
+ subclass.send(:define_method, :process) do |*args|
99
+ block.call(self, *args)
100
+ end
101
+ end
102
+
103
+ # register documentation
104
+ constant = env.set(subclass, nil)
105
+
106
+ if @desc
107
+ subclass.desc = @desc
108
+ constant.register_as(subclass.type, @desc)
109
+ @desc = nil
110
+ end
111
+
112
+ subclass
113
+ end
114
+
115
+ def task(const_name, configs={}, baseclass=@baseclass, &block)
116
+ @desc ||= Lazydoc.register_caller(Description)
117
+ const_name, prerequisites = parse_prerequisites(const_name)
118
+
119
+ if prerequisites.nil?
120
+ return declare(baseclass, const_name, configs, &block)
121
+ end
122
+
123
+ tasc = declare(Tap::Workflow, const_name, configs) do |workflow|
124
+ psr = Parser.new
125
+ args = psr.parse!(prerequisites)
126
+ warn "ignoring args: #{args.inspect}" unless args.empty?
127
+ psr.build_to(app)
128
+
129
+ obj = init("#{const_name.to_s.underscore}/task", workflow.config.to_hash)
130
+ setup = lambda {|input| exe(obj, input) }
131
+
132
+ [setup, obj]
133
+ end
134
+
135
+ namespace(const_name) do
136
+ declare(baseclass, 'Task', configs, &block)
137
+ end
138
+
139
+ tasc
140
+ end
141
+
142
+ def work(const_name, definition, configs={}, baseclass=Tap::Workflow, &block)
143
+ unless definition.kind_of?(String)
144
+ raise "workflow definition must be a string: #{definition.inspect}"
145
+ end
146
+
147
+ @desc ||= Lazydoc.register_caller(Description)
148
+ block ||= lambda {|config| node(0) }
149
+ task({const_name => definition}, configs, baseclass, &block)
150
+ end
151
+
152
+ protected
153
+
154
+ def initialize_declare(baseclass=Tap::Task, namespace=Object)
155
+ @desc = nil
156
+ @baseclass = baseclass
157
+ @namespace = namespace
158
+ end
159
+
160
+ private
161
+
162
+ def parse_prerequisites(const_name)
163
+ prerequisites = nil
164
+
165
+ if const_name.is_a?(Hash)
166
+ hash = const_name
167
+ case hash.length
168
+ when 0
169
+ const_name = nil
170
+ when 1
171
+ const_name = hash.keys[0]
172
+ prerequisites = hash[const_name]
173
+ else
174
+ raise ArgumentError, "multiple task names specified: #{hash.keys.inspect}"
175
+ end
176
+ end
177
+
178
+ if const_name.nil?
179
+ raise ArgumentError, "no constant name specified"
180
+ end
181
+
182
+ case prerequisites
183
+ when nil
184
+ when String
185
+ prerequisites = Utils.shellsplit(prerequisites)
186
+ when Array
187
+ argv = []
188
+ prerequisites.each do |prereq|
189
+ argv << '-!'
190
+ argv << prereq.to_s
191
+ end
192
+ prerequisites = argv
193
+ else
194
+ prerequisites = ['-!', prerequisites.to_s]
195
+ end
196
+
197
+ [const_name, prerequisites]
198
+ end
199
+ end
200
+ end
@@ -0,0 +1,31 @@
1
+ module Tap
2
+ module Declarations
3
+ class Context
4
+ include Declarations
5
+ include Tap::Utils
6
+
7
+ attr_reader :app
8
+
9
+ def initialize(app, ns=nil)
10
+ @app = app
11
+ initialize_declare
12
+ namespace(ns)
13
+ end
14
+
15
+ # Runs the command with system and raises an error if the command
16
+ # fails.
17
+ def sh(*cmd)
18
+ app.log :sh, cmd.join(' ')
19
+ system(*cmd) or raise "Command failed with status (#{$?.exitstatus}): [#{cmd.join(' ')}]"
20
+ end
21
+
22
+ def node(num)
23
+ app.get(num.to_s)
24
+ end
25
+
26
+ def method_missing(sym, *args, &block)
27
+ app.send(sym, *args, &block)
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,33 @@
1
+ module Tap
2
+ module Declarations
3
+ # :startdoc:::-
4
+ # A special type of Lazydoc::Comment designed to handle the comment syntax
5
+ # for task declarations.
6
+ #
7
+ # Description instances can be assigned a description, or they may parse
8
+ # one directly from the comment. Comment lines with the constant attribute
9
+ # '::' will have the value set as desc.
10
+ # :startdoc:::+
11
+ class Description < Lazydoc::Comment
12
+
13
+ # The description for self.
14
+ attr_accessor :desc
15
+
16
+ # Parses in-comment descriptions from prepended lines, if present.
17
+ def prepend(line)
18
+ if line =~ /\s::(?:\s+(.*?)\s*)?$/
19
+ self.desc = $1.to_s
20
+ false
21
+ else
22
+ super
23
+ end
24
+ end
25
+
26
+ # Resolves and returns the description.
27
+ def to_s
28
+ resolve
29
+ desc.to_s
30
+ end
31
+ end
32
+ end
33
+ end