command-set 0.8.4 → 0.9.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.
@@ -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']