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.
@@ -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("Bad arguments: #{pairs.inspect}")
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 = Hash.new
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
 
@@ -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
- if Class === @command && Command > @command
51
- return @command
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
- assign_terms(command)
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, :argument_list
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
- subclass.instance_variable_set("@name", name.dup)
121
- subclass.instance_variable_set("@argument_list", argument_list.dup )
122
- subclass.instance_variable_set("@doc_text", doc_text.dup) unless doc_text.nil?
123
- subclass.instance_variable_set("@subject_requirements", subject_requirements.dup)
124
- subclass.instance_variable_set("@defined", false)
125
- subclass.instance_variable_set("@advice_block", advice_block.dup)
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
- def path
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 = ((parent.empty? ? [name]:[parent, name]) + required_argument_list.map do |arg|
190
- "<#{arg.name}> "
191
- end + optional_argument_list.map do |arg|
192
- "[<#{arg.name}>]"
193
- end).join(" ")
194
-
195
- return docs.wrap(width)
196
- end
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
- def add_requirements(subject)
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(name)
218
- define_method(name) do
219
- @arg_hash[name]
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
- create_argument_methods(name)
234
- end
271
+ define_method(name) do
272
+ @arg_hash[name]
273
+ end
274
+ private(name)
235
275
 
236
- unless argument_list.last.nil? or argument_list.last.required?
237
- if arg.required?
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 DSL::Argument
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.keys.inspect}"
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, "#{@name}: command declared but no action defined"
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