ruby-ole 1.2.8.2 → 1.2.9
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/ChangeLog +4 -0
- data/lib/ole/ranges_io.rb +78 -49
- data/lib/ole/storage/base.rb +11 -10
- data/test/test_ranges_io.rb +1 -8
- data/test/test_storage.rb +11 -45
- metadata +18 -18
data/ChangeLog
CHANGED
data/lib/ole/ranges_io.rb
CHANGED
@@ -57,23 +57,14 @@ class RangesIO
|
|
57
57
|
@params = {:close_parent => false}.merge params
|
58
58
|
@mode = IO::Mode.new mode
|
59
59
|
@io = io
|
60
|
-
# convert ranges to arrays. check for negative ranges?
|
61
|
-
ranges ||= [0, io.size]
|
62
|
-
@ranges = ranges.map { |r| Range === r ? [r.begin, r.end - r.begin] : r }
|
63
|
-
# calculate size
|
64
|
-
@size = @ranges.inject(0) { |total, (pos, len)| total + len }
|
65
60
|
# initial position in the file
|
66
61
|
@pos = 0
|
67
|
-
|
62
|
+
self.ranges = ranges || [[0, io.size]]
|
68
63
|
# handle some mode flags
|
69
64
|
truncate 0 if @mode.truncate?
|
70
65
|
seek size if @mode.append?
|
71
66
|
end
|
72
|
-
|
73
|
-
#IOError: closed stream
|
74
|
-
# get this for reading, writing, everything...
|
75
|
-
#IOError: not opened for writing
|
76
|
-
|
67
|
+
|
77
68
|
# add block form. TODO add test for this
|
78
69
|
def self.open(*args, &block)
|
79
70
|
ranges_io = new(*args)
|
@@ -86,6 +77,36 @@ class RangesIO
|
|
86
77
|
end
|
87
78
|
end
|
88
79
|
|
80
|
+
def ranges= ranges
|
81
|
+
# convert ranges to arrays. check for negative ranges?
|
82
|
+
ranges = ranges.map { |r| Range === r ? [r.begin, r.end - r.begin] : r }
|
83
|
+
# combine ranges
|
84
|
+
if @params[:combine] == false
|
85
|
+
# might be useful for debugging...
|
86
|
+
@ranges = ranges
|
87
|
+
else
|
88
|
+
@ranges = []
|
89
|
+
next_pos = nil
|
90
|
+
ranges.each do |pos, len|
|
91
|
+
if next_pos == pos
|
92
|
+
@ranges.last[1] += len
|
93
|
+
next_pos += len
|
94
|
+
else
|
95
|
+
@ranges << [pos, len]
|
96
|
+
next_pos = pos + len
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
# calculate cumulative offsets from range sizes
|
101
|
+
@size = 0
|
102
|
+
@offsets = []
|
103
|
+
@ranges.each do |pos, len|
|
104
|
+
@offsets << @size
|
105
|
+
@size += len
|
106
|
+
end
|
107
|
+
self.pos = @pos
|
108
|
+
end
|
109
|
+
|
89
110
|
def pos= pos, whence=IO::SEEK_SET
|
90
111
|
case whence
|
91
112
|
when IO::SEEK_SET
|
@@ -95,30 +116,36 @@ class RangesIO
|
|
95
116
|
pos = @size + pos
|
96
117
|
else raise Errno::EINVAL
|
97
118
|
end
|
98
|
-
raise Errno::EINVAL unless (0
|
119
|
+
raise Errno::EINVAL unless (0..@size) === pos
|
99
120
|
@pos = pos
|
121
|
+
|
122
|
+
# do a binary search throuh @offsets to find the active range.
|
123
|
+
a, c, b = 0, 0, @offsets.length
|
124
|
+
while a < b
|
125
|
+
c = (a + b) / 2
|
126
|
+
pivot = @offsets[c]
|
127
|
+
if pos == pivot
|
128
|
+
@active = c
|
129
|
+
return
|
130
|
+
elsif pos < pivot
|
131
|
+
b = c
|
132
|
+
else
|
133
|
+
a = c + 1
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
@active = a - 1
|
100
138
|
end
|
101
139
|
|
102
140
|
alias seek :pos=
|
103
141
|
alias tell :pos
|
104
142
|
|
105
|
-
def
|
106
|
-
|
143
|
+
def rewind
|
144
|
+
seek 0
|
107
145
|
end
|
108
146
|
|
109
|
-
|
110
|
-
|
111
|
-
def offset_and_size pos
|
112
|
-
total = 0
|
113
|
-
ranges.each_with_index do |(offset, size), i|
|
114
|
-
if pos <= total + size
|
115
|
-
diff = pos - total
|
116
|
-
return [offset + diff, size - diff], i
|
117
|
-
end
|
118
|
-
total += size
|
119
|
-
end
|
120
|
-
# should be impossible for any valid pos, (0...size) === pos
|
121
|
-
raise ArgumentError, "no range for pos #{pos.inspect}"
|
147
|
+
def close
|
148
|
+
@io.close if @params[:close_parent]
|
122
149
|
end
|
123
150
|
|
124
151
|
def eof?
|
@@ -130,24 +157,26 @@ class RangesIO
|
|
130
157
|
data = ''
|
131
158
|
return data if eof?
|
132
159
|
limit ||= size
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
160
|
+
pos, len = @ranges[@active]
|
161
|
+
diff = @pos - @offsets[@active]
|
162
|
+
pos += diff
|
163
|
+
len -= diff
|
164
|
+
loop do
|
138
165
|
@io.seek pos
|
139
166
|
if limit < len
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
break
|
167
|
+
s = @io.read(limit).to_s
|
168
|
+
@pos += s.length
|
169
|
+
data << s
|
170
|
+
break
|
144
171
|
end
|
145
|
-
|
146
|
-
|
147
|
-
@pos += s.length if s
|
172
|
+
s = @io.read(len).to_s
|
173
|
+
@pos += s.length
|
148
174
|
data << s
|
149
175
|
break if s.length != len
|
150
176
|
limit -= len
|
177
|
+
break if @active == @ranges.length - 1
|
178
|
+
@active += 1
|
179
|
+
pos, len = @ranges[@active]
|
151
180
|
end
|
152
181
|
data
|
153
182
|
end
|
@@ -164,8 +193,6 @@ class RangesIO
|
|
164
193
|
end
|
165
194
|
|
166
195
|
def write data
|
167
|
-
# short cut. needed because truncate 0 may return no ranges, instead of empty range,
|
168
|
-
# thus offset_and_size fails.
|
169
196
|
return 0 if data.empty?
|
170
197
|
data_pos = 0
|
171
198
|
# if we don't have room, we can use the truncate hook to make more space.
|
@@ -176,8 +203,11 @@ class RangesIO
|
|
176
203
|
raise IOError, "unable to grow #{inspect} to write #{data.length} bytes"
|
177
204
|
end
|
178
205
|
end
|
179
|
-
|
180
|
-
|
206
|
+
pos, len = @ranges[@active]
|
207
|
+
diff = @pos - @offsets[@active]
|
208
|
+
pos += diff
|
209
|
+
len -= diff
|
210
|
+
loop do
|
181
211
|
@io.seek pos
|
182
212
|
if data_pos + len > data.length
|
183
213
|
chunk = data[data_pos..-1]
|
@@ -189,6 +219,9 @@ class RangesIO
|
|
189
219
|
@io.write data[data_pos, len]
|
190
220
|
@pos += len
|
191
221
|
data_pos += len
|
222
|
+
break if @active == @ranges.length - 1
|
223
|
+
@active += 1
|
224
|
+
pos, len = @ranges[@active]
|
192
225
|
end
|
193
226
|
data_pos
|
194
227
|
end
|
@@ -202,17 +235,13 @@ class RangesIO
|
|
202
235
|
def gets
|
203
236
|
s = read 1024
|
204
237
|
i = s.index "\n"
|
205
|
-
|
238
|
+
self.pos -= s.length - (i+1)
|
206
239
|
s[0..i]
|
207
240
|
end
|
208
241
|
alias readline :gets
|
209
242
|
|
210
243
|
def inspect
|
211
|
-
|
212
|
-
pos, len = (@ranges[offset_and_size(@pos).last] rescue [nil, nil])
|
213
|
-
range_str = pos ? "#{pos}..#{pos+len}" : 'nil'
|
214
|
-
"#<#{self.class} io=#{io.inspect}, size=#@size, pos=#@pos, "\
|
215
|
-
"range=#{range_str}>"
|
244
|
+
"#<#{self.class} io=#{io.inspect}, size=#{@size}, pos=#{@pos}>"
|
216
245
|
end
|
217
246
|
end
|
218
247
|
|
data/lib/ole/storage/base.rb
CHANGED
@@ -21,7 +21,7 @@ module Ole # :nodoc:
|
|
21
21
|
class FormatError < StandardError # :nodoc:
|
22
22
|
end
|
23
23
|
|
24
|
-
VERSION = '1.2.
|
24
|
+
VERSION = '1.2.9'
|
25
25
|
|
26
26
|
# options used at creation time
|
27
27
|
attr_reader :params
|
@@ -61,7 +61,10 @@ module Ole # :nodoc:
|
|
61
61
|
else
|
62
62
|
@io.flush
|
63
63
|
# this is for the benefit of ruby-1.9
|
64
|
-
|
64
|
+
# generates warnings on jruby though... :/
|
65
|
+
if RUBY_PLATFORM != 'java' and @io.respond_to?(:syswrite)
|
66
|
+
@io.syswrite('')
|
67
|
+
end
|
65
68
|
true
|
66
69
|
end
|
67
70
|
rescue IOError
|
@@ -602,8 +605,8 @@ module Ole # :nodoc:
|
|
602
605
|
# note that old_blocks is != @ranges.length necessarily. i'm planning to write a
|
603
606
|
# merge_ranges function that merges sequential ranges into one as an optimization.
|
604
607
|
@bat.resize_chain @blocks, size
|
605
|
-
@
|
606
|
-
|
608
|
+
@pos = size if @pos > size
|
609
|
+
self.ranges = @bat.ranges(@blocks, size)
|
607
610
|
self.first_block = @blocks.empty? ? AllocationTable::EOC : @blocks.first
|
608
611
|
|
609
612
|
# don't know if this is required, but we explicitly request our @io to grow if necessary
|
@@ -612,8 +615,6 @@ module Ole # :nodoc:
|
|
612
615
|
# maybe its ok to just seek out there later??
|
613
616
|
max = @ranges.map { |pos, len| pos + len }.max || 0
|
614
617
|
@io.truncate max if max > @io.size
|
615
|
-
|
616
|
-
@size = size
|
617
618
|
end
|
618
619
|
end
|
619
620
|
|
@@ -633,8 +634,8 @@ module Ole # :nodoc:
|
|
633
634
|
# bat migration needed! we need to backup some data. the amount of data
|
634
635
|
# should be <= @ole.header.threshold, so we can just hold it all in one buffer.
|
635
636
|
# backup this
|
636
|
-
pos = @pos
|
637
|
-
|
637
|
+
pos = [@pos, size].min
|
638
|
+
self.pos = 0
|
638
639
|
keep = read [@size, size].min
|
639
640
|
# this does a normal truncate to 0, removing our presence from the old bat, and
|
640
641
|
# rewrite the dirent's first_block
|
@@ -645,9 +646,9 @@ module Ole # :nodoc:
|
|
645
646
|
# important to do this now, before the write. as the below write will always
|
646
647
|
# migrate us back to sbat! this will now allocate us +size+ in the new bat.
|
647
648
|
super
|
648
|
-
|
649
|
+
self.pos = 0
|
649
650
|
write keep
|
650
|
-
|
651
|
+
self.pos = pos
|
651
652
|
else
|
652
653
|
super
|
653
654
|
end
|
data/test/test_ranges_io.rb
CHANGED
@@ -31,20 +31,13 @@ class TestRangesIO < Test::Unit::TestCase
|
|
31
31
|
|
32
32
|
def test_basics
|
33
33
|
assert_equal 160, @io.size
|
34
|
-
assert_match %r{size=160
|
34
|
+
assert_match %r{size=160}, @io.inspect
|
35
35
|
end
|
36
36
|
|
37
37
|
def test_truncate
|
38
38
|
assert_raises(NotImplementedError) { @io.size += 10 }
|
39
39
|
end
|
40
40
|
|
41
|
-
def test_offset_and_size
|
42
|
-
assert_equal [[100, 100], 0], @io.offset_and_size(0)
|
43
|
-
assert_equal [[150, 50], 0], @io.offset_and_size(50)
|
44
|
-
assert_equal [[5, 5], 1], @io.offset_and_size(105)
|
45
|
-
assert_raises(ArgumentError) { @io.offset_and_size 1000 }
|
46
|
-
end
|
47
|
-
|
48
41
|
def test_seek
|
49
42
|
@io.pos = 10
|
50
43
|
@io.seek(-100, IO::SEEK_END)
|
data/test/test_storage.rb
CHANGED
@@ -1,14 +1,13 @@
|
|
1
1
|
#! /usr/bin/ruby
|
2
2
|
|
3
3
|
$: << File.dirname(__FILE__) + '/../lib'
|
4
|
+
#require 'rubygems'
|
4
5
|
|
5
6
|
require 'test/unit'
|
6
7
|
require 'ole/storage'
|
7
8
|
require 'digest/sha1'
|
8
9
|
require 'stringio'
|
9
10
|
require 'tempfile'
|
10
|
-
require 'zlib'
|
11
|
-
require 'base64'
|
12
11
|
|
13
12
|
#
|
14
13
|
# = TODO
|
@@ -108,49 +107,18 @@ class TestStorageRead < Test::Unit::TestCase
|
|
108
107
|
def test_read
|
109
108
|
# the regular String#hash was different on the mac, so asserting
|
110
109
|
# against full strings. switch this to sha1 instead of this fugly blob
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
eJztVs9rE0EU/mZ32m5smyZtUlKQunhIezAV6knwYGoRxRLQBG89zCabdiE7
|
118
|
-
K9kJpt7Es1DwLxCsP+jJqycv/S/8I9SrmPgmuy0pK1ilNZd8MPt233zz7dud
|
119
|
-
H+99OXBR+pziILgYgglkqaUvIYFs0pWEBSyzIfsHLKeA3Fl0Y6wTX4d2K7bH
|
120
|
-
uEvP6i90zhuf6P21fxpp0C9nOMOvGuMcUQ1811ZuqOjSVWuzszm8eb52cFR+
|
121
|
-
K/k7yd9L/kHyV6OO8gJBezwT7/UVaj/xY9QRjXHhMDoM5rapZ39o/ntRZ2+0
|
122
|
-
sY3xv0C5xkhT1rXo7gHmBr5VWg7f+gbZqU23KTotSqaT5L+Ba2waDmjR1AsQ
|
123
|
-
qZnf6BVRvv29/5rsUtkJhXpWqiohG6LdCOu7ba+pRFud5veuMBisiKl7rmh4
|
124
|
-
ckfn8jnkv2KxC4tNYpujfhk2NjKaZyNVo0PadoLGni4sMshDM3XlcD3D2DzL
|
125
|
-
gW95oaJcmj3Rv6r174gnyguk1p9HvqtHpdmE/pTHDIUBb50VMHFfNlwS5Fig
|
126
|
-
/ihKrcQH+wPoExCflMZJjWRSxZFf3Cd+zfPd0K64T+1HgS8kZshroLrnO0EL
|
127
|
-
00VNKbc90cKqpi9UPN/phBHXrgQ37a2EwpIelI6JVSFD4kQSGxQVs14WgCMK
|
128
|
-
ZXcQ7WHtYxPWw5JupyuJqLLgeLHPEt5jz4r5CxXWcbY=
|
129
|
-
|
130
|
-
eJzt0z1oU2EcRvE3rR+tWtGtOBQ3hS4tODgWKnQRlM5dHAoZCuIHaLcsBaGT
|
131
|
-
hIBksQmlQ1wCTgF3Q+Z0cHN1lZBQQhL/N60fUEEKh5bK+cHleXlJw+2hHY5S
|
132
|
-
yqXjsruvW++/HzzK3/jwdirN3/n4ZSHu2vspXY+9Gc/ro88txN107P3YK7EP
|
133
|
-
Yq/GPo6diV2LvRabj52KfRl7IbYQeyn2Xezl2N3Yi7H12MnYT7ETsc3Yley+
|
134
|
-
ndIoZN9xN55n8eyM32F2/M6F9vHfYyTa3lm/wH/Ipjyb8mzKsynPpjyb8mzK
|
135
|
-
synPpjyb8mzKsynPpjyb8mzKsynPpjyb8mzKsynPpjyb8mzKsynPpjyb8mzK
|
136
|
-
synPpjyb8mzKsynPpjyb8mzKsynPpjyb8mzKsynPpjyb8mzKsynPpjyb8mzK
|
137
|
-
synPpjyb8mzKsynPpjyb8mzKsynPpjyb8mzKsynPpjyb8mzKsynPpjyb8mzK
|
138
|
-
synPpjyb8mzKsynPpjyb8mzKsynPpjyb8mzKsynPpjyb8mzKsynPpjyb8mzK
|
139
|
-
synPpjyb8mzKsynPpjyb8mzKsynPpjyb8mzKsynPpjyb8mzKsynPpjyb8mzK
|
140
|
-
synPpjyb8mzKsynPpjyb8mzKsynPpjyb8mzKsynPpjyb8mzKsynPpjyb8mzK
|
141
|
-
synPpjyb8mzKsynPpjyb8mzKsynPpjyb8mzKsynPpjyb8mzKsynPprx/NO33
|
142
|
-
+7VaLQ6VSqVUKpXL5cFg0Gq1Go1GtVptNpvFYrFer8dNp9M5lRc+B078dzoc
|
143
|
-
Dnu93uGh2+3G+deNDvm/z7Mpz6Y8m/JsyrMpz6Y8m/JsyrMpz6a8vYmU0rdB
|
144
|
-
SnOx2XkxnqX02/j8ZnXzb+fCrcr09r3Puexnc0efz84z8SznnzzfWH9x++HT
|
145
|
-
V+s/7//8zEnOPwDhN6kw
|
146
|
-
end
|
147
|
-
expect = data.split(/\n\s*\n/).map { |chunk| Zlib::Inflate.inflate Base64.decode64(chunk) }
|
110
|
+
sha1sums = %w[
|
111
|
+
d3d1cde9eb43ed4b77d197af879f5ca8b8837577
|
112
|
+
65b75cbdd1f94ade632baeeb0848dec2a342c844
|
113
|
+
cfc230ec7515892cfdb85e4a173e0ce364094970
|
114
|
+
ffd859d94647a11b693f06f092d1a2bccc59d50d
|
115
|
+
]
|
148
116
|
|
149
117
|
# test the ole storage type
|
150
118
|
type = 'Microsoft Word 6.0-Dokument'
|
151
119
|
assert_equal type, (@ole.root/"\001CompObj").read[32..-1][/([^\x00]+)/m, 1]
|
152
120
|
# i was actually not loading data correctly before, so carefully check everything here
|
153
|
-
assert_equal
|
121
|
+
assert_equal sha1sums, @ole.root.children.map { |child| Digest::SHA1.hexdigest child.read }
|
154
122
|
end
|
155
123
|
|
156
124
|
def test_dirent
|
@@ -169,9 +137,7 @@ class TestStorageRead < Test::Unit::TestCase
|
|
169
137
|
|
170
138
|
dirent.open('r') { |f| assert_equal 2, f.first_block }
|
171
139
|
dirent.open('w') { |f| }
|
172
|
-
|
173
|
-
dirent.open('a') { |f| }
|
174
|
-
end
|
140
|
+
dirent.open('a') { |f| }
|
175
141
|
end
|
176
142
|
|
177
143
|
def test_delete
|
@@ -206,8 +172,8 @@ class TestStorageWrite < Test::Unit::TestCase
|
|
206
172
|
assert_equal '9974e354def8471225f548f82b8d81c701221af7', sha1(io.string)
|
207
173
|
Ole::Storage.open(io, :update_timestamps => false) { }
|
208
174
|
# hash changed. used to be efa8cfaf833b30b1d1d9381771ddaafdfc95305c
|
209
|
-
# thats because i
|
210
|
-
# available blocks.
|
175
|
+
# thats because i now truncate the io, and am probably removing some trailing
|
176
|
+
# allocated available blocks.
|
211
177
|
assert_equal 'a39e3c4041b8a893c753d50793af8d21ca8f0a86', sha1(io.string)
|
212
178
|
# add a repack test here
|
213
179
|
Ole::Storage.open io, :update_timestamps => false, &:repack
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ruby-ole
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.2.
|
4
|
+
version: 1.2.9
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Charles Lowe
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2009-
|
12
|
+
date: 2009-07-14 00:00:00 +10:00
|
13
13
|
default_executable:
|
14
14
|
dependencies: []
|
15
15
|
|
@@ -28,29 +28,29 @@ files:
|
|
28
28
|
- ChangeLog
|
29
29
|
- data/propids.yaml
|
30
30
|
- bin/oletool
|
31
|
+
- lib/ole/types.rb
|
32
|
+
- lib/ole/storage.rb
|
31
33
|
- lib/ole/support.rb
|
32
34
|
- lib/ole/base.rb
|
33
|
-
- lib/ole/storage.rb
|
34
|
-
- lib/ole/file_system.rb
|
35
35
|
- lib/ole/ranges_io.rb
|
36
|
+
- lib/ole/file_system.rb
|
37
|
+
- lib/ole/types/property_set.rb
|
38
|
+
- lib/ole/types/base.rb
|
39
|
+
- lib/ole/storage/meta_data.rb
|
36
40
|
- lib/ole/storage/base.rb
|
37
41
|
- lib/ole/storage/file_system.rb
|
38
|
-
-
|
39
|
-
-
|
40
|
-
- lib/ole/types/base.rb
|
41
|
-
- lib/ole/types/property_set.rb
|
42
|
-
- test/test_types.rb
|
42
|
+
- test/test_ranges_io.rb
|
43
|
+
- test/test_storage.rb
|
43
44
|
- test/test_filesystem.rb
|
44
45
|
- test/test_support.rb
|
45
|
-
- test/
|
46
|
+
- test/test_mbat.rb
|
47
|
+
- test/test_types.rb
|
46
48
|
- test/test_meta_data.rb
|
47
|
-
- test/test_ranges_io.rb
|
48
49
|
- test/test_property_set.rb
|
49
|
-
- test/test_mbat.rb
|
50
|
-
- test/test.doc
|
51
50
|
- test/test_word_6.doc
|
52
51
|
- test/test_word_97.doc
|
53
52
|
- test/test_word_95.doc
|
53
|
+
- test/test.doc
|
54
54
|
- test/oleWithDirs.ole
|
55
55
|
- test/test_SummaryInformation
|
56
56
|
has_rdoc: true
|
@@ -80,16 +80,16 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
80
80
|
requirements: []
|
81
81
|
|
82
82
|
rubyforge_project: ruby-ole
|
83
|
-
rubygems_version: 1.
|
83
|
+
rubygems_version: 1.3.1
|
84
84
|
signing_key:
|
85
85
|
specification_version: 2
|
86
86
|
summary: Ruby OLE library.
|
87
87
|
test_files:
|
88
|
-
- test/
|
88
|
+
- test/test_ranges_io.rb
|
89
|
+
- test/test_storage.rb
|
89
90
|
- test/test_filesystem.rb
|
90
91
|
- test/test_support.rb
|
91
|
-
- test/
|
92
|
+
- test/test_mbat.rb
|
93
|
+
- test/test_types.rb
|
92
94
|
- test/test_meta_data.rb
|
93
|
-
- test/test_ranges_io.rb
|
94
95
|
- test/test_property_set.rb
|
95
|
-
- test/test_mbat.rb
|