command-set 0.9.2 → 0.10.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -50,7 +50,7 @@ module StdCmd
50
50
  end
51
51
  else
52
52
  command = subject.command_set.find_command(terms)
53
- docs = command.documentation(width)
53
+ docs = command.documentation(width, terms)
54
54
  docs.each do |docline|
55
55
  puts docline
56
56
  end
@@ -133,7 +133,7 @@ module StdCmd
133
133
  doesnt_undo
134
134
 
135
135
  action do
136
- subject.interpreter.push_mode(parent)
136
+ subject.interpreter.push_mode(nesting)
137
137
  end
138
138
  end
139
139
 
@@ -0,0 +1,230 @@
1
+ require 'command-set/arguments'
2
+ require 'forwardable'
3
+
4
+ module Command
5
+ class SetVisitor
6
+ def initialize
7
+ @subject_context = []
8
+ @command_path = []
9
+ end
10
+ attr_reader :subject_context, :command_path
11
+
12
+ def leave_from(via, terms, node)
13
+ @command_path << via
14
+ end
15
+
16
+ def arrive_at(terms, node)
17
+ end
18
+
19
+ def set_out_of_terms(node)
20
+ end
21
+
22
+ def command_out_of_terms(node)
23
+ end
24
+
25
+ def term_without_hop(terms, node)
26
+ end
27
+
28
+ def extra_terms(terms, node)
29
+ end
30
+ end
31
+
32
+ class RequirementsCollector < SetVisitor
33
+ def initialize(subject)
34
+ super()
35
+ @subject = subject
36
+ end
37
+
38
+ attr_reader :subject
39
+
40
+ def arrive_at(terms, node)
41
+ @subject.required_fields(node.subject_requirements.uniq, @subject_context)
42
+ node.argument_list.each do |argument|
43
+ @subject.required_fields(argument.subject_requirements.uniq)
44
+ end
45
+ end
46
+ end
47
+
48
+ class CommandFinder < SetVisitor
49
+ def initialize
50
+ super()
51
+ @command = nil
52
+ end
53
+
54
+ attr_reader :command
55
+
56
+ def set_out_of_terms(node)
57
+ @command ||= node
58
+ end
59
+
60
+ def command_out_of_terms(node)
61
+ @command ||= node
62
+ end
63
+
64
+ def extra_terms(terms, node)
65
+ term_without_hop(terms, node)
66
+ end
67
+
68
+ def term_without_hop(terms, node)
69
+ raise CommandException, "No such command #{terms.first}"
70
+ end
71
+ end
72
+
73
+ class CommandSetup < SetVisitor
74
+ def initialize(terms = [], hash = {})
75
+ super()
76
+ @command_class = nil
77
+ @subject = subject
78
+ @arg_hash = hash
79
+ @terms = terms.dup
80
+ @task_id = nil
81
+ @set_nesting = []
82
+ end
83
+
84
+
85
+ attr_accessor :arg_hash, :terms, :subject,
86
+ :task_id, :command_class, :set_nesting
87
+
88
+ def arrive_at(terms, node)
89
+ @command_class = node
90
+ subject = @subject.get_image(node.subject_requirements, @subject_context)
91
+ return node.consume_terms(terms, subject, @arg_hash)
92
+ end
93
+
94
+ def leave_from(via, terms, node)
95
+ if CommandSet === node
96
+ @set_nesting << node
97
+ end
98
+ super
99
+ end
100
+
101
+ def term_without_hop(terms, node)
102
+ if CommandSet === node
103
+ raise CommandException, "No such command: #{terms.first}"
104
+ end
105
+ end
106
+
107
+ def class_resolution(command_set)
108
+ @command_class = command_set.find_command(@terms.dup)
109
+ end
110
+
111
+ def resolve_command_class(command_set)
112
+ return if Class === @command_class && Command > @command_class
113
+ class_resolution(command_set)
114
+ #@args_hash = @arg_hash.merge(@args_hash)
115
+ end
116
+
117
+ def command_instance(command_set, subject)
118
+ @subject ||= subject
119
+ resolve_command_class(command_set)
120
+ command = self.command_class.new(self, task_id)
121
+ command.consume_hash(self.arg_hash)
122
+ return command
123
+ end
124
+ end
125
+
126
+ class CompletionsLister < SetVisitor
127
+ def initialize(prefix, subject, terms)
128
+ super()
129
+ @prefix = prefix
130
+ @subject = subject
131
+ @completion_list = []
132
+ @arguments = []
133
+ @original_terms = terms.dup
134
+ end
135
+
136
+ attr_reader :prefix, :subject, :completion_list
137
+
138
+ def arrive_at(terms, node)
139
+ @arguments = node.argument_list.dup
140
+ image = subject.get_image(node.subject_requirements)
141
+ until terms.empty? or @arguments.empty? do
142
+ @arguments.first.match_terms(image, terms, @arguments)
143
+ end
144
+ end
145
+
146
+ def set_out_of_terms(node)
147
+ if @arguments.empty? or not @arguments.first.required?
148
+ @completion_list = node.command_names.grep(%r{^#{prefix}.*})
149
+ end
150
+ end
151
+
152
+ def command_out_of_terms(node)
153
+ image = @subject.get_image(node.subject_requirements)
154
+ @arguments.each do |handler|
155
+ @completion_list += handler.complete(@original_terms, @prefix, image)
156
+ break unless handler.omittable?
157
+ end
158
+ end
159
+
160
+ def term_without_hop(terms, node)
161
+ return []
162
+ end
163
+ end
164
+
165
+ module Common
166
+ def path
167
+ if @parent.nil?
168
+ return []
169
+ else
170
+ return @parent.path + [@name]
171
+ end
172
+ end
173
+
174
+ def add_requirements(subject)
175
+ each_command([], RequirementsCollector.new(subject))
176
+ return subject
177
+ end
178
+
179
+ def find_command(path)
180
+ visitor = CommandFinder.new
181
+ visit(path, visitor)
182
+ return visitor.command
183
+ end
184
+
185
+ def process_terms(terms, subject)
186
+ visitor = CommandSetup.new(terms)
187
+ visitor.subject = subject
188
+ root_visit(terms, visitor)
189
+ return visitor
190
+ end
191
+
192
+ def completion_list(terms, prefix, subject)
193
+ visitor = CompletionsLister.new(prefix, subject, terms)
194
+ visit(terms, visitor)
195
+ return visitor.completion_list
196
+ end
197
+ end
198
+
199
+ class SetItem
200
+ extend Forwardable
201
+ def initialize(decorates)
202
+ @decorated = decorates
203
+ end
204
+
205
+ def_delegators :@decorated, :subject_requirements, :consume_terms
206
+
207
+ def decoration(terms, visitor); end
208
+
209
+ def each_command(terms, visitor)
210
+ decoration(terms, visitor)
211
+ return @decorated.each_command(terms, visitor)
212
+ end
213
+
214
+ def visit(terms, visitor)
215
+ decoration(terms, visitor)
216
+ return @decorated.visit(terms, visitor)
217
+ end
218
+ end
219
+
220
+ class ContextBoundary < SetItem
221
+ def initialize(decorate, context)
222
+ super(decorate)
223
+ @context = context
224
+ end
225
+
226
+ def decoration(terms, visitor)
227
+ visitor.subject_context << @context
228
+ end
229
+ end
230
+ end
@@ -19,77 +19,191 @@ module Command
19
19
  class UndefinedField; end
20
20
  Undefined = UndefinedField.new
21
21
 
22
- def required_fields(*field_names)
22
+ def initialize
23
+ @fields = {}
24
+ @contexts = {}
25
+ @protected_subfields = Hash.new {|h,k| h[k] = []}
26
+ end
27
+
28
+ def required_fields(field_names, required_at=[])
29
+ unless required_at.empty?
30
+ unless @contexts.has_key?(required_at.first)
31
+ create_context(required_at.first, Subject.new)
32
+ end
33
+ return @contexts[required_at.shift].required_fields(field_names,
34
+ required_at)
35
+ end
36
+
23
37
  field_names.map! {|name| name.to_s}
24
38
  field_names -= instance_variables.map {|var| var.sub(/^@/, "")}
39
+ bad_fields = field_names.find_all do |name|
40
+ @contexts.has_key?(name.to_sym)
41
+ end
42
+ unless bad_fields.empty?
43
+ raise CommandError, "#{bad_fields.join(", ")} are context names!"
44
+ end
45
+
25
46
  field_names.each do |field|
26
- add_accessor(field)
47
+ add_field(field)
27
48
  end
28
49
  end
29
50
 
30
51
  def verify
31
- missing = instance_variables.find_all do |var|
32
- UndefinedField === instance_variable_get(var)
52
+ missing = @fields.keys.find_all do |var|
53
+ UndefinedField === @fields[var]
33
54
  end
34
55
  unless missing.empty?
35
56
  missing.map! {|m| m.sub(/^@/,"")}
36
- raise RuntimeError, "Undefined subject field#{missing.length > 1 ? "s" : ""}: #{missing.join(", ")}"
57
+ raise RuntimeError, "Undefined subject field#{missing.length > 1 ? "s" : ""}: #{missing.join(", ")}"
37
58
  end
38
59
  return nil
39
60
  end
40
61
 
41
- def get_image(with_fields)
62
+ def get_image(with_fields, in_context=[])
63
+ in_context = in_context.dup
64
+ fields = @fields.dup
65
+ context = self
66
+ until in_context.empty?
67
+ protected_fields = @protected_subfields[in_context]
68
+ context_name = in_context.shift.to_sym
69
+ context = context.contexts[context_name]
70
+ raise CommandError, "no context: #{context_name}" if context.nil?
71
+ context.fields.each_pair do |name, value|
72
+ if not fields.key?(name) or fields[name] == Undefined or
73
+ protected_fields.include?(name)
74
+ fields[name] = value
75
+ end
76
+ end
77
+ end
78
+
42
79
  image = SubjectImage.new
43
- missing_fields = []
80
+ with_fields.map! {|field| field.to_s}
81
+ missing_fields = with_fields - fields.keys
82
+ unless missing_fields.empty?
83
+ raise CommandError, "Subject is missing fields: #{missing_fields.join(", ")}"
84
+ end
44
85
  with_fields.each do |field|
45
- begin
46
- field = field.to_s
47
- value = instance_variable_get("@#{field}")
48
- image.add_field(field, value)
49
- rescue NameError
50
- missing_fields << field
86
+ image.add_field(field, fields[field])
87
+ end
88
+ return image
89
+ end
90
+
91
+ def protect(*path)
92
+ field_name = path.pop.to_s
93
+ path.map! {|el| el.to_sym}
94
+ @protected_subfields[path] << field_name
95
+ end
96
+
97
+ def merge(context, other)
98
+ other.fields.keys.each do |name|
99
+ add_field_writer(name)
100
+ end
101
+
102
+ unless context.nil?
103
+ context = context.to_sym
104
+ if @contexts[context].nil?
105
+ return create_context(context, other)
106
+ else
107
+ return @contexts[context].merge(nil, other)
51
108
  end
52
109
  end
53
- unless missing_fields.empty?
54
- raise CommandException, "Subject is missing fields: #{missing_fields.join(", ")}"
110
+
111
+ raise CommandError unless (defined_fields & other.defined_fields).empty?
112
+
113
+ copy_fields(other)
114
+ end
115
+
116
+ def absorb(other)
117
+ other.all_fields.each do |name|
118
+ add_field_writer(name)
55
119
  end
56
- image
120
+
121
+ copy_fields(other)
122
+ copy_contexts(other)
57
123
  end
58
124
 
59
125
  protected
60
- def add_accessor(name)
61
- self.instance_variable_set("@#{name}", Undefined)
62
- meta = class << self; self; end
126
+ attr_reader :fields, :contexts
63
127
 
64
- meta.instance_eval do
128
+ def all_fields
129
+ fields = @fields.keys
130
+ @contexts.values.inject(fields) do |fields, context|
131
+ fields + context.all_fields
132
+ end
133
+ return fields
134
+ end
65
135
 
66
- define_method("#{name}=") do |value|
67
- return instance_variable_set("@#{name}", value)
136
+ def create_context(name, subject)
137
+ raise CommandError if @fields.has_key?(name.to_s)
138
+ @contexts[name] = subject
139
+ (class << self; self; end).instance_eval do
140
+ define_method("#{name}") do
141
+ return @contexts[name]
142
+ end
143
+ end
144
+ return self
145
+ end
146
+
147
+ def defined_fields
148
+ @fields.keys.reject do |name|
149
+ @fields[name] == Undefined
68
150
  end
69
151
  end
70
- end
71
- end
72
152
 
73
- #This is the object type that's actually passed to a command. It's
74
- #populated using the subject_methods that the command declared, using values
75
- #from the application Subject.
76
- class SubjectImage
153
+ def copy_fields(from)
154
+ which = from.fields.keys
155
+ required_fields(which)
156
+ other_fields = from.instance_variable_get("@fields")
157
+ which.each do |field|
158
+ @fields[field] = other_fields[field]
159
+ end
160
+ return self
161
+ end
77
162
 
78
- #You shouldn't really need to ever call this - it's used by the
79
- #interpreter to set up the image before it's passed to the command
80
- def add_field(name, value)
81
- (class << self; self; end).instance_eval do
82
- define_method("#{name}") do
83
- return value
163
+ def copy_contexts(from)
164
+ from.contexts.each_pair do |name, subject|
165
+ if contexts[name].nil?
166
+ contexts[name] = subject
167
+ else
168
+ contexts[name].merge(nil, subject)
169
+ end
84
170
  end
85
171
  end
86
- end
87
172
 
88
- def get_image(fields)
89
- #TODO: fail if I don't respond to a field
90
- return self
173
+ def add_field(name)
174
+ @fields[name] ||= Undefined
175
+ add_field_writer(name)
176
+ end
177
+
178
+ def add_field_writer(name)
179
+ (class << self; self; end).instance_eval do
180
+ define_method("#{name}=") do |value|
181
+ return @fields[name] = value
182
+ end
183
+ end
184
+ end
91
185
  end
92
- end
186
+
187
+ #This is the object type that's actually passed to a command. It's
188
+ #populated using the subject_methods that the command declared, using values
189
+ #from the application Subject.
190
+ class SubjectImage
191
+
192
+ #You shouldn't really need to ever call this - it's used by the
193
+ #interpreter to set up the image before it's passed to the command
194
+ def add_field(name, value)
195
+ (class << self; self; end).instance_eval do
196
+ define_method("#{name}") do
197
+ return value
198
+ end
199
+ end
200
+ end
201
+
202
+ def get_image(fields)
203
+ #TODO: fail if I don't respond to a field
204
+ return self
205
+ end
206
+ end
93
207
 
94
208
 
95
209
  end