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 +2 -0
- data/lib/command-set/arguments.rb +94 -80
- data/lib/command-set/command-common.rb +109 -0
- data/lib/command-set/command-set.rb +57 -58
- data/lib/command-set/command.rb +124 -118
- data/lib/command-set/dsl.rb +180 -100
- data/lib/command-set/interpreter.rb +11 -2
- data/lib/command-set/quick-interpreter.rb +9 -1
- data/lib/command-set/results.rb +2 -0
- data/lib/command-set/standard-commands.rb +2 -11
- data/lib/command-set/subject.rb +11 -7
- data/lib/command-set/text-interpreter.rb +3 -3
- metadata +4 -4
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
|
-
@
|
16
|
+
@basis = basis
|
17
17
|
end
|
18
18
|
|
19
|
-
attr_reader :name
|
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 =>
|
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
|
63
|
-
#Both the terms and arguments arrays are
|
64
|
-
#
|
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
|
-
|
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
|
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
|
-
|
181
|
-
|
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
|
-
|
197
|
-
@acceptor = @options[:acceptor]
|
215
|
+
options = Defaults.merge(options)
|
198
216
|
elsif Proc === options
|
199
|
-
|
200
|
-
|
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
|
-
|
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
|
-
|
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
|
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
|
295
|
+
return acceptor[term]
|
273
296
|
end
|
274
297
|
|
275
|
-
|
298
|
+
options[:dirs].each do |dir|
|
276
299
|
path = File.join(dir, term)
|
277
300
|
if(File.exists?(path))
|
278
|
-
return
|
301
|
+
return acceptor[path]
|
279
302
|
end
|
280
303
|
end
|
281
304
|
|
282
|
-
return
|
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,
|
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
|
344
|
+
return basis(subject).grep(%r{^#{prefix}.*})
|
325
345
|
end
|
326
346
|
|
327
347
|
def validate(term, subject)
|
328
|
-
return
|
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 [
|
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
|
-
|
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 <<
|
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
|
-
|
615
|
+
result.merge! super(subject, hash)
|
599
616
|
rescue OutOfArgumentsException; end
|
600
|
-
|
601
|
-
|
602
|
-
|
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
|