gfa 0.1.2 → 0.3.1

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
- SHA1:
3
- metadata.gz: 8388ee28f1d71339d701b32e2a4fa1538b0f391f
4
- data.tar.gz: 520590efdc7170cf805db8427574e6e6fb5a8ac1
2
+ SHA256:
3
+ metadata.gz: 0e8e61ff97b34654b7a660b011826ad5549f66933a91a09658facf58c3fd56b1
4
+ data.tar.gz: 8f85f07955e71cd38a9dfa28011c70433473ad6a1137ed3e6e217d63eeba20a8
5
5
  SHA512:
6
- metadata.gz: c8e8366d01006fbffc22ae49f3b4d619d8e8fbf25ef2c6173b427f05382a28c8a9efd7798f791d05c890e3057d3115ca8d3a6bc6d330551bf0915569c89ecc5c
7
- data.tar.gz: c6bcd711d7841585e6452cca87564b5d2e41a673e1bc4869df632db6193b928bbc6374ca370d8033db8df70e377234f3aa0a5f3a9ae96c56f053b771dff78060
6
+ metadata.gz: 5b9f8fd92cd30d9e4e5c0263e938169141749c7be43011b574f937c9645608d8e72189bfe9072d7321e3acdf7e8288cecf42c90abe3ceef2bf001780bdb3e472
7
+ data.tar.gz: ee33c0b9c0dc9adb2df96d95b792b060b248e2f991ed5a0e4b4c136d66f04b9be6cf5ca58462bce046c6cfd9e7fed6c4c6949cb5e8923eb71e3d5f53f0da2703
data/Gemfile CHANGED
@@ -1,3 +1,9 @@
1
- source "https://rubygems.org"
1
+ source 'https://rubygems.org'
2
2
  gemspec
3
- gem "codeclimate-test-reporter", group: :test, require: nil
3
+
4
+ group :test do
5
+ gem "simplecov"
6
+ gem "codeclimate-test-reporter", "~> 1.0.0"
7
+ end
8
+
9
+ gem 'rake', '~> 13.0'
data/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
- The Artistic License 2.0
1
+ The Artistic License 2.0
2
2
 
3
- Copyright (c) 2016 Luis M Rodriguez-R
3
+ Copyright (c) 2016-2023 Luis M Rodriguez-R
4
4
 
5
5
  Everyone is permitted to copy and distribute verbatim copies
6
6
  of this license document, but changing it is not allowed.
data/README.md CHANGED
@@ -13,20 +13,20 @@ This implementation follows the specifications of [GFA-spec][].
13
13
  To parse a file in GFA format:
14
14
 
15
15
  ```ruby
16
- require "gfa"
16
+ require 'gfa'
17
17
 
18
- my_gfa = GFA.load("assembly.gfa")
18
+ my_gfa = GFA.load('assembly.gfa')
19
19
  ```
20
20
 
21
21
  To load GFA strings line-by-line:
22
22
 
23
23
  ```ruby
24
- require "gfa"
24
+ require 'gfa'
25
25
 
26
26
  my_gfa = GFA.new
27
- fh = File.open("assembly.gfa", "r")
27
+ fh = File.open('assembly.gfa', 'r')
28
28
  fh.each do |ln|
29
- my_gfa << ln
29
+ my_gfa << ln
30
30
  end
31
31
  fh.close
32
32
  ```
@@ -37,15 +37,15 @@ fh.close
37
37
  After altering a GFA object, you can simply save it in a file as:
38
38
 
39
39
  ```ruby
40
- my_gfa.save("alt-assembly.gfa")
40
+ my_gfa.save('alt-assembly.gfa')
41
41
  ```
42
42
 
43
43
  Or line-by-line as:
44
44
 
45
45
  ```ruby
46
- fh = File.open("alt-assembly.gfa", "w")
46
+ fh = File.open('alt-assembly.gfa', 'w')
47
47
  my_gfa.each_line do |ln|
48
- fh.puts ln
48
+ fh.puts ln
49
49
  end
50
50
  fh.close
51
51
  ```
