gfa 0.1.1 → 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: d8694c80555e4fae39fb77a245428ec8e7ce19a2
4
- data.tar.gz: ab29f1249a26d28082e0380fc96edec934acb0e5
3
+ metadata.gz: 8388ee28f1d71339d701b32e2a4fa1538b0f391f
4
+ data.tar.gz: 520590efdc7170cf805db8427574e6e6fb5a8ac1
5
5
  SHA512:
6
- metadata.gz: fe2d1bb08aeaca8cb0b3d588a6b02965ec612d6201bf19b96927df9a2f18b4c2974829a6f9b6da608fae5ac8fd4f9c62b2acd3c8d29718ec2251afdb08ceccba
7
- data.tar.gz: c5ce23b34bc1b890e8d9c7de19a1f4cd0a2766897530b9e2563f667d5d81017b5c1245fd39bb52db3f3960513fb47670210df39124c556753874e01feeac03c0
6
+ metadata.gz: c8e8366d01006fbffc22ae49f3b4d619d8e8fbf25ef2c6173b427f05382a28c8a9efd7798f791d05c890e3057d3115ca8d3a6bc6d330551bf0915569c89ecc5c
7
+ data.tar.gz: c6bcd711d7841585e6452cca87564b5d2e41a673e1bc4869df632db6193b928bbc6374ca370d8033db8df70e377234f3aa0a5f3a9ae96c56f053b771dff78060
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source "https://rubygems.org"
2
+ gemspec
3
+ gem "codeclimate-test-reporter", group: :test, require: nil
data/LICENSE ADDED
@@ -0,0 +1,201 @@
1
+ The Artistic License 2.0
2
+
3
+ Copyright (c) 2016 Luis M Rodriguez-R
4
+
5
+ Everyone is permitted to copy and distribute verbatim copies
6
+ of this license document, but changing it is not allowed.
7
+
8
+ Preamble
9
+
10
+ This license establishes the terms under which a given free software
11
+ Package may be copied, modified, distributed, and/or redistributed.
12
+ The intent is that the Copyright Holder maintains some artistic
13
+ control over the development of that Package while still keeping the
14
+ Package available as open source and free software.
15
+
16
+ You are always permitted to make arrangements wholly outside of this
17
+ license directly with the Copyright Holder of a given Package. If the
18
+ terms of this license do not permit the full use that you propose to
19
+ make of the Package, you should contact the Copyright Holder and seek
20
+ a different licensing arrangement.
21
+
22
+ Definitions
23
+
24
+ "Copyright Holder" means the individual(s) or organization(s)
25
+ named in the copyright notice for the entire Package.
26
+
27
+ "Contributor" means any party that has contributed code or other
28
+ material to the Package, in accordance with the Copyright Holder's
29
+ procedures.
30
+
31
+ "You" and "your" means any person who would like to copy,
32
+ distribute, or modify the Package.
33
+
34
+ "Package" means the collection of files distributed by the
35
+ Copyright Holder, and derivatives of that collection and/or of
36
+ those files. A given Package may consist of either the Standard
37
+ Version, or a Modified Version.
38
+
39
+ "Distribute" means providing a copy of the Package or making it
40
+ accessible to anyone else, or in the case of a company or
41
+ organization, to others outside of your company or organization.
42
+
43
+ "Distributor Fee" means any fee that you charge for Distributing
44
+ this Package or providing support for this Package to another
45
+ party. It does not mean licensing fees.
46
+
47
+ "Standard Version" refers to the Package if it has not been
48
+ modified, or has been modified only in ways explicitly requested
49
+ by the Copyright Holder.
50
+
51
+ "Modified Version" means the Package, if it has been changed, and
52
+ such changes were not explicitly requested by the Copyright
53
+ Holder.
54
+
55
+ "Original License" means this Artistic License as Distributed with
56
+ the Standard Version of the Package, in its current version or as
57
+ it may be modified by The Perl Foundation in the future.
58
+
59
+ "Source" form means the source code, documentation source, and
60
+ configuration files for the Package.
61
+
62
+ "Compiled" form means the compiled bytecode, object code, binary,
63
+ or any other form resulting from mechanical transformation or
64
+ translation of the Source form.
65
+
66
+
67
+ Permission for Use and Modification Without Distribution
68
+
69
+ (1) You are permitted to use the Standard Version and create and use
70
+ Modified Versions for any purpose without restriction, provided that
71
+ you do not Distribute the Modified Version.
72
+
73
+
74
+ Permissions for Redistribution of the Standard Version
75
+
76
+ (2) You may Distribute verbatim copies of the Source form of the
77
+ Standard Version of this Package in any medium without restriction,
78
+ either gratis or for a Distributor Fee, provided that you duplicate
79
+ all of the original copyright notices and associated disclaimers. At
80
+ your discretion, such verbatim copies may or may not include a
81
+ Compiled form of the Package.
82
+
83
+ (3) You may apply any bug fixes, portability changes, and other
84
+ modifications made available from the Copyright Holder. The resulting
85
+ Package will still be considered the Standard Version, and as such
86
+ will be subject to the Original License.
87
+
88
+
89
+ Distribution of Modified Versions of the Package as Source
90
+
91
+ (4) You may Distribute your Modified Version as Source (either gratis
92
+ or for a Distributor Fee, and with or without a Compiled form of the
93
+ Modified Version) provided that you clearly document how it differs
94
+ from the Standard Version, including, but not limited to, documenting
95
+ any non-standard features, executables, or modules, and provided that
96
+ you do at least ONE of the following:
97
+
98
+ (a) make the Modified Version available to the Copyright Holder
99
+ of the Standard Version, under the Original License, so that the
100
+ Copyright Holder may include your modifications in the Standard
101
+ Version.
102
+
103
+ (b) ensure that installation of your Modified Version does not
104
+ prevent the user installing or running the Standard Version. In
105
+ addition, the Modified Version must bear a name that is different
106
+ from the name of the Standard Version.
107
+
108
+ (c) allow anyone who receives a copy of the Modified Version to
109
+ make the Source form of the Modified Version available to others
110
+ under
111
+
112
+ (i) the Original License or
113
+
114
+ (ii) a license that permits the licensee to freely copy,
115
+ modify and redistribute the Modified Version using the same
116
+ licensing terms that apply to the copy that the licensee
117
+ received, and requires that the Source form of the Modified
118
+ Version, and of any works derived from it, be made freely
119
+ available in that license fees are prohibited but Distributor
120
+ Fees are allowed.
121
+
122
+
123
+ Distribution of Compiled Forms of the Standard Version
124
+ or Modified Versions without the Source
125
+
126
+ (5) You may Distribute Compiled forms of the Standard Version without
127
+ the Source, provided that you include complete instructions on how to
128
+ get the Source of the Standard Version. Such instructions must be
129
+ valid at the time of your distribution. If these instructions, at any
130
+ time while you are carrying out such distribution, become invalid, you
131
+ must provide new instructions on demand or cease further distribution.
132
+ If you provide valid instructions or cease distribution within thirty
133
+ days after you become aware that the instructions are invalid, then
134
+ you do not forfeit any of your rights under this license.
135
+
136
+ (6) You may Distribute a Modified Version in Compiled form without
137
+ the Source, provided that you comply with Section 4 with respect to
138
+ the Source of the Modified Version.
139
+
140
+
141
+ Aggregating or Linking the Package
142
+
143
+ (7) You may aggregate the Package (either the Standard Version or
144
+ Modified Version) with other packages and Distribute the resulting
145
+ aggregation provided that you do not charge a licensing fee for the
146
+ Package. Distributor Fees are permitted, and licensing fees for other
147
+ components in the aggregation are permitted. The terms of this license
148
+ apply to the use and Distribution of the Standard or Modified Versions
149
+ as included in the aggregation.
150
+
151
+ (8) You are permitted to link Modified and Standard Versions with
152
+ other works, to embed the Package in a larger work of your own, or to
153
+ build stand-alone binary or bytecode versions of applications that
154
+ include the Package, and Distribute the result without restriction,
155
+ provided the result does not expose a direct interface to the Package.
156
+
157
+
158
+ Items That are Not Considered Part of a Modified Version
159
+
160
+ (9) Works (including, but not limited to, modules and scripts) that
161
+ merely extend or make use of the Package, do not, by themselves, cause
162
+ the Package to be a Modified Version. In addition, such works are not
163
+ considered parts of the Package itself, and are not subject to the
164
+ terms of this license.
165
+
166
+
167
+ General Provisions
168
+
169
+ (10) Any use, modification, and distribution of the Standard or
170
+ Modified Versions is governed by this Artistic License. By using,
171
+ modifying or distributing the Package, you accept this license. Do not
172
+ use, modify, or distribute the Package, if you do not accept this
173
+ license.
174
+
175
+ (11) If your Modified Version has been derived from a Modified
176
+ Version made by someone other than you, you are nevertheless required
177
+ to ensure that your Modified Version complies with the requirements of
178
+ this license.
179
+
180
+ (12) This license does not grant you the right to use any trademark,
181
+ service mark, tradename, or logo of the Copyright Holder.
182
+
183
+ (13) This license includes the non-exclusive, worldwide,
184
+ free-of-charge patent license to make, have made, use, offer to sell,
185
+ sell, import and otherwise transfer the Package with respect to any
186
+ patent claims licensable by the Copyright Holder that are necessarily
187
+ infringed by the Package. If you institute patent litigation
188
+ (including a cross-claim or counterclaim) against any party alleging
189
+ that the Package constitutes direct or contributory patent
190
+ infringement, then this Artistic License to you shall terminate on the
191
+ date that such litigation is filed.
192
+
193
+ (14) Disclaimer of Warranty:
194
+ THE PACKAGE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS "AS
195
+ IS' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES. THE IMPLIED
196
+ WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR
197
+ NON-INFRINGEMENT ARE DISCLAIMED TO THE EXTENT PERMITTED BY YOUR LOCAL
198
+ LAW. UNLESS REQUIRED BY LAW, NO COPYRIGHT HOLDER OR CONTRIBUTOR WILL
199
+ BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
200
+ DAMAGES ARISING IN ANY WAY OUT OF THE USE OF THE PACKAGE, EVEN IF
201
+ ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -0,0 +1,100 @@
1
+ [![Code Climate](https://codeclimate.com/github/lmrodriguezr/gfa/badges/gpa.svg)](https://codeclimate.com/github/lmrodriguezr/gfa)
2
+ [![Test Coverage](https://codeclimate.com/github/lmrodriguezr/gfa/badges/coverage.svg)](https://codeclimate.com/github/lmrodriguezr/gfa/coverage)
3
+ [![Build Status](https://travis-ci.org/lmrodriguezr/gfa.svg?branch=master)](https://travis-ci.org/lmrodriguezr/gfa)
4
+ [![Gem Version](https://badge.fury.io/rb/gfa.svg)](https://badge.fury.io/rb/gfa)
5
+
6
+ # Graphical Fragment Assembly (GFA) for Ruby
7
+
8
+ This implementation follows the specifications of [GFA-spec][].
9
+
10
+
11
+ ## Parsing GFA
12
+
13
+ To parse a file in GFA format:
14
+
15
+ ```ruby
16
+ require "gfa"
17
+
18
+ my_gfa = GFA.load("assembly.gfa")
19
+ ```
20
+
21
+ To load GFA strings line-by-line:
22
+
23
+ ```ruby
24
+ require "gfa"
25
+
26
+ my_gfa = GFA.new
27
+ fh = File.open("assembly.gfa", "r")
28
+ fh.each do |ln|
29
+ my_gfa << ln
30
+ end
31
+ fh.close
32
+ ```
33
+
34
+
35
+ ## Saving GFA
36
+
37
+ After altering a GFA object, you can simply save it in a file as:
38
+
39
+ ```ruby
40
+ my_gfa.save("alt-assembly.gfa")
41
+ ```
42
+
43
+ Or line-by-line as:
44
+
45
+ ```ruby
46
+ fh = File.open("alt-assembly.gfa", "w")
47
+ my_gfa.each_line do |ln|
48
+ fh.puts ln
49
+ end
50
+ fh.close
51
+ ```
52
+
53
+
54
+ ## Visualizing GFA
55
+
56
+ Any `GFA` object can be exported as an [`RGL`][rgl] graph using the methods
57
+ `adjacency_graph` and `implicit_graph`. For example, you can render
58
+ [tiny.gfa](https://github.com/lmrodriguezr/gfa/raw/master/data/tiny.gfa):
59
+
60
+ ```ruby
61
+ require "gfa"
62
+ require "rgl/dot"
63
+
64
+ my_gfa = GFA.load("data/tiny.gfa")
65
+ dg = my_gfa.implicit_graph
66
+ dg.write_to_graphic_file("jpg")
67
+ ```
68
+
69
+ ![tiny_dg](https://github.com/lmrodriguezr/gfa/raw/master/data/tiny.jpg)
70
+
71
+ If you don't care about orientation, you can also build an undirected graph
72
+ without orientation:
73
+
74
+ ```ruby
75
+ ug = my_gfa.implicit_graph(orient:false)
76
+ ug.write_to_graphic_file("jpg")
77
+ ```
78
+
79
+ ![tiny_ug](https://github.com/lmrodriguezr/gfa/raw/master/data/tiny_undirected.jpg)
80
+
81
+
82
+ # Installation
83
+
84
+ ```
85
+ gem install gfa
86
+ ```
87
+
88
+
89
+ # Author
90
+
91
+ [Luis M. Rodriguez-R][lrr].
92
+
93
+
94
+ # License
95
+
96
+ [Artistic License 2.0](LICENSE).
97
+
98
+ [GFA-spec]: https://github.com/pmelsted/GFA-spec
99
+ [lrr]: http://lmrodriguezr.github.io/
100
+ [rgl]: https://github.com/monora/rgl
@@ -0,0 +1,16 @@
1
+ require "rake/testtask"
2
+
3
+ $:.unshift File.join(File.dirname(__FILE__), "lib")
4
+
5
+ require "gfa/version"
6
+
7
+ SOURCES = FileList["lib/**/*.rb"]
8
+
9
+ desc "Default Task"
10
+ task :default => :test
11
+
12
+ Rake::TestTask.new do |t|
13
+ t.libs << "test"
14
+ t.pattern = "test/*_test.rb"
15
+ t.verbose = true
16
+ end
@@ -0,0 +1,4 @@
1
+ require "gfa/common"
2
+ require "gfa/parser"
3
+ require "gfa/generator"
4
+ require "gfa/graph"
@@ -0,0 +1,45 @@
1
+ require "gfa/version"
2
+ require "gfa/record"
3
+ require "gfa/field"
4
+
5
+ class GFA
6
+
7
+ # Class-level
8
+ def self.assert_format(value, regex, message)
9
+ unless value =~ regex
10
+ raise "#{message}: #{value}."
11
+ end
12
+ end
13
+
14
+ # Instance-level
15
+ attr :gfa_version, :records
16
+ GFA::Record.TYPES.each do |r_type|
17
+ plural = "#{r_type.downcase}s"
18
+ singular = "#{r_type.downcase}"
19
+ define_method(plural) do
20
+ records[r_type]
21
+ end
22
+ define_method(singular) do |k|
23
+ records[r_type][k]
24
+ end
25
+ define_method("add_#{singular}") do |v|
26
+ @records[r_type] << v
27
+ end
28
+ end
29
+
30
+ def initialize
31
+ @records = {}
32
+ GFA::Record.TYPES.each { |t| @records[t] = [] }
33
+ end
34
+
35
+ def empty?
36
+ records.values.all? { |r| r.empty? }
37
+ end
38
+
39
+ def eql?(gfa)
40
+ records == gfa.records
41
+ end
42
+
43
+ alias == eql?
44
+
45
+ end
@@ -0,0 +1,49 @@
1
+ class GFA::Field
2
+
3
+ # Class-level
4
+
5
+ CODES = {
6
+ :A => :Char,
7
+ :i => :SigInt,
8
+ :f => :Float,
9
+ :Z => :String,
10
+ :H => :Hex,
11
+ :B => :NumArray
12
+ }
13
+ TYPES = CODES.values
14
+
15
+ TYPES.each { |t| require "gfa/field/#{t.downcase}" }
16
+
17
+ [:CODES, :TYPES].each do |x|
18
+ define_singleton_method(x) { const_get(x) }
19
+ end
20
+
21
+ def self.code_class(code)
22
+ name = CODES[code.to_sym]
23
+ raise "Unknown field type: #{code}." if name.nil?
24
+ name_class(name)
25
+ end
26
+
27
+ def self.name_class(name)
28
+ const_get(name)
29
+ end
30
+
31
+ # Instance-level
32
+
33
+ attr :value
34
+
35
+ def type ; CODES[code] ; end
36
+
37
+ def code ; self.class::CODE ; end
38
+
39
+ def regex ; self.class::REGEX ; end
40
+
41
+ def to_s(with_type=true)
42
+ "#{"#{code}:" if with_type}#{value}"
43
+ end
44
+
45
+ def hash
46
+ value.hash
47
+ end
48
+
49
+ end
@@ -0,0 +1,10 @@
1
+ class GFA::Field::Char < GFA::Field
2
+ CODE = :A
3
+ REGEX = /^[!-~]$/
4
+
5
+ def initialize(f)
6
+ GFA.assert_format(f, regex, "Bad #{type}")
7
+ @value = f
8
+ end
9
+
10
+ end
@@ -0,0 +1,10 @@
1
+ class GFA::Field::Float < GFA::Field
2
+ CODE = :f
3
+ REGEX = /^[-+]?[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?$/
4
+
5
+ def initialize(f)
6
+ GFA.assert_format(f, regex, "Bad #{type}")
7
+ @value = f.to_f
8
+ end
9
+
10
+ end
@@ -0,0 +1,10 @@
1
+ class GFA::Field::Hex < GFA::Field
2
+ CODE = :H
3
+ REGEX = /^[0-9A-F]+$/
4
+
5
+ def initialize(f)
6
+ GFA.assert_format(f, regex, "Bad #{type}")
7
+ @value = f
8
+ end
9
+
10
+ end
@@ -0,0 +1,16 @@
1
+ class GFA::Field::NumArray < GFA::Field
2
+ CODE = :B
3
+ REGEX = /^[cCsSiIf](,[-+]?[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?)+$/
4
+
5
+ def initialize(f)
6
+ GFA.assert_format(f, regex, "Bad #{type}")
7
+ @value = f
8
+ end
9
+
10
+ def modifier ; value[0] ; end
11
+
12
+ def array ; value[2..-1].split(/,/) ; end
13
+
14
+ alias as_a array
15
+
16
+ end
@@ -0,0 +1,10 @@
1
+ class GFA::Field::SigInt < GFA::Field
2
+ CODE = :i
3
+ REGEX = /^[-+]?[0-9]+$/
4
+
5
+ def initialize(f)
6
+ GFA.assert_format(f, regex, "Bad #{type}")
7
+ @value = f.to_i
8
+ end
9
+
10
+ end
@@ -0,0 +1,10 @@
1
+ class GFA::Field::String < GFA::Field
2
+ CODE = :Z
3
+ REGEX = /^[ !-~]+$/
4
+
5
+ def initialize(f)
6
+ GFA.assert_format(f, regex, "Bad #{type}")
7
+ @value = f
8
+ end
9
+
10
+ end
@@ -0,0 +1,31 @@
1
+ class GFA
2
+ def save(file)
3
+ fh = File.open(file, "w")
4
+ each_line do |ln|
5
+ fh.puts ln
6
+ end
7
+ fh.close
8
+ end
9
+ def each_line(&blk)
10
+ set_version_header("1.0") if gfa_version.nil?
11
+ GFA::Record.TYPES.each do |r_type|
12
+ records[r_type].each do |record|
13
+ blk[record.to_s]
14
+ end
15
+ end
16
+ end
17
+ def set_version_header(v)
18
+ unset_version
19
+ @records[:Header] << GFA::Record::Header.new("VN:Z:#{v}")
20
+ @gfa_version = v
21
+ end
22
+ def unset_version
23
+ @records[:Header].delete_if { |o| not o.fields[:VN].nil? }
24
+ @gfa_version = nil
25
+ end
26
+ def to_s
27
+ o = ""
28
+ each_line{ |ln| o += ln + "\n" }
29
+ o
30
+ end
31
+ end
@@ -0,0 +1,100 @@
1
+ require "rgl/adjacency"
2
+ require "rgl/implicit"
3
+
4
+ class GFA
5
+
6
+ ##
7
+ # Generates a RGL::ImplicitGraph object describing the links in the GFA.
8
+ # The +opts+ argument is a hash with any of the following key-value pairs:
9
+ #
10
+ # * :orient => bool. If false, ignores strandness of the links. By default
11
+ # true.
12
+ # * :directed => bool. If false, ignores direction of the links. By defaut
13
+ # the same value as :orient.
14
+ def implicit_graph(opts={})
15
+ rgl_implicit_graph(opts)
16
+ end
17
+
18
+ ##
19
+ # Generates a RGL::DirectedAdjacencyGraph or RGL::AdjacencyGraph object.
20
+ # The +opts+ argument is a hash with the same supported key-value pairs as
21
+ # in #implicit_graph.
22
+ def adjacency_graph(opts={})
23
+ implicit_graph(opts).to_adjacency
24
+ end
25
+
26
+ private
27
+
28
+ def segment_names_with_orient
29
+ segments.flat_map do |s|
30
+ %w[+ -].map{ |orient| GFA::GraphVertex.idx(s, orient) }
31
+ end.to_set
32
+ end
33
+
34
+ def segment_names
35
+ segments.map do |s|
36
+ GFA::GraphVertex.idx(s, nil)
37
+ end.to_set
38
+ end
39
+
40
+ def rgl_implicit_graph(opts)
41
+ opts = rgl_implicit_options(opts)
42
+ RGL::ImplicitGraph.new do |g|
43
+ g.vertex_iterator do |b|
44
+ (opts[:orient] ? segment_names_with_orient :
45
+ segment_names).each(&b)
46
+ end
47
+ g.adjacent_iterator do |x,b|
48
+ rgl_implicit_adjacent_iterator(x,b,opts)
49
+ end
50
+ g.directed = opts[:directed]
51
+ end
52
+ end
53
+
54
+ def rgl_implicit_options(opts)
55
+ opts[:orient] = true if opts[:orient].nil?
56
+ opts[:directed] = opts[:orient] if opts[:directed].nil?
57
+ opts
58
+ end
59
+
60
+ def rgl_implicit_adjacent_iterator(x,b,opts)
61
+ links.each do |l|
62
+ if l.from?(x.segment, x.orient)
63
+ orient = opts[:orient] ? l.to_orient : nil
64
+ b.call(GFA::GraphVertex.idx(l.to, orient))
65
+ elsif opts[:orient] and l.to?(x.segment, orient_rc(x.orient))
66
+ orient = orient_rc(l.from_orient.value)
67
+ b.call(GFA::GraphVertex.idx(l.from, orient))
68
+ end
69
+ end
70
+ end
71
+
72
+ def orient_rc(o) o=="+" ? "-" : "+" ; end
73
+
74
+ end
75
+
76
+
77
+ class GFA::GraphVertex # :nodoc:
78
+
79
+ # Class-level
80
+ @@idx = {}
81
+ def self.idx(segment, orient)
82
+ n = GFA::GraphVertex.new(segment, orient)
83
+ @@idx[n.to_s] ||= n
84
+ @@idx[n.to_s]
85
+ end
86
+
87
+ # Instance-level
88
+ attr :segment, :orient
89
+
90
+ def initialize(segment, orient)
91
+ @segment = segment.is_a?(GFA::Record::Segment) ? segment.name.value :
92
+ segment.is_a?(GFA::Field) ? segment.value : segment
93
+ @orient = orient.is_a?(GFA::Field) ? orient.value : orient
94
+ end
95
+
96
+ def to_s
97
+ "#{segment}#{orient}"
98
+ end
99
+
100
+ end
@@ -0,0 +1,45 @@
1
+ require "gfa/record"
2
+
3
+ class GFA
4
+ # Class-level
5
+ MIN_VERSION = "1.0"
6
+ MAX_VERSION = "1.0"
7
+
8
+ def self.load(file)
9
+ gfa = GFA.new
10
+ fh = File.open(file, "r")
11
+ fh.each { |ln| gfa << ln }
12
+ fh.close
13
+ gfa
14
+ end
15
+
16
+ def self.supported_version?(v)
17
+ v.to_f >= MIN_VERSION.to_f and v.to_f <= MAX_VERSION.to_f
18
+ end
19
+
20
+ # Instance-level
21
+ def <<(obj)
22
+ obj = parse_line(obj) unless obj.is_a? GFA::Record
23
+ return if obj.nil? or obj.empty?
24
+ @records[obj.type] << obj
25
+ if obj.type==:Header and not obj.fields[:VN].nil?
26
+ set_gfa_version(obj.fields[:VN].value)
27
+ end
28
+ end
29
+
30
+ def set_gfa_version(v)
31
+ @gfa_version = v
32
+ raise "GFA version currently unsupported: #{v}." unless
33
+ GFA::supported_version? gfa_version
34
+ end
35
+
36
+ private
37
+
38
+ def parse_line(ln)
39
+ ln.chomp!
40
+ return nil if ln =~ /^\s*$/
41
+ cols = ln.split("\t")
42
+ GFA::Record.code_class(cols.shift).new(*cols)
43
+ end
44
+
45
+ end
@@ -0,0 +1,90 @@
1
+ class GFA::Record
2
+
3
+ # Class-level
4
+
5
+ CODES = {
6
+ :H => :Header,
7
+ :S => :Segment,
8
+ :L => :Link,
9
+ :C => :Containment,
10
+ :P => :Path
11
+ }
12
+ REQ_FIELDS = []
13
+ OPT_FIELDS = {}
14
+ TYPES = CODES.values
15
+
16
+ TYPES.each { |t| require "gfa/record/#{t.downcase}" }
17
+
18
+ [:CODES, :REQ_FIELDS, :OPT_FIELDS, :TYPES].each do |x|
19
+ define_singleton_method(x) { const_get(x) }
20
+ end
21
+
22
+ def self.code_class(code)
23
+ name = CODES[code.to_sym]
24
+ raise "Unknown record type: #{code}." if name.nil?
25
+ name_class(name)
26
+ end
27
+
28
+ def self.name_class(name)
29
+ const_get(name)
30
+ end
31
+
32
+ # Instance-level
33
+
34
+ attr :fields
35
+
36
+ def [](k) fields[k] ; end
37
+
38
+ def type ; CODES[code] ; end
39
+
40
+ def code ; self.class.const_get(:CODE) ; end
41
+
42
+ def empty? ; fields.empty? ; end
43
+
44
+ def to_s
45
+ o = [code.to_s]
46
+ self.class.REQ_FIELDS.each_index do |i|
47
+ o << fields[i+2].to_s(false)
48
+ end
49
+ fields.each do |k,v|
50
+ next if k.is_a? Integer
51
+ o << "#{k}:#{v}"
52
+ end
53
+ o.join("\t")
54
+ end
55
+
56
+ def hash
57
+ {code => fields}.hash
58
+ end
59
+
60
+ def eql?(rec)
61
+ hash == rec.hash
62
+ end
63
+
64
+ alias == eql?
65
+
66
+ private
67
+
68
+ def add_field(f_tag, f_type, f_value, format=nil)
69
+ unless format.nil?
70
+ msg = (f_tag.is_a?(Integer) ? "column #{f_tag}" : "#{f_tag} field")
71
+ GFA.assert_format(f_value, format, "Bad #{type} #{msg}")
72
+ end
73
+ @fields[ f_tag ] = GFA::Field.code_class(f_type).new(f_value)
74
+ end
75
+
76
+ def add_opt_field(f, known)
77
+ m = /^([A-Za-z]+):([A-Za-z]+):(.*)$/.match(f) or
78
+ raise "Cannot parse field: '#{f}'."
79
+ f_tag = m[1].to_sym
80
+ f_type = m[2].to_sym
81
+ f_value = m[3]
82
+ raise "Unknown reserved tag #{f_tag} for a #{type} record." if
83
+ known[f_tag].nil? and f_tag =~ /^[A-Z]+$/
84
+ raise "Wrong field type #{f_type} for a #{f_tag} tag," +
85
+ " expected #{known[f_tag]}" unless
86
+ known[f_tag].nil? or known[f_tag] == f_type
87
+ add_field(f_tag, f_type, f_value)
88
+ end
89
+
90
+ end
@@ -0,0 +1,24 @@
1
+ class GFA::Record::Containment < GFA::Record
2
+ CODE = :C
3
+ REQ_FIELDS = [:from, :from_orient, :to, :to_orient, :pos, :overlap]
4
+ OPT_FIELDS = {
5
+ :RC => :i,
6
+ :NM => :i
7
+ }
8
+
9
+ REQ_FIELDS.each_index do |i|
10
+ define_method(REQ_FIELDS[i]) { fields[i+2] }
11
+ end
12
+
13
+ def initialize(from, from_orient, to, to_orient, pos, overlap, *opt_fields)
14
+ @fields = {}
15
+ add_field(2, :Z, from, /^[!-)+-<>-~][!-~]*$/)
16
+ add_field(3, :Z, from_orient, /^+|-$/)
17
+ add_field(4, :Z, to, /^[!-)+-<>-~][!-~]*$/)
18
+ add_field(5, :Z, to_orient, /^+|-$/)
19
+ add_field(6, :i, pos, /^[0-9]*$/)
20
+ add_field(7, :Z, overlap, /^\*|([0-9]+[MIDNSHPX=])+$/)
21
+ opt_fields.each{ |f| add_opt_field(f, OPT_FIELDS) }
22
+ end
23
+
24
+ end
@@ -0,0 +1,13 @@
1
+ class GFA::Record::Header < GFA::Record
2
+ CODE = :H
3
+ REQ_FIELDS = []
4
+ OPT_FIELDS = {
5
+ :VN => :Z
6
+ }
7
+
8
+ def initialize(*opt_fields)
9
+ @fields = {}
10
+ opt_fields.each{ |f| add_opt_field(f, OPT_FIELDS) }
11
+ end
12
+
13
+ end
@@ -0,0 +1,50 @@
1
+ class GFA::Record::Link < GFA::Record
2
+ CODE = :L
3
+ REQ_FIELDS = [:from, :from_orient, :to, :to_orient, :overlap]
4
+ OPT_FIELDS = {
5
+ :MQ => :i,
6
+ :NM => :i,
7
+ :EC => :i,
8
+ :FC => :i,
9
+ :KC => :i
10
+ }
11
+
12
+ REQ_FIELDS.each_index do |i|
13
+ define_method(REQ_FIELDS[i]) { fields[i+2] }
14
+ end
15
+
16
+ def initialize(from, from_orient, to, to_orient, overlap, *opt_fields)
17
+ @fields = {}
18
+ add_field(2, :Z, from, /^[!-)+-<>-~][!-~]*$/)
19
+ add_field(3, :Z, from_orient, /^+|-$/)
20
+ add_field(4, :Z, to, /^[!-)+-<>-~][!-~]*$/)
21
+ add_field(5, :Z, to_orient, /^+|-$/)
22
+ add_field(6, :Z, overlap, /^\*|([0-9]+[MIDNSHPX=])+$/)
23
+ opt_fields.each{ |f| add_opt_field(f, OPT_FIELDS) }
24
+ end
25
+
26
+
27
+ def from?(segment, orient=nil)
28
+ links_from_to?(segment, orient, true)
29
+ end
30
+
31
+ def to?(segment, orient=nil)
32
+ links_from_to?(segment, orient, false)
33
+ end
34
+
35
+ private
36
+
37
+ def links_from_to?(segment, orient, from)
38
+ segment = segment_name(segment)
39
+ orient = orient.value if orient.is_a? GFA::Field
40
+ base_k = from ? 2 : 4
41
+ segment==fields[base_k].value and
42
+ (orient.nil? or orient==fields[base_k + 1].value)
43
+ end
44
+
45
+ def segment_name(segment)
46
+ segment.is_a?(GFA::Record::Segment) ? segment.name.value :
47
+ segment.is_a?(GFA::Field) ? segment.value : segment
48
+ end
49
+
50
+ end
@@ -0,0 +1,18 @@
1
+ class GFA::Record::Path < GFA::Record
2
+ CODE = :P
3
+ REQ_FIELDS = [:path_name, :segment_name, :cigar]
4
+ OPT_FIELDS = {}
5
+
6
+ REQ_FIELDS.each_index do |i|
7
+ define_method(REQ_FIELDS[i]) { fields[i+2] }
8
+ end
9
+
10
+ def initialize(path_name, segment_name, cigar, *opt_fields)
11
+ @fields = {}
12
+ add_field(2, :Z, path_name, /^[!-)+-<>-~][!-~]*$/)
13
+ add_field(3, :Z, segment_name, /^[!-)+-<>-~][!-~]*$/)
14
+ add_field(4, :Z, cigar, /^\*|([0-9]+[MIDNSHPX=])+$/)
15
+ opt_fields.each{ |f| add_opt_field(f, OPT_FIELDS) }
16
+ end
17
+
18
+ end
@@ -0,0 +1,22 @@
1
+ class GFA::Record::Segment < GFA::Record
2
+ CODE = :S
3
+ REQ_FIELDS = [:name, :sequence]
4
+ OPT_FIELDS = {
5
+ :LN => :i,
6
+ :RC => :i,
7
+ :FC => :i,
8
+ :KC => :i
9
+ }
10
+
11
+ REQ_FIELDS.each_index do |i|
12
+ define_method(REQ_FIELDS[i]) { fields[i+2] }
13
+ end
14
+
15
+ def initialize(name, sequence, *opt_fields)
16
+ @fields = {}
17
+ add_field(2, :Z, name, /^[!-)+-<>-~][!-~]*$/)
18
+ add_field(3, :Z, sequence, /^\*|[A-Za-z=.]+$/)
19
+ opt_fields.each{ |f| add_opt_field(f, OPT_FIELDS) }
20
+ end
21
+
22
+ end
@@ -0,0 +1,7 @@
1
+ class GFA
2
+ VERSION = "0.1.2"
3
+ VERSION_ARRAY = VERSION.split(/\./).map { |x| x.to_i } # :nodoc:
4
+ VERSION_MAJOR = VERSION_ARRAY[0] # :nodoc:
5
+ VERSION_MINOR = VERSION_ARRAY[1] # :nodoc:
6
+ VERSION_BUILD = VERSION_ARRAY[2] # :nodoc:
7
+ end
@@ -0,0 +1,34 @@
1
+ require "test_helper"
2
+
3
+ class CommonTest < Test::Unit::TestCase
4
+
5
+ def test_assert_format
6
+ assert_raise do
7
+ GFA.assert_format("tsooq", /^.$/, "Not a char")
8
+ end
9
+ assert_nothing_raised do
10
+ GFA.assert_format("z", /^.$/, "Not a char")
11
+ end
12
+ end
13
+
14
+ def test_empty
15
+ gfa = GFA.new
16
+ assert(gfa.empty?)
17
+ assert_equal(GFA.new, gfa)
18
+ end
19
+
20
+ def test_record_getters
21
+ gfa = GFA.new
22
+ assert_respond_to(gfa, :headers)
23
+ assert_equal([], gfa.links)
24
+ assert_nil( gfa.segment(0) )
25
+ end
26
+
27
+ def test_record_setters
28
+ gfa = GFA.new
29
+ assert_respond_to(gfa, :add_path)
30
+ gfa.add_containment("zooq")
31
+ assert_equal("zooq", gfa.records[:Containment].first)
32
+ end
33
+
34
+ end
@@ -0,0 +1,50 @@
1
+ require "test_helper"
2
+
3
+ class FieldTest < Test::Unit::TestCase
4
+
5
+ def test_char
6
+ f = GFA::Field::Char.new("%")
7
+ assert_equal("%", f.value)
8
+ assert_raise do
9
+ GFA::Field::Char.new(" ")
10
+ end
11
+ assert_raise do
12
+ GFA::Field::Char.new("")
13
+ end
14
+ assert_raise do
15
+ GFA::Field::Char.new("^.^")
16
+ end
17
+ end
18
+
19
+ def test_sigint
20
+ end
21
+
22
+ def test_float
23
+ f = GFA::Field::Float.new("1.3e-5")
24
+ assert_equal(1.3e-5, f.value)
25
+ assert_raise do
26
+ GFA::Field::Float.new("e-5")
27
+ end
28
+ end
29
+
30
+ def test_string
31
+ end
32
+
33
+ def test_hex
34
+ f = GFA::Field::Hex.new("C3F0")
35
+ assert_equal("C3F0", f.value)
36
+ assert_raise do
37
+ GFA::Field::Hex.new("C3PO")
38
+ end
39
+ end
40
+
41
+ def test_numarray
42
+ f = GFA::Field::NumArray.new("i,1,2,3")
43
+ assert_equal(%w[1 2 3], f.array)
44
+ assert_equal("i", f.modifier)
45
+ assert_raise do
46
+ GFA::Field::NumArray.new("c,1,e,3")
47
+ end
48
+ end
49
+
50
+ end
@@ -0,0 +1,47 @@
1
+ require "test_helper"
2
+ require "gfa/parser"
3
+
4
+ class ParserTest < Test::Unit::TestCase
5
+
6
+ def test_load
7
+ sample_f = File.expand_path("../fixtures/sample.gfa",__FILE__)
8
+ assert_respond_to(GFA, :load)
9
+ pre_fhs = ObjectSpace.each_object(IO).count{ |i| not i.closed? }
10
+ sample = GFA.load(sample_f)
11
+ post_fhs = ObjectSpace.each_object(IO).count{ |i| not i.closed? }
12
+ assert_equal(pre_fhs, post_fhs)
13
+ assert_equal(1, sample.headers.size)
14
+ assert_equal(6, sample.segments.size)
15
+ assert_equal(4, sample.links.size)
16
+ assert(sample.containments.empty?)
17
+ assert(sample.paths.empty?)
18
+ assert_respond_to(sample, :records)
19
+ end
20
+
21
+ def test_version_suppport
22
+ gfa = GFA.new
23
+ assert_raise { gfa.set_gfa_version("0.9") }
24
+ assert_raise { gfa.set_gfa_version("1.1") }
25
+ assert_nothing_raised { gfa.set_gfa_version("1.0") }
26
+ end
27
+
28
+ def test_line_by_line
29
+ gfa = GFA.new
30
+ assert_respond_to(gfa, :<<)
31
+ # Empty
32
+ gfa << " "
33
+ assert(gfa.empty?)
34
+ gfa << "H"
35
+ assert(gfa.empty?)
36
+ # Segment
37
+ assert_equal(0, gfa.segments.size)
38
+ gfa << "S\t1\tACTG"
39
+ assert(!gfa.empty?)
40
+ assert_equal(1, gfa.segments.size)
41
+ # Version
42
+ assert_nil(gfa.gfa_version)
43
+ gfa << GFA::Record::Header.new("VN:Z:1.0")
44
+ assert_equal("1.0", gfa.gfa_version)
45
+ end
46
+
47
+ end
@@ -0,0 +1,73 @@
1
+ require "test_helper"
2
+
3
+ class RecordTest < Test::Unit::TestCase
4
+
5
+ def setup
6
+ $rec_h = GFA::Record::Header.new("VN:Z:1.0")
7
+ $rec_p = GFA::Record::Path.new("a", "b", "*")
8
+ end
9
+
10
+ def test_class_methods
11
+ assert_respond_to(GFA::Record, :CODES)
12
+ assert_respond_to(GFA::Record, :TYPES)
13
+ end
14
+
15
+ def test_to_s
16
+ assert_equal("H\tVN:Z:1.0", $rec_h.to_s)
17
+ assert_equal("P\ta\tb\t*", $rec_p.to_s)
18
+ end
19
+
20
+ def test_hash
21
+ other_h = GFA::Record::Header.new("VN:Z:1.0")
22
+ assert_equal($rec_h.hash, other_h.hash)
23
+ assert_equal($rec_h, other_h)
24
+ end
25
+
26
+ def test_reserved_fields
27
+ assert_nothing_raised do
28
+ GFA::Record::Path.new("a", "b", "*", "smile:Z:(-:")
29
+ GFA::Record::Header.new("Ooo:i:3")
30
+ GFA::Record::Header.new("oOo:i:2")
31
+ GFA::Record::Header.new("ooO:i:1")
32
+ end
33
+ assert_raise do
34
+ GFA::Record::Header.new("OOPS:i:3")
35
+ end
36
+ end
37
+
38
+ def test_header
39
+ end
40
+
41
+ def test_segment
42
+ end
43
+
44
+ def test_link
45
+ l = GFA::Record::Link.new("Seg1","+","Seg2","-","*","NM:i:123")
46
+ assert_equal("+", l.from_orient.value)
47
+ assert_equal(123, l[:NM].value)
48
+ assert(l.from?("Seg1"))
49
+ assert(l.from?("Seg1", "+"))
50
+ assert(l.to?("Seg2", "-"))
51
+ assert(! l.from?("Seg2"))
52
+ assert(! l.from?("Seg1", "-"))
53
+ end
54
+
55
+ def test_containment
56
+ assert_raise do
57
+ GFA::Record::Containment.new("Seg1","+","Seg2","-","*","RC:i:123")
58
+ end
59
+ c = GFA::Record::Containment.new("Seg1","+","Seg2","-","10","*","RC:i:123")
60
+ assert_equal("+", c.from_orient.value)
61
+ assert_equal(10, c.pos.value)
62
+ assert_equal(123, c[:RC].value)
63
+ end
64
+
65
+ def test_path
66
+ assert_raise do
67
+ GFA::Record::Path.new("PathA","SegB\t","*")
68
+ end
69
+ p = GFA::Record::Path.new("PathA","SegB","*")
70
+ assert_equal("*", p.cigar.value)
71
+ end
72
+
73
+ end
@@ -0,0 +1,6 @@
1
+ require "codeclimate-test-reporter"
2
+ CodeClimate::TestReporter.start
3
+
4
+ require "rubygems"
5
+ require "test/unit"
6
+ require "gfa/common"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: gfa
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.1.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Luis M. Rodriguez-R
@@ -56,13 +56,48 @@ description: GFA is a graph representation of fragment assemblies
56
56
  email: lmrodriguezr@gmail.com
57
57
  executables: []
58
58
  extensions: []
59
- extra_rdoc_files: []
60
- files: []
59
+ extra_rdoc_files:
60
+ - README.md
61
+ files:
62
+ - lib/gfa/common.rb
63
+ - lib/gfa/field/char.rb
64
+ - lib/gfa/field/float.rb
65
+ - lib/gfa/field/hex.rb
66
+ - lib/gfa/field/numarray.rb
67
+ - lib/gfa/field/sigint.rb
68
+ - lib/gfa/field/string.rb
69
+ - lib/gfa/field.rb
70
+ - lib/gfa/generator.rb
71
+ - lib/gfa/graph.rb
72
+ - lib/gfa/parser.rb
73
+ - lib/gfa/record/containment.rb
74
+ - lib/gfa/record/header.rb
75
+ - lib/gfa/record/link.rb
76
+ - lib/gfa/record/path.rb
77
+ - lib/gfa/record/segment.rb
78
+ - lib/gfa/record.rb
79
+ - lib/gfa/version.rb
80
+ - lib/gfa.rb
81
+ - test/common_test.rb
82
+ - test/field_test.rb
83
+ - test/parser_test.rb
84
+ - test/record_test.rb
85
+ - test/test_helper.rb
86
+ - Gemfile
87
+ - Rakefile
88
+ - README.md
89
+ - LICENSE
61
90
  homepage: https://github.com/lmrodriguezr/gfa
62
91
  licenses: []
63
92
  metadata: {}
64
93
  post_install_message:
65
- rdoc_options: []
94
+ rdoc_options:
95
+ - lib
96
+ - README.md
97
+ - --main
98
+ - README.md
99
+ - --title
100
+ - Graphical Fragment Assembly (GFA) for Ruby
66
101
  require_paths:
67
102
  - lib
68
103
  required_ruby_version: !ruby/object:Gem::Requirement