command-set 0.9.2 → 0.10.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.
@@ -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