command-set 0.9.2 → 0.10.0
Sign up to get free protection for your applications and to get access to all the features.
- data/doc/Specifications +64 -17
- data/lib/command-set.rb +1 -1
- data/lib/command-set/arguments.rb +40 -36
- data/lib/command-set/command-set.rb +68 -31
- data/lib/command-set/command.rb +123 -53
- data/lib/command-set/dsl.rb +54 -45
- data/lib/command-set/formatter/base.rb +186 -0
- data/lib/command-set/formatter/strategy.rb +243 -0
- data/lib/command-set/formatter/xml.rb +56 -0
- data/lib/command-set/{interpreter.rb → interpreter/base.rb} +43 -36
- data/lib/command-set/{batch-interpreter.rb → interpreter/batch.rb} +0 -0
- data/lib/command-set/{quick-interpreter.rb → interpreter/quick.rb} +4 -4
- data/lib/command-set/{text-interpreter.rb → interpreter/text.rb} +29 -25
- data/lib/command-set/results.rb +4 -478
- data/lib/command-set/standard-commands.rb +2 -2
- data/lib/command-set/structural.rb +230 -0
- data/lib/command-set/subject.rb +153 -39
- metadata +13 -8
- data/lib/command-set/command-common.rb +0 -110
data/lib/command-set/command.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
require 'command-set/arguments'
|
2
|
-
require 'command-set/
|
2
|
+
require 'command-set/structural'
|
3
3
|
module Command
|
4
4
|
|
5
5
|
#A thin wrapper on Array to maintain undo/redo state.
|
@@ -33,6 +33,7 @@ module Command
|
|
33
33
|
end
|
34
34
|
end
|
35
35
|
|
36
|
+
=begin
|
36
37
|
#An abstraction of the lifecycle of a command. Allows invocations to be
|
37
38
|
#postponed temporarily, or for the command to be instantiated and still
|
38
39
|
#passed around. Client code should almost never need to see this class,
|
@@ -43,16 +44,25 @@ module Command
|
|
43
44
|
@args_hash = args
|
44
45
|
@terms = []
|
45
46
|
@command = cmd
|
47
|
+
@execution_context = nil
|
46
48
|
end
|
47
49
|
|
48
50
|
attr_accessor :task_id, :command, :args_hash, :terms
|
49
51
|
|
52
|
+
def class_resolution(command_set, path, subject)
|
53
|
+
return command_set.process_terms(path, subject)
|
54
|
+
end
|
55
|
+
|
50
56
|
def resolve_command_class(command_set, subject)
|
51
|
-
|
57
|
+
if @execution_context.nil? && Class === @command && Command > @command
|
58
|
+
@execution_context = TermProcessor.new(subject)
|
59
|
+
else
|
52
60
|
command_path = @command
|
53
|
-
|
54
|
-
|
55
|
-
|
61
|
+
@execution_context = class_resolution(command_set,
|
62
|
+
command_path,
|
63
|
+
subject)
|
64
|
+
@command = @execution_context.command_class
|
65
|
+
@args_hash = @execution_context.arg_hash.merge(@args_hash)
|
56
66
|
@terms = command_path
|
57
67
|
end
|
58
68
|
|
@@ -61,15 +71,29 @@ module Command
|
|
61
71
|
|
62
72
|
def command_instance(command_set, subject)
|
63
73
|
command_class = resolve_command_class(command_set, subject)
|
64
|
-
command = command_class.new(
|
74
|
+
command = command_class.new(@execution_context, task_id)
|
65
75
|
command.consume_hash(@args_hash)
|
66
76
|
return command
|
67
77
|
end
|
68
78
|
end
|
79
|
+
=end
|
80
|
+
|
81
|
+
class AnchoredCommandSetup < CommandSetup
|
82
|
+
def initialize(command_set, cmd = [], args = {})
|
83
|
+
super(cmd, args)
|
84
|
+
@anchored_at = command_set
|
85
|
+
end
|
86
|
+
|
87
|
+
attr_accessor :anchored_at
|
88
|
+
|
89
|
+
def class_resolution(current_set)
|
90
|
+
@command_class = @anchored_at.find_command(@terms.dup)
|
91
|
+
end
|
92
|
+
end
|
69
93
|
|
70
94
|
#An overworked exception class. It captures details about the command
|
71
95
|
#being interrupted as it propagates up the stack.
|
72
|
-
class ResumeFrom < ::Exception
|
96
|
+
class ResumeFrom < ::Exception
|
73
97
|
def initialize(pause_deck, msg = "")
|
74
98
|
super(msg)
|
75
99
|
@setup = CommandSetup.new
|
@@ -83,7 +107,7 @@ module Command
|
|
83
107
|
|
84
108
|
module CommandClassMethods
|
85
109
|
attr_reader :subject_requirements, :argument_list,
|
86
|
-
:advice_block, :
|
110
|
+
:advice_block, :context
|
87
111
|
|
88
112
|
def consume_terms(terms, subject, arg_hash)
|
89
113
|
begin
|
@@ -123,27 +147,22 @@ module Command
|
|
123
147
|
def optional_argument(arg, values=nil, &get_values)
|
124
148
|
optional.argument(arg, values, &get_values)
|
125
149
|
end
|
126
|
-
|
127
|
-
def factory
|
128
|
-
return self
|
129
|
-
end
|
130
150
|
end
|
131
151
|
|
132
152
|
class Command
|
133
153
|
@name = "unnamed command"
|
134
|
-
@parent = nil
|
135
154
|
@argument_list=[]
|
136
155
|
@parent_arguments=[]
|
137
156
|
@doc_text = nil
|
138
157
|
@subject_requirements=[:chain_of_command]
|
139
158
|
@defined = false
|
140
159
|
@advice_block = proc {}
|
160
|
+
@context = []
|
141
161
|
|
142
162
|
class << self
|
143
163
|
alias_method :instance, :new
|
144
164
|
|
145
165
|
attr_reader :name, :doc_text, :defined
|
146
|
-
attr_accessor :parent
|
147
166
|
|
148
167
|
def inherited(subclass)
|
149
168
|
[ [:name, proc {name.dup}],
|
@@ -153,6 +172,7 @@ module Command
|
|
153
172
|
[:subject_requirements, proc {subject_requirements.dup}],
|
154
173
|
[:defined, proc {false}],
|
155
174
|
[:advice_block, proc {advice_block.dup}],
|
175
|
+
[:context, proc {[]}]
|
156
176
|
].each do |var, prok|
|
157
177
|
subclass.instance_variable_set("@#{var.to_s}", prok.call)
|
158
178
|
end
|
@@ -163,6 +183,7 @@ module Command
|
|
163
183
|
@argument_list = original.argument_list.dup
|
164
184
|
@parent_arguments = original.parent_arguments.dup
|
165
185
|
@subject_requirements = original.subject_requirements.dup
|
186
|
+
@context = []
|
166
187
|
end
|
167
188
|
|
168
189
|
#Establishes a subclass of Command. This is important because commands are actually
|
@@ -172,17 +193,11 @@ module Command
|
|
172
193
|
#define the class completely.
|
173
194
|
#
|
174
195
|
#For examples, see Command::StandardCommands
|
175
|
-
def setup(
|
176
|
-
|
177
|
-
unless parent.nil? or CommandSet === parent
|
178
|
-
raise RuntimeError, "Command parents must be CommandSets or nil"
|
179
|
-
end
|
180
|
-
|
196
|
+
def setup(new_name=nil, &block)
|
181
197
|
command_class = Class.new(self)
|
182
198
|
new_name = new_name.to_s
|
183
199
|
|
184
200
|
command_class.instance_variable_set("@name", new_name)
|
185
|
-
command_class.instance_variable_set("@parent", parent)
|
186
201
|
|
187
202
|
command_class.instance_eval &block
|
188
203
|
|
@@ -190,12 +205,16 @@ module Command
|
|
190
205
|
return command_class
|
191
206
|
end
|
192
207
|
|
193
|
-
def each_command(
|
194
|
-
|
208
|
+
def each_command(path, visitor)
|
209
|
+
visitor.arrive_at(path, self)
|
210
|
+
end
|
211
|
+
|
212
|
+
def parent_argument_list
|
213
|
+
@parent_arguments
|
195
214
|
end
|
196
215
|
|
197
216
|
def visit(terms, visitor)
|
198
|
-
|
217
|
+
visitor.arrive_at(terms, self)
|
199
218
|
|
200
219
|
if(terms.empty?)
|
201
220
|
return visitor.command_out_of_terms(self)
|
@@ -204,6 +223,8 @@ module Command
|
|
204
223
|
end
|
205
224
|
end
|
206
225
|
|
226
|
+
alias root_visit visit
|
227
|
+
|
207
228
|
def single_consume(argument, arg_hash, subject, terms)
|
208
229
|
arg_hash.merge! argument.consume(subject, terms)
|
209
230
|
end
|
@@ -220,20 +241,20 @@ module Command
|
|
220
241
|
def inspect
|
221
242
|
arguments = argument_list.map{|arg| arg.name}
|
222
243
|
|
223
|
-
return "#<Class:#{"
|
244
|
+
return "#<Class:#{"%0#x" % self.object_id} - Command:#{name()}(#{arguments.join(", ")})>"
|
224
245
|
end
|
225
246
|
|
226
|
-
def documentation(width,
|
247
|
+
def documentation(width, prefix=[])
|
227
248
|
if @doc_text.nil?
|
228
|
-
return short_docs(width)
|
249
|
+
return short_docs(width, prefix)
|
229
250
|
else
|
230
251
|
return short_docs(width) +
|
231
252
|
["\n"] + @doc_text.wrap(width)
|
232
253
|
end
|
233
254
|
end
|
234
255
|
|
235
|
-
def short_docs(width,
|
236
|
-
docs = [
|
256
|
+
def short_docs(width, prefix=[])
|
257
|
+
docs = prefix + [name]
|
237
258
|
docs += argument_list.map do |arg|
|
238
259
|
if arg.required?
|
239
260
|
"<#{arg.name}>"
|
@@ -247,7 +268,7 @@ module Command
|
|
247
268
|
end
|
248
269
|
|
249
270
|
def create_argument_methods
|
250
|
-
names =
|
271
|
+
names = argument_list.inject([]) do |list, arg|
|
251
272
|
list + [*arg.names]
|
252
273
|
end
|
253
274
|
|
@@ -263,6 +284,11 @@ module Command
|
|
263
284
|
private(name.to_s + "=")
|
264
285
|
end
|
265
286
|
end
|
287
|
+
|
288
|
+
def push_context(part)
|
289
|
+
return unless Symbol === part
|
290
|
+
@context.unshift(part)
|
291
|
+
end
|
266
292
|
end
|
267
293
|
|
268
294
|
extend DSL::CommandDefinition
|
@@ -272,12 +298,19 @@ module Command
|
|
272
298
|
|
273
299
|
class DontResume; end
|
274
300
|
|
275
|
-
def initialize(
|
301
|
+
def initialize(execution_context, resume=nil)
|
276
302
|
raise CommandException, "#{@name}: unrecognized command" unless self.class.defined?
|
277
|
-
|
278
|
-
@
|
279
|
-
|
280
|
-
@
|
303
|
+
@path = execution_context.command_path
|
304
|
+
@nesting = execution_context.set_nesting
|
305
|
+
|
306
|
+
@argument_list = self.class.argument_list.dup
|
307
|
+
@subject_requirements = self.class.subject_requirements.dup
|
308
|
+
resolve_parent_arguments
|
309
|
+
|
310
|
+
subject = execution_context.subject
|
311
|
+
context = execution_context.subject_context
|
312
|
+
@subject_image = subject.get_image(subject_requirements || [], context)
|
313
|
+
|
281
314
|
@arg_hash = {}
|
282
315
|
@should_undo = true
|
283
316
|
@validation_problem = CommandException.new("No arguments provided!")
|
@@ -286,30 +319,67 @@ module Command
|
|
286
319
|
@main_collector = nil
|
287
320
|
end
|
288
321
|
|
289
|
-
|
322
|
+
def resolve_parent_arguments
|
323
|
+
missing = []
|
324
|
+
names = []
|
325
|
+
self.class.parent_argument_list.uniq.each do |name|
|
326
|
+
found = nil
|
327
|
+
@nesting.reverse.each do |set_nesting|
|
328
|
+
found = set_nesting.argument_list.find do |argument|
|
329
|
+
argument.names.include? name
|
330
|
+
end
|
331
|
+
|
332
|
+
unless found.nil?
|
333
|
+
@subject_requirements += found.subject_requirements
|
334
|
+
@argument_list << found
|
335
|
+
break
|
336
|
+
end
|
337
|
+
end
|
338
|
+
if found.nil?
|
339
|
+
missing << name
|
340
|
+
else
|
341
|
+
names += [*found.names]
|
342
|
+
end
|
343
|
+
end
|
344
|
+
|
345
|
+
unless missing.empty?
|
346
|
+
raise CommandError,
|
347
|
+
"No parent has an argument named \"#{missing.join(", ")}\""
|
348
|
+
end
|
349
|
+
|
350
|
+
names.each do |name|
|
351
|
+
(class << self; self; end).instance_eval do
|
352
|
+
define_method(name) do
|
353
|
+
@arg_hash[name]
|
354
|
+
end
|
355
|
+
private(name)
|
356
|
+
|
357
|
+
define_method(name.to_s + "=") do |value|
|
358
|
+
@arg_hash[name] = value
|
359
|
+
end
|
360
|
+
private(name.to_s + "=")
|
361
|
+
end
|
362
|
+
end
|
363
|
+
end
|
364
|
+
|
365
|
+
attr_reader :arg_hash, :path, :nesting
|
290
366
|
|
291
367
|
def name
|
292
368
|
self.class.name
|
293
369
|
end
|
294
370
|
|
295
371
|
def required_arguments
|
296
|
-
|
372
|
+
@argument_list.find_all do |arg|
|
297
373
|
arg.required?
|
298
374
|
end
|
299
375
|
end
|
300
376
|
|
301
|
-
def optional_arguments
|
302
|
-
self.class.argument_list.find_all do |arg|
|
303
|
-
not arg.required?
|
304
|
-
end
|
305
|
-
end
|
306
|
-
|
307
377
|
def all_arguments
|
308
|
-
|
378
|
+
@argument_list
|
309
379
|
end
|
310
380
|
|
311
381
|
def subject_requirements
|
312
|
-
|
382
|
+
@subject_requirements
|
313
383
|
end
|
314
384
|
|
315
385
|
def inspect
|
@@ -318,27 +388,27 @@ module Command
|
|
318
388
|
end
|
319
389
|
|
320
390
|
def parent
|
321
|
-
|
391
|
+
@nesting.last
|
322
392
|
end
|
323
393
|
|
324
394
|
def go(collector)
|
325
395
|
return if @resume_from == DontResume
|
326
396
|
unless self.respond_to? :execute
|
327
|
-
raise CommandException, "#{self.
|
397
|
+
raise CommandException, "#{self.path}: command declared but no action defined"
|
328
398
|
end
|
329
399
|
|
330
400
|
@main_collector = collector
|
331
401
|
begin
|
332
402
|
validate_arguments
|
333
|
-
|
403
|
+
verify_image
|
334
404
|
execute
|
335
405
|
join_undo
|
336
406
|
rescue Interrupt
|
337
407
|
puts "Command cancelled"
|
338
408
|
rescue ResumeFrom => rf
|
339
409
|
rf.setup.task_id = @last_completed_task
|
340
|
-
rf.setup.
|
341
|
-
rf.setup.
|
410
|
+
rf.setup.command_class = self.class
|
411
|
+
rf.setup.arg_hash = arg_hash.dup
|
342
412
|
raise rf
|
343
413
|
end
|
344
414
|
end
|
@@ -347,11 +417,11 @@ module Command
|
|
347
417
|
formatter.receive_advice(&self.class.advice_block)
|
348
418
|
end
|
349
419
|
|
350
|
-
def
|
420
|
+
def verify_image
|
351
421
|
return if subject_requirements.nil?
|
352
422
|
subject_requirements.each do |requirement|
|
353
423
|
begin
|
354
|
-
if Subject::UndefinedField === @
|
424
|
+
if Subject::UndefinedField === @subject_image.send(requirement)
|
355
425
|
raise CommandException, "\"#{name}\" requires \"#{requirement.to_s}\" to be set"
|
356
426
|
end
|
357
427
|
rescue NameError => ne
|
@@ -374,7 +444,7 @@ module Command
|
|
374
444
|
|
375
445
|
all_arguments.each do |argument|
|
376
446
|
begin
|
377
|
-
@arg_hash.merge! argument.consume_hash(@
|
447
|
+
@arg_hash.merge! argument.consume_hash(@subject_image, args_hash)
|
378
448
|
rescue ArgumentInvalidException => aie
|
379
449
|
wrong_values.merge! aie.pairs
|
380
450
|
rescue OutOfArgumentsException
|
data/lib/command-set/dsl.rb
CHANGED
@@ -21,6 +21,7 @@ Action:: utility functions within the command action block
|
|
21
21
|
#the new commands.
|
22
22
|
def include_commands(set, *commands)
|
23
23
|
new_commands = set.command_list
|
24
|
+
options = Hash === commands.first ? commands.shift : {}
|
24
25
|
|
25
26
|
commands.map!{|c| c.to_s}
|
26
27
|
unless commands.empty?
|
@@ -29,13 +30,25 @@ Action:: utility functions within the command action block
|
|
29
30
|
end
|
30
31
|
end
|
31
32
|
|
33
|
+
unless new_commands.empty?
|
34
|
+
@included_sets << [set, options]
|
35
|
+
end
|
36
|
+
|
37
|
+
if new_commands.has_key?(nil)
|
38
|
+
apply_root_blocks(set.root_blocks)
|
39
|
+
end
|
40
|
+
|
32
41
|
new_commands.each_pair do|name, command|
|
42
|
+
next if name.nil?
|
33
43
|
if(CommandSet === @command_list[name])
|
34
44
|
next unless CommandSet === command
|
35
45
|
@command_list[name].include_commands(command)
|
36
46
|
else
|
37
47
|
@command_list[name] = command.clone
|
38
|
-
|
48
|
+
end
|
49
|
+
unless(options[:context].nil?)
|
50
|
+
@command_list[name] = ContextBoundary.new(@command_list[name],
|
51
|
+
options[:context])
|
39
52
|
end
|
40
53
|
end
|
41
54
|
end
|
@@ -88,7 +101,6 @@ Action:: utility functions within the command action block
|
|
88
101
|
if Class === name_or_command_class && Command > name_or_command_class
|
89
102
|
if block.nil?
|
90
103
|
command = name_or_command_class.dup
|
91
|
-
command.parent = self
|
92
104
|
name = command.name
|
93
105
|
else
|
94
106
|
name = name_or_nil.to_s
|
@@ -104,7 +116,7 @@ Action:: utility functions within the command action block
|
|
104
116
|
else
|
105
117
|
raise RuntimeError, "#{name_or_command_class} is neither a Command class nor a name!"
|
106
118
|
end
|
107
|
-
command = Command.setup(
|
119
|
+
command = Command.setup(name, &block)
|
108
120
|
end
|
109
121
|
|
110
122
|
@command_list[name] = command
|
@@ -121,7 +133,7 @@ Action:: utility functions within the command action block
|
|
121
133
|
if (@command_list.has_key? name) && (CommandSet === @command_list[name])
|
122
134
|
command = @command_list[name]
|
123
135
|
else
|
124
|
-
command = CommandSet.new(
|
136
|
+
command = CommandSet.new(name)
|
125
137
|
@command_list[name] = command
|
126
138
|
end
|
127
139
|
|
@@ -129,11 +141,14 @@ Action:: utility functions within the command action block
|
|
129
141
|
#paths_update(command, name)
|
130
142
|
end
|
131
143
|
|
132
|
-
#
|
133
|
-
#
|
134
|
-
#
|
144
|
+
#When the behavior of a includeable command set should alter the root
|
145
|
+
#command of another command set, use root_command to wrap the command
|
146
|
+
#definition methods - loose command definition stuff will apply to the
|
147
|
+
#command as is, but won't be updated into including sets.
|
148
|
+
#
|
149
|
+
#As a for instance, take a look at StndCmds::Mode
|
135
150
|
def root_command(&block)
|
136
|
-
|
151
|
+
apply_root_blocks([block])
|
137
152
|
end
|
138
153
|
|
139
154
|
#This is the method that makes DSL::CommandSetDefinition available.
|
@@ -141,6 +156,10 @@ Action:: utility functions within the command action block
|
|
141
156
|
def define_commands(&block)
|
142
157
|
instance_eval(&block)
|
143
158
|
end
|
159
|
+
|
160
|
+
def subject_defaults(&block)
|
161
|
+
@subject_defaults = proc &block
|
162
|
+
end
|
144
163
|
end
|
145
164
|
|
146
165
|
#The meta-programmatic machinery to create arguments quickly. Includes
|
@@ -366,7 +385,7 @@ Action:: utility functions within the command action block
|
|
366
385
|
def subject_methods(*methods)
|
367
386
|
@subject_requirements += [*methods]
|
368
387
|
end
|
369
|
-
|
388
|
+
alias subject_method subject_methods
|
370
389
|
|
371
390
|
#Creates a parent argument reference: an argument that references an
|
372
391
|
#argument from a subcommand. This lets a subcommand collect the
|
@@ -377,18 +396,7 @@ Action:: utility functions within the command action block
|
|
377
396
|
#the mode is started.
|
378
397
|
def parent_argument(name)
|
379
398
|
name = name.to_s
|
380
|
-
|
381
|
-
until (search_in = search_in.parent()).nil? do
|
382
|
-
found = parent.argument_list.find do |argument|
|
383
|
-
argument.names.include? name
|
384
|
-
end
|
385
|
-
unless found.nil?
|
386
|
-
arg = found
|
387
|
-
@parent_arguments << found
|
388
|
-
return
|
389
|
-
end
|
390
|
-
end
|
391
|
-
raise CommandError, "No parent has an argument named \"#{name}\""
|
399
|
+
@parent_arguments << name.to_s
|
392
400
|
end
|
393
401
|
|
394
402
|
def parent_arguments(*names)
|
@@ -397,7 +405,6 @@ Action:: utility functions within the command action block
|
|
397
405
|
end
|
398
406
|
end
|
399
407
|
|
400
|
-
|
401
408
|
#The core of the Command. Define a block that performs the command.
|
402
409
|
#Within it, you can treat your arguments as readable private attributes
|
403
410
|
#and call methods from DSL::Action
|
@@ -518,7 +525,7 @@ Action:: utility functions within the command action block
|
|
518
525
|
#This is how you'll access the Command::Subject object that's the
|
519
526
|
#interface of every command to the program state.
|
520
527
|
def subject
|
521
|
-
@
|
528
|
+
@subject_image
|
522
529
|
end
|
523
530
|
|
524
531
|
#:section: Pause and Resume
|
@@ -561,30 +568,32 @@ Action:: utility functions within the command action block
|
|
561
568
|
#will cause that command to be invoked before returning control to the
|
562
569
|
#user.
|
563
570
|
def chain(*args)
|
564
|
-
|
565
|
-
setup
|
566
|
-
setup.
|
567
|
-
|
568
|
-
|
569
|
-
|
570
|
-
|
571
|
-
|
572
|
-
|
573
|
-
|
574
|
-
|
575
|
-
|
576
|
-
|
577
|
-
|
578
|
-
|
579
|
-
raise CommandException, "Can't chain #{args.inspect}"
|
580
|
-
end
|
571
|
+
anchor = CommandSet === args.first ? args.shift : self.parent
|
572
|
+
setup = AnchoredCommandSetup.new(anchor)
|
573
|
+
setup.arg_hash = Hash === args.last ? args.pop : {}
|
574
|
+
|
575
|
+
if args.length == 1
|
576
|
+
args = args[0]
|
577
|
+
case args
|
578
|
+
when Array
|
579
|
+
setup.terms = args
|
580
|
+
when String
|
581
|
+
setup.terms = [args]
|
582
|
+
when Symbol
|
583
|
+
setup.terms = [args.to_s]
|
584
|
+
when Class
|
585
|
+
setup.command_class = args
|
581
586
|
else
|
582
|
-
|
583
|
-
raise CommandException, "Can't chain #{args.inspect}"
|
584
|
-
else
|
585
|
-
args.map{|arg| arg.to_s}
|
586
|
-
end
|
587
|
+
raise CommandException, "Can't chain #{args.inspect}"
|
587
588
|
end
|
589
|
+
else
|
590
|
+
if args.find{|arg| not (String === arg or Symbol === arg)}
|
591
|
+
raise CommandException, "Can't chain #{args.inspect}"
|
592
|
+
else
|
593
|
+
setup.terms = args.map{|arg| arg.to_s}
|
594
|
+
end
|
595
|
+
end
|
596
|
+
|
588
597
|
subject.chain_of_command.push(setup)
|
589
598
|
end
|
590
599
|
|