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
@@ -0,0 +1,186 @@
|
|
1
|
+
module Command::Results
|
2
|
+
#The end of the Results train. Formatter objects are supposed to output to the user events that they
|
3
|
+
#receive from their presenters. To simplify this process, a number of common IO functions are delegated
|
4
|
+
#to an IO object - usually Command::raw_stdout.
|
5
|
+
#
|
6
|
+
#This class in particular is pretty quiet - probably not helpful for everyday use.
|
7
|
+
#Of course, for some purposes, singleton methods might be very useful
|
8
|
+
class Formatter
|
9
|
+
module Styler
|
10
|
+
Foregrounds = {
|
11
|
+
'black' => 30,
|
12
|
+
'red' => 31,
|
13
|
+
'green' => 32,
|
14
|
+
'yellow' => 33,
|
15
|
+
'blue' => 34,
|
16
|
+
'magenta' => 35,
|
17
|
+
'cyan' => 36,
|
18
|
+
'white' => 37
|
19
|
+
}
|
20
|
+
|
21
|
+
Backgrounds = {}
|
22
|
+
|
23
|
+
Foregrounds.each() do |name, value|
|
24
|
+
Backgrounds[name] = value + 10
|
25
|
+
end
|
26
|
+
|
27
|
+
Extras = {
|
28
|
+
'clear' => 0,
|
29
|
+
'bold' => 1,
|
30
|
+
'underline' => 4,
|
31
|
+
'reversed' => 7
|
32
|
+
}
|
33
|
+
|
34
|
+
def style(text, options)
|
35
|
+
options ||= {}
|
36
|
+
if options.key? :format_advice
|
37
|
+
options = options.merge(options[:format_advice])
|
38
|
+
end
|
39
|
+
aliased = {
|
40
|
+
:foreground => options[:color],
|
41
|
+
:extra => options[:text_style]
|
42
|
+
}
|
43
|
+
options = aliased.merge(options)
|
44
|
+
markup = code_for(Foregrounds, options[:foreground]) +
|
45
|
+
code_for(Backgrounds, options[:background]) +
|
46
|
+
code_for(Extras, options[:extra])
|
47
|
+
return text if markup.empty?
|
48
|
+
return markup + text + code_for(Extras, "clear")
|
49
|
+
end
|
50
|
+
|
51
|
+
def code_for(kind, name)
|
52
|
+
if kind.has_key?(name.to_s)
|
53
|
+
"\e[#{kind[name.to_s]}m"
|
54
|
+
else
|
55
|
+
""
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
extend Forwardable
|
60
|
+
|
61
|
+
class FormatAdvisor
|
62
|
+
def initialize(formatter)
|
63
|
+
@advisee = formatter
|
64
|
+
end
|
65
|
+
|
66
|
+
def list(&block)
|
67
|
+
@advisee.advice[:list] << proc(&block)
|
68
|
+
end
|
69
|
+
|
70
|
+
def item(&block)
|
71
|
+
@advisee.advice[:item] << proc(&block)
|
72
|
+
end
|
73
|
+
|
74
|
+
def output(&block)
|
75
|
+
@advisee.advice[:output] << proc(&block)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def notify(msg, item)
|
80
|
+
if msg == :start
|
81
|
+
start
|
82
|
+
return
|
83
|
+
end
|
84
|
+
if msg == :done
|
85
|
+
finish
|
86
|
+
return
|
87
|
+
end
|
88
|
+
|
89
|
+
apply_advice(item)
|
90
|
+
|
91
|
+
if List === item
|
92
|
+
case msg
|
93
|
+
when :saw_begin
|
94
|
+
saw_begin_list(item)
|
95
|
+
when :saw_end
|
96
|
+
saw_end_list(item)
|
97
|
+
when :arrive
|
98
|
+
closed_begin_list(item)
|
99
|
+
when :leave
|
100
|
+
closed_end_list(item)
|
101
|
+
end
|
102
|
+
else
|
103
|
+
case msg
|
104
|
+
when :arrive
|
105
|
+
closed_item(item)
|
106
|
+
when :saw
|
107
|
+
saw_item(item)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
def initialize(out = nil, err = nil)
|
113
|
+
@out_to = out || ::Command::raw_stdout
|
114
|
+
@err_to = err || ::Command::raw_stderr
|
115
|
+
@advisor = FormatAdvisor.new(self)
|
116
|
+
@advice = {:list => [], :item => [], :output => []}
|
117
|
+
end
|
118
|
+
|
119
|
+
def apply_advice(item)
|
120
|
+
type = List === item ? :list : :item
|
121
|
+
|
122
|
+
item.options[:format_advice] =
|
123
|
+
@advice[type].inject(default_advice(type)) do |advice, advisor|
|
124
|
+
result = advisor[item]
|
125
|
+
break if result == :DONE
|
126
|
+
if Hash === result
|
127
|
+
advice.merge(result)
|
128
|
+
else
|
129
|
+
advice
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
attr_reader :advice
|
135
|
+
|
136
|
+
def receive_advice(&block)
|
137
|
+
@advisor.instance_eval(&block)
|
138
|
+
end
|
139
|
+
|
140
|
+
def default_advice(type)
|
141
|
+
{}
|
142
|
+
end
|
143
|
+
|
144
|
+
def_delegators :@out_to, :p, :puts, :print, :printf, :putc, :write, :write_nonblock, :flush
|
145
|
+
|
146
|
+
def self.inherited(sub)
|
147
|
+
sub.extend Forwardable
|
148
|
+
sub.class_eval do
|
149
|
+
def_delegators :@out_to, :p, :puts, :print, :printf, :putc, :write, :write_nonblock, :flush
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
#Presenter callback: output is beginning
|
154
|
+
def start; end
|
155
|
+
|
156
|
+
#Presenter callback: a list has just started
|
157
|
+
def saw_begin_list(list); end
|
158
|
+
|
159
|
+
#Presenter callback: an item has just been added
|
160
|
+
def saw_item(item); end
|
161
|
+
|
162
|
+
#Presenter callback: a list has just ended
|
163
|
+
def saw_end_list(list); end
|
164
|
+
|
165
|
+
#Presenter callback: a list opened, tree order
|
166
|
+
def closed_begin_list(list); end
|
167
|
+
|
168
|
+
#Presenter callback: an item added, tree order
|
169
|
+
def closed_item(item); end
|
170
|
+
|
171
|
+
#Presenter callback: an list closed, tree order
|
172
|
+
def closed_end_list(list); end
|
173
|
+
|
174
|
+
#Presenter callback: output is done
|
175
|
+
def finish; end
|
176
|
+
end
|
177
|
+
|
178
|
+
#The simplest useful Formatter: it outputs the value of every item in tree order. Think of
|
179
|
+
#it as what would happen if you just let puts and p go directly to the screen, without the
|
180
|
+
#annoying consequences of threading, etc.
|
181
|
+
class TextFormatter < Formatter
|
182
|
+
def closed_item(value)
|
183
|
+
puts value
|
184
|
+
end
|
185
|
+
end
|
186
|
+
end
|
@@ -0,0 +1,243 @@
|
|
1
|
+
require 'command-set/formatter/base'
|
2
|
+
|
3
|
+
module Command::Results
|
4
|
+
class StrategyFormatter < Formatter
|
5
|
+
class FormatStrategy
|
6
|
+
extend Forwardable
|
7
|
+
include Formatter::Styler
|
8
|
+
|
9
|
+
def initialize(name, formatter)
|
10
|
+
@name = name
|
11
|
+
@formatter = formatter
|
12
|
+
setup
|
13
|
+
end
|
14
|
+
|
15
|
+
def setup; end
|
16
|
+
|
17
|
+
def_delegators :@formatter, :p, :puts, :print, :printf, :putc, :write, :write_nonblock, :flush
|
18
|
+
|
19
|
+
attr_reader :name
|
20
|
+
|
21
|
+
def switch_to(name)
|
22
|
+
unless name == self.name
|
23
|
+
return true
|
24
|
+
end
|
25
|
+
return false
|
26
|
+
end
|
27
|
+
|
28
|
+
def finish
|
29
|
+
@formatter.pop_strategy(self.name)
|
30
|
+
end
|
31
|
+
|
32
|
+
#Presenter callback: a list has just started
|
33
|
+
def saw_begin_list(list); end
|
34
|
+
|
35
|
+
#Presenter callback: an item has just been added
|
36
|
+
def saw_item(item); end
|
37
|
+
|
38
|
+
#Presenter callback: a list has just ended
|
39
|
+
def saw_end_list(list); end
|
40
|
+
|
41
|
+
#Presenter callback: a list opened, tree order
|
42
|
+
def closed_begin_list(list);
|
43
|
+
end
|
44
|
+
|
45
|
+
#Presenter callback: an item added, tree order
|
46
|
+
def closed_item(item); end
|
47
|
+
|
48
|
+
#Presenter callback: an list closed, tree order
|
49
|
+
def closed_end_list(list);
|
50
|
+
if list.options[:strategy_start] == self
|
51
|
+
finish
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
def out
|
57
|
+
@formatter.out_to
|
58
|
+
end
|
59
|
+
|
60
|
+
def err
|
61
|
+
@formatter.err_to
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
@strategies = {:default => FormatStrategy}
|
66
|
+
|
67
|
+
class << self
|
68
|
+
def strategy(name, base_klass = FormatStrategy, &def_block)
|
69
|
+
@strategies[name.to_sym] = Class.new(base_klass, &def_block)
|
70
|
+
end
|
71
|
+
|
72
|
+
def inherited(sub)
|
73
|
+
self.instance_variables.each do |var|
|
74
|
+
value = self.instance_variable_get(var)
|
75
|
+
if value.nil?
|
76
|
+
sub.instance_variable_set(var, nil)
|
77
|
+
else
|
78
|
+
sub.instance_variable_set(var, value.dup)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def strategy_set(formatter)
|
84
|
+
set = {}
|
85
|
+
@strategies.each_pair do |name, klass|
|
86
|
+
set[name] = klass.new(name, formatter)
|
87
|
+
end
|
88
|
+
return set
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def initialize(out = nil, err = nil)
|
93
|
+
super(out, err)
|
94
|
+
@strategies = self.class.strategy_set(self)
|
95
|
+
@strategy_stack = [@strategies[:default]]
|
96
|
+
end
|
97
|
+
|
98
|
+
attr_reader :out_to, :err_to
|
99
|
+
|
100
|
+
def_delegators :current_strategy, :saw_begin_list, :saw_item,
|
101
|
+
:saw_end_list
|
102
|
+
|
103
|
+
def current_strategy
|
104
|
+
@strategy_stack.last
|
105
|
+
end
|
106
|
+
|
107
|
+
def push_strategy(name)
|
108
|
+
if @strategies.has_key?(name)
|
109
|
+
@strategy_stack.push(@strategies[name])
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
def pop_strategy(name)
|
114
|
+
if current_strategy.name == name
|
115
|
+
@strategy_stack.pop
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
def closed_begin_list(list)
|
120
|
+
going_to = current_strategy.name
|
121
|
+
unless list.options[:format_advice].nil? or
|
122
|
+
(next_strategy = list.options[:format_advice][:type]).nil? or
|
123
|
+
@strategies[next_strategy].nil? or
|
124
|
+
not current_strategy.switch_to(next_strategy)
|
125
|
+
going_to = next_strategy
|
126
|
+
end
|
127
|
+
push_strategy(going_to)
|
128
|
+
current_strategy.closed_begin_list(list)
|
129
|
+
end
|
130
|
+
|
131
|
+
def closed_end_list(list)
|
132
|
+
current_strategy.closed_end_list(list)
|
133
|
+
current_strategy.finish
|
134
|
+
end
|
135
|
+
|
136
|
+
def closed_item(item)
|
137
|
+
unless item.options[:format_advice].nil? or
|
138
|
+
(once = item.options[:format_advice][:type]).nil? or
|
139
|
+
@strategies[once].nil? or
|
140
|
+
not current_strategy.switch_to(once)
|
141
|
+
@strategies[once].closed_item(item)
|
142
|
+
else
|
143
|
+
current_strategy.closed_item(item)
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
strategy :default do
|
148
|
+
def closed_item(value)
|
149
|
+
puts value
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
strategy :progress do
|
154
|
+
def switch_to(name); false; end
|
155
|
+
def closed_begin_list(list)
|
156
|
+
puts unless list.depth == 0
|
157
|
+
justify = 0 || list[:format_advice][:justify]
|
158
|
+
print style(list.to_s.ljust(justify), list.options)
|
159
|
+
end
|
160
|
+
|
161
|
+
def closed_item(item)
|
162
|
+
print style(".", item.options)
|
163
|
+
flush
|
164
|
+
end
|
165
|
+
|
166
|
+
def closed_end_list(list)
|
167
|
+
puts
|
168
|
+
super
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
strategy :indent do
|
173
|
+
def setup
|
174
|
+
@indent_level = 0
|
175
|
+
end
|
176
|
+
|
177
|
+
def indent
|
178
|
+
return " " * @indent_level
|
179
|
+
end
|
180
|
+
|
181
|
+
def closed_begin_list(list)
|
182
|
+
super
|
183
|
+
puts indent + style(list.to_s, list.options)
|
184
|
+
@indent_level += 1
|
185
|
+
@indent_level
|
186
|
+
end
|
187
|
+
|
188
|
+
def closed_item(item)
|
189
|
+
item.to_s.split(/\s*\n\s*/).each do |line|
|
190
|
+
puts indent + style(line, item.options)
|
191
|
+
end
|
192
|
+
super
|
193
|
+
end
|
194
|
+
|
195
|
+
def closed_end_list(list)
|
196
|
+
@indent_level -= 1
|
197
|
+
@indent_level
|
198
|
+
super
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
strategy :invisible do
|
203
|
+
def closed_item(value); end
|
204
|
+
end
|
205
|
+
|
206
|
+
strategy :skip do
|
207
|
+
def closed_begin_list(list)
|
208
|
+
finish
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
strategy :chatty do
|
213
|
+
def switch_to(name); false; end
|
214
|
+
|
215
|
+
def saw_begin_list(list)
|
216
|
+
err.print style("B", list.options)
|
217
|
+
end
|
218
|
+
|
219
|
+
def saw_item(list)
|
220
|
+
err.print style(".", list.options)
|
221
|
+
end
|
222
|
+
|
223
|
+
def saw_end_list(list)
|
224
|
+
err.print style("E", list.options)
|
225
|
+
end
|
226
|
+
|
227
|
+
def closed_begin_list(list);
|
228
|
+
clean_options = list.options.dup
|
229
|
+
clean_options.delete(:strategy_start)
|
230
|
+
puts "> #{list.to_s} (depth=#{list.depth} #{clean_options.inspect})"
|
231
|
+
end
|
232
|
+
def closed_item(list)
|
233
|
+
puts " " + list.to_s +
|
234
|
+
unless(list.options.empty?)
|
235
|
+
" " + list.options.inspect
|
236
|
+
else
|
237
|
+
""
|
238
|
+
end
|
239
|
+
end
|
240
|
+
def closed_end_list(list); puts "< " + list.to_s; end
|
241
|
+
end
|
242
|
+
end
|
243
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'command-set/formatter/base'
|
2
|
+
|
3
|
+
module Command::Results
|
4
|
+
#A trivial and obvious Formatter: produces well-formed XML fragments based on events. It even
|
5
|
+
#indents. Might be handy for more complicated output processing, since you could feed the document
|
6
|
+
#to a XSLT processor.
|
7
|
+
class XMLFormatter < Formatter
|
8
|
+
def initialize(out = nil, err = nil, indent=" ", newline="\n")
|
9
|
+
super(out, err)
|
10
|
+
@indent = indent
|
11
|
+
@newline = newline
|
12
|
+
@indent_level=0
|
13
|
+
end
|
14
|
+
|
15
|
+
def line(string)
|
16
|
+
print "#{@indent * @indent_level}#{string}#@newline"
|
17
|
+
end
|
18
|
+
|
19
|
+
def closed_begin_list(name)
|
20
|
+
line "<#{name}#{xmlize_options(name)}>"
|
21
|
+
@indent_level += 1
|
22
|
+
end
|
23
|
+
|
24
|
+
def closed_item(value)
|
25
|
+
line "<item value=\"#{value}\"#{xmlize_options(value)} />"
|
26
|
+
end
|
27
|
+
|
28
|
+
def closed_end_list(name)
|
29
|
+
@indent_level -= 1
|
30
|
+
if @indent_level < 0
|
31
|
+
@indent_level = 0
|
32
|
+
return
|
33
|
+
end
|
34
|
+
line "</#{name}>"
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def flatten_value(value)
|
40
|
+
case value
|
41
|
+
when Hash
|
42
|
+
return value.map do |name, value|
|
43
|
+
"#{name}: #{value}"
|
44
|
+
end.join("; ")
|
45
|
+
else
|
46
|
+
return value.to_s
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def xmlize_options(item)
|
51
|
+
item.options.inject("") do |string, (name, value)|
|
52
|
+
string + " #{name}=\"#{flatten_value(value)}\""
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|