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 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
- - should have Commands that know their own paths
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
- A fresh Subject
149
+ Command::Subject
115
150
  - should allow a field to be required
116
151
  - should allow multiple fields to be required
117
152
 
118
- A Subject with required fields
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
- #<Command::CommandSet:0xb75fe940>
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.304894 seconds
306
+ Finished in 0.677035 seconds
260
307
 
261
- 161 examples, 0 failures
308
+ 190 examples, 0 failures
data/lib/command-set.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  require 'command-set/command'
2
2
  require 'command-set/command-set'
3
- require 'command-set/text-interpreter'
3
+ require 'command-set/interpreter/text'
@@ -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, provide input prompts (like
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 based on, and the explicit names of the
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 subject
39
- #The completion should be an array of completion options. If the completions have a common
40
- #prefix, completion will enter it for the user. As a clever trick for providing hints:
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 true if the input is valid, or else
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 data to the host command
53
- #Returns the parsed data or raises ArgumentInvalidException
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 optional
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(@wrapped_with, arg))
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(up, down)
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
- decorated.match_terms(subject, terms, arguments)
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 not @name_pos.nil? and terms[-@name_pos] == name.to_s
460
- return decorated.complete(terms, prefix, subject)
455
+ if %r{^#{prefix.to_s}.*} =~ name.to_s
456
+ return [name.to_s]
461
457
  else
462
- if %r{^#{prefix.to_s}.*} =~ name.to_s
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
- decorated.match_terms(subject, terms, arguments.dup)
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
- arguments.shift
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 alternating argument's sub-arguments, the behavior is undefined
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(self, arg)
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/command-common'
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.class.path.join(" ") +": "+ super
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 parent.command_names.include?(terms.first)
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(parent=nil, name="")
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
- attr_accessor :documentation, :parent, :most_recent_args
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(&block)
200
- @command_list.each_value do |command|
201
- command.each_command(&block)
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 visit(terms, visitor)
206
- if(terms.empty?)
207
- return visitor.set_out_of_terms(self)
208
- end
209
-
210
- next_hop = @command_list[terms.first]
211
- if(next_hop.nil?)
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.leave_from(terms, self)
233
+ return visitor.term_without_hop(terms, self) if(next_hop.nil?)
218
234
 
219
- visitor.arrive_at(terms, next_hop)
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 add_requirements(subject)
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 = [parent, @name].join(" ")
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