positionrange 0.6.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,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