command-set 0.8.4 → 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -34,7 +34,7 @@ Action:: utility functions within the command action block
34
34
  next unless CommandSet === command
35
35
  @command_list[name].include_commands(command)
36
36
  else
37
- @command_list[name] = command.dup
37
+ @command_list[name] = command.clone
38
38
  @command_list[name].parent=self
39
39
  end
40
40
  end
@@ -63,7 +63,7 @@ Action:: utility functions within the command action block
63
63
  raise RuntimeError,"#{set.inspect} isn't a CommandSet"
64
64
  end
65
65
 
66
- set = set.find_command(*path).first
66
+ set = set.find_command(path)
67
67
 
68
68
  if CommandSet === set
69
69
  include_commands(set, *commands)
@@ -94,10 +94,12 @@ Action:: utility functions within the command action block
94
94
  name = name_or_nil.to_s
95
95
  command = name_or_command_class.setup(self, name, &block)
96
96
  end
97
+ elsif name_or_command_class.nil?
98
+ command = @command_list[nil]
99
+ command.instance_eval(&block)
100
+ return
97
101
  else
98
- if name_or_command_class.nil?
99
- name = nil
100
- elsif String === name_or_command_class or Symbol === name_or_command_class
102
+ if String === name_or_command_class or Symbol === name_or_command_class
101
103
  name = name_or_command_class.to_s
102
104
  else
103
105
  raise RuntimeError, "#{name_or_command_class} is neither a Command class nor a name!"
@@ -141,67 +143,6 @@ Action:: utility functions within the command action block
141
143
  end
142
144
  end
143
145
 
144
- #These are the methods made available by Command::setup, and thus by
145
- #DSL::CommandSetDefinition#command
146
- module CommandDefinition
147
- #See Command::Subject. If this command will make use of fields of the
148
- #subject, it must declare them using subject_methods. You're then
149
- #guaranteed that the subject will either have those fields defined, or
150
- #an error will be thrown at runtime. Pass a list of symbols, as you
151
- #would to Class#attribute
152
- def subject_methods(*methods)
153
- @subject_requirements += [*methods]
154
- end
155
-
156
- #The core of the Command. Define a block that performs the command.
157
- #Within it, you can treat your arguments as readable private attributes
158
- #and call methods from DSL::Action
159
- def action(&block)
160
- define_method(:execute, &block)
161
- end
162
-
163
- #Commands should either define an undo block (that will reverse
164
- #whatever their action did) or else call doesnt_undo - for things that
165
- #don't change any state.
166
- #
167
- #One particularly useful feature is that each invocation is it's own
168
- #object, so you can set instance variables to save the old state if
169
- #you want.
170
- def undo(&block)
171
- define_method(:undo, &block)
172
- define_method(:undoable?) do
173
- return true
174
- end
175
- subject_requirements << :undo_stack
176
- end
177
-
178
- #Lets the interpreter know that this command intentionally doesn't
179
- #provide an undo block - that there's nothing to undo. Use it for
180
- #informational commands, primarily. Commands that neither declare
181
- #that they 'doesnt_undo' nor provide an undo block will raise a
182
- #warning to the user whenever they're called.
183
- def doesnt_undo
184
- define_method(:undoable?) { return true }
185
- define_method(:join_undo) {}
186
- end
187
-
188
- #Used to explain to the formatter subsystem how best to format your
189
- #output. It can sometimes be useful to output lots of data, and then
190
- #use format_advice to eliminate and shuffle it around.
191
- #
192
- #For more information see Command::Formatter::FormatAdvisor
193
- def format_advice(&block)
194
- @advice_block = proc &block
195
- end
196
-
197
- #Every command should provide a little text to describe what it does.
198
- #This will be nicely formatted on output, so feel free to use heredocs
199
- #and indent so that it looks nice in the code.
200
- def document(text)
201
- @doc_text = text.gsub(%r{\s+}, " ").strip
202
- end
203
- end
204
-
205
146
  #The meta-programmatic machinery to create arguments quickly. Includes
206
147
  #methods such that argument classes can register themselves into the DSL.
207
148
  #Much of this module is unfortunately obtuse - it's designed so that
@@ -227,14 +168,30 @@ Action:: utility functions within the command action block
227
168
  #
228
169
  #:include: doc/argumentDSL
229
170
  module Argument
