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