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,84 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # usage: tap [workflow] [--- tapfile] [-d-]
4
+ #
5
+ # workflow:
6
+ # [break] [left:]right [ARGS...] Constants are identified by matching the
7
+ # left and right paths; all subsequent args
8
+ # example: are passed to the constant. Breaks delimit
9
+ # tap:load -> Tap::Tasks::Load and the syntax repeats.
10
+ #
11
+ # breaks:
12
+ # - Delimiter, separates object argvs
13
+ # -- Delimits argvs and enques the next object
14
+ # -:[qai][.class] Sequence - joins previous and next objects
15
+ # -/obj[/sig] Signal - enques signal with argv
16
+ # --/obj[/sig] Signal - executes signal with argv
17
+ # -@ obj Enque - enques obj with argv
18
+ # -! obj Execute - executes obj with argv
19
+ # -. .- Escape begin/end
20
+ # --- End workflow
21
+ #
22
+ # env:
23
+ # TAP_GEMS Gem patterns to match and register
24
+ # TAP_PATH Directory paths to register
25
+ # TAPENV Signal files for env
26
+ # TAPRC Signal files for app
27
+ # TAPFILE Declaration files
28
+ #
29
+ # examples:
30
+ # tap load str -: dump A simple sequence workflow
31
+ # tap load str - dump - join 0 1 Manually build the join
32
+ # tap --/use debugger -d- Debugging utilities
33
+ # tap prompt Open a prompt
34
+ # tap list List resources
35
+ #
36
+
37
+ require 'tap'
38
+ require 'tap/parser'
39
+
40
+ begin
41
+ options = Tap.options
42
+
43
+ if ARGV[-1] == '-d-'
44
+ ARGV.pop
45
+ options[:debug] = 'true'
46
+ end
47
+
48
+ if ARGV == ['--help']
49
+ puts Lazydoc.usage(__FILE__)
50
+ puts "\nversion #{Tap::VERSION} -- #{Tap::WEBSITE}"
51
+ exit(0)
52
+ end
53
+
54
+ app = Tap.setup(options)
55
+ parser = Tap::Parser.new
56
+
57
+ loop do
58
+ break if ARGV.empty?
59
+ parser.parse!(ARGV)
60
+ parser.build_to(app)
61
+
62
+ break if ARGV.empty?
63
+ config_parser = ConfigParser.new(app.config,
64
+ :option_break => Tap::Parser::BREAK,
65
+ :keep_break => true,
66
+ :clear_config => false,
67
+ :add_defaults => false)
68
+ config_parser.add(app.class.configurations)
69
+
70
+ config_parser.scan(ARGV) do |file|
71
+ app.call('sig' => 'load', 'args' => [file])
72
+ end
73
+ end
74
+
75
+ app.run
76
+ rescue
77
+ if $DEBUG || options[:debug]
78
+ raise $!
79
+ end
80
+ $stderr.puts $!.message
81
+ exit(1)
82
+ end
83
+
84
+ exit(0)
data/doc/API CHANGED
@@ -1,70 +1,71 @@
1
- = Application Programming Interface
1
+ = Application Programming Interfaces
2
2
 
3
- Applications require the following methods for nodes, joins, and middleware.
4
- Tap provides modules and base classes that implement these APIs.
3
+ All of the objects used by tap have two APIs -- one regarding the specific
4
+ type of object (ex a task, a join, or middleware), and another regarding how
5
+ applications find and use the objects. These APIs allow users to make custom
6
+ objects.
5
7
 
6
- === {Node}[link:classes/Tap/App/Node.html] ({Task}[link:classes/Tap/Task.html])
8
+ == Object Interface
7
9
 
8
- call(*inputs) # any return is allowed
9
- joins() # returns an array of joins, or nil (optional)
10
+ Applications require the following methods for tasks, joins, and middleware.
11
+ Tap provides base classes that implement these APIs. Objects satisfying the
12
+ task, join, or middleware API are referred to as workflow objects.
10
13
 
