gfa 0.2.0 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +22 -18
- data/bin/gfa-add-gaf +70 -0
- data/bin/gfa-subgraph +41 -0
- data/lib/gfa/common.rb +33 -8
- data/lib/gfa/field/char.rb +2 -1
- data/lib/gfa/field/float.rb +18 -1
- data/lib/gfa/field/hex.rb +18 -1
- data/lib/gfa/field/json.rb +10 -1
- data/lib/gfa/field/numarray.rb +29 -4
- data/lib/gfa/field/sigint.rb +14 -1
- data/lib/gfa/field/string.rb +10 -1
- data/lib/gfa/field.rb +82 -10
- data/lib/gfa/generator.rb +3 -3
- data/lib/gfa/graph.rb +139 -4
- data/lib/gfa/parser.rb +78 -22
- data/lib/gfa/record/comment.rb +7 -2
- data/lib/gfa/record/containment.rb +12 -7
- data/lib/gfa/record/has_from_to.rb +47 -0
- data/lib/gfa/record/header.rb +2 -0
- data/lib/gfa/record/jump.rb +11 -30
- data/lib/gfa/record/link.rb +11 -29
- data/lib/gfa/record/path.rb +32 -6
- data/lib/gfa/record/segment.rb +8 -4
- data/lib/gfa/record/walk.rb +6 -6
- data/lib/gfa/record.rb +34 -14
- data/lib/gfa/record_set/comment_set.rb +3 -0
- data/lib/gfa/record_set/containment_set.rb +4 -0
- data/lib/gfa/record_set/header_set.rb +3 -0
- data/lib/gfa/record_set/jump_set.rb +3 -0
- data/lib/gfa/record_set/link_set.rb +3 -0
- data/lib/gfa/record_set/path_set.rb +4 -0
- data/lib/gfa/record_set/segment_set.rb +4 -0
- data/lib/gfa/record_set/walk_set.rb +3 -0
- data/lib/gfa/record_set.rb +121 -0
- data/lib/gfa/version.rb +1 -1
- data/test/common_test.rb +5 -5
- data/test/field_test.rb +52 -26
- data/test/parser_test.rb +52 -13
- data/test/record_test.rb +7 -0
- data/test/test_helper.rb +5 -0
- metadata +18 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 97e6400338884b4ceb1161778c26c5d6de6ee71616be1b5caae6aa0691d88395
|
4
|
+
data.tar.gz: 2fe8103598246724d98e3ceeecce92b5564f45bf029bf034978277cde59b4caa
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3beac70ac4c3d4e46bd01399351fbc5e5ffcdaac5bd2a653b7f17c8f29df5c13e48a11575c42fa0ec78cba62485528148687614e9373ac6cc5dd773f97cd67a6
|
7
|
+
data.tar.gz: c488a4b26604ffc95228d5aa454399b4da8facecb9a90c2579be3c36446637bc60b89578c5a5443047ac020c4c9fb9b980fd3943fdb56e7993ebfcc7c4e60876
|
data/README.md
CHANGED
@@ -7,28 +7,35 @@
|
|
7
7
|
|
8
8
|
This implementation follows the specifications of [GFA-spec][].
|
9
9
|
|
10
|
+
To load the library:
|
11
|
+
|
12
|
+
```ruby
|
13
|
+
require 'gfa'
|
14
|
+
```
|
10
15
|
|
11
16
|
## Parsing GFA
|
12
17
|
|
13
18
|
To parse a file in GFA format:
|
14
19
|
|
15
20
|
```ruby
|
16
|
-
require 'gfa'
|
17
|
-
|
18
21
|
my_gfa = GFA.load('assembly.gfa')
|
19
22
|
```
|
20
23
|
|
21
|
-
|
24
|
+
For large GFA files, you can also parse them in parallel:
|
22
25
|
|
23
26
|
```ruby
|
24
|
-
|
27
|
+
my_gfa = GFA.load_parallel('large-graph.gfa', 4)
|
28
|
+
```
|
29
|
+
|
30
|
+
To load GFA strings line-by-line:
|
25
31
|
|
32
|
+
```ruby
|
26
33
|
my_gfa = GFA.new
|
27
|
-
|
28
|
-
fh.each do |ln|
|
29
|
-
|
34
|
+
File.open('assembly.gfa', 'r') do |fh|
|
35
|
+
fh.each do |ln|
|
36
|
+
my_gfa << ln
|
37
|
+
end
|
30
38
|
end
|
31
|
-
fh.close
|
32
39
|
```
|
33
40
|
|
34
41
|
|
@@ -58,12 +65,11 @@ Any `GFA` object can be exported as an [`RGL`][rgl] graph using the methods
|
|
58
65
|
[tiny.gfa](https://github.com/lmrodriguezr/gfa/raw/master/data/tiny.gfa):
|
59
66
|
|
60
67
|
```ruby
|
61
|
-
require
|
62
|
-
require "rgl/dot"
|
68
|
+
require 'rgl/dot'
|
63
69
|
|
64
|
-
my_gfa = GFA.load(
|
70
|
+
my_gfa = GFA.load('data/tiny.gfa')
|
65
71
|
dg = my_gfa.implicit_graph
|
66
|
-
dg.write_to_graphic_file(
|
72
|
+
dg.write_to_graphic_file('jpg')
|
67
73
|
```
|
68
74
|
|
69
75
|
![tiny_dg](https://github.com/lmrodriguezr/gfa/raw/master/data/tiny.jpg)
|
@@ -72,8 +78,8 @@ If you don't care about orientation, you can also build an undirected graph
|
|
72
78
|
without orientation:
|
73
79
|
|
74
80
|
```ruby
|
75
|
-
ug = my_gfa.implicit_graph(orient:false)
|
76
|
-
ug.write_to_graphic_file(
|
81
|
+
ug = my_gfa.implicit_graph(orient: false)
|
82
|
+
ug.write_to_graphic_file('jpg')
|
77
83
|
```
|
78
84
|
|
79
85
|
![tiny_ug](https://github.com/lmrodriguezr/gfa/raw/master/data/tiny_undirected.jpg)
|
@@ -88,11 +94,9 @@ gem install gfa
|
|
88
94
|
Or add the following line to your Gemfile:
|
89
95
|
|
90
96
|
```ruby
|
91
|
-
gem
|
97
|
+
gem 'gfa'
|
92
98
|
```
|
93
99
|
|
94
|
-
and run `bundle install` from your shell.
|
95
|
-
|
96
100
|
|
97
101
|
# Author
|
98
102
|
|
@@ -103,6 +107,6 @@ and run `bundle install` from your shell.
|
|
103
107
|
|
104
108
|
[Artistic License 2.0](LICENSE).
|
105
109
|
|
106
|
-
[GFA-spec]: https://github.com/
|
110
|
+
[GFA-spec]: https://github.com/GFA-spec/GFA-spec
|
107
111
|
[lrr]: https://rodriguez-r.com/
|
108
112
|
[rgl]: https://github.com/monora/rgl
|
data/bin/gfa-add-gaf
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
# @package MiGA
|
4
|
+
# @license Artistic-2.0
|
5
|
+
|
6
|
+
$LOAD_PATH.push File.expand_path('../../lib', __FILE__)
|
7
|
+
$LOAD_PATH.push File.expand_path('../../lib', File.realpath(__FILE__))
|
8
|
+
|
9
|
+
require 'gfa'
|
10
|
+
|
11
|
+
input_gfa, input_gaf, output, degree, threads = ARGV
|
12
|
+
|
13
|
+
unless degree
|
14
|
+
$stderr.puts <<~HELP
|
15
|
+
gfa-add-gaf <input-gfa> <input-gaf> <output> <degree> [<pref> [<threads>]]
|
16
|
+
|
17
|
+
<input-gfa> Input GFA file to read
|
18
|
+
<input-gaf> Input GAF file to read
|
19
|
+
<output> Output GFA file to write
|
20
|
+
<degree> Maximum degree of separation between the segment set in the GAF
|
21
|
+
and any other included segments. If 0, only segments are
|
22
|
+
included. If 1, only the target segments, records linking to
|
23
|
+
them, and segments linked by those records. Any integer > 1
|
24
|
+
includes additional expansion rounds for those linked segments.
|
25
|
+
Use -1 to include the complete original GAF without subsetting.
|
26
|
+
<pref> A prefix to name all recorded paths
|
27
|
+
By default: Based on the GAF file name
|
28
|
+
<threads> If passed, parallelize process with these many threads
|
29
|
+
HELP
|
30
|
+
exit(1)
|
31
|
+
end
|
32
|
+
|
33
|
+
$stderr.puts "Loading GFA: #{input_gfa}"
|
34
|
+
gfa = GFA.load_parallel(input_gfa, (threads || 1).to_i)
|
35
|
+
|
36
|
+
$stderr.puts "Loading GAF: #{input_gaf}"
|
37
|
+
$stderr.puts "- Minimum identity: #{0.95}"
|
38
|
+
pref ||= File.basename(input_gaf, '.gaf').gsub(/[^!-)+-<>-~]/, '_')
|
39
|
+
segments = []
|
40
|
+
File.open(input_gaf, 'r') do |fh|
|
41
|
+
fh.each do |ln|
|
42
|
+
row = ln.chomp.split("\t")
|
43
|
+
opt = Hash[row[12..].map { |i| i.split(':', 2) }]
|
44
|
+
opt.each { |k, v| opt[k] = GFA::Field[v] }
|
45
|
+
next if opt['id'] && opt['id'].value < 0.95
|
46
|
+
|
47
|
+
gaf_path = row[5]
|
48
|
+
seg_names = []
|
49
|
+
gaf_path.scan(/[><]?[^><]+/).each do |seg|
|
50
|
+
seg_orient = seg.match?(/^</) ? '-' : '+'
|
51
|
+
seg_name = seg.sub(/^[><]/, '')
|
52
|
+
seg_names << "#{seg_name}#{seg_orient}"
|
53
|
+
segments << seg_name unless segments.include?(seg_name)
|
54
|
+
end
|
55
|
+
gfa << GFA::Record::Path.new(
|
56
|
+
"#{pref}_#{$.}", seg_names.join(','), opt['cg']&.value || '*'
|
57
|
+
)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
$stderr.puts "- Found #{segments.size} linked segments"
|
61
|
+
|
62
|
+
degree = degree.to_i
|
63
|
+
if degree >= 0
|
64
|
+
$stderr.puts 'Subsetting graph'
|
65
|
+
gfa = gfa.subgraph(segments, degree: degree)
|
66
|
+
end
|
67
|
+
|
68
|
+
$stderr.puts "Saving GFA: #{output}"
|
69
|
+
gfa.save(output)
|
70
|
+
|
data/bin/gfa-subgraph
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
# @package MiGA
|
4
|
+
# @license Artistic-2.0
|
5
|
+
|
6
|
+
$LOAD_PATH.push File.expand_path('../../lib', __FILE__)
|
7
|
+
$LOAD_PATH.push File.expand_path('../../lib', File.realpath(__FILE__))
|
8
|
+
|
9
|
+
require 'gfa'
|
10
|
+
|
11
|
+
input, output, degree, segments, threads = ARGV
|
12
|
+
|
13
|
+
unless segments
|
14
|
+
$stderr.puts <<~HELP
|
15
|
+
Select a set of segments and include only elements of the GFA linked to
|
16
|
+
those segments (directly or indirectly)
|
17
|
+
|
18
|
+
gfa-subgraph <input> <output> <degree> <segments> [<threads>]
|
19
|
+
|
20
|
+
<input> Input GFA file to read
|
21
|
+
<output> Output GFA file to write
|
22
|
+
<degree> Maximum degree of separation between the segment set and any
|
23
|
+
other included segments. If 0, only segments are included.
|
24
|
+
If 1, only the target segments, records linking to them, and
|
25
|
+
segments linked by those records. Any integer > 1 includes
|
26
|
+
additional expansion rounds for those linked segments.
|
27
|
+
<segments> Comma-delimited list of segment segments
|
28
|
+
<threads> If passed, parallelize process with these many threads
|
29
|
+
HELP
|
30
|
+
exit(1)
|
31
|
+
end
|
32
|
+
|
33
|
+
$stderr.puts "Loading GFA: #{input}"
|
34
|
+
gfa = GFA.load_parallel(input, (threads || 1).to_i)
|
35
|
+
|
36
|
+
$stderr.puts 'Subsetting graph'
|
37
|
+
gfa = gfa.subgraph(segments.split(','), degree: degree.to_i)
|
38
|
+
|
39
|
+
$stderr.puts "Saving GFA: #{output}"
|
40
|
+
gfa.save(output)
|
41
|
+
|
data/lib/gfa/common.rb
CHANGED
@@ -1,30 +1,33 @@
|
|
1
1
|
require 'gfa/version'
|
2
|
-
require 'gfa/
|
2
|
+
require 'gfa/record_set'
|
3
3
|
require 'gfa/field'
|
4
4
|
|
5
5
|
class GFA
|
6
6
|
# Class-level
|
7
7
|
def self.assert_format(value, regex, message)
|
8
|
-
unless value =~ regex
|
9
|
-
raise "#{message}: #{value}
|
8
|
+
unless value =~ /^(?:#{regex})$/
|
9
|
+
raise "#{message}: #{value}"
|
10
10
|
end
|
11
11
|
end
|
12
12
|
|
13
13
|
# Instance-level
|
14
|
-
attr :gfa_version, :records
|
14
|
+
attr :gfa_version, :records, :opts
|
15
15
|
|
16
16
|
GFA::Record.TYPES.each do |r_type|
|
17
|
-
plural = "#{r_type.downcase}s"
|
18
17
|
singular = "#{r_type.downcase}"
|
18
|
+
plural = "#{singular}s"
|
19
19
|
|
20
20
|
define_method(plural) { records[r_type] }
|
21
21
|
define_method(singular) { |k| records[r_type][k] }
|
22
22
|
define_method("add_#{singular}") { |v| @records[r_type] << v }
|
23
23
|
end
|
24
24
|
|
25
|
-
def initialize
|
25
|
+
def initialize(opts = {})
|
26
26
|
@records = {}
|
27
|
-
|
27
|
+
@opts = { index: true, index_id: false, comments: false }.merge(opts)
|
28
|
+
GFA::Record.TYPES.each do |t|
|
29
|
+
@records[t] = GFA::RecordSet.name_class(t).new(self)
|
30
|
+
end
|
28
31
|
end
|
29
32
|
|
30
33
|
def empty?
|
@@ -35,5 +38,27 @@ class GFA
|
|
35
38
|
records == gfa.records
|
36
39
|
end
|
37
40
|
|
38
|
-
|
41
|
+
def ==(gfa)
|
42
|
+
eql?(gfa)
|
43
|
+
end
|
44
|
+
|
45
|
+
def size
|
46
|
+
records.values.map(&:size).inject(0, :+)
|
47
|
+
end
|
48
|
+
|
49
|
+
def merge!(gfa)
|
50
|
+
raise "Unsupported object: #{gfa}" unless gfa.is_a? GFA
|
51
|
+
|
52
|
+
GFA::Record.TYPES.each do |t|
|
53
|
+
@records[t].merge!(gfa.records[t])
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def indexed?
|
58
|
+
records.values.all?(&:indexed?)
|
59
|
+
end
|
60
|
+
|
61
|
+
def rebuild_index!
|
62
|
+
@records.each_value(&:rebuild_index!)
|
63
|
+
end
|
39
64
|
end
|
data/lib/gfa/field/char.rb
CHANGED
data/lib/gfa/field/float.rb
CHANGED
@@ -1,9 +1,26 @@
|
|
1
1
|
class GFA::Field::Float < GFA::Field
|
2
2
|
CODE = :f
|
3
|
-
REGEX =
|
3
|
+
REGEX = /[-+]?[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?/
|
4
|
+
NATIVE_FUN = :to_f
|
5
|
+
|
6
|
+
def to_f
|
7
|
+
value
|
8
|
+
end
|
9
|
+
|
10
|
+
def to_i
|
11
|
+
value.to_i
|
12
|
+
end
|
4
13
|
|
5
14
|
def initialize(f)
|
6
15
|
GFA.assert_format(f, regex, "Bad #{type}")
|
7
16
|
@value = f.to_f
|
8
17
|
end
|
18
|
+
|
19
|
+
def equivalent?(field)
|
20
|
+
if field.is_a?(GFA::Field::NumArray)
|
21
|
+
return field.size == 1 && field.first.to_f == value
|
22
|
+
end
|
23
|
+
|
24
|
+
super
|
25
|
+
end
|
9
26
|
end
|
data/lib/gfa/field/hex.rb
CHANGED
@@ -1,9 +1,26 @@
|
|
1
1
|
class GFA::Field::Hex < GFA::Field
|
2
2
|
CODE = :H
|
3
|
-
REGEX =
|
3
|
+
REGEX = /[0-9A-F]+/
|
4
|
+
NATIVE_FUN = :to_i
|
4
5
|
|
5
6
|
def initialize(f)
|
6
7
|
GFA.assert_format(f, regex, "Bad #{type}")
|
7
8
|
@value = f
|
8
9
|
end
|
10
|
+
|
11
|
+
def to_i
|
12
|
+
value.to_i(16)
|
13
|
+
end
|
14
|
+
|
15
|
+
def to_f
|
16
|
+
to_i.to_f
|
17
|
+
end
|
18
|
+
|
19
|
+
def equivalent?(field)
|
20
|
+
if field.is_a? GFA::Field::NumArray
|
21
|
+
return field.size == 1 && field.first.to_i == value
|
22
|
+
end
|
23
|
+
|
24
|
+
super
|
25
|
+
end
|
9
26
|
end
|
data/lib/gfa/field/json.rb
CHANGED
@@ -1,9 +1,18 @@
|
|
1
1
|
class GFA::Field::Json < GFA::Field
|
2
2
|
CODE = :J
|
3
|
-
REGEX =
|
3
|
+
REGEX = /[ !-~]+/
|
4
|
+
NATIVE_FUN = :to_s
|
4
5
|
|
5
6
|
def initialize(f)
|
6
7
|
GFA.assert_format(f, regex, "Bad #{type}")
|
7
8
|
@value = f
|
8
9
|
end
|
10
|
+
|
11
|
+
def equivalent?(field)
|
12
|
+
# TODO
|
13
|
+
# We should parse the contents when comparing two GFA::Field::Json to
|
14
|
+
# evaluate equivalencies such as 'J:{ "a" : 1 }' ~ 'J:{"a":1}' (spaces)
|
15
|
+
# or 'J:{"a":1,"b":2}' ~ 'J:{"b":2,"a":1}' (element order)
|
16
|
+
super
|
17
|
+
end
|
9
18
|
end
|
data/lib/gfa/field/numarray.rb
CHANGED
@@ -1,17 +1,30 @@
|
|
1
1
|
class GFA::Field::NumArray < GFA::Field
|
2
2
|
CODE = :B
|
3
|
-
REGEX =
|
3
|
+
REGEX = /[cCsSiIf](,[-+]?[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?)+/
|
4
|
+
NATIVE_FUN = :to_a
|
4
5
|
|
5
6
|
def initialize(f)
|
6
7
|
GFA.assert_format(f, regex, "Bad #{type}")
|
7
8
|
@value = f
|
8
9
|
end
|
9
10
|
|
10
|
-
def modifier
|
11
|
+
def modifier
|
12
|
+
value[0]
|
13
|
+
end
|
11
14
|
|
12
|
-
def
|
15
|
+
def modifier_fun
|
16
|
+
modifier == 'f' ? :to_f : :to_i
|
17
|
+
end
|
13
18
|
|
14
|
-
|
19
|
+
def array
|
20
|
+
@array ||= value[2..-1].split(',').map(&modifier_fun)
|
21
|
+
end
|
22
|
+
|
23
|
+
alias to_a array
|
24
|
+
|
25
|
+
%i[empty? size count length first last].each do |i|
|
26
|
+
define_method(i) { array.send(i) }
|
27
|
+
end
|
15
28
|
|
16
29
|
def number_type
|
17
30
|
{
|
@@ -21,4 +34,16 @@ class GFA::Field::NumArray < GFA::Field
|
|
21
34
|
f: 'float'
|
22
35
|
}[modifier.to_sym]
|
23
36
|
end
|
37
|
+
|
38
|
+
def equivalent?(field)
|
39
|
+
return true if eql?(field)
|
40
|
+
|
41
|
+
if field.respond_to?(:to_a)
|
42
|
+
field.to_a.map(&modifier_fun) == array
|
43
|
+
elsif size == 1 && field.respond_to?(modifier_fun)
|
44
|
+
field.send(modifier_fun) == first
|
45
|
+
else
|
46
|
+
false
|
47
|
+
end
|
48
|
+
end
|
24
49
|
end
|
data/lib/gfa/field/sigint.rb
CHANGED
@@ -1,9 +1,22 @@
|
|
1
1
|
class GFA::Field::SigInt < GFA::Field
|
2
2
|
CODE = :i
|
3
|
-
REGEX =
|
3
|
+
REGEX = /[-+]?[0-9]+/
|
4
|
+
NATIVE_FUN = :to_i
|
4
5
|
|
5
6
|
def initialize(f)
|
6
7
|
GFA.assert_format(f, regex, "Bad #{type}")
|
7
8
|
@value = f.to_i
|
8
9
|
end
|
10
|
+
|
11
|
+
def to_i
|
12
|
+
value
|
13
|
+
end
|
14
|
+
|
15
|
+
def equivalent?(field)
|
16
|
+
if field.is_a?(GFA::Field::NumArray)
|
17
|
+
return field.size == 1 && field.first.to_i == value
|
18
|
+
end
|
19
|
+
|
20
|
+
super
|
21
|
+
end
|
9
22
|
end
|
data/lib/gfa/field/string.rb
CHANGED
@@ -1,6 +1,15 @@
|
|
1
1
|
class GFA::Field::String < GFA::Field
|
2
2
|
CODE = :Z
|
3
|
-
REGEX =
|
3
|
+
REGEX = /[ !-~]+/
|
4
|
+
NATIVE_FUN = :to_s
|
5
|
+
|
6
|
+
def to_f
|
7
|
+
value.to_f
|
8
|
+
end
|
9
|
+
|
10
|
+
def to_i(base = 10)
|
11
|
+
value.to_i(base)
|
12
|
+
end
|
4
13
|
|
5
14
|
def initialize(f)
|
6
15
|
GFA.assert_format(f, regex, "Bad #{type}")
|
data/lib/gfa/field.rb
CHANGED
@@ -12,7 +12,7 @@ class GFA::Field
|
|
12
12
|
TYPES = CODES.values
|
13
13
|
TYPES.each { |t| require "gfa/field/#{t.downcase}" }
|
14
14
|
|
15
|
-
[
|
15
|
+
%i[CODES TYPES].each do |x|
|
16
16
|
define_singleton_method(x) { const_get(x) }
|
17
17
|
end
|
18
18
|
|
@@ -25,23 +25,95 @@ class GFA::Field
|
|
25
25
|
def self.name_class(name)
|
26
26
|
const_get(name)
|
27
27
|
end
|
28
|
-
|
28
|
+
|
29
|
+
def self.[](string)
|
30
|
+
code, value = string.split(':', 2)
|
31
|
+
code_class(code).new(value)
|
32
|
+
end
|
33
|
+
|
29
34
|
# Instance-level
|
30
35
|
|
31
36
|
attr :value
|
32
37
|
|
33
|
-
def type
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
def
|
38
|
-
|
39
|
-
|
38
|
+
def type
|
39
|
+
CODES[code]
|
40
|
+
end
|
41
|
+
|
42
|
+
def code
|
43
|
+
self.class::CODE
|
44
|
+
end
|
45
|
+
|
46
|
+
def regex
|
47
|
+
self.class::REGEX
|
48
|
+
end
|
49
|
+
|
50
|
+
def native_fun
|
51
|
+
self.class::NATIVE_FUN
|
52
|
+
end
|
53
|
+
|
54
|
+
def to_native
|
55
|
+
native_fun == :to_s ? to_s(false) : send(native_fun)
|
56
|
+
end
|
57
|
+
|
58
|
+
def to_s(with_type = true)
|
40
59
|
"#{"#{code}:" if with_type}#{value}"
|
41
60
|
end
|
42
|
-
|
61
|
+
|
43
62
|
def hash
|
44
63
|
value.hash
|
45
64
|
end
|
46
65
|
|
66
|
+
##
|
67
|
+
# Evaluate equivalency of contents. All the following fields are distinct but
|
68
|
+
# contain the same information, and are therefore considered equivalent:
|
69
|
+
# Z:123, i:123, f:123.0, B:i,123, H:7b
|
70
|
+
#
|
71
|
+
# Note that the information content is determined by the class of the first
|
72
|
+
# operator. For example:
|
73
|
+
# - 'i:123' ~ 'f:123.4' is true because values are compared as integers
|
74
|
+
# - 'f:123.4' ~ 'i:123' if false because values are compared as floats
|
75
|
+
def equivalent?(field)
|
76
|
+
return true if eql?(field) # Might be faster, so testing this first
|
77
|
+
|
78
|
+
if field.respond_to?(native_fun)
|
79
|
+
if field.is_a?(GFA::Field) && native_fun == :to_s
|
80
|
+
field.to_s(false) == to_native
|
81
|
+
else
|
82
|
+
field.send(native_fun) == to_native
|
83
|
+
end
|
84
|
+
else
|
85
|
+
field == value
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
##
|
90
|
+
# Non-equivalent to +field+, same as +!equivalent?+
|
91
|
+
def !~(field)
|
92
|
+
!self.~(field)
|
93
|
+
end
|
94
|
+
|
95
|
+
##
|
96
|
+
# Same as +equivalent?+
|
97
|
+
def ~(field)
|
98
|
+
equivalent?(field)
|
99
|
+
end
|
100
|
+
|
101
|
+
##
|
102
|
+
# Evaluate equality. Note that fields with equivalent values evaluate as
|
103
|
+
# different. For example, the following fields have equivalent information,
|
104
|
+
# but they all evaluate as different: Z:123, i:123, f:123.0, B:i,123, H:7b.
|
105
|
+
# To test equivalency of contents instead, use +equivalent?+
|
106
|
+
def eql?(field)
|
107
|
+
if field.is_a?(GFA::Field)
|
108
|
+
type == field.type && value == field.value
|
109
|
+
else
|
110
|
+
field.is_a?(value.class) && value == field
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
##
|
115
|
+
# Same as +eql?+
|
116
|
+
def ==(field)
|
117
|
+
eql?(field)
|
118
|
+
end
|
47
119
|
end
|
data/lib/gfa/generator.rb
CHANGED
@@ -8,9 +8,9 @@ class GFA
|
|
8
8
|
end
|
9
9
|
|
10
10
|
def each_line(&blk)
|
11
|
-
set_version_header('1.
|
11
|
+
set_version_header('1.2') if gfa_version.nil?
|
12
12
|
GFA::Record.TYPES.each do |r_type|
|
13
|
-
records[r_type].each do |record|
|
13
|
+
records[r_type].set.each do |record|
|
14
14
|
blk[record.to_s]
|
15
15
|
end
|
16
16
|
end
|
@@ -23,7 +23,7 @@ class GFA
|
|
23
23
|
end
|
24
24
|
|
25
25
|
def unset_version
|
26
|
-
|
26
|
+
headers.set.delete_if { |o| !o.fields[:VN].nil? }
|
27
27
|
@gfa_version = nil
|
28
28
|
end
|
29
29
|
|