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.
- data/doc/Specifications +64 -17
- data/lib/command-set.rb +1 -1
- data/lib/command-set/arguments.rb +40 -36
- data/lib/command-set/command-set.rb +68 -31
- data/lib/command-set/command.rb +123 -53
- data/lib/command-set/dsl.rb +54 -45
- data/lib/command-set/formatter/base.rb +186 -0
- data/lib/command-set/formatter/strategy.rb +243 -0
- data/lib/command-set/formatter/xml.rb +56 -0
- data/lib/command-set/{interpreter.rb → interpreter/base.rb} +43 -36
- data/lib/command-set/{batch-interpreter.rb → interpreter/batch.rb} +0 -0
- data/lib/command-set/{quick-interpreter.rb → interpreter/quick.rb} +4 -4
- data/lib/command-set/{text-interpreter.rb → interpreter/text.rb} +29 -25
- data/lib/command-set/results.rb +4 -478
- data/lib/command-set/standard-commands.rb +2 -2
- data/lib/command-set/structural.rb +230 -0
- data/lib/command-set/subject.rb +153 -39
- metadata +13 -8
- data/lib/command-set/command-common.rb +0 -110
@@ -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(
|
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
|
data/lib/command-set/subject.rb
CHANGED
@@ -19,77 +19,191 @@ module Command
|
|
19
19
|
class UndefinedField; end
|
20
20
|
Undefined = UndefinedField.new
|
21
21
|
|
22
|
-
def
|
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
|
-
|
47
|
+
add_field(field)
|
27
48
|
end
|
28
49
|
end
|
29
50
|
|
30
51
|
def verify
|
31
|
-
missing =
|
32
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
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
|
-
|
54
|
-
|
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
|
-
|
120
|
+
|
121
|
+
copy_fields(other)
|
122
|
+
copy_contexts(other)
|
57
123
|
end
|
58
124
|
|
59
125
|
protected
|
60
|
-
|
61
|
-
self.instance_variable_set("@#{name}", Undefined)
|
62
|
-
meta = class << self; self; end
|
126
|
+
attr_reader :fields, :contexts
|
63
127
|
|
64
|
-
|
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
|
-
|
67
|
-
|
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
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
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
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
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
|
-
|
89
|
-
|
90
|
-
|
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
|
-
|
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
|