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