gfa 0.1.2 → 0.3.1

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.
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