bio-restriction_enzyme 1.0.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.
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