command-set 0.8.4 → 0.9.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/argumentDSL CHANGED
@@ -30,7 +30,9 @@ The shorthand argument methods are:
30
30
  +multiword_argument+:: MultiArgument
31
31
  +nonvalidating_proc_argument+:: NoValidateProcArgument
32
32
  +number_argument+:: NumberArgument
33
+ +parent_argument+:: ParentArgument
33
34
  +proc_argument+:: ProcArgument
35
+ +range_argument+:: NumberArgument
34
36
  +regex_argument+:: RegexpArgument
35
37
  +regexp_argument+:: RegexpArgument
36
38
  +rest_argument+:: RestOfLineArgument
@@ -11,12 +11,29 @@ module Command
11
11
  DSL::Argument::register_argument(self, shorthand, type)
12
12
  end
13
13
 
14
- def initialize(name)
14
+ def initialize(name, basis=nil)
15
15
  @name = name.to_s
16
- @value = nil
16
+ @basis = basis
17
17
  end
18
18
 
19
- attr_reader :name, :value
19
+ attr_reader :name
20
+
21
+ def names
22
+ return name()
23
+ end
24
+
25
+ def basis(subject = nil)
26
+ return @basis unless DSL::Argument::SubjectDeferral === @basis
27
+ return @basis.realize(subject)
28
+ end
29
+
30
+ def subject_requirements
31
+ if DSL::Argument::SubjectDeferral === @basis
32
+ return @basis.subject_requirements
33
+ else
34
+ return []
35
+ end
36
+ end
20
37
 
21
38
  #Provides a list of completion options based on a string prefix and the subject
22
39
  #The completion should be an array of completion options. If the completions have a common
@@ -39,7 +56,7 @@ module Command
39
56
  unless validate(term, subject)
40
57
  raise ArgumentInvalidException, {@name => term}
41
58
  end
42
- return {@name => parse(subject, term)}
59
+ return {@name => term}
43
60
  end
44
61
 
45
62
  def consume_hash(subject, hash)
@@ -59,9 +76,10 @@ module Command
59
76
  end
60
77
  end
61
78
 
62
- #Used for completion, to skip along terms and arguments until we're at a place where completion begins.
63
- #Both the terms and arguments arrays are modified by this method, so a number of clever tricks can be played
64
- #to make completion work.
79
+ #Used for completion, to skip along terms and arguments until we're at a
80
+ #place where completion begins. Both the terms and arguments arrays are
81
+ #modified by this method, so a number of clever tricks can be played to
82
+ #make completion work.
65
83
  def match_terms(subject, terms, arguments)
66
84
  arguments.shift
67
85
  validate(terms.shift, subject)
@@ -83,7 +101,18 @@ module Command
83
101
  end
84
102
  end
85
103
 
104
+ class ParentArgument < Argument
105
+ register "parent"
106
+
107
+
108
+ def match_terms(subject, terms, arguments)
109
+ arguments.shift
110
+ end
111
+ end
112
+
86
113
  class ArgumentDecorator < Argument
114
+ include DSL::Argument
115
+
87
116
  def self.register(name)
88
117
  DSL::Argument::register_decorator(self, name)
89
118
  end
@@ -122,13 +151,7 @@ module Command
122
151
  #Input has to be a number, in the range passed to create the argument
123
152
  class NumberArgument < Argument
124
153
  register "number", Range
125
-
126
- def initialize(name, range=nil)
127
- super(name)
128
- @range = range
129
- end
130
-
131
- attr_accessor :range
154
+ register "range"
132
155
 
133
156
  def complete(prefix, subject)
134
157
  return [] unless validate(prefix, subject)
@@ -137,13 +160,14 @@ module Command
137
160
 
138
161
  def validate(term, subject)
139
162
  value = parse(subject, term)
163
+ range = basis(subject)
140
164
  return false if not range.nil? and not range.include?(value)
141
165
  return true if %r{^0(\D.*)?} =~ term
142
166
  return value != 0
143
167
  end
144
168
 
145
169
  def parse(subject, term)
146
- @value = term.to_i
170
+ return term.to_i
147
171
  end
148
172
  end
149
173
 
@@ -152,21 +176,29 @@ module Command
152
176
  register "regexp", Regexp
153
177
  register "regex"
154
178
 
