positionrange 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,249 @@
1
+ #--#
2
+ # Copyright: (c) 2006-2008 The LogiLogi Foundation <foundation@logilogi.org>
3
+ #
4
+ # License:
5
+ # This file is part of the PositionRange Library. PositionRange is Free
6
+ # Software. You can run/distribute/modify PositionRange under the terms of
7
+ # the GNU Affero General Public License version 3. The Affero GPL states
8
+ # that running a modified version or a derivative work also requires you to
9
+ # make the sourcecode of that work available to everyone that can interact
10
+ # with it. We chose the Affero GPL to ensure that PositionRange remains open
11
+ # and libre (LICENSE.txt contains the full text of the legally binding
12
+ # license).
13
+ #++#
14
+ #
15
+ # PositionRanges allow one to model ranges of text.
16
+ #
17
+ # PositionRanges can be compared, sorted and parsed from and to
18
+ # strings.
19
+ #
20
+ # You can do most interesting things with PositionRanges in a
21
+ # PositionRangeList.
22
+ #
23
+ # They are wrappers around the Range class, and thus can be directly
24
+ # fed into the index-operator of strings.
25
+ #
26
+ # PositionRanges are excluding the last position, so:
27
+ #
28
+ # first...last, not first..last
29
+
30
+ $:.unshift(File.dirname(__FILE__)) unless
31
+ $:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
32
+
33
+ class PositionRange < Range
34
+ include Comparable
35
+
36
+ @@attributes = []
37
+
38
+ def attributes
39
+ return @@attributes
40
+ end
41
+
42
+ ### Constants
43
+
44
+ # Mainly used by PositionRange::List
45
+ MaximumSize = 2 ** 31
46
+
47
+ ### Regular expressions
48
+
49
+ # Regexp building blocks
50
+ BLOCK_POSITION_RANGE = '(?:\d+,\d+)'
51
+
52
+ # Check-regexps
53
+ CHECK_POSITION_RANGE_RE = /^#{BLOCK_POSITION_RANGE}$/
54
+ end
55
+
56
+ require 'position_range/error'
57
+ require 'position_range/list'
58
+
59
+ class PositionRange
60
+ ### Constructors
61
+
62
+ # Initializes a new PositionRange.
63
+ #
64
+ # Note that PositionRanges cannot be descending.
65
+ #
66
+ # Options:
67
+ # * <tt>:<any attribute you need></tt> - Usefull for associating Links or
68
+ # Remarks with this range.
69
+ #
70
+ def initialize(first, last, options = {})
71
+ if first < 0
72
+ raise PositionRange::Error.new(first, last), 'Tried to create a negative PositionRange'
73
+ end
74
+ if first > last
75
+ raise PositionRange::Error.new(first, last), 'Tried to create a descending PositionRange'
76
+ end
77
+ if last > MaximumSize
78
+ raise PositionRange::Error.new(first, last), 'Tried to create a PositionRange that is' +
79
+ ' larger than the MaximumSize'
80
+ end
81
+
82
+ options.each_key do |attribute|
83
+ if !self.respond_to?(attribute)
84
+ self.define_attribute(attribute.to_s)
85
+ end
86
+ self.send(attribute.to_s + '=', options[attribute])
87
+ end
88
+
89
+ super(first, last, true)
90
+ end
91
+
92
+ ### Class methods
93
+
94
+ # Creates a PositionRange from a string.
95
+ #
96
+ # The syntax is:
97
+ # <begin position>,<end position>
98
+ #
99
+ # Where the end position is included in the range.
100
+ #
101
+ # The optional options var allows one to pass attributes to the new
102
+ # PositionRange
103
+ #
104
+ def self.from_s(position_range_string, options = {})
105
+ if position_range_string !~ CHECK_POSITION_RANGE_RE
106
+ raise StandardError.new, 'Invalid position_range string given: ' +
107
+ position_range_string
108
+ end
109
+ p_r_arr = position_range_string.split(',')
110
+ return PositionRange.new(p_r_arr[0].to_i, p_r_arr[1].to_i, options)
111
+ end
112
+
113
+ ### Methods
114
+
115
+ # Returns the size of the range, that is last - first
116
+ #
117
+ # NOTE that PositionRanges cannot become negative.
118
+ #
119
+ def size
120
+ return self.last - self.first
121
+ end
122
+
123
+ # Duplicates the current object, except for the two required
124
+ # arguments, which set the begin and end positions of the new
125
+ # PositionRange.
126
+ #
127
+ def new_dup(first, last)
128
+ attributes_hash = Hash.new
129
+ self.attributes.each {|attribute|
130
+ attributes_hash[attribute.to_sym] = self.send(attribute)
131
+ }
132
+ PositionRange.new(first, last, attributes_hash)
133
+ end
134
+
135
+ # Returns this PositionRange substracted by the previous
136
+ #
137
+ # NOTE: The substracted PositionRange must overlap with at least
138
+ # one side of this one. If it's begin-position is bigger than this
139
+ # one's and it's end position smaller than this one's, no
140
+ # meaningfull output is guaranteed.
141
+ #
142
+ def -(other)
143
+ if other.begin >= self.end or other.end <= self.begin
144
+ return self
145
+ elsif other.begin < self.begin and other.end > self.end
146
+ return nil
147
+ elsif other.end < self.end
148
+ return self.new_dup(other.end, self.end)
149
+ elsif other.begin > self.begin
150
+ return self.new_dup(self.begin, other.begin)
151
+ end
152
+ end
153
+
154
+ # Returns true if there is overlap between the PositionRange
155
+ # given as other, and this range.
156
+ #
157
+ # Other values are treated as Range normally does.
158
+ #
159
+ def ===(other)
160
+ if other.kind_of?(PositionRange)
161
+ if self.size > other.size
162
+ return ((self) === other.begin or (self) === other.end)
163
+ else
164
+ return ((other) === self.begin or (other) === self.end)
165
+ end
166
+ else
167
+ super(other)
168
+ end
169
+ end
170
+
171
+ # Comparison
172
+
173
+ # Comparisons happen in two stages.
174
+ #
175
+ # First the begin-positions are compared.
176
+ #
177
+ # 4..5 > 1..3 => true
178
+ # 2..3 > 1..3 => true
179
+ #
180
+ # If those are equal the end-positions are compared.
181
+ #
182
+ # 1..3 > 1..2
183
+ #
184
+ # If also the end-positions are equal, 0 is returned
185
+ #
186
+ # 1..2 == 1..2
187
+ #
188
+ def <=>(other)
189
+ if self.begin < other.begin
190
+ return -1
191
+ elsif self.begin > other.begin
192
+ return 1
193
+ else
194
+ if self.end < other.end
195
+ return -1
196
+ elsif self.end > other.end
197
+ return 1
198
+ else
199
+ return 0
200
+ end
201
+ end
202
+ end
203
+
204
+ # Returns true if the pointer_attributes (link and authorship) are equal
205
+ #
206
+ def has_equal_pointer_attributes?(other_position_range)
207
+ self.attributes.each {|attribute|
208
+ if self.send(attribute) != other_position_range.send(attribute)
209
+ return false
210
+ end
211
+ }
212
+ return true
213
+ end
214
+
215
+ # Turns a PositionRange into a string
216
+ #
217
+ def to_s
218
+ return self.begin.to_s + ',' + self.end.to_s
219
+ end
220
+
221
+ ### Sub-functions
222
+
223
+ protected
224
+
225
+ # Allows the dynamic adding of attributes
226
+ #
227
+ def method_missing(method_id, *arguments)
228
+ if method_id.to_s[-1..-1] == '='
229
+ attribute = method_id.to_s.slice!(0...-1)
230
+ self.define_attribute(attribute)
231
+ self.send(method_id.to_s, *arguments)
232
+ elsif arguments.empty?
233
+ return nil
234
+ else
235
+ super(method_id, *arguments)
236
+ end
237
+ end
238
+
239
+ # Defines the given string as an attribute.
240
+ #
241
+ # (attr_accessor)
242
+ #
243
+ def define_attribute(attribute)
244
+ PositionRange.class_eval {
245
+ attr_accessor attribute
246
+ }
247
+ @@attributes.push(attribute)
248
+ end
249
+ end
@@ -0,0 +1 @@
1
+ require 'position_range'
@@ -0,0 +1,449 @@
1
+ #--#
2
+ # Copyright: (c) 2006-2008 The LogiLogi Foundation <foundation@logilogi.org>
3
+ #
4
+ # License:
5
+ # This file is part of the PositionRange Library. PositionRange is free
6
+ # software. You can run/distribute/modify PositionRange under the terms of
7
+ # the GNU Affero General Public License version 3. The Affero GPL states
8
+ # that running a modified version or a derivative work also requires you to
9
+ # make the sourcecode of that work available to everyone that can interact
10
+ # with it. We chose the Affero GPL to ensure that PositionRange remains open
11
+ # and libre (LICENSE.txt contains the full text of the legally binding
12
+ # license).
13
+ #++#
14
+
15
+ require File.dirname(__FILE__) + '/test_helper.rb'
16
+
17
+ class PositionRangeListTest < Test::Unit::TestCase
18
+ ### Parsing & Creating
19
+
20
+ def test_parsing
21
+ assert_equal PositionRange::List.new([PositionRange.new(2,8)]),
22
+ PositionRange::List.from_s('2,8')
23
+ assert_equal PositionRange::List.new([PositionRange.new(1,2),PositionRange.new(1,5),
24
+ PositionRange.new(3,4)]),PositionRange::List.from_s('1,2:1,5:3,4')
25
+ assert_equal PositionRange::List.new([PositionRange.new(1,1),PositionRange.new(1,3),
26
+ PositionRange.new(3,3)]),PositionRange::List.from_s('1,1:1,3:3,3')
27
+ assert_equal PositionRange::List.new,
28
+ PositionRange::List.from_s('')
29
+
30
+ assert_equal '1,3:4,6',
31
+ PositionRange::List.new([PositionRange.new(1,3),PositionRange.new(4,6)]).to_s
32
+ assert_equal '',
33
+ PositionRange::List.new.to_s
34
+
35
+ assert_raise(StandardError) {
36
+ PositionRange::List.from_s('1,2-3,4')
37
+ }
38
+ end
39
+
40
+ def test_new_around
41
+ assert_equal PositionRange::List.from_s('0,5'),
42
+ PositionRange::List.new_around('12345')
43
+ assert_equal PositionRange::List.from_s('0,3'),
44
+ PositionRange::List.new_around([1,2,3])
45
+ assert_equal PositionRange::List.new,
46
+ PositionRange::List.new_around('')
47
+ end
48
+
49
+ ### Getters
50
+
51
+ def test_range_size
52
+ assert_equal 5, PositionRange::List.from_s('2,4:5,8').range_size
53
+ assert_equal 8, PositionRange::List.from_s('1,4:22,24:5,8').range_size
54
+ assert_equal 0, PositionRange::List.new.range_size
55
+ end
56
+
57
+ def test_below
58
+ assert PositionRange::List.from_s('1,3:5,6').below?(7)
59
+ assert PositionRange::List.from_s('0,408:500,520').below?(520)
60
+ assert_equal false,
61
+ PositionRange::List.from_s('0,408:500,520').below?(519)
62
+ end
63
+
64
+ def test_within
65
+ assert PositionRange::List.from_s('1,3:5,6').within?(
66
+ PositionRange::List.from_s('0,8'))
67
+ assert PositionRange::List.from_s('1,3:5,6').within?(
68
+ PositionRange::List.from_s('1,6'))
69
+ assert PositionRange::List.from_s('5,6:1,3').within?(
70
+ PositionRange::List.from_s('1,6'))
71
+
72
+ assert_equal false,
73
+ PositionRange::List.from_s('5,7:1,3').within?(
74
+ PositionRange::List.from_s('1,6'))
75
+ assert_equal false,
76
+ PositionRange::List.from_s('0,408:500,520').within?(
77
+ PositionRange::List.from_s('0,519'))
78
+ end
79
+
80
+ def test_index
81
+ p = PositionRange::List.from_s('1,5:7,9')
82
+ assert_equal 0, p.index(PositionRange.new(1,5))
83
+ assert_equal 1, p.index(PositionRange.new(7,9))
84
+
85
+ assert_equal nil, p.index(PositionRange.new(7,9,:attrobo => 1),
86
+ :dont_ignore_attributes => true)
87
+
88
+ p = PositionRange::List.new([PositionRange.new(1,5,:attrobo => 1),
89
+ PositionRange.new(1,5,:attrobo => 2)])
90
+ assert_equal 0, p.index(PositionRange.new(1,5,:attrobo => 2))
91
+ assert_equal 1, p.index(PositionRange.new(1,5,:attrobo => 2),
92
+ :dont_ignore_attributes => true)
93
+ end
94
+
95
+ ### Lowlevel methods
96
+
97
+ def test_intersection
98
+ assert_equal PositionRange::List.from_s('3,5:8,11'),
99
+ PositionRange::List.from_s('1,5:8,17') &
100
+ PositionRange::List.from_s('3,11')
101
+
102
+ assert_equal PositionRange::List.from_s('10,13'),
103
+ PositionRange::List.from_s('3,5:10,16') &
104
+ PositionRange::List.from_s('10,13')
105
+
106
+ assert_equal PositionRange::List.from_s('4,11:13,21:22,29:35,42:62,68:342,349:357,360'),
107
+ PositionRange::List.from_s('4,11:13,21:22,29:35,42:62,68:342,349:357,360:410,420') &
108
+ PositionRange::List.from_s('0,408')
109
+
110
+ # empty
111
+ assert_equal PositionRange::List.new,
112
+ PositionRange::List.from_s('3,7') &
113
+ PositionRange::List.from_s('200,205')
114
+ assert_equal PositionRange::List.new,
115
+ PositionRange::List.from_s('4,77') &
116
+ PositionRange::List.new
117
+ assert_equal PositionRange::List.new,
118
+ PositionRange::List.new &
119
+ PositionRange::List.new
120
+ end
121
+
122
+ def test_substract
123
+ assert_equal PositionRange::List.new,
124
+ PositionRange::List.from_s('2,7') -
125
+ PositionRange::List.from_s('1,8')
126
+
127
+ assert_equal PositionRange::List.from_s('2,6:7,12'),
128
+ PositionRange::List.from_s('1,15') -
129
+ PositionRange::List.from_s('1,2:6,7:12,20')
130
+
131
+ assert_equal PositionRange::List.from_s('1,2:5,8'),
132
+ PositionRange::List.from_s('1,8') -
133
+ PositionRange::List.from_s('2,5')
134
+
135
+ assert_equal PositionRange::List.from_s('1,3:9,11'),
136
+ PositionRange::List.from_s('1,5:7,11') -
137
+ PositionRange::List.from_s('3,9')
138
+
139
+ assert_equal PositionRange::List.from_s('1,2:8,9:13,15:20,21'),
140
+ PositionRange::List.from_s('1,3:7,9:13,15:19,21') -
141
+ PositionRange::List.from_s('2,8:10,11:18,20:21,50')
142
+
143
+ assert_equal PositionRange::List.from_s('1,3:6,8'),
144
+ PositionRange::List.from_s('1,5:4,8') -
145
+ PositionRange::List.from_s('3,6')
146
+
147
+ assert_equal PositionRange::List.from_s('10,14'),
148
+ PositionRange::List.from_s('3,5:10,16') -
149
+ PositionRange::List.from_s('0,10:14,200000')
150
+
151
+ assert_equal PositionRange::List.from_s('3,5:10,16'),
152
+ PositionRange::List.from_s('3,5:10,16') -
153
+ PositionRange::List.from_s('21,2147483647')
154
+
155
+ assert_equal PositionRange::List.from_s('3,5'),
156
+ PositionRange::List.from_s('3,5:10,16') -
157
+ PositionRange::List.from_s('6,2147483647')
158
+
159
+ assert_equal PositionRange::List.new,
160
+ PositionRange::List.from_s('5,15:16,25') -
161
+ PositionRange::List.from_s('5,15:16,25:10,20')
162
+
163
+ # empty
164
+ assert_equal PositionRange::List.new,
165
+ PositionRange::List.from_s('2,5') -
166
+ PositionRange::List.from_s('2,5')
167
+ assert_equal PositionRange::List.new,
168
+ PositionRange::List.new -
169
+ PositionRange::List.from_s('2,3')
170
+ assert_equal PositionRange::List.new,
171
+ PositionRange::List.new -
172
+ PositionRange::List.new
173
+
174
+ # attributes
175
+ p1 = PositionRange.new(1,5,:attr => 1)
176
+ p2 = PositionRange.new(1,5,:attr => 2)
177
+ assert_equal PositionRange::List.new([p1]),
178
+ PositionRange::List.new([p1]) - PositionRange::List.new([p2])
179
+
180
+ assert_equal PositionRange::List.new(),
181
+ PositionRange::List.new([p1]).substract!(
182
+ PositionRange::List.new([p2]),:ignore_attributes => true)
183
+
184
+ old = PositionRange::List.from_s('5,15:16,25')
185
+ new = old.dup << PositionRange.new(10,20, :attr => 5)
186
+ assert_equal PositionRange::List.new, old - new
187
+ end
188
+
189
+ def test_delete
190
+ assert_equal PositionRange::List.from_s('1,3:6,8'),
191
+ PositionRange::List.from_s('1,5:4,8').delete(PositionRange.new(3,6))
192
+ end
193
+
194
+ def test_invert
195
+ # default maximum size
196
+ assert_equal PositionRange::List.from_s('0,5:15,' + PositionRange::MaximumSize.to_s),
197
+ PositionRange::List.from_s('5,15').invert!
198
+ assert_equal PositionRange::List.from_s('1,5:15,' + PositionRange::MaximumSize.to_s),
199
+ PositionRange::List.from_s('0,1:5,15').invert!
200
+ assert_equal PositionRange::List.from_s('15,' + PositionRange::MaximumSize.to_s),
201
+ PositionRange::List.from_s('0,5:5,15').invert!
202
+
203
+ # specified maximum size
204
+ assert_equal PositionRange::List.from_s('0,5:6,17:21,27'),
205
+ PositionRange::List.from_s('5,6:17,21:27,50').invert!(50)
206
+ assert_equal PositionRange::List.from_s('0,5:6,17:21,27:50,55'),
207
+ PositionRange::List.from_s('5,6:17,21:27,50').invert!(55)
208
+
209
+ # empty stuff
210
+ assert_equal PositionRange::List.from_s('0,55'),
211
+ PositionRange::List.new.invert!(55)
212
+ assert_equal PositionRange::List.new,
213
+ PositionRange::List.new.invert!(0)
214
+ end
215
+
216
+ def test_line_up_overlaps
217
+ assert_equal PositionRange::List.from_s('0,2:2,6:2,6:6,8'),
218
+ PositionRange::List.from_s('2,6:0,8').line_up_overlaps!
219
+ assert_equal PositionRange::List.from_s('1,2:1,2:10,14:14,18:14,18:20,23'),
220
+ PositionRange::List.from_s('1,2:1,2:10,18:14,18:20,23').line_up_overlaps!
221
+
222
+ p = PositionRange::List.new([
223
+ PositionRange.new(5,8, :link => :a),
224
+ PositionRange.new(0,15, :authorship => 1),
225
+ PositionRange.new(10,30, :authorship => :c)])
226
+
227
+ output = PositionRange::List.new([
228
+ PositionRange.new(0,5, :authorship => 1),
229
+ PositionRange.new(5,8, :link => :a),
230
+ PositionRange.new(5,8, :authorship => 1),
231
+ PositionRange.new(8,10, :authorship => 1),
232
+ PositionRange.new(10,15, :authorship => 1),
233
+ PositionRange.new(10,15, :authorship => :c),
234
+ PositionRange.new(15,30, :link => :c)])
235
+
236
+ assert_equal output, p.line_up_overlaps!
237
+
238
+ # ender
239
+ p = PositionRange::List.new([
240
+ PositionRange.new(28,38, :lo => 1),
241
+ PositionRange.new(31,38, :la => 2),
242
+ PositionRange.new(33,38, :lu => 3)])
243
+
244
+ output = PositionRange::List.new([
245
+ PositionRange.new(28,31, :lo => 1),
246
+ PositionRange.new(31,33, :la => 2),
247
+ PositionRange.new(31,33, :lo => 1),
248
+ PositionRange.new(33,38, :lu => 3),
249
+ PositionRange.new(33,38, :la => 2),
250
+ PositionRange.new(33,38, :lo => 1)])
251
+
252
+ assert_equal output, p.line_up_overlaps!
253
+
254
+ # middler
255
+ p = PositionRange::List.new([
256
+ PositionRange.new(43,61, :lo => 1),
257
+ PositionRange.new(45,58, :la => 2),
258
+ PositionRange.new(48,58, :lu => 3)])
259
+
260
+ p = PositionRange::List.from_s('43,61:45,58:48,58')
261
+ output = PositionRange::List.new([
262
+ PositionRange.new(43,45, :lo => 1),
263
+ PositionRange.new(45,48, :la => 2),
264
+ PositionRange.new(45,48, :lo => 1),
265
+ PositionRange.new(48,58, :lu => 3),
266
+ PositionRange.new(48,58, :la => 2),
267
+ PositionRange.new(48,58, :lo => 1),
268
+ PositionRange.new(58,61, :lo => 1)])
269
+
270
+ assert_equal output, p.line_up_overlaps!
271
+
272
+ # empty
273
+ assert_equal PositionRange::List.new,
274
+ PositionRange::List.new.line_up_overlaps!
275
+ end
276
+
277
+ def test_merge_adjacents
278
+ # same pointer attributes
279
+ assert_equal PositionRange::List.from_s('2,8'),
280
+ PositionRange::List.from_s('2,4:4,8').merge_adjacents!
281
+
282
+ assert_equal PositionRange::List.from_s('2,4:6,13'),
283
+ PositionRange::List.from_s('2,4:6,10:10,13').merge_adjacents!
284
+
285
+ assert_equal PositionRange::List.from_s('6,9:2,4:10,13'),
286
+ PositionRange::List.from_s('6,9:2,4:10,13').merge_adjacents!
287
+
288
+ assert_equal PositionRange::List.from_s('1,4'),
289
+ PositionRange::List.from_s('1,2:2,3:3,4').merge_adjacents!
290
+
291
+ # different pointer attributes
292
+ p1 = PositionRange.new(2,5,:link => :a)
293
+ p2 = PositionRange.new(5,8,:link => :b)
294
+ assert_equal PositionRange::List.from_s('2,5:5,8'),
295
+ PositionRange::List.new([p1,p2]).merge_adjacents!
296
+
297
+ assert_equal PositionRange::List.from_s('2,8'),
298
+ PositionRange::List.new([p1,p2]).merge_adjacents!(:ignore_attributes => true)
299
+
300
+ # empty
301
+ assert_equal PositionRange::List.new,
302
+ PositionRange::List.new.merge_adjacents!
303
+ end
304
+
305
+ def test_translate
306
+ p = PositionRange::List.from_s('10,13:16,18')
307
+ p2 = p.dup
308
+
309
+ assert_equal PositionRange::List.from_s('13,16:19,21'),
310
+ p.translate!(3)
311
+ assert_equal PositionRange::List.from_s('8,11:14,16'),
312
+ p2.translate!(-2)
313
+
314
+ # empty
315
+ assert_equal PositionRange::List.new,
316
+ PositionRange::List.new.translate!(5)
317
+ end
318
+
319
+ def test_insert_at_ranges
320
+ # Without skipping
321
+ assert_equal PositionRange::List.from_s('0,10:50,59:15,20'),
322
+ PositionRange::List.from_s('0,10:15,20').insert_at_ranges!(
323
+ PositionRange::List.from_s('50,59'),
324
+ PositionRange::List.from_s('11,20'))
325
+
326
+ # With skipping
327
+ assert_equal PositionRange::List.from_s('39,49:100,102:6,7:16,20'),
328
+ PositionRange::List.from_s('39,49:16,20').insert_at_ranges!(
329
+ PositionRange::List.from_s('100,102:6,7'),
330
+ PositionRange::List.from_s('10,12:19,20'),
331
+ PositionRange::List.from_s('12,19'))
332
+
333
+ # with multiple elements in one range to insert at
334
+ assert_equal PositionRange::List.from_s('0,10:35,36:33,34:15,20'),
335
+ PositionRange::List.from_s('0,10:15,20').insert_at_ranges!(
336
+ PositionRange::List.from_s('35,36:33,34'),
337
+ PositionRange::List.from_s('10,12'))
338
+
339
+ # with cutting
340
+ assert_equal PositionRange::List.from_s('0,8:50,63:8,10:15,20'),
341
+ PositionRange::List.from_s('0,10:15,20').insert_at_ranges!(
342
+ PositionRange::List.from_s('50,63'),
343
+ PositionRange::List.from_s('8,21'))
344
+
345
+ assert_equal PositionRange::List.from_s('0,100:430,480:100,408:500,519'),
346
+ PositionRange::List.from_s('0,408:500,519').insert_at_ranges!(
347
+ PositionRange::List.from_s('430,480'),
348
+ PositionRange::List.from_s('159,209'),
349
+ PositionRange::List.from_s('100,159'))
350
+
351
+ # the cut-bug
352
+ assert_equal PositionRange::List.from_s('0,750:100,150:20,30:150,190:40,50:190,250'),
353
+ PositionRange::List.from_s('0,750:100,250').insert_at_ranges!(
354
+ PositionRange::List.from_s('20,30:40,50'),
355
+ PositionRange::List.from_s('800,810:850,860'))
356
+ end
357
+
358
+ ### Highlevel methods
359
+
360
+ def test_translate_to_view
361
+ p = PositionRange::List.from_s('3,5:10,16')
362
+ # basic transition
363
+ assert_equal PositionRange::List.from_s('2,4:9,15'),
364
+ p.translate_to_view(PositionRange::List.from_s('1,20'))
365
+ # chop off the end
366
+ assert_equal PositionRange::List.from_s('2,4:9,10'),
367
+ p.translate_to_view(PositionRange::List.from_s('1,11'))
368
+ # chop off first snippet
369
+ assert_equal PositionRange::List.from_s('3,4'),
370
+ p.translate_to_view(PositionRange::List.from_s('7,11'))
371
+ # two snippets into one
372
+ assert_equal PositionRange::List.from_s('2,7'),
373
+ p.translate_to_view(PositionRange::List.from_s('1,5:10,13'))
374
+ # last before the first
375
+ assert_equal PositionRange::List.from_s('3,9:16,18'),
376
+ p.translate_to_view(PositionRange::List.from_s('7,20:0,6'))
377
+
378
+ # empty
379
+ assert_equal PositionRange::List.new,
380
+ PositionRange::List.new.translate_to_view(
381
+ PositionRange::List.from_s('5,8'))
382
+ end
383
+
384
+ def test_translate_from_view
385
+ p = PositionRange::List.from_s('3,5:10,16')
386
+ # basic transition
387
+ assert_equal PositionRange::List.from_s('13,15:20,26'),
388
+ p.translate_from_view(PositionRange::List.from_s('10,30'))
389
+ # different samples
390
+ assert_equal PositionRange::List.from_s('8,10:36,42'),
391
+ p.translate_from_view(PositionRange::List.from_s('5,12:33,50'))
392
+ # splitting into different abs position-ranges
393
+ assert_equal PositionRange::List.from_s('3,5:35,38:50,53'),
394
+ p.translate_from_view(PositionRange::List.from_s('0,5:30,38:50,90'))
395
+ # last before the first
396
+ assert_equal PositionRange::List.from_s('203,205:3,9'),
397
+ p.translate_from_view(PositionRange::List.from_s('200,207:0,30'))
398
+
399
+ # swapped
400
+ p = PositionRange::List.from_s('5,6:1,2')
401
+ assert_equal PositionRange::List.from_s('6,7:2,3'),
402
+ p.translate_from_view(PositionRange::List.from_s('1,8'))
403
+
404
+ # empty
405
+ assert_equal PositionRange::List.new,
406
+ PositionRange::List.new.translate_from_view(
407
+ PositionRange::List.from_s('5,8'))
408
+ end
409
+
410
+ def test_stack_adjacent
411
+ assert_equal PositionRange::List.from_s('0,3:3,23'),
412
+ PositionRange::List.from_s('50,53:10,30').stack_adjacent
413
+
414
+ # with space inbetween
415
+ assert_equal PositionRange::List.from_s('0,3:4,24'),
416
+ PositionRange::List.from_s('50,53:10,30').stack_adjacent(:space => 1)
417
+ end
418
+
419
+ def test_cluster_overlaps
420
+ p = PositionRange::List.from_s('1,2:1,2:10,18:14,18:20,23')
421
+ output = [
422
+ PositionRange::List.from_s('1,2:1,2'),
423
+ PositionRange::List.from_s('10,14'),
424
+ PositionRange::List.from_s('14,18:14,18'),
425
+ PositionRange::List.from_s('20,23')
426
+ ]
427
+ assert_equal output, p.cluster_overlaps
428
+
429
+ # empty
430
+ assert_equal PositionRange::List.new,
431
+ PositionRange::List.new.cluster_overlaps
432
+ end
433
+
434
+ def test_apply_to_string
435
+ p = PositionRange::List.from_s('4,6:8,9:0,2')
436
+ assert_equal '56912', p.apply_to_string('123456789')
437
+
438
+ p = PositionRange::List.from_s('0,408:500,520')
439
+ assert_equal 'a' * p.range_size, p.apply_to_string('a' * 520)
440
+
441
+ # with separator
442
+ p = PositionRange::List.from_s('0,5:5,10')
443
+ assert_equal 'aaaaa&bbbbb', p.apply_to_string('aaaaabbbbb', :separator => '&')
444
+ assert_equal 'aaaaa%&bbbbb', p.apply_to_string('aaaaabbbbb', :separator => '%&')
445
+
446
+ # empty
447
+ assert_equal '', PositionRange::List.new.apply_to_string('12345')
448
+ end
449
+ end