probatio 0.9.0 → 1.1.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +11 -1
- data/README.md +307 -0
- data/exe/proba +245 -1
- data/lib/probatio/assertions.rb +324 -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,324 @@
|
|
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
|
+
def assert_not_error(*as, &block)
|
162
|
+
|
163
|
+
block = block || as.find { |a| a.is_a?(Proc) }
|
164
|
+
|
165
|
+
err = nil;
|
166
|
+
begin; block.call; rescue => err; end
|
167
|
+
|
168
|
+
return "no error expected but returned #{err.class} #{err.name}" if err
|
169
|
+
|
170
|
+
true
|
171
|
+
end
|
172
|
+
alias assert_no_error assert_not_error
|
173
|
+
|
174
|
+
# Checks whether its "_assert_something", if that's the case,
|
175
|
+
# just flags the assertion as :pending an moves on
|
176
|
+
#
|
177
|
+
def method_missing(name, *args, &block)
|
178
|
+
|
179
|
+
n = name.to_s
|
180
|
+
|
181
|
+
if n.start_with?('_assert') && self.respond_to?(n[1..-1])
|
182
|
+
|
183
|
+
Probatio.despatch(:assertion_pending, self, @__child)
|
184
|
+
|
185
|
+
:pending
|
186
|
+
|
187
|
+
else
|
188
|
+
|
189
|
+
super
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
# Jack of all trade assert
|
194
|
+
#
|
195
|
+
def assert(*as)
|
196
|
+
|
197
|
+
count = {
|
198
|
+
rexes: 0, hashes: 0, arrays: 0, strings: 0, scalars: 0, others: 0 }
|
199
|
+
|
200
|
+
as.each { |a|
|
201
|
+
k =
|
202
|
+
case a
|
203
|
+
when Regexp then :rexes
|
204
|
+
when Hash then :hashes
|
205
|
+
when Array then :arrays
|
206
|
+
when String then :strings
|
207
|
+
else :others; end
|
208
|
+
count[k] = count[k] + 1
|
209
|
+
count[:scalars] += 1 if %i[ rexes strings ].include?(k) }
|
210
|
+
|
211
|
+
if as.length == 1 && count[:hashes] == 1
|
212
|
+
|
213
|
+
assert_hashy(*as)
|
214
|
+
|
215
|
+
elsif as.length == 1
|
216
|
+
|
217
|
+
assert_truthy(*as)
|
218
|
+
|
219
|
+
elsif count[:rexes] == 1 && count[:strings] == as.length - 1
|
220
|
+
|
221
|
+
assert_match(*as)
|
222
|
+
|
223
|
+
#elsif count[:arrays] > 0
|
224
|
+
# assert_include(*as)
|
225
|
+
|
226
|
+
else
|
227
|
+
|
228
|
+
assert_equal(*as)
|
229
|
+
end
|
230
|
+
end
|
231
|
+
|
232
|
+
protected
|
233
|
+
|
234
|
+
def do_assert(as, msg, &block)
|
235
|
+
|
236
|
+
do_assert_(as) do
|
237
|
+
|
238
|
+
rs, ws = as.partition(&block)
|
239
|
+
wi = ws.empty? ? -1 : as.index(ws.first)
|
240
|
+
|
241
|
+
wi == -1 ? true :
|
242
|
+
"not #{msg}: arg[#{wi}]: #{val_to_s(ws.first)}"
|
243
|
+
end
|
244
|
+
end
|
245
|
+
|
246
|
+
def do_assert_(as, &block)
|
247
|
+
|
248
|
+
Probatio.despatch(:assertion_enter, self, @__child)
|
249
|
+
|
250
|
+
case r =
|
251
|
+
begin; block.call; rescue => err; err; end
|
252
|
+
|
253
|
+
when StandardError, Hash, String
|
254
|
+
|
255
|
+
aerr = make_assertion_error(as, r)
|
256
|
+
|
257
|
+
Probatio.despatch(:test_fail, self, @__child, aerr)
|
258
|
+
|
259
|
+
raise aerr
|
260
|
+
|
261
|
+
when Exception
|
262
|
+
|
263
|
+
Probatio.despatch(:test_exception, self, @__child, r)
|
264
|
+
|
265
|
+
raise r
|
266
|
+
|
267
|
+
when :pending
|
268
|
+
|
269
|
+
:pending
|
270
|
+
|
271
|
+
else
|
272
|
+
|
273
|
+
true
|
274
|
+
end
|
275
|
+
|
276
|
+
ensure
|
277
|
+
|
278
|
+
#Probatio.despatch(:test_succeed, self, @__child)
|
279
|
+
Probatio.despatch(:assertion_leave, self, @__child)
|
280
|
+
end
|
281
|
+
|
282
|
+
def make_assertion_error(arguments, result)
|
283
|
+
|
284
|
+
Probatio::AssertionError
|
285
|
+
.new(
|
286
|
+
extract_assert_method(caller),
|
287
|
+
arguments,
|
288
|
+
result,
|
289
|
+
@__child,
|
290
|
+
*extract_file_and_line(caller))
|
291
|
+
end
|
292
|
+
|
293
|
+
def extract_file_and_line(backtrace)
|
294
|
+
|
295
|
+
#l = backtrace.find { |l|
|
296
|
+
# ! l.index('lib/probatio/assertions.rb') &&
|
297
|
+
# ! l.index('_helper.rb') }
|
298
|
+
l = backtrace.find { |l| l.index('_test.rb') }
|
299
|
+
|
300
|
+
m = l && l.match(/([^:]+):(\d+)/)
|
301
|
+
m && [ m[1], m[2].to_i ]
|
302
|
+
end
|
303
|
+
|
304
|
+
def extract_assert_method(backtrace)
|
305
|
+
|
306
|
+
backtrace.inject(nil) { |r, l|
|
307
|
+
r ||
|
308
|
+
begin
|
309
|
+
m = l.match(/[^a-zA-Z_](assert_[a-z0-9_]+)/)
|
310
|
+
m && m[1]
|
311
|
+
end }
|
312
|
+
end
|
313
|
+
|
314
|
+
MAX_VS_LENGTH = 42
|
315
|
+
|
316
|
+
def val_to_s(v)
|
317
|
+
|
318
|
+
vs = v.inspect
|
319
|
+
vs = vs[0, MAX_VS_LENGTH - 1] + '…' if vs.length > MAX_VS_LENGTH
|
320
|
+
|
321
|
+
">#{vs}<"
|
322
|
+
end
|
323
|
+
end
|
324
|
+
|
@@ -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
|
+
|