155
- def initialize(name, regex)
156
- super(name)
157
- @regex = regex
158
- end
159
-
160
179
  def complete(prefix, subject)
161
180
  return [prefix]
162
181
  end
163
182
 
164
183
  def validate(term, subject)
165
- return @regex =~ term
184
+ return basis(subject) =~ term
166
185
  end
167
186
  end
168
187
 
188
+
169
189
  #File access. Completes paths, validates file options.
190
+ #You create a FileArgument either with an optional hash of options, or
191
+ #a block that evaluates whether or not a path is acceptable.
192
+ #
193
+ #The default options hash looks like:
194
+ #
195
+ # :prune_patterns => [/^\./], #regexs of paths to eliminate from
196
+ # #completion
197
+ # :dirs => [ENV['PWD']], #works essentially the PATH env variable
198
+ # :acceptor => proc do |path| #Is a good path?
199
+ # return (File.exists?(path) and not File.directory?(path))
200
+ # end #The default wants existent non-dirs
201
+ #
170
202
  class FileArgument < Argument
171
203
  register "file"
172
204
  Defaults = {
@@ -177,30 +209,18 @@ module Command
177
209
  end
178
210
  }
179
211
 
180
- #You create a FileArgument either with an optional hash of options, or
181
- #a block that evaluates whether or not a path is acceptable.
182
- #
183
- #The default options hash looks like:
184
- #
185
- # :prune_patterns => [/^\./], #regexs of paths to eliminate from
186
- # #completion
187
- # :dirs => [ENV['PWD']], #essentially the PATH env variable
188
- # :acceptor => proc do |path| #Is a good path?
189
- # return (File.exists?(path) and not File.directory?(path))
190
- # end #The default wants existent non-dirs
191
- #
192
- def initialize(name, options={})
193
- super(name)
194
- options ||= {}
212
+ def basis(subject=nil)
213
+ options = super(subject) || {}
195
214
  if Hash === options
196
- @options = Defaults.merge(options)
197
- @acceptor = @options[:acceptor]
215
+ options = Defaults.merge(options)
198
216
  elsif Proc === options
199
- @options = Defaults.dup
200
- @acceptor = proc &options
217
+ options = Defaults.dup
218
+ acceptor = proc &options
219
+ options[:acceptor] = acceptor
201
220
  else
202
221
  raise "File argument needs hash or proc!"
203
222
  end
223
+ return options
204
224
  end
205
225
 
206
226
  def fs
@@ -209,7 +229,8 @@ module Command
209
229
 
210
230
  def complete(prefix, subject)
211
231
  list = []