11
- The signature for call defines the arguments that must be enqued to the node
12
- or passed to the node via a join. All signature constructs are allowed
13
- including multiple arguments, default arguments and splats (blocks are
14
- technically allowed but will never receive a value).
14
+ === {Task}[link:classes/Tap/Task.html]
15
15
 
16
- The optional joins method specifies an array of joins to be called by a
17
- running application when the node completes. Each join is called in order. An
18
- empty array specifies the default application joins should be called; nil
19
- specifies that no joins should be called. No joins will be called when joins
20
- is left undefined.
16
+ call(input) # any return is allowed
17
+ joins() # returns an array of joins, or nil (optional)
18
+
19
+ The call method takes an object and returns an object. There are no formal
20
+ constraints for the input/output objects. The optional joins method returns an
21
+ array of joins to be called when the task completes, or nil if no joins are to
22
+ be called. Each join is called in order.
21
23
 
22
24
  === {Join}[link:classes/Tap/Join.html]
23
25
 
24
- call(result) # any return is allowed
26
+ call(output) # any return is allowed
25
27
 
26
- The call method receives the result of input nodes. The result of call is not
27
- used; call must internally performing the join actions.
28
+ The call method receives the output of a task. The result of call is not used;
29
+ call internally performs the join actions.
28
30
 
29
31
  === {Middleware}[link:classes/Tap/Middleware.html]
30
32
 
31
33
  Middleware.new(stack, *args) # returns an instance of middleware
32
- call(node, inputs=[]) # any return is allowed
34
+ call(node, input) # return is the task output
33
35
  stack() # returns the original stack
34
36
 
35
- Middleware wraps the execution of nodes. Nodes and inputs are passed to the
36
- middleware during execution; the middleware is responsible for processing the
37
- node or passing it into the stack using the same call API. By default the base
38
- stack invokes call on the node with the inputs. Joins are performed after the
39
- middleware returns.
37
+ Middleware wraps the execution of tasks. Tasks and inputs are passed to the
38
+ middleware during execution. Joins are performed with the call output.
39
+
40
+ == {Application Interface}[link:classes/Tap/App/Api.html]
41
+
42
+ Tap defines an interface allowing applications to create and manage objects
43
+ using signals. The application interface is distinct from the workflow APIs,
44
+ although typically one will be implemented on top of the other. Objects
45
+ satisfying the application interface are referred to as application objects.
40
46
 
41
- == Application Interface
47
+ The application interface allows instantiation of a class from a hash and
48
+ serialization of an instance back into a hash. The hash is referred to as a
49
+ specification and must be serializable as {JSON}[http://json.org/] (meaning
50
+ the hash must consist of simple object types: numbers, strings, hashes, and
51
+ arrays).
42
52
 
43
- In addition to the APIs for individual workflow objects, Tap defines an
44
- application interface allowing objects to be created and modified using
45
- signals sent to an application (think HTTP to a web app). Signals are simple
46
- hash constructs and typically form the basis for user interfaces. The
47
- application interface is distinct from the object APIs, although it is typical
48
- to implement the application interface on top of an object API.
53
+ The application interface consists of two methods:
49
54
 
