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.
- data/doc/argumentDSL +2 -0
- data/lib/command-set/arguments.rb +94 -80
- data/lib/command-set/command-common.rb +109 -0
- data/lib/command-set/command-set.rb +57 -58
- data/lib/command-set/command.rb +124 -118
- data/lib/command-set/dsl.rb +180 -100
- data/lib/command-set/interpreter.rb +11 -2
- data/lib/command-set/quick-interpreter.rb +9 -1
- data/lib/command-set/results.rb +2 -0
- data/lib/command-set/standard-commands.rb +2 -11
- data/lib/command-set/subject.rb +11 -7
- data/lib/command-set/text-interpreter.rb +3 -3
- metadata +4 -4
data/lib/command-set/dsl.rb
CHANGED
@@ -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.
|
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(
|
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
|
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
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
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
|
-
|
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(
|
290
|
+
def special_argument(name, values=nil, &get_values)
|
340
291
|
me = /:in `([^']*)/.match(caller(0)[0])[1]
|
341
|
-
|
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
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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(
|
561
|
+
def chain(*args)
|
503
562
|
setup = CommandSetup.new
|
504
|
-
setup.
|
505
|
-
setup.
|
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
|
-
|
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
|
data/lib/command-set/results.rb
CHANGED
@@ -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']
|