212
- search_path = @options[:dirs].dup
232
+ options = basis(subject)
233
+ search_path = options[:dirs].dup
213
234
  match = %r{^#{Regexp.escape(prefix)}.*}
214
235
  infix = ""
215
236
 
@@ -237,7 +258,7 @@ module Command
237
258
  catch(:bad_path) do
238
259
  next unless match =~ path
239
260
 
240
- @options[:prune_patterns].each do |pat|
261
+ options[:prune_patterns].each do |pat|
241
262
  if pat =~ path
242
263
  throw :bad_path
243
264
  end
@@ -245,7 +266,7 @@ module Command
245
266
 
246
267
  candidate = File.join(dir,path)
247
268
  if(File.file?(candidate))
248
- throw :bad_path unless @acceptor[candidate]
269
+ throw :bad_path unless options[:acceptor].call(candidate)
249
270
  end
250
271
 
251
272
  if(File.directory?(candidate))
@@ -268,27 +289,32 @@ module Command
268
289
  end
269
290
 
270
291
  def validate(term, subject)
292
+ options = basis(subject)
293
+ acceptor = options[:acceptor]
271
294
  if(%r{^#{fs}} =~ term)
272
- return @acceptor[term]
295
+ return acceptor[term]
273
296
  end
274
297
 
275
- @options[:dirs].each do |dir|
298
+ options[:dirs].each do |dir|
276
299
  path = File.join(dir, term)
277
300
  if(File.exists?(path))
278
- return @acceptor[path]
301
+ return acceptor[path]
279
302
  end
280
303
  end
281
304
 
282
- return @acceptor[File.join(@options[:dirs].first, term)]
305
+ return acceptor[File.join(options[:dirs].first, term)]
283
306
  end
284
307
  end
285
308
 
286
309
  #Using FiddlyArguments is sometimes unavoidable, but it kind of stinks.
287
310
  #You assign blocks that validate, complete and parse the input. You're
288
311
  #probably better off subclassing Argument.
312
+ #
313
+ #n.b. that FiddlyArguments can't use the +subject+ keyword to use the
314
+ #application state as their basis
289
315
  class FiddlyArgument < Argument
290
- def initialize(name, &block)
291
- super(name)
316
+ def initialize(name, block)
317
+ super(name, nil)
292
318
 
293
319
  (class << self; self; end).class_eval &block
294
320
  end
@@ -298,7 +324,6 @@ module Command
298
324
  define_method :complete, &block
299
325
  end
300
326
 
301
-
302
327
  def self.validation(&block)
303
328
  raise TypeError unless block.arity == 2
304
329
  define_method :validate, &block
@@ -315,17 +340,12 @@ module Command
315
340
  register "array", Array
316
341
  register "choose"
317
342
 
318
- def initialize(name, array)
319
- super(name)
320
- @options = array
321
- end
322
-
323
343
  def complete(prefix, subject)
324
- return @options.grep(%r{^#{prefix}.*})
344
+ return basis(subject).grep(%r{^#{prefix}.*})
325
345
  end
326
346
 
327
347
  def validate(term, subject)
328
- return @options.include?(term)
348
+ return basis(subject).include?(term)
329
349
  end
330
350
  end
331
351
 
@@ -334,13 +354,8 @@ module Command
334
354
  register "string", String
335
355
  register "any"
336
356
 
337
- def initialize(name, string)
338
- super(name)
339
- @description = string
340
- end
341
-
342
357
  def complete(prefix, subject)
343
- return [@description, ""]
358
+ return [basis(subject), ""]
344
359
  end
345
360
 
346
361
  def validate(term, subject)
@@ -356,10 +371,7 @@ module Command
356
371
  def consume(subject, arguments)
357
372
  term = arguments.join(" ")
358
373
  arguments.clear
359
- unless validate(term, subject)
360
- raise ArgumentInvalidException, {@name => term}
361
- end
362
- return {@name => parse(subject, term)}
374
+ return {@name => term}
363
375
  end
364
376
  end
365
377
 
@@ -376,7 +388,7 @@ module Command
376
388
 
377
389
  def initialize(name, prok)
378
390
  raise TypeError, "Block not arity 2: #{prok.arity}" unless prok.arity == 2
379
- super(name)
391
+ super(name, nil)
380
392
  @process = proc &prok
381
393
  end
382
394
 
@@ -456,7 +468,7 @@ module Command
456
468
 
457
469
  def initialize(name, block)
458
470
  raise TypeError, "Block arity is #{prok.arity}, not 2" unless block.arity == 2
459
- super(name)
471
+ super(name,nil)
460
472
  @process = proc &block
461
473
  end
462
474
 
@@ -465,7 +477,7 @@ module Command
465
477
  end
466
478
 
467
479
  def validate(terms, subject)
468
- return (not @process[[*terms], subject].empty?)
480
+ return (not @process[[*terms.dup], subject].empty?)
469
481
  end
470
482
 
471
483
  def consume(subject, arguments)
@@ -523,7 +535,7 @@ module Command
523
535
  until arguments.empty? do
524
536
  trying = arguments.shift
525
537
  if(validate(trying, subject))
526
- value << parse(subject,trying)
538
+ value << trying
527
539
  else
528
540
  arguments.unshift(trying)
529
541
  break
@@ -564,8 +576,8 @@ module Command
564
576
  return @names
565
577
  else
566
578
  name = name.to_s
567
- @names << name
568
579
  @name = name
580
+ @names << name
569
581
  end
570
582
  end
571
583
 
@@ -588,22 +600,24 @@ module Command
588
600
  term = arguments.first
589
601
  catcher = first_catch(term, subject)
590
602
  result = catcher.consume(subject, arguments)
591
- @value = catcher.value
592
603
  return result.merge({@name => result[catcher.name]})
593
604
  end
594
605
 
606
+ def subject_requirements
607
+ @sub_arguments.inject([]) do |list, arg|
608
+ list + arg.subject_requirements
609
+ end
610
+ end
595
611
  #If a hash is used for arguments that includes more than one of alternating argument's sub-arguments, the behavior is undefined
596
612
  def consume_hash(subject, hash)
613
+ result = {}
597
614
  begin
598
- return super(subject, hash)
615
+ result.merge! super(subject, hash)
599
616
  rescue OutOfArgumentsException; end
600
- @sub_arguments.each do |arg|
601
- unless hash[arg.name].nil?
602
- result = arg.consume_hash(subject, hash)
603
- return result.merge({@name => result[arg.name]})
604
- end
617
+
618
+ return @sub_arguments.inject(result) do |result, arg|
619
+ result.merge arg.consume_hash(subject, hash)
605
620
  end
606
- raise OutOfArgumentsException, "Missing argument: #@name!"
607
621
  end
608
622
 
609
623
  def match_terms(subject, terms, arguments)
@@ -0,0 +1,109 @@
1
+ require 'command-set/arguments'
2
+ require 'ostruct'
3
+
4
+ module Command
5
+ class SetVisitor < OpenStruct
6
+ def leave_from(terms, node)
7
+ end
8
+
9
+ def arrive_at(terms, node)
10
+ end
11
+
12
+ def set_out_of_terms(node)
13
+ end
14
+
15
+ def command_out_of_terms(node)
16
+ end
17
+
18
+ def term_without_hop(terms, node)
19
+ end
20
+
21
+ def extra_terms(terms, node)
22
+ end
23
+ end
24
+
25
+ module Common
26
+ def path
27
+ if @parent.nil?
28
+ return []
29
+ else
30
+ return @parent.path + [@name]
31
+ end
32
+ end
33
+
34
+ def add_requirements(subject)
35
+ each_command do |command|
36
+ subject.required_fields(*(command.subject_requirements.uniq))
37
+ command.argument_list.each do |argument|
38
+ subject.required_fields(*(argument.subject_requirements.uniq))
39
+ end
40
+ end
41
+ return subject
42
+ end
43
+
44
+ def find_command(path)
45
+ visitor = SetVisitor.new(:command => nil)
46
+ def visitor.set_out_of_terms(node)
47
+ self.command = node
48
+ end
49
+
50
+ def visitor.arrive_at(terms, node)
51
+ self.command = node
52
+ end
53
+
54
+ def visitor.term_without_hop(terms, node)
55
+ raise CommandException, "No such command #{terms.first}"
56
+ end
57
+
58
+ visit(path, visitor)
59
+ return visitor.command
60
+ end
61
+
62
+ def process_terms(terms, subject, arg_hash={})
63
+ visitor = SetVisitor.new(:command_class => nil, :subject => subject, :arg_hash => {})
64
+ def visitor.arrive_at(terms, node)
65
+ self.command_class = node.factory
66
+ subject = self.subject.get_image(node.subject_requirements)
67
+ return node.consume_terms(terms, subject, self.arg_hash)
68
+ end
69
+ visit(terms, visitor)
70
+ return visitor
71
+ end
72
+
73
+ def completion_list(terms, prefix, subject)
74
+ visitor = SetVisitor.new(:prefix => prefix,
75
+ :subject => subject,
76
+ :completion_list => [],
77
+ :arguments => [])
78
+ def visitor.arrive_at(terms, node)
79
+ self.arguments = node.argument_list.dup
80
+ image = subject.get_image(node.subject_requirements)
81
+ until(terms.empty? or arguments.empty?) do
82
+ arguments.first.match_terms(image, terms, arguments)
83
+ end
84
+ end
85
+
86
+ def visitor.set_out_of_terms(node)
87
+ if arguments.empty? or not arguments.first.required?
88
+ self.completion_list = node.command_names.grep(%r{^#{prefix}.*})
89
+ end
90
+ self.command_out_of_terms(node)
91
+ end
92
+
93
+ def visitor.command_out_of_terms(node)
94
+ image = subject.get_image(node.subject_requirements)
95
+ arguments.each do |handler|
96
+ self.completion_list += handler.complete(prefix, image)
97
+ break unless handler.omittable?
98
+ end
99
+ end
100
+
101
+ def visitor.term_without_hop(terms, node)
102
+ return []
103
+ end
104
+
105
+ visit(terms, visitor)
106
+ return visitor.completion_list
107
+ end
108
+ end
109
+ end