tap 0.19.0 → 1.3.0
Sign up to get free protection for your applications and to get access to all the features.
- data/History +100 -45
- data/MIT-LICENSE +1 -1
- data/README +95 -51
- data/bin/tap +11 -57
- data/bin/tapexe +84 -0
- data/doc/API +91 -139
- data/doc/Configuration +93 -0
- data/doc/Examples/Command Line +10 -42
- data/doc/Examples/Tapfile +124 -0
- data/doc/Ruby to Ruby +87 -0
- data/doc/Workflow Syntax +185 -0
- data/lib/tap.rb +74 -5
- data/lib/tap/app.rb +217 -310
- data/lib/tap/app/api.rb +44 -23
- data/lib/tap/app/queue.rb +11 -12
- data/lib/tap/app/stack.rb +4 -4
- data/lib/tap/declarations.rb +200 -0
- data/lib/tap/declarations/context.rb +31 -0
- data/lib/tap/declarations/description.rb +33 -0
- data/lib/tap/env.rb +133 -779
- data/lib/tap/env/cache.rb +87 -0
- data/lib/tap/env/constant.rb +94 -39
- data/lib/tap/env/path.rb +71 -0
- data/lib/tap/join.rb +42 -78
- data/lib/tap/joins/gate.rb +85 -0
- data/lib/tap/joins/switch.rb +4 -2
- data/lib/tap/joins/sync.rb +3 -3
- data/lib/tap/middleware.rb +5 -5
- data/lib/tap/middlewares/debugger.rb +18 -58
- data/lib/tap/parser.rb +115 -183
- data/lib/tap/root.rb +162 -239
- data/lib/tap/signal.rb +72 -0
- data/lib/tap/signals.rb +20 -2
- data/lib/tap/signals/class_methods.rb +38 -43
- data/lib/tap/signals/configure.rb +19 -0
- data/lib/tap/signals/help.rb +5 -7
- data/lib/tap/signals/load.rb +49 -0
- data/lib/tap/signals/module_methods.rb +1 -0
- data/lib/tap/task.rb +46 -275
- data/lib/tap/tasks/dump.rb +21 -16
- data/lib/tap/tasks/list.rb +184 -0
- data/lib/tap/tasks/load.rb +4 -4
- data/lib/tap/tasks/prompt.rb +128 -0
- data/lib/tap/tasks/signal.rb +42 -0
- data/lib/tap/tasks/singleton.rb +35 -0
- data/lib/tap/tasks/stream.rb +64 -0
- data/lib/tap/utils.rb +83 -0
- data/lib/tap/version.rb +2 -2
- data/lib/tap/workflow.rb +124 -0
- data/tap.yml +0 -0
- metadata +59 -24
- data/cmd/console.rb +0 -43
- data/cmd/manifest.rb +0 -118
- data/cmd/run.rb +0 -145
- data/doc/Examples/Workflow +0 -40
- data/lib/tap/app/node.rb +0 -29
- data/lib/tap/env/context.rb +0 -61
- data/lib/tap/env/gems.rb +0 -63
- data/lib/tap/env/manifest.rb +0 -179
- data/lib/tap/env/minimap.rb +0 -308
- data/lib/tap/intern.rb +0 -50
- data/lib/tap/joins.rb +0 -9
- data/lib/tap/prompt.rb +0 -36
- data/lib/tap/root/utils.rb +0 -220
- data/lib/tap/root/versions.rb +0 -138
- data/lib/tap/signals/signal.rb +0 -68
data/bin/tapexe
ADDED
@@ -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
|
1
|
+
= Application Programming Interfaces
|
2
2
|
|
3
|
-
|
4
|
-
|
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
|
-
|
8
|
+
== Object Interface
|
7
9
|
|
8
|
-
|
9
|
-
|
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
|
-
|
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
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
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(
|
26
|
+
call(output) # any return is allowed
|
25
27
|
|
26
|
-
The call method receives the
|
27
|
-
|
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,
|
34
|
+
call(node, input) # return is the task output
|
33
35
|
stack() # returns the original stack
|
34
36
|
|
35
|
-
Middleware wraps the execution of
|
36
|
-
middleware during execution
|
37
|
-
|
38
|
-
|
39
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
51
|
-
|
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
|
-
|
58
|
+
As an example:
|
58
59
|
|
59
|
-
class
|
60
|
+
class Example
|
60
61
|
class << self
|
61
|
-
# Build takes a specification
|
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.
|
64
|
+
def build(spec={}, app=Tap::App.current)
|
64
65
|
end
|
65
66
|
end
|
66
67
|
|
67
|
-
# Takes no inputs and returns a specification
|
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
|
78
|
-
implemented but add functionality for specific, common use cases.
|
79
|
-
|
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
|
-
|
82
|
-
class Stub
|
89
|
+
class Example
|
83
90
|
class << self
|
84
|
-
#
|
85
|
-
#
|
86
|
-
#
|
87
|
-
#
|
88
|
-
|
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.
|
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
|
102
|
-
#
|
103
|
-
#
|
104
|
-
#
|
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,
|
108
|
-
# method for a
|
109
|
-
# must be built after
|
110
|
-
# returns refs to their input and output
|
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
|
-
#
|
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)
|
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
|
-
===
|
138
|
+
=== Object References
|
132
139
|
|
133
|
-
Specifications often require references to other
|
134
|
-
refers to input and output
|
135
|
-
variables that, unlike the
|
136
|
-
|
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
|
140
|
-
application object:
|
145
|
+
As an example:
|
141
146
|
|
142
|
-
class
|
143
|
-
def initialize(
|
144
|
-
@
|
147
|
+
class A
|
148
|
+
def initialize(b)
|
149
|
+
@b = b
|
145
150
|
end
|
146
151
|
|
147
152
|
def to_spec
|
148
|
-
{'
|
153
|
+
{'b' => app.var(@b)} # store a variable into the spec
|
149
154
|
end
|
150
155
|
|
151
156
|
def associations
|
152
|
-
[[@
|
157
|
+
[[@b], nil] # establish a build order
|
153
158
|
end
|
154
159
|
|
155
160
|
class << self
|
156
|
-
def build(spec={}, app=Tap::App.
|
157
|
-
|
158
|
-
new(
|
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
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
reference
|
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
|
-
|
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.
|
data/doc/Configuration
ADDED
@@ -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!
|