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.
- data/CHANGELOG.txt +3 -0
- data/LICENSE.txt +662 -0
- data/README.txt +87 -0
- data/Rakefile +98 -0
- data/install.rb +30 -0
- data/lib/position_range/error.rb +28 -0
- data/lib/position_range/list.rb +505 -0
- data/lib/position_range/version.rb +9 -0
- data/lib/position_range.rb +249 -0
- data/lib/positionrange.rb +1 -0
- data/test/position_range_list_test.rb +449 -0
- data/test/position_range_test.rb +146 -0
- data/test/test_helper.rb +3 -0
- metadata +66 -0
@@ -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
|