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
@@ -1,10 +1,12 @@
|
|
1
1
|
require 'strscan'
|
2
2
|
require 'thread'
|
3
|
+
require 'forwardable'
|
3
4
|
require 'command-set/arguments'
|
4
5
|
require 'command-set/command'
|
5
6
|
require 'command-set/subject'
|
6
7
|
require 'command-set/results'
|
7
8
|
require 'command-set/dsl'
|
9
|
+
require 'command-set/command-common'
|
8
10
|
|
9
11
|
|
10
12
|
#:stopdoc:
|
@@ -122,13 +124,24 @@ module Command
|
|
122
124
|
class ArgumentInvalidException < CommandException
|
123
125
|
def initialize(pairs)
|
124
126
|
@pairs = pairs.dup
|
125
|
-
super("
|
127
|
+
super("Invalid arguments: #{pairs.map{|n,v| "#{n}: #{v.inspect}"}.join(", ")}")
|
126
128
|
end
|
127
129
|
|
128
130
|
attr_reader :pairs
|
129
131
|
end
|
130
132
|
class ArgumentUnrecognizedException < CommandException; end
|
131
133
|
|
134
|
+
class RootCommand < Command
|
135
|
+
class << self
|
136
|
+
def single_consume(argument, arg_hash, subject, terms)
|
137
|
+
if not argument.required? and parent.command_names.include?(terms.first)
|
138
|
+
raise OutOfArgumentsException, "next is a command name"
|
139
|
+
end
|
140
|
+
super
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
132
145
|
#This class packs up a set of commands, for presentation to an
|
133
146
|
#interpreter. CommandSet objects are defined using methods from
|
134
147
|
#DSL::CommandSetDefinition
|
@@ -136,13 +149,15 @@ module Command
|
|
136
149
|
def initialize(parent=nil, name="")
|
137
150
|
@parent = parent
|
138
151
|
@name = name
|
139
|
-
@command_list =
|
152
|
+
@command_list = { nil => RootCommand.setup(self, nil) {} }
|
140
153
|
@subject_template = nil
|
141
154
|
@documentation = ""
|
142
155
|
@prompt = nil
|
156
|
+
@arguments = []
|
157
|
+
@most_recent_args = {}
|
143
158
|
end
|
144
159
|
|
145
|
-
attr_accessor :documentation, :parent
|
160
|
+
attr_accessor :documentation, :parent, :most_recent_args
|
146
161
|
|
147
162
|
class << self
|
148
163
|
#The preferred way to use a CommandSet is to call CommandSet::define_commands with
|
@@ -173,9 +188,44 @@ module Command
|
|
173
188
|
end
|
174
189
|
|
175
190
|
include DSL::CommandSetDefinition
|
191
|
+
include Common
|
192
|
+
extend Forwardable
|
193
|
+
|
194
|
+
def_delegators("@command_list[nil]", *CommandClassMethods.instance_methods)
|
195
|
+
def_delegators("@command_list[nil]", *DSL::CommandDefinition.instance_methods)
|
176
196
|
|
177
197
|
#:section: Workhorse methods - not usually used by client code
|
178
198
|
#
|
199
|
+
def each_command(&block)
|
200
|
+
@command_list.each_value do |command|
|
201
|
+
command.each_command(&block)
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
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
|
216
|
+
|
217
|
+
visitor.leave_from(terms, self)
|
218
|
+
|
219
|
+
visitor.arrive_at(terms, next_hop)
|
220
|
+
|
221
|
+
return next_hop.visit(terms, visitor)
|
222
|
+
end
|
223
|
+
|
224
|
+
def consume_terms(terms, subject, arg_hash)
|
225
|
+
@command_list[nil].consume_terms(terms, subject, arg_hash)
|
226
|
+
@most_recent_args = arg_hash.dup
|
227
|
+
return arg_hash
|
228
|
+
end
|
179
229
|
|
180
230
|
def get_root
|
181
231
|
command = @command_list[nil]
|
@@ -204,61 +254,6 @@ module Command
|
|
204
254
|
return subject
|
205
255
|
end
|
206
256
|
|
207
|
-
#Given a set of terms, returns a Command or CommandSet, plus an extraneous (probably argument) terms
|
208
|
-
def find_command(*terms)
|
209
|
-
if terms.empty?
|
210
|
-
return self,[]
|
211
|
-
end
|
212
|
-
|
213
|
-
command_name = terms.shift
|
214
|
-
|
215
|
-
command = @command_list[command_name]
|
216
|
-
|
217
|
-
if Class === command && Command > command
|
218
|
-
if command.defined?
|
219
|
-
return command,terms
|
220
|
-
else
|
221
|
-
raise CommandError, "#{command_name}: unfinalized command: #{command.inspect}"
|
222
|
-
end
|
223
|
-
elsif CommandSet === command
|
224
|
-
return command.find_command(*terms)
|
225
|
-
elsif command.nil?
|
226
|
-
if @command_list[nil].nil?
|
227
|
-
raise CommandException, "Unknown command: #{command_name.inspect}"
|
228
|
-
else
|
229
|
-
return @command_list[nil], terms.unshift(command_name)
|
230
|
-
end
|
231
|
-
else
|
232
|
-
raise ScriptError, "Somehow a non-command made it's " +
|
233
|
-
"way into the command registry. -- (#{command.inspect})"
|
234
|
-
end
|
235
|
-
end
|
236
|
-
|
237
|
-
def completion_list(terms, prefix, subject)
|
238
|
-
if terms.length == 0
|
239
|
-
list = @command_list.keys.grep(%r{^#{prefix}.*})
|
240
|
-
if @command_list.has_key?(nil)
|
241
|
-
list += @command_list[nil].completion_list([], prefix, subject)
|
242
|
-
end
|
243
|
-
return list
|
244
|
-
end
|
245
|
-
|
246
|
-
begin
|
247
|
-
command,terms = find_command(*terms)
|
248
|
-
return command.completion_list(terms, prefix, subject)
|
249
|
-
rescue CommandException => ce
|
250
|
-
return []
|
251
|
-
end
|
252
|
-
end
|
253
|
-
|
254
|
-
def path
|
255
|
-
if @parent.nil?
|
256
|
-
return []
|
257
|
-
else
|
258
|
-
return @parent.path + [@name]
|
259
|
-
end
|
260
|
-
end
|
261
|
-
|
262
257
|
def documentation(width, parent="")
|
263
258
|
docs = ""
|
264
259
|
docs = @command_list.to_a.reject do |el|
|
@@ -278,6 +273,10 @@ module Command
|
|
278
273
|
def command_list
|
279
274
|
return @command_list.dup
|
280
275
|
end
|
276
|
+
|
277
|
+
def command_names
|
278
|
+
return @command_list.keys.compact
|
279
|
+
end
|
281
280
|
end
|
282
281
|
end
|
283
282
|
|
data/lib/command-set/command.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'command-set/arguments'
|
2
|
+
require 'command-set/command-common'
|
2
3
|
module Command
|
3
4
|
|
4
5
|
#A thin wrapper on Array to maintain undo/redo state.
|
@@ -46,41 +47,22 @@ module Command
|
|
46
47
|
|
47
48
|
attr_accessor :task_id, :command, :args_hash, :terms
|
48
49
|
|
49
|
-
def resolve_command_class(command_set)
|
50
|
-
|
51
|
-
|
50
|
+
def resolve_command_class(command_set, subject)
|
51
|
+
unless Class === @command && Command > @command
|
52
|
+
command_path = @command
|
53
|
+
result = command_set.process_terms(command_path, subject)
|
54
|
+
@command = result.command_class
|
55
|
+
@args_hash = result.arg_hash.merge(@args_hash)
|
56
|
+
@terms = command_path
|
52
57
|
end
|
53
58
|
|
54
|
-
command_path = @command
|
55
|
-
@command,terms = command_set.find_command(*command_path)
|
56
|
-
|
57
|
-
if CommandSet === @command
|
58
|
-
if (cmd = @command.get_root).nil?
|
59
|
-
raise CommandException, "Incomplete command #{command_path.join(" ")}"
|
60
|
-
else
|
61
|
-
terms = [@command]
|
62
|
-
@command = cmd
|
63
|
-
end
|
64
|
-
end
|
65
|
-
|
66
|
-
@terms = terms
|
67
|
-
|
68
59
|
return @command
|
69
60
|
end
|
70
61
|
|
71
|
-
def assign_terms(cmd)
|
72
|
-
terms = @terms.dup
|
73
|
-
cmd.each_consumer do |consumer|
|
74
|
-
terms = consumer[terms]
|
75
|
-
end
|
76
|
-
|
77
|
-
cmd.consume_hash(@args_hash)
|
78
|
-
end
|
79
|
-
|
80
62
|
def command_instance(command_set, subject)
|
81
|
-
command_class = resolve_command_class(command_set)
|
63
|
+
command_class = resolve_command_class(command_set, subject)
|
82
64
|
command = command_class.new(subject, task_id)
|
83
|
-
|
65
|
+
command.consume_hash(@args_hash)
|
84
66
|
return command
|
85
67
|
end
|
86
68
|
end
|
@@ -99,30 +81,88 @@ module Command
|
|
99
81
|
|
100
82
|
class ResumeFromOnlyThis < ResumeFrom; end
|
101
83
|
|
84
|
+
module CommandClassMethods
|
85
|
+
attr_reader :subject_requirements, :argument_list,
|
86
|
+
:advice_block, :parent_arguments
|
87
|
+
|
88
|
+
def consume_terms(terms, subject, arg_hash)
|
89
|
+
begin
|
90
|
+
argument_list.each do |argument|
|
91
|
+
raise TypeError, "#{terms.inspect} isn't an Array!" unless Array === terms
|
92
|
+
begin
|
93
|
+
if terms.empty?
|
94
|
+
raise OutOfArgumentsException, "argument #{argument.inspect} required!"
|
95
|
+
end
|
96
|
+
single_consume(argument, arg_hash, subject, terms)
|
97
|
+
rescue ArgumentInvalidException => aie
|
98
|
+
@validation_problem = aie
|
99
|
+
raise
|
100
|
+
end
|
101
|
+
end
|
102
|
+
rescue OutOfArgumentsException
|
103
|
+
end
|
104
|
+
@validation_problem = nil
|
105
|
+
return arg_hash
|
106
|
+
end
|
107
|
+
|
108
|
+
def embed_argument(arg)
|
109
|
+
unless argument_list.last.nil? or argument_list.last.required?
|
110
|
+
if arg.required?
|
111
|
+
raise NoMethodError, "Can't define required arguments after optionals"
|
112
|
+
end
|
113
|
+
end
|
114
|
+
@subject_requirements += arg.subject_requirements
|
115
|
+
|
116
|
+
argument_list << arg
|
117
|
+
end
|
118
|
+
|
119
|
+
def executeable?
|
120
|
+
instance_methods.include?("execute")
|
121
|
+
end
|
122
|
+
|
123
|
+
def optional_argument(arg, values=nil, &get_values)
|
124
|
+
optional.argument(arg, values, &get_values)
|
125
|
+
end
|
126
|
+
|
127
|
+
def factory
|
128
|
+
return self
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
102
132
|
class Command
|
103
133
|
@name = "unnamed command"
|
104
134
|
@parent = nil
|
105
135
|
@argument_list=[]
|
136
|
+
@parent_arguments=[]
|
106
137
|
@doc_text = nil
|
107
138
|
@subject_requirements=[:chain_of_command]
|
108
139
|
@defined = false
|
109
140
|
@advice_block = proc {}
|
141
|
+
|
110
142
|
class << self
|
111
143
|
alias_method :instance, :new
|
112
144
|
|
113
|
-
attr_reader :name, :
|
114
|
-
alias_method :required_argument_list, :argument_list
|
115
|
-
alias_method :optional_argument_list, :argument_list
|
116
|
-
attr_reader :doc_text, :subject_requirements, :defined, :advice_block
|
145
|
+
attr_reader :name, :doc_text, :defined
|
117
146
|
attr_accessor :parent
|
118
147
|
|
119
148
|
def inherited(subclass)
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
149
|
+
[ [:name, proc {name.dup}],
|
150
|
+
[:argument_list, proc {argument_list.dup }],
|
151
|
+
[:parent_arguments, proc {parent_arguments.dup }],
|
152
|
+
[:doc_text, proc {doc_text.nil? ? nil : doc_text.dup}],
|
153
|
+
[:subject_requirements, proc {subject_requirements.dup}],
|
154
|
+
[:defined, proc {false}],
|
155
|
+
[:advice_block, proc {advice_block.dup}],
|
156
|
+
].each do |var, prok|
|
157
|
+
subclass.instance_variable_set("@#{var.to_s}", prok.call)
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
def initialize_copy(original)
|
162
|
+
super
|
163
|
+
@argument_list = original.argument_list.dup
|
164
|
+
@parent_arguments = original.parent_arguments.dup
|
165
|
+
@subject_requirements = original.subject_requirements.dup
|
126
166
|
end
|
127
167
|
|
128
168
|
#Establishes a subclass of Command. This is important because commands are actually
|
@@ -140,8 +180,6 @@ module Command
|
|
140
180
|
|
141
181
|
command_class = Class.new(self)
|
142
182
|
new_name = new_name.to_s
|
143
|
-
#Of interest: klass.instance_eval { def method } creates klass::method
|
144
|
-
#but klass.class_eval { def method } creates klass#method
|
145
183
|
|
146
184
|
command_class.instance_variable_set("@name", new_name)
|
147
185
|
command_class.instance_variable_set("@parent", parent)
|
@@ -152,8 +190,27 @@ module Command
|
|
152
190
|
return command_class
|
153
191
|
end
|
154
192
|
|
193
|
+
def each_command(&block)
|
194
|
+
block.call(self)
|
195
|
+
end
|
196
|
+
|
197
|
+
def visit(terms, visitor)
|
198
|
+
result = visitor.arrive_at(terms, self)
|
199
|
+
|
200
|
+
if(terms.empty?)
|
201
|
+
return visitor.command_out_of_terms(self)
|
202
|
+
else
|
203
|
+
return visitor.extra_terms(terms, self)
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
def single_consume(argument, arg_hash, subject, terms)
|
208
|
+
arg_hash.merge! argument.consume(subject, terms)
|
209
|
+
end
|
210
|
+
|
155
211
|
def defined
|
156
212
|
@defined = true
|
213
|
+
create_argument_methods
|
157
214
|
end
|
158
215
|
|
159
216
|
def defined?
|
@@ -162,18 +219,8 @@ module Command
|
|
162
219
|
|
163
220
|
def inspect
|
164
221
|
arguments = argument_list.map{|arg| arg.name}
|
165
|
-
argument_list.each do |argument|
|
166
|
-
arguments << argument.name
|
167
|
-
end
|
168
|
-
return "#<Class:#{self.object_id} - Command:#{path().join(" ")}(#{arguments.join(", ")})>"
|
169
|
-
end
|
170
222
|
|
171
|
-
|
172
|
-
if @parent.nil?
|
173
|
-
[@name]
|
174
|
-
else
|
175
|
-
@parent.path + [@name]
|
176
|
-
end
|
223
|
+
return "#<Class:#{"%#x" % self.object_id} - Command:#{path().join(" ")}(#{arguments.join(", ")})>"
|
177
224
|
end
|
178
225
|
|
179
226
|
def documentation(width, parent="")
|
@@ -186,21 +233,22 @@ module Command
|
|
186
233
|
end
|
187
234
|
|
188
235
|
def short_docs(width, parent="")
|
189
|
-
docs =
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
236
|
+
docs = [path]
|
237
|
+
docs += argument_list.map do |arg|
|
238
|
+
if arg.required?
|
239
|
+
"<#{arg.name}>"
|
240
|
+
else
|
241
|
+
"[#{arg.name}]"
|
242
|
+
end
|
243
|
+
end
|
244
|
+
|
197
245
|
|
198
|
-
|
199
|
-
subject.required_fields(*(@subject_requirements.uniq))
|
246
|
+
return docs.join(" ").wrap(width)
|
200
247
|
end
|
201
248
|
|
202
249
|
def completion_list(terms, prefix, subject)
|
203
250
|
arguments = argument_list.dup
|
251
|
+
subject = subject.get_image(subject_requirements)
|
204
252
|
|
205
253
|
until(terms.empty? or arguments.empty?) do
|
206
254
|
arguments.first.match_terms(subject, terms, arguments)
|
@@ -214,42 +262,28 @@ module Command
|
|
214
262
|
return completion
|
215
263
|
end
|
216
264
|
|
217
|
-
def create_argument_methods
|
218
|
-
|
219
|
-
|
265
|
+
def create_argument_methods
|
266
|
+
names = (argument_list + parent_arguments).inject([]) do |list, arg|
|
267
|
+
list + [*arg.names]
|
220
268
|
end
|
221
|
-
private(name)
|
222
|
-
|
223
|
-
define_method(name.to_s + "=") do |value|
|
224
|
-
@arg_hash[name] = value
|
225
|
-
end
|
226
|
-
private(name.to_s + "=")
|
227
|
-
end
|
228
|
-
|
229
|
-
def embed_argument(arg)
|
230
|
-
names = [*arg.name]
|
231
269
|
|
232
270
|
names.each do |name|
|
233
|
-
|
234
|
-
|
271
|
+
define_method(name) do
|
272
|
+
@arg_hash[name]
|
273
|
+
end
|
274
|
+
private(name)
|
235
275
|
|
236
|
-
|
237
|
-
|
238
|
-
raise NoMethodError, "Can't define required arguments after optionals"
|
276
|
+
define_method(name.to_s + "=") do |value|
|
277
|
+
@arg_hash[name] = value
|
239
278
|
end
|
279
|
+
private(name.to_s + "=")
|
240
280
|
end
|
241
|
-
|
242
|
-
argument_list << arg
|
243
|
-
end
|
244
|
-
|
245
|
-
def optional_argument(arg, values=nil, &get_values)
|
246
|
-
optional.argument(arg, values, &get_values)
|
247
281
|
end
|
248
|
-
|
249
282
|
end
|
250
283
|
|
251
284
|
extend DSL::CommandDefinition
|
252
|
-
extend
|
285
|
+
extend CommandClassMethods
|
286
|
+
extend Common
|
253
287
|
include DSL::Action
|
254
288
|
|
255
289
|
class DontResume; end
|
@@ -287,7 +321,7 @@ module Command
|
|
287
321
|
end
|
288
322
|
|
289
323
|
def all_arguments
|
290
|
-
self.class.argument_list
|
324
|
+
self.class.argument_list + self.class.parent_arguments
|
291
325
|
end
|
292
326
|
|
293
327
|
def subject_requirements
|
@@ -296,7 +330,7 @@ module Command
|
|
296
330
|
|
297
331
|
def inspect
|
298
332
|
name = self.class.name
|
299
|
-
return "#<Command:#{name}>:#{self.object_id} #{@arg_hash.
|
333
|
+
return "#<Command:#{name}>:#{"%#x" % self.object_id} #{@arg_hash.inspect}"
|
300
334
|
end
|
301
335
|
|
302
336
|
def parent
|
@@ -306,7 +340,7 @@ module Command
|
|
306
340
|
def go(collector)
|
307
341
|
return if @resume_from == DontResume
|
308
342
|
unless self.respond_to? :execute
|
309
|
-
raise CommandException, "#{
|
343
|
+
raise CommandException, "#{self.class.path}: command declared but no action defined"
|
310
344
|
end
|
311
345
|
|
312
346
|
@main_collector = collector
|
@@ -387,34 +421,6 @@ module Command
|
|
387
421
|
@validation_problem = nil
|
388
422
|
end
|
389
423
|
|
390
|
-
def yield_consumer(argument, &block)
|
391
|
-
blk = proc(&block)
|
392
|
-
consumer = proc do |args|
|
393
|
-
raise TypeError, "#{args.inspect} isn't an Array!" unless Array === args
|
394
|
-
begin
|
395
|
-
if args.empty?
|
396
|
-
raise OutOfArgumentsException, "argument #{argument.inspect} required!"
|
397
|
-
end
|
398
|
-
@arg_hash.merge! argument.consume(@subject, args)
|
399
|
-
return args
|
400
|
-
rescue ArgumentInvalidException => aie
|
401
|
-
@validation_problem = aie
|
402
|
-
raise
|
403
|
-
end
|
404
|
-
end
|
405
|
-
blk[consumer]
|
406
|
-
end
|
407
|
-
|
408
|
-
def each_consumer(&block)
|
409
|
-
begin
|
410
|
-
all_arguments.each do |argument|
|
411
|
-
yield_consumer(argument, &block)
|
412
|
-
end
|
413
|
-
rescue OutOfArgumentsException
|
414
|
-
end
|
415
|
-
@validation_problem = nil
|
416
|
-
end
|
417
|
-
|
418
424
|
def undoable?
|
419
425
|
return false
|
420
426
|
end
|