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.
@@ -1,5 +1,5 @@
1
1
  require 'command-set/arguments'
2
- require 'command-set/command-common'
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
- unless Class === @command && Command > @command
57
+ if @execution_context.nil? && Class === @command && Command > @command
58
+ @execution_context = TermProcessor.new(subject)
59
+ else
52
60
  command_path = @command
53
- result = command_set.process_terms(command_path, subject)
54
- @command = result.command_class
55
- @args_hash = result.arg_hash.merge(@args_hash)
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(subject, task_id)
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, :parent_arguments
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(parent, new_name=nil, &block)
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(&block)
194
- block.call(self)
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
- result = visitor.arrive_at(terms, self)
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:#{"%#x" % self.object_id} - Command:#{path().join(" ")}(#{arguments.join(", ")})>"
244
+ return "#<Class:#{"%0#x" % self.object_id} - Command:#{name()}(#{arguments.join(", ")})>"
224
245
  end
225
246
 
226
- def documentation(width, parent="")
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, parent="")
236
- docs = [path]
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 = (argument_list + parent_arguments).inject([]) do |list, arg|
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(subject, resume=nil)
301
+ def initialize(execution_context, resume=nil)
276
302
  raise CommandException, "#{@name}: unrecognized command" unless self.class.defined?
277
- fields = subject_requirements || []
278
- @subject = subject.get_image(fields)
279
- @puts_box = nil
280
- @puts_lock = Mutex.new
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
- attr_reader :arg_hash
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
- self.class.argument_list.find_all do |arg|
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
- self.class.argument_list + self.class.parent_arguments
378
+ @argument_list
309
379
  end
310
380
 
311
381
  def subject_requirements
312
- self.class.subject_requirements
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
- self.class.parent
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.class.path}: command declared but no action defined"
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
- verify_subject
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.command = self.class
341
- rf.setup.args_hash = arg_hash.dup
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 verify_subject
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 === @subject.send(requirement)
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(@subject, args_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
@@ -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
- @command_list[name].parent=self
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(self, name, &block)
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(self, name)
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
- #Occasionally it makes sense for a subcommand to do something when
133
- #invoked on it's own. Define that command using root_command as you
134
- #would a normal command
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
- command(nil, &block)
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
- search_in = parent
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
- @subject
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
- setup = CommandSetup.new
565
- setup.args_hash = Hash === args.last ? args.pop : {}
566
- setup.command =
567
- if args.length == 1
568
- args = args[0]
569
- case args
570
- when Array
571
- args
572
- when String
573
- [args]
574
- when Symbol
575
- [args.to_s]
576
- when Class
577
- args
578
- else
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
- if args.find{|arg| not (String === arg or Symbol === arg)}
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