@@ -58,12 +58,12 @@ Any `GFA` object can be exported as an [`RGL`][rgl] graph using the methods
58
58
  [tiny.gfa](https://github.com/lmrodriguezr/gfa/raw/master/data/tiny.gfa):
59
59
 
60
60
  ```ruby
61
- require "gfa"
62
- require "rgl/dot"
61
+ require 'gfa'
62
+ require 'rgl/dot'
63
63
 
64
- my_gfa = GFA.load("data/tiny.gfa")
64
+ my_gfa = GFA.load('data/tiny.gfa')
65
65
  dg = my_gfa.implicit_graph
66
- dg.write_to_graphic_file("jpg")
66
+ dg.write_to_graphic_file('jpg')
67
67
  ```
68
68
 
69
69
  ![tiny_dg](https://github.com/lmrodriguezr/gfa/raw/master/data/tiny.jpg)
@@ -72,8 +72,8 @@ If you don't care about orientation, you can also build an undirected graph
72
72
  without orientation:
73
73
 
74
74
  ```ruby
75
- ug = my_gfa.implicit_graph(orient:false)
76
- ug.write_to_graphic_file("jpg")
75
+ ug = my_gfa.implicit_graph(orient: false)
76
+ ug.write_to_graphic_file('jpg')
77
77
  ```
78
78
 
79
79
  ![tiny_ug](https://github.com/lmrodriguezr/gfa/raw/master/data/tiny_undirected.jpg)
@@ -85,6 +85,14 @@ ug.write_to_graphic_file("jpg")
85
85
  gem install gfa
86
86
  ```
87
87
 
88
+ Or add the following line to your Gemfile:
89
+
90
+ ```ruby
91
+ gem 'gfa'
92
+ ```
93
+
94
+ and run `bundle install` from your shell.
95
+
88
96
 
89
97
  # Author
90
98
 
@@ -96,5 +104,5 @@ gem install gfa
96
104
  [Artistic License 2.0](LICENSE).
97
105
 
98
106
  [GFA-spec]: https://github.com/pmelsted/GFA-spec
99
- [lrr]: http://lmrodriguezr.github.io/
107
+ [lrr]: https://rodriguez-r.com/
100
108
  [rgl]: https://github.com/monora/rgl
data/Rakefile CHANGED
@@ -1,16 +1,17 @@
1
- require "rake/testtask"
1
+ require 'bundler/gem_tasks'
2
+ require 'rake/testtask'
2
3
 
3
- $:.unshift File.join(File.dirname(__FILE__), "lib")
4
+ $:.unshift File.join(File.dirname(__FILE__), 'lib')
4
5
 
5
- require "gfa/version"
6
+ require 'gfa/version'
6
7
 
7
- SOURCES = FileList["lib/**/*.rb"]
8
+ SOURCES = FileList['lib/**/*.rb']
8
9
 
9
- desc "Default Task"
10
+ desc 'Default Task'
10
11
  task :default => :test
11
12
 
12
13
  Rake::TestTask.new do |t|
13
- t.libs << "test"
14
- t.pattern = "test/*_test.rb"
15
- t.verbose = true
14
+ t.libs << 'test'
15
+ t.pattern = 'test/*_test.rb'
16
+ t.verbose = true
16
17
  end
data/lib/gfa/common.rb CHANGED
@@ -1,45 +1,42 @@
1
- require "gfa/version"
2
- require "gfa/record"
3
- require "gfa/field"
1
+ require 'gfa/version'
2
+ require 'gfa/record_set'
3
+ require 'gfa/field'
4
4
 
5
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
-
6
+ # Class-level
7
+ def self.assert_format(value, regex, message)
8
+ unless value =~ /^(?:#{regex})$/
9
+ raise "#{message}: #{value}"
10
+ end
11
+ end
12
+
13
+ # Instance-level
14
+ attr :gfa_version, :records, :opts
15
+
16
+ GFA::Record.TYPES.each do |r_type|
17
+ plural = "#{r_type.downcase}s"
18
+ singular = "#{r_type.downcase}"
19
+
20
+ define_method(plural) { records[r_type] }
21
+ define_method(singular) { |k| records[r_type][k] }
22
+ define_method("add_#{singular}") { |v| @records[r_type] << v }
23
+ end
24
+
25
+ def initialize(opts = {})
26
+ @records = {}
27
+ @opts = { index: true, comments: false }.merge(opts)
28
+ GFA::Record.TYPES.each do |t|
29
+ @records[t] = GFA::RecordSet.name_class(t).new(self)
30
+ end
31
+ end
32
+
33
+ def empty?
34
+ records.empty? || records.values.all?(&:empty?)
35
+ end
36
+
37
+ def eql?(gfa)
38
+ records == gfa.records
39
+ end
40
+
41
+ alias == eql?
45
42
  end
@@ -1,10 +1,10 @@
1
1
  class GFA::Field::Char < GFA::Field
2
2
  CODE = :A
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
9
-
10
10
  end
@@ -1,10 +1,26 @@
1
1
  class GFA::Field::Float < GFA::Field
2
2
  CODE = :f
3
- REGEX = /^[-+]?[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?$/
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
9
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
10
26
  end
data/lib/gfa/field/hex.rb CHANGED
@@ -1,10 +1,26 @@
1
1
  class GFA::Field::Hex < GFA::Field
2
2
  CODE = :H
3
- REGEX = /^[0-9A-F]+$/
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
9
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
10
26
  end
@@ -0,0 +1,18 @@
1
+ class GFA::Field::Json < GFA::Field
2
+ CODE = :J
3
+ REGEX = /[ !-~]+/
4
+ NATIVE_FUN = :to_s
5
+
6
+ def initialize(f)
7
+ GFA.assert_format(f, regex, "Bad #{type}")
8
+ @value = f
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
18
+ end
@@ -1,16 +1,49 @@
1
1
  class GFA::Field::NumArray < GFA::Field
2
2
  CODE = :B
3
- REGEX = /^[cCsSiIf](,[-+]?[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?)+$/
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 ; value[0] ; end
11
+ def modifier
12
+ value[0]
13
+ end
14
+
15
+ def modifier_fun
16
+ modifier == 'f' ? :to_f : :to_i
17
+ end
11
18
 
12
- def array ; value[2..-1].split(/,/) ; end
13
-
14
- alias as_a array
19
+ def array
20
+ @array ||= value[2..-1].split(',').map(&modifier_fun)
21
+ end
22
+
23
+ alias to_a array
15
24
 
25
+ %i[empty? size count length first last].each do |i|
26
+ define_method(i) { array.send(i) }
27
+ end
28
+
29
+ def number_type
30
+ {
31
+ c: 'int8_t', C: 'uint8_t',
32
+ s: 'int16_t', S: 'uint16_t',
33
+ i: 'int32_t', I: 'uint32_t',
34
+ f: 'float'
35
+ }[modifier.to_sym]
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
16
49
  end
@@ -1,10 +1,22 @@
1
1
  class GFA::Field::SigInt < GFA::Field
2
2
  CODE = :i
3
- REGEX = /^[-+]?[0-9]+$/
4
-
3
+ REGEX = /[-+]?[0-9]+/
4
+ NATIVE_FUN = :to_i
5
+
5
6
  def initialize(f)
6
7
  GFA.assert_format(f, regex, "Bad #{type}")
7
8
  @value = f.to_i
8
9
  end
9
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
10
22
  end
@@ -1,10 +1,18 @@
1
1
  class GFA::Field::String < GFA::Field
2
2
  CODE = :Z
3
- REGEX = /^[ !-~]+$/
4
-
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
13
+
5
14
  def initialize(f)
6
15
  GFA.assert_format(f, regex, "Bad #{type}")
7
16
  @value = f
8
17
  end
9
-
10
18
  end
data/lib/gfa/field.rb CHANGED
@@ -1,20 +1,18 @@
1
1
  class GFA::Field
2
-
3
2
  # Class-level
4
-
5
3
  CODES = {
6
- :A => :Char,
7
- :i => :SigInt,
8
- :f => :Float,
9
- :Z => :String,
10
- :H => :Hex,
11
- :B => :NumArray
4
+ A: :Char,
5
+ i: :SigInt,
6
+ f: :Float,
7
+ Z: :String,
8
+ J: :Json, # Excluding new-line and tab characters
9
+ H: :Hex,
10
+ B: :NumArray
12
11
  }
13
12
  TYPES = CODES.values
14
-
15
13
  TYPES.each { |t| require "gfa/field/#{t.downcase}" }
16
14
 
17
- [:CODES, :TYPES].each do |x|
15
+ %i[CODES TYPES].each do |x|
18
16
  define_singleton_method(x) { const_get(x) }
19
17
  end
20
18
 
@@ -27,23 +25,95 @@ class GFA::Field
27
25
  def self.name_class(name)
28
26
  const_get(name)
29
27
  end
30
-
28
+
29
+ def self.[](string)
30
+ code, value = string.split(':', 2)
31
+ code_class(code).new(value)
32
+ end
33
+
31
34
  # Instance-level
32
35
 
33
36
  attr :value
34
37
 
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)
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)
42
59
  "#{"#{code}:" if with_type}#{value}"
43
60
  end
44
-
61
+
45
62
  def hash
46
63
  value.hash
47
64
  end
48
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
49
119
  end
data/lib/gfa/generator.rb CHANGED
@@ -1,31 +1,35 @@
1
1
  class GFA
2
- def save(file)
3
- fh = File.open(file, "w")
4
- each_line do |ln|
5
- fh.puts ln
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
+
10
+ def each_line(&blk)
11
+ set_version_header('1.1') if gfa_version.nil?
12
+ GFA::Record.TYPES.each do |r_type|
13
+ records[r_type].each do |record|
14
+ blk[record.to_s]
6
15
  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
16
+ end
17
+ end
18
+
19
+ def set_version_header(v)
20
+ unset_version
21
+ @records[:Header] << GFA::Record::Header.new("VN:Z:#{v}")
22
+ @gfa_version = v
23
+ end
24
+
25
+ def unset_version
26
+ @records[:Header].delete_if { |o| !o.fields[:VN].nil? }
27
+ @gfa_version = nil
28
+ end
29
+
30
+ def to_s
31
+ o = ''
32
+ each_line { |ln| o += ln + "\n" }
33
+ o
34
+ end
31
35
  end