probatio 0.9.0 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +6 -1
- data/README.md +301 -0
- data/exe/proba +245 -1
- data/lib/probatio/assertions.rb +311 -0
- data/lib/probatio/debug.rb +35 -0
- data/lib/probatio/examples/a_plugin.rb +14 -0
- data/lib/probatio/examples/a_test.rb +126 -0
- data/lib/probatio/mangle.rb +42 -0
- data/lib/probatio/more.rb +151 -0
- data/lib/probatio/plug.rb +72 -0
- data/lib/probatio/plugins.rb +253 -0
- data/lib/probatio/waiters.rb +44 -0
- data/lib/probatio.rb +773 -157
- data/probatio.gemspec +2 -7
- metadata +17 -8
@@ -0,0 +1,311 @@
|
|
1
|
+
|
2
|
+
#
|
3
|
+
# probatio/assertions.rb
|
4
|
+
|
5
|
+
class Probatio::Context
|
6
|
+
|
7
|
+
# Beware: ws.any? returns false when ws == [ false ]
|
8
|
+
|
9
|
+
def assert_nil(*as)
|
10
|
+
|
11
|
+
do_assert(as, 'nil') { |a| a == nil }
|
12
|
+
end
|
13
|
+
|
14
|
+
def assert_not_nil(*as)
|
15
|
+
|
16
|
+
do_assert(as, 'not nil') { |a| a != nil }
|
17
|
+
end
|
18
|
+
|
19
|
+
def assert_truthy(*as)
|
20
|
+
|
21
|
+
do_assert(as, 'truthy') { |a| !! a }
|
22
|
+
end
|
23
|
+
alias assert_trueish assert_truthy
|
24
|
+
|
25
|
+
def assert_falsey(*as)
|
26
|
+
|
27
|
+
do_assert(as, 'falsey') { |a| ! a }
|
28
|
+
end
|
29
|
+
alias assert_falsy assert_falsey
|
30
|
+
alias assert_falseish assert_falsey
|
31
|
+
|
32
|
+
def assert_true(*as)
|
33
|
+
|
34
|
+
do_assert(as, 'true') { |a| a == true }
|
35
|
+
end
|
36
|
+
|
37
|
+
def assert_false(*as)
|
38
|
+
|
39
|
+
do_assert(as, 'false') { |a| a == false }
|
40
|
+
end
|
41
|
+
|
42
|
+
def assert_any(*as)
|
43
|
+
|
44
|
+
do_assert(as, 'any') { |a| a.respond_to?(:size) && a.size > 0 }
|
45
|
+
end
|
46
|
+
|
47
|
+
def assert_empty(*as)
|
48
|
+
|
49
|
+
do_assert(as, 'empty') { |a| a.respond_to?(:size) && a.size == 0 }
|
50
|
+
end
|
51
|
+
|
52
|
+
def assert_equal(*as)
|
53
|
+
|
54
|
+
do_assert(as, 'equal') { |a| a == as[0] }
|
55
|
+
end
|
56
|
+
|
57
|
+
def assert_match(*as)
|
58
|
+
|
59
|
+
strings, others = as.partition { |a| a.is_a?(String) }
|
60
|
+
rex = others.find { |o| o.is_a?(Regexp) } || strings.pop
|
61
|
+
|
62
|
+
do_assert(strings, 'matched') { |s| s.match?(rex) }
|
63
|
+
end
|
64
|
+
|
65
|
+
def assert_start_with(*as)
|
66
|
+
|
67
|
+
fail ArgumentError.new(
|
68
|
+
"assert_start_with expects strings, not #{x.inspect}") \
|
69
|
+
if as.find { |a| ! a.is_a?(String) }
|
70
|
+
|
71
|
+
sta = as.inject { |s, a| s.length < a.length ? s : a }
|
72
|
+
|
73
|
+
do_assert(as.filter { |a| a != sta }, 'starting with') { |a|
|
74
|
+
a.start_with?(sta) }
|
75
|
+
end
|
76
|
+
|
77
|
+
def assert_end_with(*as)
|
78
|
+
|
79
|
+
fail ArgumentError.new(
|
80
|
+
"assert_end_with expects strings, not #{x.inspect}") \
|
81
|
+
if as.find { |a| ! a.is_a?(String) }
|
82
|
+
|
83
|
+
sta = as.inject { |s, a| s.length < a.length ? s : a }
|
84
|
+
|
85
|
+
do_assert(as.filter { |a| a != sta }, 'ending with') { |a|
|
86
|
+
a.end_with?(sta) }
|
87
|
+
end
|
88
|
+
|
89
|
+
def assert_include(*as)
|
90
|
+
|
91
|
+
ai =
|
92
|
+
as.index { |a| a.is_a?(Array) } ||
|
93
|
+
fail(ArgumentError.new("assert_include found no array"))
|
94
|
+
|
95
|
+
arr = as.delete_at(ai)
|
96
|
+
|
97
|
+
do_assert(as, 'included') { |e| arr.include?(e) }
|
98
|
+
end
|
99
|
+
|
100
|
+
def assert_size(*as)
|
101
|
+
|
102
|
+
ai =
|
103
|
+
as.index { |a| a.is_a?(Integer) && a >= 0 } ||
|
104
|
+
fail(ArgumentError.new("assert_size found no integer >= 0"))
|
105
|
+
|
106
|
+
sz = as.delete_at(ai)
|
107
|
+
|
108
|
+
as = as.collect { |a| a.respond_to?(:size) ? a.size : a.class }
|
109
|
+
|
110
|
+
do_assert(as, "size #{sz}") { |a| a == sz }
|
111
|
+
end
|
112
|
+
alias assert_count assert_size
|
113
|
+
|
114
|
+
def assert_hashy(*as)
|
115
|
+
|
116
|
+
do_assert(as[0].to_a, 'hashy equal') { |k, v| k == v }
|
117
|
+
end
|
118
|
+
|
119
|
+
def assert_instance_of(*as)
|
120
|
+
|
121
|
+
moc = as.find { |a| a.is_a?(Module) }
|
122
|
+
as.delete(moc)
|
123
|
+
|
124
|
+
do_assert(as, 'instance of') { |e| e.is_a?(moc) }
|
125
|
+
end
|
126
|
+
alias assert_is_a assert_instance_of
|
127
|
+
|
128
|
+
def assert_error(*as, &block)
|
129
|
+
|
130
|
+
block = block || as.find { |a| a.is_a?(Proc) }
|
131
|
+
as.delete(block)
|
132
|
+
|
133
|
+
fail ArgumentError.new("assert_error expects a block or a proc") \
|
134
|
+
unless block
|
135
|
+
|
136
|
+
err = nil;
|
137
|
+
begin; block.call; rescue => err; end
|
138
|
+
|
139
|
+
return "no error raised" unless err.is_a?(StandardError)
|
140
|
+
|
141
|
+
as.each do |a|
|
142
|
+
|
143
|
+
case a
|
144
|
+
when String
|
145
|
+
return "error message #{err.message} is not #{a.inspect}" \
|
146
|
+
unless err.message == a
|
147
|
+
when Regexp
|
148
|
+
return "error message #{err.message} did not match #{a.inspect}" \
|
149
|
+
unless err.message.match(a)
|
150
|
+
when Module
|
151
|
+
return "error is of class #{err.class} not #{a.name}" \
|
152
|
+
unless err.is_a?(a)
|
153
|
+
else
|
154
|
+
fail ArgumentError.new("assert_error cannot fathom #{a.inspect}")
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
true
|
159
|
+
end
|
160
|
+
|
161
|
+
# Checks whether its "_assert_something", if that's the case,
|
162
|
+
# just flags the assertion as :pending an moves on
|
163
|
+
#
|
164
|
+
def method_missing(name, *args, &block)
|
165
|
+
|
166
|
+
n = name.to_s
|
167
|
+
|
168
|
+
if n.start_with?('_assert') && self.respond_to?(n[1..-1])
|
169
|
+
|
170
|
+
Probatio.despatch(:assertion_pending, self, @__child)
|
171
|
+
|
172
|
+
:pending
|
173
|
+
|
174
|
+
else
|
175
|
+
|
176
|
+
super
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
# Jack of all trade assert
|
181
|
+
#
|
182
|
+
def assert(*as)
|
183
|
+
|
184
|
+
count = {
|
185
|
+
rexes: 0, hashes: 0, arrays: 0, strings: 0, scalars: 0, others: 0 }
|
186
|
+
|
187
|
+
as.each { |a|
|
188
|
+
k =
|
189
|
+
case a
|
190
|
+
when Regexp then :rexes
|
191
|
+
when Hash then :hashes
|
192
|
+
when Array then :arrays
|
193
|
+
when String then :strings
|
194
|
+
else :others; end
|
195
|
+
count[k] = count[k] + 1
|
196
|
+
count[:scalars] += 1 if %i[ rexes strings ].include?(k) }
|
197
|
+
|
198
|
+
if as.length == 1 && count[:hashes] == 1
|
199
|
+
|
200
|
+
assert_hashy(*as)
|
201
|
+
|
202
|
+
elsif as.length == 1
|
203
|
+
|
204
|
+
assert_truthy(*as)
|
205
|
+
|
206
|
+
elsif count[:rexes] == 1 && count[:strings] == as.length - 1
|
207
|
+
|
208
|
+
assert_match(*as)
|
209
|
+
|
210
|
+
#elsif count[:arrays] > 0
|
211
|
+
# assert_include(*as)
|
212
|
+
|
213
|
+
else
|
214
|
+
|
215
|
+
assert_equal(*as)
|
216
|
+
end
|
217
|
+
end
|
218
|
+
|
219
|
+
protected
|
220
|
+
|
221
|
+
def do_assert(as, msg, &block)
|
222
|
+
|
223
|
+
do_assert_(as) do
|
224
|
+
|
225
|
+
rs, ws = as.partition(&block)
|
226
|
+
wi = ws.empty? ? -1 : as.index(ws.first)
|
227
|
+
|
228
|
+
wi == -1 ? true :
|
229
|
+
"not #{msg}: arg[#{wi}]: #{val_to_s(ws.first)}"
|
230
|
+
end
|
231
|
+
end
|
232
|
+
|
233
|
+
def do_assert_(as, &block)
|
234
|
+
|
235
|
+
Probatio.despatch(:assertion_enter, self, @__child)
|
236
|
+
|
237
|
+
case r =
|
238
|
+
begin; block.call; rescue => err; err; end
|
239
|
+
|
240
|
+
when StandardError, Hash, String
|
241
|
+
|
242
|
+
aerr = make_assertion_error(as, r)
|
243
|
+
|
244
|
+
Probatio.despatch(:test_fail, self, @__child, aerr)
|
245
|
+
|
246
|
+
raise aerr
|
247
|
+
|
248
|
+
when Exception
|
249
|
+
|
250
|
+
Probatio.despatch(:test_exception, self, @__child, r)
|
251
|
+
|
252
|
+
raise r
|
253
|
+
|
254
|
+
when :pending
|
255
|
+
|
256
|
+
:pending
|
257
|
+
|
258
|
+
else
|
259
|
+
|
260
|
+
true
|
261
|
+
end
|
262
|
+
|
263
|
+
ensure
|
264
|
+
|
265
|
+
#Probatio.despatch(:test_succeed, self, @__child)
|
266
|
+
Probatio.despatch(:assertion_leave, self, @__child)
|
267
|
+
end
|
268
|
+
|
269
|
+
def make_assertion_error(arguments, result)
|
270
|
+
|
271
|
+
Probatio::AssertionError
|
272
|
+
.new(
|
273
|
+
extract_assert_method(caller),
|
274
|
+
arguments,
|
275
|
+
result,
|
276
|
+
@__child,
|
277
|
+
*extract_file_and_line(caller))
|
278
|
+
end
|
279
|
+
|
280
|
+
def extract_file_and_line(backtrace)
|
281
|
+
|
282
|
+
#l = backtrace.find { |l|
|
283
|
+
# ! l.index('lib/probatio/assertions.rb') &&
|
284
|
+
# ! l.index('_helper.rb') }
|
285
|
+
l = backtrace.find { |l| l.index('_test.rb') }
|
286
|
+
|
287
|
+
m = l && l.match(/([^:]+):(\d+)/)
|
288
|
+
m && [ m[1], m[2].to_i ]
|
289
|
+
end
|
290
|
+
|
291
|
+
def extract_assert_method(backtrace)
|
292
|
+
|
293
|
+
backtrace.inject(nil) { |r, l|
|
294
|
+
r ||
|
295
|
+
begin
|
296
|
+
m = l.match(/[^a-zA-Z_](assert_[a-z0-9_]+)/)
|
297
|
+
m && m[1]
|
298
|
+
end }
|
299
|
+
end
|
300
|
+
|
301
|
+
MAX_VS_LENGTH = 42
|
302
|
+
|
303
|
+
def val_to_s(v)
|
304
|
+
|
305
|
+
vs = v.inspect
|
306
|
+
vs = vs[0, MAX_VS_LENGTH - 1] + '…' if vs.length > MAX_VS_LENGTH
|
307
|
+
|
308
|
+
">#{vs}<"
|
309
|
+
end
|
310
|
+
end
|
311
|
+
|
@@ -0,0 +1,35 @@
|
|
1
|
+
|
2
|
+
#
|
3
|
+
# probatio/debug.rb
|
4
|
+
|
5
|
+
module Probatio
|
6
|
+
|
7
|
+
module DebugMethods
|
8
|
+
|
9
|
+
def debug(*as, &block)
|
10
|
+
|
11
|
+
return unless $_PROBATIO_DEBUG.include?(as.shift)
|
12
|
+
|
13
|
+
$stderr.print $_PROBATIO_COLOURS.green
|
14
|
+
$stderr.puts(*as) if as.any?
|
15
|
+
$stderr.puts block.call if block
|
16
|
+
$stderr.print $_PROBATIO_COLOURS.reset
|
17
|
+
end
|
18
|
+
|
19
|
+
def dbg_s(*as, &block)
|
20
|
+
|
21
|
+
debug(:s, *as, &block)
|
22
|
+
end
|
23
|
+
|
24
|
+
def dbg_m(*as, &block)
|
25
|
+
|
26
|
+
debug(:m, *as, &block)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
class << self
|
31
|
+
|
32
|
+
include DebugMethods
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
@@ -0,0 +1,126 @@
|
|
1
|
+
|
2
|
+
group 'core' do
|
3
|
+
|
4
|
+
setup do
|
5
|
+
# occurs once before tests and sub-groups in group 'core'
|
6
|
+
end
|
7
|
+
teadowm do
|
8
|
+
# occurs once after tests and sub-groups in group 'core'
|
9
|
+
end
|
10
|
+
|
11
|
+
group 'alpha' do
|
12
|
+
|
13
|
+
before do
|
14
|
+
# is run in the separate test context before _each_ test
|
15
|
+
end
|
16
|
+
after do
|
17
|
+
# is run in the separate test context after _each_ test
|
18
|
+
end
|
19
|
+
|
20
|
+
test 'one' do
|
21
|
+
|
22
|
+
MyLib.do_this_or_that()
|
23
|
+
|
24
|
+
assert_nil nil
|
25
|
+
assert_not_nil [], 1
|
26
|
+
|
27
|
+
assert_true true
|
28
|
+
assert_false 1 > 2
|
29
|
+
|
30
|
+
assert_truthy "yes", "no"
|
31
|
+
assert_trueish "yes", "no"
|
32
|
+
assert_falsy nil, false
|
33
|
+
assert_falsey nil, false
|
34
|
+
assert_falseish nil, false
|
35
|
+
|
36
|
+
assert_any %w[ an array ], 'a string'
|
37
|
+
assert_empty [], ''
|
38
|
+
|
39
|
+
assert_size [], 0
|
40
|
+
assert_size 'foo', 3
|
41
|
+
assert_size { a: 1 }, 1
|
42
|
+
assert_count %w[ a b c ], 3
|
43
|
+
|
44
|
+
assert_equal 'one', 'o' + 'ne'
|
45
|
+
# checks that all its arguments are equal
|
46
|
+
|
47
|
+
assert_match 'one', /^one$/
|
48
|
+
# checks that it receives a regex and one or more strings
|
49
|
+
# and that all those strings match the regex
|
50
|
+
|
51
|
+
assert_start_with 'one', 'one two or three'
|
52
|
+
# checks that the shortest string is the start of the remaining string
|
53
|
+
# arguments
|
54
|
+
assert_end_with 'three', 'one two or three'
|
55
|
+
# checks that the shortest string is the end of the remaining string
|
56
|
+
# arguments
|
57
|
+
|
58
|
+
assert_include 1, [ 1, 'two' ]
|
59
|
+
# checks that the first array argument includes all other arguments
|
60
|
+
|
61
|
+
assert_error(ArgumentError) { do_this_or_that() }
|
62
|
+
# checks that the given block raises an ArgumentError
|
63
|
+
assert_error(ArgumentError, /bad/) { do_this_or_that() }
|
64
|
+
# checks that the given block raises an ArgumentError and
|
65
|
+
# the error message matches the /bad/ regexp
|
66
|
+
assert_error lambda { do_this_or_that() }, ArgumentError
|
67
|
+
# checks that the given Proc raises an ArgumentError
|
68
|
+
assert_error lambda { do_this_or_that() }, ArgumentError, 'bad'
|
69
|
+
# checks that the given Proc raises an ArgumentError and
|
70
|
+
# the error message == "bad"
|
71
|
+
|
72
|
+
assert_hashy(
|
73
|
+
this_thing => 1,
|
74
|
+
that_thing => 'two')
|
75
|
+
# combines two assert_equals in one
|
76
|
+
|
77
|
+
assert_instance_of 1, Integer
|
78
|
+
assert_is_a Integer, 123
|
79
|
+
# checks that value or set of values are of a given of class
|
80
|
+
|
81
|
+
assert 1, 1
|
82
|
+
# behaves like assert_equal
|
83
|
+
assert 'one', /one/i
|
84
|
+
# behaves like assert_match
|
85
|
+
assert 11 => '10'.to_i + 1
|
86
|
+
# assert equality between key and value
|
87
|
+
assert 'one' => 'on' + 'e', 'two' => :two.to_s
|
88
|
+
# assert equality between keys and values
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
group 'bravo' do
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
group 'core' do
|
97
|
+
#
|
98
|
+
# it's OK to re-open a group to add sub-groups, tests,
|
99
|
+
# and setups, teardowns, befores, or afters
|
100
|
+
#
|
101
|
+
# it's OK to re-open a group in another file, as long
|
102
|
+
# as it's the same name at the same point in the name hierarchy
|
103
|
+
|
104
|
+
_test 'not yet' do
|
105
|
+
#
|
106
|
+
# prefix a test, a group, or other element with _
|
107
|
+
# marks it as _pending
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
group 'core', 'sub-core', 'sub-sub-core' do
|
112
|
+
#
|
113
|
+
# it's OK to specifiy a path of group names
|
114
|
+
|
115
|
+
test 'this' do
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
group 'core < sub-core < sub-sub-core' do
|
120
|
+
#
|
121
|
+
# this is also ok...
|
122
|
+
|
123
|
+
test 'that' do
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
@@ -0,0 +1,42 @@
|
|
1
|
+
|
2
|
+
#
|
3
|
+
# probatio/mangle.rb
|
4
|
+
|
5
|
+
module Probatio
|
6
|
+
|
7
|
+
class << self
|
8
|
+
|
9
|
+
def mangle(dirs, files, switches)
|
10
|
+
|
11
|
+
dry = switches['-y'] || switches['--dry']
|
12
|
+
|
13
|
+
(
|
14
|
+
dirs.collect { |d| Dir[File.join(d, '**', '*_spec.rb')] }.flatten +
|
15
|
+
files.select { |f| f.match?(/_spec\.rb$/) }
|
16
|
+
)
|
17
|
+
.each do |path|
|
18
|
+
|
19
|
+
puts c.dark_gray(". considering #{c.green(path)}")
|
20
|
+
p1 = path[0..-8] + 'test.rb'
|
21
|
+
puts c.dark_gray(" .. writing to #{c.light_green(p1)}")
|
22
|
+
|
23
|
+
next if dry
|
24
|
+
|
25
|
+
File.open(p1, 'wb') do |f|
|
26
|
+
f.write(
|
27
|
+
File.read(path)
|
28
|
+
.gsub(/^(\s*)describe(\s+)/) { "#{$1}group#{$2}" }
|
29
|
+
.gsub(/^(\s*)context(\s+)/) { "#{$1}group#{$2}" }
|
30
|
+
.gsub(/^(\s*)it(\s+)/) { "#{$1}test#{$2}" }
|
31
|
+
.gsub(/^(\s*)expect(\(|\s)/) { "#{$1}assert#{$2}" }
|
32
|
+
.gsub(/\)\.to (eq|match)\(/) { ', ' }
|
33
|
+
.gsub(/\n\s*, */) { ",\n" }
|
34
|
+
)
|
35
|
+
end
|
36
|
+
|
37
|
+
puts c.dark_gray(" .. wrote to #{c.light_green(p1)}")
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
@@ -0,0 +1,151 @@
|
|
1
|
+
|
2
|
+
#
|
3
|
+
# probatio/more.rb
|
4
|
+
|
5
|
+
|
6
|
+
module Probatio; class << self
|
7
|
+
|
8
|
+
def monow; @ts = Process.clock_gettime(Process::CLOCK_MONOTONIC); end
|
9
|
+
def monow_and_delta; ts0, ts1 = @ts, monow; [ ts1, ts1 - (ts0 || ts1) ]; end
|
10
|
+
|
11
|
+
def seconds_to_time_s(f)
|
12
|
+
|
13
|
+
i = f.to_i
|
14
|
+
d = i / (24 * 3600); i = i % (24 * 3600)
|
15
|
+
h = i / 3600; i = i % 3600
|
16
|
+
m = i / 60; i = i % 60
|
17
|
+
s = i
|
18
|
+
|
19
|
+
ms = ((
|
20
|
+
f < 1 ? '%0.6f' :
|
21
|
+
f < 60 ? '%0.3f' :
|
22
|
+
''
|
23
|
+
) % (f % 1.0))[2..-1] || ''
|
24
|
+
ms = ms.insert(3, '_') if ms.length > 3
|
25
|
+
|
26
|
+
[ d > 0 ? "#{d}d" : nil,
|
27
|
+
h > 0 ? "#{h}h" : nil,
|
28
|
+
m > 0 ? "#{m}m" : nil,
|
29
|
+
"#{s}s",
|
30
|
+
"#{ms}" ].compact.join('')
|
31
|
+
end
|
32
|
+
alias to_time_s seconds_to_time_s
|
33
|
+
|
34
|
+
|
35
|
+
def to_rexes_and_strs(a)
|
36
|
+
|
37
|
+
a && a.collect { |e| to_rex_or_str(e) }
|
38
|
+
end
|
39
|
+
|
40
|
+
def to_rex_or_str(s)
|
41
|
+
|
42
|
+
m = s.match(/^\/(.+)\/([imx]*)$/); return s unless m
|
43
|
+
|
44
|
+
pat = m[1]; opts = m[2]
|
45
|
+
|
46
|
+
ropts = opts.each_char.inject(0) { |r, c|
|
47
|
+
case c
|
48
|
+
when 'i' then r |= Regexp::IGNORECASE
|
49
|
+
#when 'm' then r |= Regexp::MULTILINE
|
50
|
+
when 'x' then r |= Regexp::EXTENDED
|
51
|
+
else r; end }
|
52
|
+
|
53
|
+
Regexp.new(pat, ropts)
|
54
|
+
end
|
55
|
+
|
56
|
+
def term_width
|
57
|
+
|
58
|
+
(IO.console.winsize[1] rescue nil) ||
|
59
|
+
(`tput cols`.strip.to_i rescue nil) ||
|
60
|
+
80
|
61
|
+
end
|
62
|
+
end; end
|
63
|
+
|
64
|
+
|
65
|
+
module Cerata; class << self
|
66
|
+
|
67
|
+
# TODO deal with double width characters...
|
68
|
+
|
69
|
+
def horizontal_a_to_s(a, indent='')
|
70
|
+
|
71
|
+
return '[]' if a.empty?
|
72
|
+
|
73
|
+
o = StringIO.new
|
74
|
+
|
75
|
+
o << indent << '[ '
|
76
|
+
a1 = a.dup; while e = a1.shift
|
77
|
+
o << e.inspect
|
78
|
+
o << ', ' if a1.any?
|
79
|
+
end
|
80
|
+
o << ' ]'
|
81
|
+
|
82
|
+
o.string
|
83
|
+
end
|
84
|
+
|
85
|
+
def horizontal_h_to_s(h, indent='')
|
86
|
+
|
87
|
+
return '{}' if h.empty?
|
88
|
+
|
89
|
+
o = StringIO.new
|
90
|
+
|
91
|
+
o << indent << '{ '
|
92
|
+
kvs = h.to_a; while kv = kvs.shift
|
93
|
+
o << "#{kv.first}: " << kv[1].inspect
|
94
|
+
o << ', ' if kvs.any?
|
95
|
+
end
|
96
|
+
o << ' }'
|
97
|
+
|
98
|
+
o.string
|
99
|
+
end
|
100
|
+
|
101
|
+
def vertical_h_to_s(h, indent='')
|
102
|
+
|
103
|
+
return '{}' if h.empty?
|
104
|
+
|
105
|
+
o = StringIO.new
|
106
|
+
|
107
|
+
o << indent << "{\n"
|
108
|
+
h.each { |k, v| o << indent << "#{k}: " << v.inspect << ",\n" }
|
109
|
+
o << indent << '}'
|
110
|
+
|
111
|
+
o.string
|
112
|
+
end
|
113
|
+
|
114
|
+
# A "table" here is an array of hashes
|
115
|
+
#
|
116
|
+
def table_to_s(a, indent='')
|
117
|
+
|
118
|
+
return '[]' if a.empty?
|
119
|
+
|
120
|
+
all_keys =
|
121
|
+
a.collect { |h| h.keys }.flatten.uniq.map(&:to_s)
|
122
|
+
key_widths =
|
123
|
+
all_keys.inject({}) { |h, k| h[k] = k.length; h }
|
124
|
+
val_widths =
|
125
|
+
a.inject({}) { |w, h|
|
126
|
+
h.each { |k, v| k = k.to_s; w[k] = [ w[k] || 0, v.inspect.length ].max }
|
127
|
+
w }
|
128
|
+
|
129
|
+
o = StringIO.new
|
130
|
+
|
131
|
+
o << indent << "[\n"
|
132
|
+
|
133
|
+
a.each do |h|
|
134
|
+
o << indent << '{ '
|
135
|
+
kvs = h.to_a; while kv = kvs.shift
|
136
|
+
k, v = kv[0].to_s, kv[1].inspect
|
137
|
+
kl, vl = key_widths[k], val_widths[k]
|
138
|
+
kf = "%#{kl}s"
|
139
|
+
vf = v.start_with?('"') ? "%#{vl}s" : "%-#{vl}s"
|
140
|
+
o << ("#{kf}: #{vf}" % [ k, v ])
|
141
|
+
o << ', ' if kvs.any?
|
142
|
+
end
|
143
|
+
o << " },\n"
|
144
|
+
end
|
145
|
+
|
146
|
+
o << indent << ']'
|
147
|
+
|
148
|
+
o.string
|
149
|
+
end
|
150
|
+
end; end
|
151
|
+
|