bio-restriction_enzyme 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) hide show
  1. data/.document +5 -0
  2. data/COPYING.txt +121 -0
  3. data/Gemfile +10 -0
  4. data/LICENSE.txt +7 -0
  5. data/README.rdoc +22 -0
  6. data/Rakefile +53 -0
  7. data/VERSION +1 -0
  8. data/bio-restriction_enzyme.gemspec +99 -0
  9. data/lib/bio-restriction_enzyme.rb +1 -0
  10. data/lib/bio/util/restriction_enzyme.rb +218 -0
  11. data/lib/bio/util/restriction_enzyme/analysis.rb +241 -0
  12. data/lib/bio/util/restriction_enzyme/analysis_basic.rb +209 -0
  13. data/lib/bio/util/restriction_enzyme/cut_symbol.rb +99 -0
  14. data/lib/bio/util/restriction_enzyme/double_stranded.rb +313 -0
  15. data/lib/bio/util/restriction_enzyme/double_stranded/aligned_strands.rb +127 -0
  16. data/lib/bio/util/restriction_enzyme/double_stranded/cut_location_pair.rb +95 -0
  17. data/lib/bio/util/restriction_enzyme/double_stranded/cut_location_pair_in_enzyme_notation.rb +30 -0
  18. data/lib/bio/util/restriction_enzyme/double_stranded/cut_locations.rb +68 -0
  19. data/lib/bio/util/restriction_enzyme/double_stranded/cut_locations_in_enzyme_notation.rb +99 -0
  20. data/lib/bio/util/restriction_enzyme/range/cut_range.rb +16 -0
  21. data/lib/bio/util/restriction_enzyme/range/cut_ranges.rb +39 -0
  22. data/lib/bio/util/restriction_enzyme/range/horizontal_cut_range.rb +59 -0
  23. data/lib/bio/util/restriction_enzyme/range/sequence_range.rb +249 -0
  24. data/lib/bio/util/restriction_enzyme/range/sequence_range/calculated_cuts.rb +236 -0
  25. data/lib/bio/util/restriction_enzyme/range/sequence_range/fragment.rb +43 -0
  26. data/lib/bio/util/restriction_enzyme/range/sequence_range/fragments.rb +33 -0
  27. data/lib/bio/util/restriction_enzyme/range/vertical_cut_range.rb +69 -0
  28. data/lib/bio/util/restriction_enzyme/single_strand.rb +193 -0
  29. data/lib/bio/util/restriction_enzyme/single_strand/cut_locations_in_enzyme_notation.rb +127 -0
  30. data/lib/bio/util/restriction_enzyme/single_strand_complement.rb +15 -0
  31. data/lib/bio/util/restriction_enzyme/string_formatting.rb +103 -0
  32. data/test/bio-restriction_enzyme/analysis/test_calculated_cuts.rb +281 -0
  33. data/test/bio-restriction_enzyme/analysis/test_cut_ranges.rb +87 -0
  34. data/test/bio-restriction_enzyme/analysis/test_sequence_range.rb +223 -0
  35. data/test/bio-restriction_enzyme/double_stranded/test_aligned_strands.rb +84 -0
  36. data/test/bio-restriction_enzyme/double_stranded/test_cut_location_pair.rb +58 -0
  37. data/test/bio-restriction_enzyme/double_stranded/test_cut_location_pair_in_enzyme_notation.rb +56 -0
  38. data/test/bio-restriction_enzyme/double_stranded/test_cut_locations.rb +35 -0
  39. data/test/bio-restriction_enzyme/double_stranded/test_cut_locations_in_enzyme_notation.rb +87 -0
  40. data/test/bio-restriction_enzyme/single_strand/test_cut_locations_in_enzyme_notation.rb +66 -0
  41. data/test/bio-restriction_enzyme/test_analysis.rb +228 -0
  42. data/test/bio-restriction_enzyme/test_cut_symbol.rb +27 -0
  43. data/test/bio-restriction_enzyme/test_double_stranded.rb +98 -0
  44. data/test/bio-restriction_enzyme/test_single_strand.rb +131 -0
  45. data/test/bio-restriction_enzyme/test_single_strand_complement.rb +131 -0
  46. data/test/bio-restriction_enzyme/test_string_formatting.rb +43 -0
  47. data/test/helper.rb +17 -0
  48. data/test/test_bio-restriction_enzyme.rb +21 -0
  49. metadata +153 -0
