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