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.
@@ -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