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.
@@ -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