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.
- data/doc/Specifications +64 -17
- data/lib/command-set.rb +1 -1
- data/lib/command-set/arguments.rb +40 -36
- data/lib/command-set/command-set.rb +68 -31
- data/lib/command-set/command.rb +123 -53
- data/lib/command-set/dsl.rb +54 -45
- data/lib/command-set/formatter/base.rb +186 -0
- data/lib/command-set/formatter/strategy.rb +243 -0
- data/lib/command-set/formatter/xml.rb +56 -0
- data/lib/command-set/{interpreter.rb → interpreter/base.rb} +43 -36
- data/lib/command-set/{batch-interpreter.rb → interpreter/batch.rb} +0 -0
- data/lib/command-set/{quick-interpreter.rb → interpreter/quick.rb} +4 -4
- data/lib/command-set/{text-interpreter.rb → interpreter/text.rb} +29 -25
- data/lib/command-set/results.rb +4 -478
- data/lib/command-set/standard-commands.rb +2 -2
- data/lib/command-set/structural.rb +230 -0
- data/lib/command-set/subject.rb +153 -39
- metadata +13 -8
- data/lib/command-set/command-common.rb +0 -110
data/doc/Specifications
CHANGED
@@ -18,7 +18,26 @@ An established command set
|
|
18
18
|
- should return subcommands
|
19
19
|
- should accept new commands in a subcommand
|
20
20
|
- should add Command objects
|
21
|
-
|
21
|
+
|
22
|
+
Command::CommandSet with nested arguments
|
23
|
+
- should honor all arguments
|
24
|
+
- should prefer to parse commands over optional parent arguments
|
25
|
+
|
26
|
+
Command::CommandSet with cascading alternating subject-based arguments
|
27
|
+
- should complete properly
|
28
|
+
- should stash string for mode command retrieval
|
29
|
+
- should stash number for mode command retrieval
|
30
|
+
|
31
|
+
Command::CommandSet with subject defaults
|
32
|
+
- should set defaults on its subject
|
33
|
+
- should be useable with an interpreter without update
|
34
|
+
- should override the defaults of included sets
|
35
|
+
- should not raise an error when included sets collide
|
36
|
+
- should raise an error when included sets collide
|
37
|
+
|
38
|
+
Command::CommandSet with contextualized inclusions
|
39
|
+
- should properly assign context to commands
|
40
|
+
- should keep items neatly in contexts
|
22
41
|
|
23
42
|
Command::DSL::Argument
|
24
43
|
- should create an ArrayArgument from an array
|
@@ -74,24 +93,36 @@ Command::AlternatingArgument with reused subargument names
|
|
74
93
|
- should consume to number
|
75
94
|
- should raise with invalid arguments
|
76
95
|
|
96
|
+
Command::Argument with a complex set of decorators
|
97
|
+
- should parse one pick
|
98
|
+
- should parse many numbers
|
99
|
+
- should parse numbers, pick and more numbers
|
100
|
+
- should complete 'when now'
|
101
|
+
- should complete 'when now now'
|
102
|
+
- should not complete 'now'
|
103
|
+
|
77
104
|
Command::CommandSetup
|
78
|
-
- should create Command from complete path
|
79
105
|
- should create Command from command path and arg hash
|
80
106
|
- should create Command from command path and arg hash (string keys)
|
81
|
-
- should create Command from incomplete path and arg hash
|
82
|
-
- should create Command from class and arg hash
|
83
107
|
|
84
108
|
Command::TextInterpreter
|
85
109
|
- should verify and return the subject that it's passed
|
86
110
|
- should set up and verify an actual subject
|
87
111
|
- should return the same command_set it's assigned
|
88
|
-
- should raise Runtime Error if told to start without subject
|
89
112
|
- should split line at spaces
|
90
113
|
- should split around quoted strings
|
91
114
|
- should not split escaped spaces
|
92
|
-
- should safely absorb wrapping spaces
|
93
115
|
- should ignore embedded quotes
|
94
116
|
- should ignore escaped quotes
|
117
|
+
- should ignore escaped quotes
|
118
|
+
- should split multiple spaces only once
|
119
|
+
- should ignore leading spaces
|
120
|
+
- should leave an empty word at the end of a line
|
121
|
+
- should leave an empty word at the end of a line after quoted words
|
122
|
+
|
123
|
+
Command::TextInterpreter has a readline completion routine that
|
124
|
+
- should complete prefixes of space-embedding arguments
|
125
|
+
- should return tricky error message instead of raising an exception
|
95
126
|
|
96
127
|
Command::TextInterpreter all set up
|
97
128
|
- should complete words in the commmand-line
|
@@ -106,16 +137,33 @@ Command::TextInterpreter with a command with a named argument
|
|
106
137
|
- should complete words in the commmand-line
|
107
138
|
- should process a command-line
|
108
139
|
|
140
|
+
Command::TextInterpreter with a complex command set
|
141
|
+
- should process commands in mode that reference mode subject
|
142
|
+
- should issue an error message on bad commands
|
143
|
+
|
109
144
|
Command::TextInterpreter with serious problems
|
110
145
|
- should ask the user to quit instead of Ctrl-C
|
111
146
|
- should catch command errors and continue
|
112
147
|
- should catch exceptions and quit
|
113
148
|
|
114
|
-
|
149
|
+
Command::Subject
|
115
150
|
- should allow a field to be required
|
116
151
|
- should allow multiple fields to be required
|
117
152
|
|
118
|
-
|
153
|
+
Command::Subject when merging with other subjects
|
154
|
+
- should not allow a merge with colliding fields
|
155
|
+
- should allow a merge with colliding unfulfilled requirements
|
156
|
+
- should allow a colliding merge into a context
|
157
|
+
- should not allow contexts to collide with fields
|
158
|
+
- should not allow fields to collide with contexts
|
159
|
+
|
160
|
+
Command::Subject with contextual sub-subjects
|
161
|
+
- should project an image without context
|
162
|
+
- should not absorb fields from contexts
|
163
|
+
- should project an image within a context
|
164
|
+
- should allow assignment of fields within contexts
|
165
|
+
|
166
|
+
Command::Subject with required fields
|
119
167
|
- should not verify if fields are not set
|
120
168
|
- should not verify if some fields are not set
|
121
169
|
- should verify if all fields are set
|
@@ -163,12 +211,13 @@ Command::Command with tasks
|
|
163
211
|
- should resume after the designated task
|
164
212
|
|
165
213
|
A chain of commands
|
166
|
-
- should chain with a complete command path
|
167
|
-
- should chain when an optional argument is omitted
|
168
214
|
- should chain with a path to the command and hashed args
|
169
|
-
- should chain with mixed and overlapping terms
|
170
215
|
- should chain with a class and an arg hash
|
171
216
|
|
217
|
+
Command::Command with a subject argument basis
|
218
|
+
- should use the subject to accept input
|
219
|
+
- should use the subject to reject input
|
220
|
+
|
172
221
|
Command::Results::Formatter
|
173
222
|
- should process into the correct list
|
174
223
|
- should allow explicit options to come through from command
|
@@ -176,9 +225,6 @@ Command::Results::Formatter
|
|
176
225
|
- should mark up items as directed by Command#format_advice
|
177
226
|
- should mark up items as directed by Command#format_advice
|
178
227
|
|
179
|
-
Command::Results::StrategyFormatter
|
180
|
-
- should
|
181
|
-
|
182
228
|
A command set with the Set command
|
183
229
|
- should actually have the set command
|
184
230
|
- should reply to empty arguments with first level keys
|
@@ -200,8 +246,9 @@ A CommandSet with Help
|
|
200
246
|
- should gracefully recover if a command has nil documentation
|
201
247
|
- should cope with nil command names in full help listing
|
202
248
|
|
203
|
-
|
249
|
+
Command::StandardCommands::Mode
|
204
250
|
- should enter and leave a mode
|
251
|
+
- should retain the arguments to the mode
|
205
252
|
|
206
253
|
Command::StandardCommand::Resume
|
207
254
|
- should pause and resume
|
@@ -256,6 +303,6 @@ Command::Results::StrategyFormatter with chatty strategy
|
|
256
303
|
Command::Results::StrategyFormatter with progress strategy
|
257
304
|
- should format two lists
|
258
305
|
|
259
|
-
Finished in 0.
|
306
|
+
Finished in 0.677035 seconds
|
260
307
|
|
261
|
-
|
308
|
+
190 examples, 0 failures
|
data/lib/command-set.rb
CHANGED
@@ -1,12 +1,12 @@
|
|
1
1
|
require 'command-set/dsl'
|
2
2
|
|
3
3
|
module Command
|
4
|
-
#An Argument has a name and a value. They're used to to validate input,
|
5
|
-
#tab-completion or pop-up menus.
|
4
|
+
#An Argument has a name and a value. They're used to to validate input,
|
5
|
+
#provide input prompts (like tab-completion or pop-up menus.
|
6
6
|
class Argument
|
7
7
|
|
8
|
-
#Used for new Argument subclasses to register the types they can be
|
9
|
-
# arguments
|
8
|
+
#Used for new Argument subclasses to register the types they can be
|
9
|
+
#based on, and the explicit names of the arguments
|
10
10
|
def self.register(shorthand, type=nil)
|
11
11
|
DSL::Argument::register_argument(self, shorthand, type)
|
12
12
|
end
|
@@ -35,22 +35,23 @@ module Command
|
|
35
35
|
end
|
36
36
|
end
|
37
37
|
|
38
|
-
#Provides a list of completion options based on a string prefix and the
|
39
|
-
#The completion should be an array of completion options. If
|
40
|
-
#prefix, completion will enter it for the
|
41
|
-
# [ "This is a hint", "" ]
|
38
|
+
#Provides a list of completion options based on a string prefix and the
|
39
|
+
#subject The completion should be an array of completion options. If
|
40
|
+
#the completions have a common prefix, completion will enter it for the
|
41
|
+
#user. As a clever trick for providing hints: [ "This is a hint", "" ]
|
42
42
|
def complete(original_terms, prefix, subject)
|
43
43
|
raise NotImplementedError, "complete not implemented in #{self.class.name}"
|
44
44
|
end
|
45
45
|
|
46
|
-
#Validates the input string against the type of the argument. Returns
|
47
|
-
#false
|
46
|
+
#Validates the input string against the type of the argument. Returns
|
47
|
+
#true if the input is valid, or else false
|
48
48
|
def validate(term, subject)
|
49
49
|
raise NotImplementedError, "validate not implemented in #{self.class.name}"
|
50
50
|
end
|
51
51
|
|
52
|
-
#Pulls strings from an ordered list of inputs and provides the parsed
|
53
|
-
#Returns the parsed data or raises
|
52
|
+
#Pulls strings from an ordered list of inputs and provides the parsed
|
53
|
+
#data to the host command Returns the parsed data or raises
|
54
|
+
#ArgumentInvalidException
|
54
55
|
def consume(subject, arguments)
|
55
56
|
term = arguments.shift
|
56
57
|
unless validate(term, subject)
|
@@ -90,7 +91,8 @@ module Command
|
|
90
91
|
false
|
91
92
|
end
|
92
93
|
|
93
|
-
#Used in validation to require some arguments, and allow others to be
|
94
|
+
#Used in validation to require some arguments, and allow others to be
|
95
|
+
#optional
|
94
96
|
def required?
|
95
97
|
true
|
96
98
|
end
|
@@ -119,7 +121,7 @@ module Command
|
|
119
121
|
end
|
120
122
|
|
121
123
|
def embed_argument(arg)
|
122
|
-
@wrapped_by.embed_argument(@wrap_with.new(
|
124
|
+
@wrapped_by.embed_argument(@wrap_with.new(arg))
|
123
125
|
end
|
124
126
|
end
|
125
127
|
|
@@ -143,8 +145,7 @@ module Command
|
|
143
145
|
["@decorated"]
|
144
146
|
end
|
145
147
|
|
146
|
-
def initialize(
|
147
|
-
@wrapping_decorator = up
|
148
|
+
def initialize(down)
|
148
149
|
@decorated = down
|
149
150
|
end
|
150
151
|
|
@@ -219,8 +220,8 @@ module Command
|
|
219
220
|
if Hash === options
|
220
221
|
options = Defaults.merge(options)
|
221
222
|
elsif Proc === options
|
222
|
-
options = Defaults.dup
|
223
223
|
acceptor = proc &options
|
224
|
+
options = Defaults.dup
|
224
225
|
options[:acceptor] = acceptor
|
225
226
|
else
|
226
227
|
raise "File argument needs hash or proc!"
|
@@ -431,11 +432,6 @@ module Command
|
|
431
432
|
class Named < ArgumentDecoration
|
432
433
|
register_as "named"
|
433
434
|
|
434
|
-
def initialize(*args)
|
435
|
-
super
|
436
|
-
@name_pos = nil
|
437
|
-
end
|
438
|
-
|
439
435
|
def consume(subject, arguments)
|
440
436
|
if arguments.first == name
|
441
437
|
arguments.shift
|
@@ -447,23 +443,19 @@ module Command
|
|
447
443
|
|
448
444
|
def match_terms(subject, terms, arguments)
|
449
445
|
if terms.first == name.to_s
|
450
|
-
@name_pos = terms.length
|
451
446
|
terms.shift
|
452
|
-
|
447
|
+
arguments.shift
|
448
|
+
arguments.unshift(decorated)
|
453
449
|
else
|
454
450
|
arguments.shift
|
455
451
|
end
|
456
452
|
end
|
457
453
|
|
458
454
|
def complete(terms, prefix, subject)
|
459
|
-
if
|
460
|
-
return
|
455
|
+
if %r{^#{prefix.to_s}.*} =~ name.to_s
|
456
|
+
return [name.to_s]
|
461
457
|
else
|
462
|
-
|
463
|
-
return [name.to_s]
|
464
|
-
else
|
465
|
-
return []
|
466
|
-
end
|
458
|
+
return []
|
467
459
|
end
|
468
460
|
end
|
469
461
|
|
@@ -527,6 +519,9 @@ module Command
|
|
527
519
|
#
|
528
520
|
#Will collect an array into +some_files+ of validated files.
|
529
521
|
class Repeating < ArgumentDecoration
|
522
|
+
class Omittable < ArgumentDecoration
|
523
|
+
def omittable?; true; end
|
524
|
+
end
|
530
525
|
register "repeating"
|
531
526
|
register "many"
|
532
527
|
|
@@ -554,12 +549,20 @@ module Command
|
|
554
549
|
|
555
550
|
def match_terms(subject, terms, arguments)
|
556
551
|
old_length = terms.length + 1
|
552
|
+
middle_arguments = nil
|
557
553
|
while terms.length < old_length and not terms.empty? do
|
554
|
+
middle_arguments = [decorated]
|
558
555
|
old_length = terms.length
|
559
|
-
|
556
|
+
until middle_arguments.empty? or terms.empty? do
|
557
|
+
middle_arguments.first.match_terms(subject, terms, middle_arguments)
|
558
|
+
end
|
560
559
|
end
|
561
560
|
|
562
|
-
|
561
|
+
if(terms.empty? and middle_arguments.empty?)
|
562
|
+
middle_arguments = [Omittable.new(decorated)]
|
563
|
+
end
|
564
|
+
|
565
|
+
arguments[0,1] = *middle_arguments
|
563
566
|
return true
|
564
567
|
end
|
565
568
|
|
@@ -576,7 +579,7 @@ module Command
|
|
576
579
|
#"decorator" method with the argument declarations inside. The first
|
577
580
|
#argument that can parse the input will be assigned - others will get nil.
|
578
581
|
class AlternatingArgument < Argument
|
579
|
-
# initialize(embed_in){ yield if block_given? }
|
582
|
+
# initialize(embed_in){ yield if block_given? }
|
580
583
|
def initialize(embed_in, &block)
|
581
584
|
@wrapping_decorator = embed_in
|
582
585
|
@names = []
|
@@ -623,7 +626,8 @@ module Command
|
|
623
626
|
list + arg.subject_requirements
|
624
627
|
end
|
625
628
|
end
|
626
|
-
#If a hash is used for arguments that includes more than one of
|
629
|
+
#If a hash is used for arguments that includes more than one of
|
630
|
+
#alternating argument's sub-arguments, the behavior is undefined
|
627
631
|
def consume_hash(subject, hash)
|
628
632
|
result = @sub_arguments.inject({}) do |result, arg|
|
629
633
|
result.merge arg.consume_hash(subject, hash)
|
@@ -651,7 +655,7 @@ module Command
|
|
651
655
|
|
652
656
|
def embed_argument(arg)
|
653
657
|
@names << arg.name
|
654
|
-
@sub_arguments << Optional.new(
|
658
|
+
@sub_arguments << Optional.new(arg)
|
655
659
|
end
|
656
660
|
private
|
657
661
|
|
@@ -6,7 +6,7 @@ require 'command-set/command'
|
|
6
6
|
require 'command-set/subject'
|
7
7
|
require 'command-set/results'
|
8
8
|
require 'command-set/dsl'
|
9
|
-
require 'command-set/
|
9
|
+
require 'command-set/structural'
|
10
10
|
|
11
11
|
|
12
12
|
#:stopdoc:
|
@@ -114,7 +114,7 @@ module Command
|
|
114
114
|
return @raw_input.inspect() +": "+ super
|
115
115
|
end
|
116
116
|
else
|
117
|
-
return @command.
|
117
|
+
return @command.path.join(" ") +": "+ super
|
118
118
|
end
|
119
119
|
end
|
120
120
|
|
@@ -133,8 +133,14 @@ module Command
|
|
133
133
|
|
134
134
|
class RootCommand < Command
|
135
135
|
class << self
|
136
|
+
def setup(host, name)
|
137
|
+
klass = super(name)
|
138
|
+
klass.instance_variable_set("@host", host)
|
139
|
+
return klass
|
140
|
+
end
|
141
|
+
|
136
142
|
def single_consume(argument, arg_hash, subject, terms)
|
137
|
-
if not argument.required? and
|
143
|
+
if not argument.required? and @host.command_names.include?(terms.first)
|
138
144
|
raise OutOfArgumentsException, "next is a command name"
|
139
145
|
end
|
140
146
|
super
|
@@ -146,18 +152,32 @@ module Command
|
|
146
152
|
#interpreter. CommandSet objects are defined using methods from
|
147
153
|
#DSL::CommandSetDefinition
|
148
154
|
class CommandSet
|
149
|
-
def initialize(
|
150
|
-
@parent = parent
|
155
|
+
def initialize(name="")
|
151
156
|
@name = name
|
152
157
|
@command_list = { nil => RootCommand.setup(self, nil) {} }
|
158
|
+
@included_sets = []
|
153
159
|
@subject_template = nil
|
154
160
|
@documentation = ""
|
155
161
|
@prompt = nil
|
156
162
|
@arguments = []
|
157
163
|
@most_recent_args = {}
|
164
|
+
@subject_defaults = proc {|s|}
|
165
|
+
@context = []
|
166
|
+
@root_blocks = []
|
158
167
|
end
|
159
168
|
|
160
|
-
|
169
|
+
def initialize_copy(original)
|
170
|
+
super
|
171
|
+
base_list = original.instance_variable_get("@command_list")
|
172
|
+
@command_list = {}
|
173
|
+
@context = [] #original.context.dup
|
174
|
+
base_list.each_pair do |name, cmd|
|
175
|
+
@command_list[name] = cmd.dup
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
attr_accessor :documentation, :most_recent_args
|
180
|
+
attr_reader :root_blocks
|
161
181
|
|
162
182
|
class << self
|
163
183
|
#The preferred way to use a CommandSet is to call CommandSet::define_commands with
|
@@ -196,31 +216,33 @@ module Command
|
|
196
216
|
|
197
217
|
#:section: Workhorse methods - not usually used by client code
|
198
218
|
#
|
199
|
-
def each_command(
|
200
|
-
@command_list.
|
201
|
-
command.each_command(
|
219
|
+
def each_command(path, visitor)
|
220
|
+
@command_list.each_pair do |term, command|
|
221
|
+
command.each_command(path + [term], visitor)
|
202
222
|
end
|
203
223
|
end
|
204
224
|
|
205
|
-
def
|
206
|
-
if(terms.empty?)
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
return visitor.term_without_hop(terms, self)
|
213
|
-
else
|
214
|
-
terms.shift
|
215
|
-
end
|
225
|
+
def root_visit(terms, visitor)
|
226
|
+
next_hop = if(terms.empty?)
|
227
|
+
visitor.set_out_of_terms(self)
|
228
|
+
@command_list[nil]
|
229
|
+
else
|
230
|
+
@command_list[terms.first]
|
231
|
+
end
|
216
232
|
|
217
|
-
visitor.
|
233
|
+
return visitor.term_without_hop(terms, self) if(next_hop.nil?)
|
218
234
|
|
219
|
-
visitor.
|
235
|
+
visitor.leave_from(terms.shift, terms, self)
|
220
236
|
|
221
237
|
return next_hop.visit(terms, visitor)
|
222
238
|
end
|
223
239
|
|
240
|
+
def visit(terms, visitor)
|
241
|
+
visitor.arrive_at(terms, self)
|
242
|
+
|
243
|
+
root_visit(terms, visitor)
|
244
|
+
end
|
245
|
+
|
224
246
|
def consume_terms(terms, subject, arg_hash)
|
225
247
|
@command_list[nil].consume_terms(terms, subject, arg_hash)
|
226
248
|
@most_recent_args = arg_hash.dup
|
@@ -231,6 +253,28 @@ module Command
|
|
231
253
|
command = @command_list[nil]
|
232
254
|
end
|
233
255
|
|
256
|
+
def apply_root_blocks(blocks)
|
257
|
+
@root_blocks += blocks
|
258
|
+
blocks.each do |block|
|
259
|
+
@command_list[nil].instance_eval(&block)
|
260
|
+
end
|
261
|
+
end
|
262
|
+
|
263
|
+
def get_subject
|
264
|
+
subject = Subject.new
|
265
|
+
add_requirements(subject)
|
266
|
+
add_defaults(subject)
|
267
|
+
return subject
|
268
|
+
end
|
269
|
+
|
270
|
+
def add_defaults(subject)
|
271
|
+
included_subject = @included_sets.inject(Subject.new) do |merger, (subset, options)|
|
272
|
+
merger.merge(options[:context], subset.get_subject)
|
273
|
+
end
|
274
|
+
subject.absorb(included_subject)
|
275
|
+
@subject_defaults.call(subject)
|
276
|
+
end
|
277
|
+
|
234
278
|
def prompt
|
235
279
|
if @prompt.nil?
|
236
280
|
if @name.empty?
|
@@ -247,20 +291,13 @@ module Command
|
|
247
291
|
@prompt = [match, replace]
|
248
292
|
end
|
249
293
|
|
250
|
-
def
|
251
|
-
@command_list.each_value do |command|
|
252
|
-
command.add_requirements subject
|
253
|
-
end
|
254
|
-
return subject
|
255
|
-
end
|
256
|
-
|
257
|
-
def documentation(width, parent="")
|
294
|
+
def documentation(width, prefix=[])
|
258
295
|
docs = ""
|
259
296
|
docs = @command_list.to_a.reject do |el|
|
260
297
|
el[0].nil?
|
261
298
|
end
|
262
299
|
|
263
|
-
parent = [
|
300
|
+
parent = prefix + [@name]
|
264
301
|
docs = docs.sort_by{|el| el[0]}.inject([]) do |doclist,cmd|
|
265
302
|
doclist += cmd[1].short_docs(width, parent)
|
266
303
|
end
|