230
- #Sugar for creating an alternating argument. Basically, an
231
- #alternating argument is a series of arguments, any of which could be
232
- #set. They either need to be of distinct types, or use +named+ to
233
- #distinguish between them.
234
- def alternating_argument(name, &block)
235
- arg = AlternatingArgument.new(self, &block)
236
- arg.name(name)
237
- embed_argument(arg)
171
+ class SubjectDeferral
172
+ def method_missing(name, *args, &block)
173
+ @deferred_calls << [name, args, block]
174
+ return self
175
+ end
176
+
177
+ def initialize
178
+ @deferred_calls = []
179
+ end
180
+
181
+ def subject_requirements
182
+ return [@deferred_calls.first.first]
183
+ end
184
+
185
+ def realize(subject)
186
+ return @deferred_calls.inject(subject) do |obj, call|
187
+ obj = obj.__send__(call[0], *call[1])
188
+ if call[2].nil?
189
+ obj
190
+ else
191
+ call[2].call(obj)
192
+ end
193
+ end
194
+ end
238
195
  end
239
196
 
240
197
  @@decorator_map={}
@@ -324,29 +281,15 @@ Action:: utility functions within the command action block
324
281
  #Don't look to closely at the source. It does bad things.
325
282
  def create_decorator(&block)
326
283
  me = /:in `([^']*)/.match(caller(0)[0])[1]
327
- decorator = @@decorator_map[me].allocate
328
- decorator.extend DSL::Argument
329
- embed_in = self
330
- decorator.instance_eval do
331
- initialize(embed_in, &block)
332
- end
333
- return decorator
284
+ return @@decorator_map[me].new(self, &block)
334
285
  end
335
286
 
336
287
  #This method functions analogously to create_decorator, except it
337
288
  #works for arguments, not decorators. It's worth looking at as the
338
289
  #call signature for all +funky_argument+ style calls.
339
- def special_argument(arg, values=nil, &get_values)
290
+ def special_argument(name, values=nil, &get_values)
340
291
  me = /:in `([^']*)/.match(caller(0)[0])[1]
341
- name = nil
342
- argument = nil
343
- if(::Command::Argument === arg)
344
- name = arg.name
345
- argument = arg
346
- else
347
- name = arg
348
- argument = @@shorthand_map[me].new(name, get_values||values)
349
- end
292
+ argument = @@shorthand_map[me].new(name, get_values||values)
350
293
  return self.embed_argument(argument)
351
294
  end
352
295
 
@@ -354,20 +297,42 @@ Action:: utility functions within the command action block
354
297
  #be used - which means that you can explicitly create and argument and
355
298
  #embed it. Otherwise, the values of +values+ or +get_values+ will be
356
299
  #used to create the argument
357
- def argument(arg, values=nil, &get_values)
300
+ def argument(arg, *values, &get_values)
358
301
  name = nil
359
302
  argument = nil
360
303
  if(::Command::Argument === arg)
361
304
  name = arg.name
362
305
  argument = arg
306
+ elsif(Class === arg and ::Command::Argument > arg)
307
+ argument = arg.new(values[0], get_values||values[1])
363
308
  else
364
309
  name = arg
365
- argument = create(name, get_values||values)
310
+ argument = create(name, get_values||values.first)
366
311
  end
367
312
 
368
313
  return self.embed_argument(argument)
369
314
  end
370
315
 
316
+ #Returns a SubjectDeferral Ultimately, this allows you to reference
317
+ #and base an argument on a value in the subject. Check this out:
318
+ #
319
+ # number_argument :which_little_pig subject.pigs {|pigs| 1..pigs.length}
320
+ #
321
+ #When that argument is evaluated, the pigs (is_a? Array) field of
322
+ #the subject will get turned into a range: from 1 to it's length.
323
+ def subject
324
+ return SubjectDeferral.new
325
+ end
326
+
327
+ #Sugar for creating an alternating argument. Basically, an
328
+ #alternating argument is a series of arguments, any of which could be
329
+ #set. They either need to be of distinct types, or use +named+ to
330
+ #distinguish between them.
331
+ def alternating_argument(name, &block)
332
+ arg = AlternatingArgument.new(self, &block)
333
+ arg.name(name)
334
+ end
335
+
371
336
  #The method used to instantiate arguments based on their values.
372
337
  #Searches all registered Argument classes, from children up, until one
373
338
  #admits to being able to create arguments based on the value.
@@ -386,6 +351,100 @@ Action:: utility functions within the command action block
386
351
  end
387
352
  end
388
353
 
354
+ #These are the methods made available by Command::setup, and thus by
355
+ #DSL::CommandSetDefinition#command
356
+ module CommandDefinition
357
+ include Argument
358
+
359
+ #See Command::Subject. If this command will make use of fields of the
360
+ #subject, it must declare them using subject_methods. You're then
361
+ #guaranteed that the subject will either have those fields defined, or
362
+ #an error will be thrown at runtime. Pass a list of symbols, as you
363
+ #would to Class#attribute
364
+ def subject_methods(*methods)
365
+ @subject_requirements += [*methods]
366
+ end
367
+
368
+
369
+ #Creates a parent argument reference: an argument that references an
370
+ #argument from a subcommand. This lets a subcommand collect the
371
+ #arguments common to its commands and do two things: make command line
372
+ #calls more natural (+box+ +grape_box+ +add+ +grapes+ instead of +box+
373
+ #+add+ +grape_box+ +grapes+) and also make modes more useful, since
374
+ #they can collect the arguments that would otherwise be repeated when
375
+ #the mode is started.
376
+ def parent_argument(name)
377
+ name = name.to_s
378
+ search_in = parent
379
+ until (search_in = search_in.parent()).nil? do
380
+ found = parent.argument_list.find do |argument|
381
+ argument.names.include? name
382
+ end
383
+ unless found.nil?
384
+ arg = found
385
+ @parent_arguments << found
386
+ return
387
+ end
388
+ end
389
+ raise CommandError, "No parent has an argument named \"#{name}\""
390
+ end
391
+
392
+ def parent_arguments(*names)
393
+ names.each do |name|
394
+ parent_argument(name)
395
+ end
396
+ end
397
+
398
+
399
+ #The core of the Command. Define a block that performs the command.
400
+ #Within it, you can treat your arguments as readable private attributes
401
+ #and call methods from DSL::Action
402
+ def action(&block)
403
+ define_method(:execute, &block)
404
+ end
405
+
406
+ #Commands should either define an undo block (that will reverse
407
+ #whatever their action did) or else call doesnt_undo - for things that
408
+ #don't change any state.
409
+ #
410
+ #One particularly useful feature is that each invocation is it's own
411
+ #object, so you can set instance variables to save the old state if
412
+ #you want.
413
+ def undo(&block)
414
+ define_method(:undo, &block)
415
+ define_method(:undoable?) do
416
+ return true
417
+ end
418
+ subject_requirements << :undo_stack
419
+ end
420
+
421
+ #Lets the interpreter know that this command intentionally doesn't
422
+ #provide an undo block - that there's nothing to undo. Use it for
423
+ #informational commands, primarily. Commands that neither declare
424
+ #that they 'doesnt_undo' nor provide an undo block will raise a
425
+ #warning to the user whenever they're called.
426
+ def doesnt_undo
427
+ define_method(:undoable?) { return true }
428
+ define_method(:join_undo) {}
429
+ end
430
+
431
+ #Used to explain to the formatter subsystem how best to format your
432
+ #output. It can sometimes be useful to output lots of data, and then
433
+ #use format_advice to eliminate and shuffle it around.
434
+ #
435
+ #For more information see Command::Formatter::FormatAdvisor
436
+ def format_advice(&block)
437
+ @advice_block = proc &block
438
+ end
439
+
440
+ #Every command should provide a little text to describe what it does.
441
+ #This will be nicely formatted on output, so feel free to use heredocs
442
+ #and indent so that it looks nice in the code.
443
+ def document(text)
444
+ @doc_text = text.gsub(%r{\s+}, " ").strip
445
+ end
446
+ end
447
+
389
448
  #The DSL for formatting. A lot of code will do just fine with the
390
449
  #Kernel#puts #that Results intercepts. More involved output control
391
450
  #starts by including CommandSet::DSL::Formatting, and using
@@ -403,20 +462,20 @@ Action:: utility functions within the command action block
403
462
  #Tells the main collector to begin a list. Subsequent output will be
404
463
  #gathered into that list. For more, check out Results::Collector
405
464
  def begin_list(name, options={})
406
- @main_collector.begin_list(name, options)
465
+ $stdout.relevant_collector.begin_list(name, options)
407
466
  end
408
467
 
409
468
  #Tells the main collector to end the current list. For more, check out
410
469
  #Results::Collector
411
470
  def end_list
412
- @main_collector.end_list
471
+ $stdout.relevant_collector.end_list
413
472
  end
414
473
 
415
474
  #Clean way to create an item of output. Allows for various options to
416
475
  #be added. The normal output method (#puts, #p, #write...) are all
417
476
  #diverted within a command, and effectively create no-option items.
418
477
  def item(name, options={})
419
- @main_collector.item(name, options)
478
+ $stdout.relevant_collector.item(name, options)
420
479
  end
421
480
 
422
481
  #This returns a new Results::Collector, which can allow for some very
@@ -425,7 +484,7 @@ Action:: utility functions within the command action block
425
484
  #multiple lists at once, for instance a status list (with hashmarks)
426
485
  #and results(with useful data) list.
427
486
  def sub_collector
428
- @main_collector.dup
487
+ $stdout.relevant_collector.dup
429
488
  end
430
489
  end
431
490
 
@@ -499,10 +558,31 @@ Action:: utility functions within the command action block
499
558
  #Calling chain with either a command class or a command path allows
500
559
  #will cause that command to be invoked before returning control to the
501
560
  #user.
502
- def chain(klass_or_path, args)
561
+ def chain(*args)
503
562
  setup = CommandSetup.new
504
- setup.command = klass_or_path
505
- setup.args_hash = args || {}
563
+ setup.args_hash = Hash === args.last ? args.pop : {}
564
+ setup.command =
565
+ if args.length == 1
566
+ args = args[0]
567
+ case args
568
+ when Array
569
+ args
570
+ when String
571
+ [args]
572
+ when Symbol
573
+ [args.to_s]
574
+ when Class
575
+ args
576
+ else
577
+ raise CommandException, "Can't chain #{args.inspect}"
578
+ end
579
+ else
580
+ if args.find{|arg| not (String === arg or Symbol === arg)}
581
+ raise CommandException, "Can't chain #{args.inspect}"
582
+ else
583
+ args.map{|arg| arg.to_s}
584
+ end
585
+ end
506
586
  subject.chain_of_command.push(setup)
507
587
  end
508
588
 
@@ -79,7 +79,7 @@ module Command
79
79
  raise RuntimeError, "Sub-modes must be CommandSets!"
80
80
  end
81
81
 
82
- @sub_modes.push(mode)
82
+ @sub_modes.push([mode, mode.most_recent_args])
83
83
  return nil
84
84
  end
85
85
 
@@ -118,6 +118,7 @@ module Command
118
118
  #You'll almost never want to override this method.
119
119
  def next_command
120
120
  setup = @commands_pending.shift
121
+ setup.args_hash = default_arg_hash.merge(setup.args_hash)
121
122
  return setup.command_instance(current_command_set, build_subject)
122
123
  end
123
124
 
@@ -143,6 +144,9 @@ module Command
143
144
  $stdout.add_dispatcher(collector)
144
145
  until @commands_pending.empty?
145
146
  cmd = next_command
147
+ unless cmd.class.executeable?
148
+ raise CommandException, "incomplete command"
149
+ end
146
150
  if ( @behavior[:warn_no_undo] and not cmd.undoable? )
147
151
  confirm = prompt_user("\"#{raw_input}\" cannot be undone. Continue? ")
148
152
  if not ["yes", "y", "sure", "i suppose", "okay"].include? confirm.strip.downcase
@@ -187,9 +191,14 @@ module Command
187
191
  return nil
188
192
  end
189
193
 
194
+ def default_arg_hash
195
+ return {} if @sub_modes.empty?
196
+ return @sub_modes.last.last
197
+ end
198
+
190
199
  def current_command_set
191
200
  return @command_set if @sub_modes.empty?
192
- return @sub_modes.last
201
+ return @sub_modes.last.first
193
202
  end
194
203
 
195
204
  def build_subject
@@ -70,7 +70,11 @@ module Command
70
70
 
71
71
  #Accepts it's input as multiple arguments for convenience
72
72
  def process_input(*words)
73
- super(words)
73
+ if words.length == 1 and Array === words.first
74
+ super words.first
75
+ else
76
+ super(words)
77
+ end
74
78
  end
75
79
 
76
80
  #Always returns "yes" so that undo warnings can be ignored.
@@ -83,6 +87,10 @@ module Command
83
87
  CommandSetup.new(words)
84
88
  end
85
89
 
90
+ def complete_input(terms, word)
91
+ return current_command_set.completion_list(terms, word, build_subject)
92
+ end
93
+
86
94
  #See PermissiveSubject
87
95
  def get_subject
88
96
  return PermissiveSubject.new
@@ -92,6 +92,8 @@ module Command
92
92
  #Kernel -- Which usually makes sense, but IO has a bunch of methods that
93
93
  #Kernel defines to basically delegate to an IO.... I have a headache
94
94
  #now.
95
+ #It might just make sense to explicitly do the delegation, rather than
96
+ #use DelegateClass
95
97
  methods = IO.public_instance_methods(false)
96
98
  methods -= self.public_instance_methods(false)
97
99
  methods |= ['class']