command-set 0.8.4 → 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
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