50
- Tap::App::Api implements the application interface in a general way and is the
51
- baseclass for Tap::Task, Tap::Join, and Tap::Middleware. The basic idea is to
52
- allow instantiation of a class from a hash and serialization of an instance
53
- back into a hash. The hash is referred to as a specification and must be
54
- serializable as {JSON}[http://json.org/], basically meaning the hash must
55
- consist of simple object types: numbers, strings, hashes, and arrays.
55
+ Const.build(spec, app) # returns an instance of self
56
+ to_spec # returns a spec
56
57
 
57
- The application interface consists of two methods, build and to_spec:
58
+ As an example:
58
59
 
59
- class Stub
60
+ class Example
60
61
  class << self
61
- # Build takes a specification hash and returns an instance of self.
62
+ # Build takes a specification and returns an instance of self.
62
63
  # The spec must be serializable as JSON.
63
- def build(spec={}, app=Tap::App.instance)
64
+ def build(spec={}, app=Tap::App.current)
64
65
  end
65
66
  end
66
67
 
67
- # Takes no inputs and returns a specification hash that, when built,
68
+ # Takes no inputs and returns a specification that, when built,
68
69
  # returns an object like self.
69
70
  #
70
71
  # obj.class.build(obj.to_spec) # => returns an object like obj
@@ -74,22 +75,29 @@ The application interface consists of two methods, build and to_spec:
74
75
  end
75
76
  end
76
77
 
77
- The application API reserves several additional methods that do not need to be
78
- implemented but add functionality for specific, common use cases. If they are
79
- present they must adhere to these specifications.
78
+ The application interface reserves several additional methods that do not need
79
+ to be implemented but add functionality for specific, common use cases. These
80
+ are:
81
+
82
+ Const.parse(argv, app) # returns an instance of self, cannot modify ARGV
83
+ Const.parse!(argv, app) # same as parse but can modify ARGV
84
+ signal(sig) # returns an object responding to call
85
+ associations # returns an array like [refs, brefs]
86
+
87
+ If present they must do the following:
80
88
 
81
- # Optional methods #
82
- class Stub
89
+ class Example
83
90
  class << self
84
- # Parse takes an argument vector (an array, usually from the command
85
- # line) and returns an instance of self and any remaining arguments
86
- # in an array like [instance, args]. The remaining arguments may be
87
- # nil. Parse cannot modify argv.
88
- def parse(argv=ARGV, app=Tap::App.instance)
91
+ # Takes an argument vector (an array, usually from the command line)
92
+ # and returns an instance of self. If a block is given, parse
93
+ # yields the instance and remaining args and returns block result.
94
+ #
95
+ # Parse should not modify argv.
96
+ def parse(argv=ARGV, app=Tap::App.current)
89
97
  end
90
98
 
91
99
  # Same as parse, but able to modify argv.
92
- def parse!(argv=ARGV, app=Tap::App.instance)
100
+ def parse!(argv=ARGV, app=Tap::App.current)
93
101
  end
94
102
  end
95
103
 
@@ -98,18 +106,17 @@ present they must adhere to these specifications.
98
106
  def signal(sig)
99
107
  end
100
108
 
101
- # Returns a nested array of workflow objects associated with self (ex
102
- # input/output nodes for a join). The array should be structured like
103
- # [refs, brefs], where refs are references to objects that must be built
104
- # BEFORE self and brefs are back-references to objects that must be built
105
- # AFTER self.
109
+ # Returns a nested array of application objects associated with self.
110
+ # The array should be structured like [refs, brefs], where refs are
111
+ # references to objects that must be built BEFORE self and brefs are
112
+ # back-references to objects that must be built AFTER self.
106
113
  #
107
- # For example, nodes must be built before joins. As such, the associations
108
- # method for a node returns a brefs for each of its joins. Similarly, joins
109
- # must be built after nodes and hence the associations method for a join
110
- # returns refs to their input and output nodes:
114
+ # For example, tasks must be built before joins. As such, the associations
115
+ # method for a task returns a brefs for each of its joins. Similarly, joins
116
+ # must be built after tasks and hence the associations method for a join
117
+ # returns refs to their input and output tasks:
111
118
  #
112
- # node.associations # => [nil, join]
119
+ # task.associations # => [nil, join]
113
120
  # join.associations # => [inputs + outputs, nil]
114
121
  #
115
122
  # Nil is a valid return for associations, indicating no associations.
@@ -118,7 +125,7 @@ present they must adhere to these specifications.
118
125
  end
119
126
 
120
127
  The parse methods are used for building objects from interfaces that provide
121
- an array of inputs (ex the command line) rather a hash; without them objects
128
+ an array of inputs rather a hash (ex the command line); without them objects
122
129
  are effectively excluded from use within these interfaces.
123
130
 
124
131
  Signals can be used to interact with specific objects from a user interface
@@ -128,96 +135,41 @@ cannot receive signals.
128
135
  The associations method is used to order complex builds and is described in
129
136
  more detail below.
130
137
 
131
- === Spec References
138
+ === Object References
132
139
 
133
- Specifications often require references to other resources, as when a join
134
- refers to input and output nodes. These references are normally specified as
135
- variables that, unlike the resource itself, are easily serializable as JSON
136
- and may be used in multiple places. Apps are constructed to do this easily via
137
- the +obj+ and +var+ methods.
140
+ Specifications often require references to other application objects, as when
141
+ a join refers to input and output tasks. These references are normally
142
+ specified as variables that, unlike the object itself, are serializable as
143
+ JSON. Apps manage variables via the +obj+ and +var+ methods.
138
144
 
139
- As an example, consider the Sample class that references some other
140
- application object:
145
+ As an example:
141
146
 
142
- class Sample
143
- def initialize(object)
144
- @object = object
147
+ class A
148
+ def initialize(b)
149
+ @b = b
145
150
  end
146
151
 
147
152
  def to_spec
148
- {'key' => app.var(@object)} # store a variable into the spec
153
+ {'b' => app.var(@b)} # store a variable into the spec
149
154
  end
150
155
 
151
156
  def associations
152
- [[@object], nil] # establish a build order
157
+ [[@b], nil] # establish a build order
153
158
  end
154
159
 
155
160
  class << self
156
- def build(spec={}, app=Tap::App.instance)
157
- object = app.obj(spec['key']) # retrieve an object referenced by the spec
158
- new(object)
161
+ def build(spec={}, app=Tap::App.current)
162
+ b = app.obj(spec['b']) # retrieve an object referenced by the spec
163
+ new(b)
159
164
  end
160
165
  end
161
166
  end
162
167
 
163
- Using this technique the spec will have a serializable variable representing
164
- the object and the app will be able to properly schematize and rebuild the
165
- instance and all its references. Apps use the associations array to determine
166
- the correct build order for the references. In the example the @object
167
- reference must be built before the Sample instance and correspondingly the
168
- associations method returns @object in the 'ref' array.
168
+ The to_spec method records a variable identifying @b, rather than @b itself,
169
+ which allows the spec to be properly serialized. Likewise the build method
170
+ de-references the variable to retrieve b when initializing a new instance; the
171
+ associations array is used to ensure that b is built by the time A.build tries
172
+ to de-reference the variable.
169
173
 
170
174
  Note that only references to objects implementing the application interface
171
- may be stored this way; references to objects that do not implement the
172
- application interface must be serialized and deserialized by the build/to_spec
173
- methods internally.
174
-
175
- === Resource Identifiers
176
-
177
- Tap discovers application resources using resource identifiers (ie constant
178
- attributes, see {Lazydoc}[http://tap.rubyforge.org/lazydoc]). Resources
179
- identified in this way can be automatically loaded by the Tap::Env. If no
180
- identifiers are specified for a resource, the user must manually load the
181
- resource files.
182
-
183
- As an example, this identifies the Sample constant as an 'example' resource.
184
-
185
- [lib/file.rb]
186
-
187
- # Sample::example summary
188
- class Sample
189
- end
190
-
191
- A resource can be identified by zero or more identifiers. Typically all
192
- identifiers will be put in the same file as the class, but this does not have
193
- to be the case; applications automatically require all files that identify a
194
- resource. The order in which the files are required is indeterminate and it is
195
- up to the user to ensure consistency. For example:
196
-
197
- [a.rb]
198
- # Sample::a
199
- class Sample
200
- end
201
-
202
- [b.rb]
203
- require 'a'
204
-
205
- # Sample::b
206
- class Sample
207
- end
208
-
209
- Here the require statement ensures a.rb is always required before b.rb. Note
210
- that consistency is automatic when all identifiers are in the same file (and
211
- thus only one file is required).
212
-
213
- The constant name will be inferred from file path if no constant name is
214
- specified. This is the most compact form for identifying a resource:
215
-
216
- [lib/sample.rb]
217
-
218
- # ::example summary
219
- class Sample
220
- end
221
-
222
- In this case no constant name is specified, so 'Sample' is inferred from
223
- 'sample.rb'. Constant names are determined from the path using camelization.
175
+ should be stored this way.
@@ -0,0 +1,93 @@
1
+ = Configuration
2
+
3
+ Configuring tap is a matter of setting ENV variables that tell the executable
4
+ what to make available to workflows. Each of the ENV variables can be treated
5
+ like PATH, where multiple paths may be joined by a colon. The config files
6
+ specified in the ENV are handled in order as listed, before the command line
7
+ workflow is parsed.
8
+
9
+ === TAP_GEMS (default '.')
10
+
11
+ Specifies globs of gems to automatically load into the environment. Versions
12
+ can be specified as normal, separated by a comma. Use an empty string to
13
+ specify no gems.
14
+
15
+ % gem install tap-tasks
16
+ % TAP_GEMS=. tap inspect a b c
17
+ ["a", "b", "c"]
18
+ % TAP_GEMS=tap-ta* tap inspect a b c
19
+ ["a", "b", "c"]
20
+ % TAP_GEMS='tap-tasks, >= 0.6.0' tap inspect a b c
21
+ ["a", "b", "c"]
22
+ % TAP_GEMS=nomatch tap inspect a b c
23
+ unresolvable constant: "inspect"
24
+ % TAP_GEMS= tap inspect a b c
25
+ unresolvable constant: "inspect"
26
+
27
+ Note that all matching gems will be activated when tap launches.
28
+
29
+ === TAP_PATH (default '.')
30
+
31
+ Specifies directories to be scanned and registered with the tap env. All files
32
+ matching TAP_PATH/lib/**/*.rb will be scanned for constants; TAP_PATH itself
33
+ will be registered as a path in env. If TAP_PATH/tap.yml exists, it will be
34
+ loaded as a map of paths.
35
+
36
+ As a shorthand, just know that any constants under the lib directory of
37
+ TAP_PATH will be discovered.
38
+
39
+ [dir/lib/goodnight.rb]
40
+ require 'tap/task'
41
+
42
+ # ::task
43
+ class Goodnight < Tap::Task
44
+ def process(input)
45
+ puts "goodnight #{input}"
46
+ end
47
+ end
48
+
49
+ % tap goodnight moon
50
+ unresolvable constant: "goodnight"
51
+ % TAP_PATH=dir tap goodnight moon
52
+ goodnight moon
53
+
54
+ === TAPENV (default 'tapenv')
55
+
56
+ Specifies signal files to be loaded in the env signaling context. These files
57
+ can be used to manually adjust an environment by setting/unsetting constants
58
+ and resource paths.
59
+
60
+ [tapenv]
61
+ unset Tap::Tasks::Dump
62
+
63
+ % tap load a -: dump
64
+ unresolvable constant: "dump"
65
+
66
+ === TAPRC (default '~/.taprc:taprc')
67
+
68
+ Specifies signal files to be loaded in the app signaling context. These files
69
+ can be used to manually build workflows, or configure the app.
70
+
71
+ [taprc]
72
+ set loader load
73
+ set dumper dump
74
+
75
+ % tap - join loader dumper -/enq loader 'goodnight moon'
76
+ goodnight moon
77
+
78
+ === TAPFILE (default 'tapfile')
79
+
80
+ Specifies ruby files that will be executed in the app context (ie using the
81
+ binding of the app instance). Tapfiles can be used to declare tasks, typically
82
+ using the Tap::Declarations module, or to manually setup workflows.
83
+
84
+ [tapfile]
85
+ require 'tap/declarations'
86
+ extend Tap::Declarations
87
+
88
+ task :goodnight do |config, args|
89
+ "Goodnight #{args}!"
90
+ end
91
+
92
+ % tap goodnight Moon -: dump
93
+ Goodnight Moon!