command-set 0.9.2 → 0.10.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.
- 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
|
|