@@ -0,0 +1,313 @@
1
+ # bio/util/restriction_enzyme/double_stranded.rb - DoubleStranded restriction enzyme sequence
2
+
3
+ require 'bio/util/restriction_enzyme'
4
+
5
+ module Bio
6
+ class RestrictionEnzyme
7
+
8
+ # A pair of SingleStrand and SingleStrandComplement objects with methods to
9
+ # add utility to their relation.
10
+ #
11
+ # = Notes
12
+ # * This is created by Bio::RestrictionEnzyme.new for convenience.
13
+ # * The two strands accessible are +primary+ and +complement+.
14
+ # * SingleStrand methods may be used on DoubleStranded and they will be passed to +primary+.
15
+ #
16
+ #
17
+ # FIXME needs better docs
18
+ class DoubleStranded
19
+
20
+ autoload :AlignedStrands, 'bio/util/restriction_enzyme/double_stranded/aligned_strands'
21
+ autoload :CutLocations, 'bio/util/restriction_enzyme/double_stranded/cut_locations'
22
+ autoload :CutLocationPair, 'bio/util/restriction_enzyme/double_stranded/cut_location_pair'
23
+ autoload :CutLocationsInEnzymeNotation, 'bio/util/restriction_enzyme/double_stranded/cut_locations_in_enzyme_notation'
24
+ autoload :CutLocationPairInEnzymeNotation, 'bio/util/restriction_enzyme/double_stranded/cut_location_pair_in_enzyme_notation'
25
+
26
+ include CutSymbol
27
+ extend CutSymbol
28
+ include StringFormatting
29
+ extend StringFormatting
30
+
31
+ # The primary strand
32
+ attr_reader :primary
33
+
34
+ # The complement strand
35
+ attr_reader :complement
36
+
37
+ # Cut locations in 0-based index format, DoubleStranded::CutLocations object
38
+ attr_reader :cut_locations
39
+
40
+ # Cut locations in enzyme index notation, DoubleStranded::CutLocationsInEnzymeNotation object
41
+ attr_reader :cut_locations_in_enzyme_notation
42
+
43
+ # [+erp+] One of three possible parameters: The name of an enzyme, a REBASE::EnzymeEntry object, or a nucleotide pattern with a cut mark.
44
+ # [+raw_cut_pairs+] The cut locations in enzyme index notation.
45
+ #
46
+ # Enzyme index notation:: 1.._n_, value before 1 is -1
47
+ #
48
+ # Examples of the allowable cut locations for +raw_cut_pairs+ follows. 'p' and
49
+ # 'c' refer to a cut location on the 'p'rimary and 'c'omplement strands.
50
+ #
51
+ # 1, [3,2], [20,22], 57
52
+ # p, [p,c], [p, c], p
53
+ #
54
+ # Which is the same as:
55
+ #
56
+ # 1, (3..2), (20..22), 57
57
+ # p, (p..c), (p..c), p
58
+ #
59
+ # Examples of partial cuts:
60
+ # 1, [nil,2], [20,nil], 57
61
+ # p, [p, c], [p, c], p
62
+ #
63
+ def initialize(erp, *raw_cut_pairs)
64
+ # 'erp' : 'E'nzyme / 'R'ebase / 'P'attern
65
+ k = erp.class
66
+
67
+ if k == Bio::REBASE::EnzymeEntry
68
+ # Passed a Bio::REBASE::EnzymeEntry object
69
+
70
+ unless raw_cut_pairs.empty?
71
+ err = "A Bio::REBASE::EnzymeEntry object was passed, however the cut locations contained values. Ambiguous or redundant.\n"
72
+ err += "inspect = #{raw_cut_pairs.inspect}"
73
+ raise ArgumentError, err
74
+ end
75
+ initialize_with_rebase( erp )
76
+
77
+ elsif erp.kind_of? String
78
+ # Passed something that could be an enzyme pattern or an anzyme name
79
+
80
+ # Decide if this String is an enzyme name or a pattern
81
+ if Bio::RestrictionEnzyme.enzyme_name?( erp )
82
+ # FIXME we added this to rebase...
83
+ # Check if it's a known name
84
+ known_enzyme = false
85
+ known_enzyme = true if Bio::RestrictionEnzyme.rebase[ erp ]
86
+
87
+ # Try harder to find the enzyme
88
+ unless known_enzyme
89
+ re = %r"^#{erp}$"i
90
+ Bio::RestrictionEnzyme.rebase.each { |name, v| (known_enzyme = true; erp = name; break) if name =~ re }
91
+ end
92
+
93
+ if known_enzyme
94
+ initialize_with_rebase( Bio::RestrictionEnzyme.rebase[erp] )
95
+ else
96
+ raise IndexError, "No entry found for enzyme named '#{erp}'"
97
+ end
98
+
99
+ else
100
+ # Not an enzyme name, so a pattern is assumed
101
+ if erp =~ re_cut_symbol
102
+ initialize_with_pattern_and_cut_symbols( erp )
103
+ else
104
+ initialize_with_pattern_and_cut_locations( erp, raw_cut_pairs )
105
+ end
106
+ end
107
+
108
+ elsif k == NilClass
109
+ err = "Passed a nil value. Perhaps you tried to pass a Bio::REBASE::EnzymeEntry that does not exist?\n"
110
+ err += "inspect = #{erp.inspect}"
111
+ raise ArgumentError, err
112
+ else
113
+ err = "I don't know what to do with class #{k} for erp.\n"
114
+ err += "inspect = #{erp.inspect}"
115
+ raise ArgumentError, err
116
+ end
117
+
118
+ end
119
+
120
+ # See AlignedStrands.align
121
+ def aligned_strands
122
+ AlignedStrands.align(@primary.pattern, @complement.pattern)
123
+ end
124
+
125
+ # See AlignedStrands.align_with_cuts
126
+ def aligned_strands_with_cuts
127
+ AlignedStrands.align_with_cuts(@primary.pattern, @complement.pattern, @primary.cut_locations, @complement.cut_locations)
128
+ end
129
+
130
+ # Returns +true+ if the cut pattern creates blunt fragments.
131
+ # (opposite of sticky)
132
+ def blunt?
133
+ as = aligned_strands_with_cuts
134
+ ary = [as.primary, as.complement]
135
+ ary.collect! { |seq| seq.split( cut_symbol ) }
136
+ # convert the cut sections to their lengths
137
+ ary.each { |i| i.collect! { |c| c.length } }
138
+ ary[0] == ary[1]
139
+ end
140
+
141
+ # Returns +true+ if the cut pattern creates sticky fragments.
142
+ # (opposite of blunt)
143
+ def sticky?
144
+ !blunt?
145
+ end
146
+
147
+ # Takes a RestrictionEnzyme object and a numerical offset to the sequence and
148
+ # returns an EnzymeAction
149
+ #
150
+ # +restriction_enzyme+:: RestrictionEnzyme
151
+ # +offset+:: Numerical offset of where the enzyme action occurs on the seqeunce
152
+ def create_action_at( offset )
153
+ # x is the size of the fully aligned sequence with maximum padding needed
154
+ # to make a match on the primary and complement strand.
155
+ #
156
+ # For example -
157
+ # Note how EcoRII needs extra padding on the beginning and ending of the
158
+ # sequence 'ccagg' to make the match since the cut must occur between
159
+ # two nucleotides and can not occur on the very end of the sequence.
160
+ #
161
+ # EcoRII:
162
+ # :blunt: "0"
163
+ # :c2: "5"
164
+ # :c4: "0"
165
+ # :c1: "-1"
166
+ # :pattern: CCWGG
167
+ # :len: "5"
168
+ # :name: EcoRII
169
+ # :c3: "0"
170
+ # :ncuts: "2"
171
+ #
172
+ # -1 1 2 3 4 5
173
+ # 5' - n^c c w g g n - 3'
174
+ # 3' - n g g w c c^n - 5'
175
+ #
176
+ # (w == [at])
177
+
178
+ x = aligned_strands.primary.size
179
+
180
+ enzyme_action = EnzymeAction.new( offset,
181
+ offset + x-1,
182
+ offset,
183
+ offset + x-1)
184
+
185
+ @cut_locations.each do |cut_location_pair|
186
+ # cut_pair is a DoubleStranded::CutLocationPair
187
+ p, c = cut_location_pair.primary, cut_location_pair.complement
188
+ if c >= p
189
+ enzyme_action.add_cut_range(offset+p, nil, nil, offset+c)
190
+ else
191
+ enzyme_action.add_cut_range(nil, offset+p, offset+c, nil)
192
+ end
193
+ end
194
+
195
+ enzyme_action
196
+ end
197
+
198
+ # An EnzymeAction is a way of representing a potential effect that a
199
+ # RestrictionEnzyme may have on a nucleotide sequence, an 'action'.
200
+ #
201
+ # Multiple cuts in multiple locations on a sequence may occur in one
202
+ # 'action' if it is done by a single enzyme.
203
+ #
204
+ # An EnzymeAction is a series of locations that represents where the restriction
205
+ # enzyme will bind on the sequence, as well as what ranges are cut on the
206
+ # sequence itself. The complexity is due to the fact that our virtual
207
+ # restriction enzyme may create multiple segments from its cutting action,
208
+ # on which another restriction enzyme may operate upon.
209
+ #
210
+ # For example, the DNA sequence:
211
+ #
212
+ # 5' - G A A T A A A C G A - 3'
213
+ # 3' - C T T A T T T G C T - 5'
214
+ #
215
+ # When mixed with the restriction enzyme with the following cut pattern:
216
+ #
217
+ # 5' - A|A T A A A C|G - 3'
218
+ # +-+ +
219
+ # 3' - T T|A T T T G|C - 5'
220
+ #
221
+ # And also mixed with the restriction enzyme of the following cut pattern:
222
+ #
223
+ # 5' - A A|A C - 3'
224
+ # +-+
225
+ # 3' - T|T T G - 5'
226
+ #
227
+ # Would result in a DNA sequence with these cuts:
228
+ #
229
+ # 5' - G A|A T A A|A C|G A - 3'
230
+ # +-+ +-+ +
231
+ # 3' - C T T|A T|T T G|C T - 5'
232
+ #
233
+ # Or these separate "free-floating" sequences:
234
+ #
235
+ # 5' - G A - 3'
236
+ # 3' - C T T - 5'
237
+ #
238
+ # 5' - A T A A - 3'
239
+ # 3' - A T - 5'
240
+ #
241
+ # 5' - A C - 3'
242
+ # 3' - T T G - 5'
243
+ #
244
+ # 5' - G A - 3'
245
+ # 3' - C T - 5'
246
+ #
247
+ # This would be represented by two EnzymeActions - one for each
248
+ # RestrictionEnzyme.
249
+ #
250
+ # This is, however, subject to competition. If the second enzyme reaches
251
+ # the target first, the the first enzyme will not be able to find the
252
+ # appropriate bind site.
253
+ #
254
+ # FIXME complete these docs
255
+ #
256
+ # To initialize an EnzymeAction you must first instantiate it with the
257
+ # beginning and ending locations of where it will operate on a nucleotide
258
+ # sequence.
259
+ #
260
+ # Next the ranges of cu
261
+ #
262
+ # An EnzymeAction is
263
+ # Defines a single enzyme action, in this case being a range that correlates
264
+ # to the DNA sequence that may contain it's own internal cuts.
265
+ class EnzymeAction < Bio::RestrictionEnzyme::Range::SequenceRange
266
+ end
267
+
268
+ #########
269
+ protected
270
+ #########
271
+
272
+ def initialize_with_pattern_and_cut_symbols( s )
273
+ p_cl = SingleStrand::CutLocationsInEnzymeNotation.new( strip_padding(s) )
274
+ s = Bio::Sequence::NA.new( strip_cuts_and_padding(s) )
275
+
276
+ # * Reflect cuts that are in enzyme notation
277
+ # * 0 is not a valid enzyme index, decrement 0 and all negative
278
+ c_cl = p_cl.collect {|n| (n >= s.length or n < 1) ? ((s.length - n) - 1) : (s.length - n)}
279
+
280
+ create_cut_locations( p_cl.zip(c_cl) )
281
+ create_primary_and_complement( s, p_cl, c_cl )
282
+ end
283
+
284
+ def initialize_with_pattern_and_cut_locations( s, raw_cl )
285
+ create_cut_locations(raw_cl)
286
+ create_primary_and_complement( Bio::Sequence::NA.new(s), @cut_locations_in_enzyme_notation.primary, @cut_locations_in_enzyme_notation.complement )
287
+ end
288
+
289
+ def create_primary_and_complement(primary_seq, p_cuts, c_cuts)
290
+ @primary = SingleStrand.new( primary_seq, p_cuts )
291
+ @complement = SingleStrandComplement.new( primary_seq.forward_complement, c_cuts )
292
+ end
293
+
294
+ def create_cut_locations(raw_cl)
295
+ @cut_locations_in_enzyme_notation = CutLocationsInEnzymeNotation.new( *raw_cl.collect {|cl| CutLocationPairInEnzymeNotation.new(cl)} )
296
+ @cut_locations = @cut_locations_in_enzyme_notation.to_array_index
297
+ end
298
+
299
+ def initialize_with_rebase( e )
300
+ p_cl = [e.primary_strand_cut1, e.primary_strand_cut2]
301
+ c_cl = [e.complementary_strand_cut1, e.complementary_strand_cut2]
302
+
303
+ # If there's no cut in REBASE it's represented as a 0.
304
+ # 0 is an invalid index, it just means no cut.
305
+ p_cl.delete(0)
306
+ c_cl.delete(0)
307
+ raise IndexError unless p_cl.size == c_cl.size
308
+ initialize_with_pattern_and_cut_locations( e.pattern, p_cl.zip(c_cl) )
309
+ end
310
+
311
+ end # DoubleStranded
312
+ end # RestrictionEnzyme
313
+ end # Bio
@@ -0,0 +1,127 @@
1
+ # bio/util/restriction_enzyme/double_stranded/aligned_strands.rb - Align two SingleStrand objects
2
+
3
+ require 'bio/util/restriction_enzyme'
4
+
5
+ module Bio
6
+ class RestrictionEnzyme
7
+ class DoubleStranded
8
+
9
+ # Align two SingleStrand objects and return a Result
10
+ # object with +primary+ and +complement+ accessors.
11
+ #
12
+ class AlignedStrands
13
+ extend CutSymbol
14
+ extend StringFormatting
15
+
16
+ # Creates a new object.
17
+ # ---
18
+ # *Returns*:: Bio::RestrictionEnzyme::DoubleStranded::AlignedStrands object
19
+ def initialize; super; end
20
+
21
+ # The object returned for alignments
22
+ Result = Struct.new(:primary, :complement)
23
+
24
+ # Pad and align two String objects without cut symbols.
25
+ #
26
+ # This will look for the sub-sequence without left and right 'n' padding
27
+ # and re-apply 'n' padding to both strings on both sides equal to the
28
+ # maximum previous padding on that side.
29
+ #
30
+ # The sub-sequences stripped of left and right 'n' padding must be of equal
31
+ # length.
32
+ #
33
+ # Example:
34
+ # AlignedStrands.align('nngattacannnnn', 'nnnnnctaatgtnn') # =>
35
+ # <struct Bio::RestrictionEnzyme::DoubleStranded::AlignedStrands::Result
36
+ # primary="nnnnngattacannnnn",
37
+ # complement="nnnnnctaatgtnnnnn">
38
+ #
39
+ # ---
40
+ # *Arguments*
41
+ # * +a+: Primary strand
42
+ # * +b+: Complementary strand
43
+ # *Returns*:: +Result+ object with equal padding on both strings
44
+ def self.align(a, b)
45
+ a = a.to_s
46
+ b = b.to_s
47
+ validate_input( strip_padding(a), strip_padding(b) )
48
+ left = [left_padding(a), left_padding(b)].sort.last
49
+ right = [right_padding(a), right_padding(b)].sort.last
50
+
51
+ p = left + strip_padding(a) + right
52
+ c = left + strip_padding(b) + right
53
+ Result.new(p,c)
54
+ end
55
+
56
+ # Pad and align two String objects with cut symbols.
57
+ #
58
+ # Example:
59
+ # AlignedStrands.with_cuts('nngattacannnnn', 'nnnnnctaatgtnn', [0, 10, 12], [0, 2, 12]) # =>
60
+ # <struct Bio::RestrictionEnzyme::DoubleStranded::AlignedStrands::Result
61
+ # primary="n n n n^n g a t t a c a n n^n n^n",
62
+ # complement="n^n n^n n c t a a t g t n^n n n n">
63
+ #
64
+ # Notes:
65
+ # * To make room for the cut symbols each nucleotide is spaced out.
66
+ # * This is meant to be able to handle multiple cuts and completely
67
+ # unrelated cutsites on the two strands, therefore no biological
68
+ # algorithm assumptions (shortcuts) are made.
69
+ #
70
+ # The sequences stripped of left and right 'n' padding must be of equal
71
+ # length.
72
+ #
73
+ # ---
74
+ # *Arguments*
75
+ # * +a+: Primary sequence
76
+ # * +b+: Complementary sequence
77
+ # * +a_cuts+: Primary strand cut locations in 0-based index notation
78
+ # * +b_cuts+: Complementary strand cut locations in 0-based index notation
79
+ # *Returns*:: +Result+ object with equal padding on both strings and spacing between bases
80
+ def self.align_with_cuts(a,b,a_cuts,b_cuts)
81
+ a = a.to_s
82
+ b = b.to_s
83
+ validate_input( strip_padding(a), strip_padding(b) )
84
+
85
+ a_left, a_right = left_padding(a), right_padding(a)
86
+ b_left, b_right = left_padding(b), right_padding(b)
87
+
88
+ left_diff = a_left.length - b_left.length
89
+ right_diff = a_right.length - b_right.length
90
+
91
+ (right_diff > 0) ? (b_right += 'n' * right_diff) : (a_right += 'n' * right_diff.abs)
92
+
93
+ a_adjust = b_adjust = 0
94
+
95
+ if left_diff > 0
96
+ b_left += 'n' * left_diff
97
+ b_adjust = left_diff
98
+ else
99
+ a_left += 'n' * left_diff.abs
100
+ a_adjust = left_diff.abs
101
+ end
102
+
103
+ a = a_left + strip_padding(a) + a_right
104
+ b = b_left + strip_padding(b) + b_right
105
+
106
+ a_cuts.sort.reverse.each { |c| a.insert(c+1+a_adjust, cut_symbol) }
107
+ b_cuts.sort.reverse.each { |c| b.insert(c+1+b_adjust, cut_symbol) }
108
+
109
+ Result.new( add_spacing(a), add_spacing(b) )
110
+ end
111
+
112
+ #########
113
+ protected
114
+ #########
115
+
116
+ def self.validate_input(a,b)
117
+ unless a.size == b.size
118
+ err = "Result sequences are not the same size. Does not align sequences with differing lengths after strip_padding.\n"
119
+ err += "#{a.size}, #{a.inspect}\n"
120
+ err += "#{b.size}, #{b.inspect}"
121
+ raise ArgumentError, err
122
+ end
123
+ end
124
+ end # AlignedStrands
125
+ end # DoubleStranded
126
+ end # RestrictionEnzyme
127
+ end # Bio
@@ -0,0 +1,95 @@
1
+ # bio/util/restriction_enzyme/double_stranded/cut_location_pair.rb - Stores a cut location pair in 0-based index notation
2
+
3
+ require 'bio/util/restriction_enzyme'
4
+
5
+ module Bio
6
+ class RestrictionEnzyme
7
+ class DoubleStranded
8
+
9
+ # Stores a single cut location pair in 0-based index notation for use with
10
+ # DoubleStranded enzyme sequences.
11
+ #
12
+ class CutLocationPair < Array
13
+ # Location of the cut on the primary strand.
14
+ # Corresponds - or 'pairs' - to the complement cut.
15
+ # A value of +nil+ is an explicit representation of 'no cut'.
16
+ attr_reader :primary
17
+
18
+ # Location of the cut on the complementary strand.
19
+ # Corresponds - or 'pairs' - to the primary cut.
20
+ # A value of +nil+ is an explicit representation of 'no cut'.
21
+ attr_reader :complement
22
+
23
+ # CutLocationPair constructor.
24
+ #
25
+ # Stores a single cut location pair in 0-based index notation for use with
26
+ # DoubleStranded enzyme sequences.
27
+ #
28
+ # Example:
29
+ # clp = CutLocationPair.new(3,2)
30
+ # clp.primary # 3
31
+ # clp.complement # 2
32
+ #
33
+ # ---
34
+ # *Arguments*
35
+ # * +pair+: May be two values represented as an Array, a Range, or a
36
+ # combination of Integer and nil values. The first value
37
+ # represents a cut on the primary strand, the second represents
38
+ # a cut on the complement strand.
39
+ # *Returns*:: nothing
40
+ def initialize( *pair )
41
+ a = b = nil
42
+
43
+ if pair[0].kind_of? Array
44
+ a,b = init_with_array( pair[0] )
45
+
46
+ # no idea why this barfs without the second half during test/runner.rb
47
+ # are there two Range objects running around?
48
+ elsif pair[0].kind_of? Range or (pair[0].class.to_s == 'Range')
49
+ #elsif pair[0].kind_of? Range
50
+ a,b = init_with_array( [pair[0].first, pair[0].last] )
51
+
52
+ elsif pair[0].kind_of? Integer or pair[0].kind_of? NilClass
53
+ a,b = init_with_array( [pair[0], pair[1]] )
54
+
55
+ else
56
+ raise ArgumentError, "#{pair[0].class} is an invalid class type to initalize CutLocationPair."
57
+ end
58
+
59
+ super( [a,b] )
60
+ @primary = a
61
+ @complement = b
62
+ return
63
+ end
64
+
65
+ #########
66
+ protected
67
+ #########
68
+
69
+ def init_with_array( ary )
70
+ validate_1(ary)
71
+ a = ary.shift
72
+ ary.empty? ? b = nil : b = ary.shift
73
+ validate_2(a,b)
74
+ [a,b]
75
+ end
76
+
77
+ def validate_1( ary )
78
+ unless ary.size == 1 or ary.size == 2
79
+ raise ArgumentError, "Must be one or two elements."
80
+ end
81
+ end
82
+
83
+ def validate_2( a, b )
84
+ if (a != nil and a < 0) or (b != nil and b < 0)
85
+ raise ArgumentError, "0-based index notation only. Negative values are illegal."
86
+ end
87
+
88
+ if a == nil and b == nil
89
+ raise ArgumentError, "Neither strand has a cut. Ambiguous."
90
+ end
91
+ end
92
+ end # CutLocationPair
93
+ end # DoubleStranded
94
+ end # RestrictionEnzyme
95
+ end # Bio