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 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