command-set 0.9.2 → 0.10.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.
- 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
|