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