rgfa 1.2.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.
Files changed (43) hide show
  1. checksums.yaml +7 -0
  2. data/bin/gfadiff.rb +420 -0
  3. data/bin/rgfa-findcrisprs.rb +208 -0
  4. data/bin/rgfa-mergelinear.rb +14 -0
  5. data/bin/rgfa-simdebruijn.rb +86 -0
  6. data/lib/rgfa.rb +376 -0
  7. data/lib/rgfa/byte_array.rb +74 -0
  8. data/lib/rgfa/cigar.rb +157 -0
  9. data/lib/rgfa/connectivity.rb +131 -0
  10. data/lib/rgfa/containments.rb +97 -0
  11. data/lib/rgfa/error.rb +3 -0
  12. data/lib/rgfa/field_array.rb +87 -0
  13. data/lib/rgfa/field_parser.rb +109 -0
  14. data/lib/rgfa/field_validator.rb +241 -0
  15. data/lib/rgfa/field_writer.rb +108 -0
  16. data/lib/rgfa/headers.rb +76 -0
  17. data/lib/rgfa/line.rb +721 -0
  18. data/lib/rgfa/line/containment.rb +87 -0
  19. data/lib/rgfa/line/header.rb +92 -0
  20. data/lib/rgfa/line/link.rb +379 -0
  21. data/lib/rgfa/line/path.rb +106 -0
  22. data/lib/rgfa/line/segment.rb +209 -0
  23. data/lib/rgfa/linear_paths.rb +285 -0
  24. data/lib/rgfa/lines.rb +155 -0
  25. data/lib/rgfa/links.rb +242 -0
  26. data/lib/rgfa/logger.rb +192 -0
  27. data/lib/rgfa/multiplication.rb +156 -0
  28. data/lib/rgfa/numeric_array.rb +196 -0
  29. data/lib/rgfa/paths.rb +98 -0
  30. data/lib/rgfa/rgl.rb +194 -0
  31. data/lib/rgfa/segment_ends_path.rb +9 -0
  32. data/lib/rgfa/segment_info.rb +162 -0
  33. data/lib/rgfa/segments.rb +99 -0
  34. data/lib/rgfa/sequence.rb +65 -0
  35. data/lib/rgfatools.rb +102 -0
  36. data/lib/rgfatools/artifacts.rb +29 -0
  37. data/lib/rgfatools/copy_number.rb +126 -0
  38. data/lib/rgfatools/invertible_segments.rb +104 -0
  39. data/lib/rgfatools/linear_paths.rb +140 -0
  40. data/lib/rgfatools/multiplication.rb +194 -0
  41. data/lib/rgfatools/p_bubbles.rb +66 -0
  42. data/lib/rgfatools/superfluous_links.rb +64 -0
  43. metadata +97 -0
@@ -0,0 +1,155 @@
1
+ require_relative "error"
2
+
3
+ #
4
+ # Methods for the RGFA class, which allow to handle lines of multiple types.
5
+ #
6
+ module RGFA::Lines
7
+
8
+ # Add a line to a RGFA
9
+ #
10
+ # @overload <<(gfa_line_string)
11
+ # @param [String] gfa_line_string representation of a RGFA line
12
+ # @overload <<(gfa_line)
13
+ # @param [RGFA::Line] gfa_line instance of a subclass of RGFA::Line
14
+ # @raise [RGFA::DuplicatedLabelError] if multiple segment or path lines
15
+ # with the same name are added
16
+ # @return [RGFA] self
17
+ def <<(gfa_line)
18
+ gfa_line = gfa_line.to_rgfa_line(validate: @validate)
19
+ rt = gfa_line.record_type
20
+ case rt
21
+ when :H
22
+ add_header(gfa_line)
23
+ when :S
24
+ add_segment(gfa_line)
25
+ when :L
26
+ add_link(gfa_line)
27
+ when :C
28
+ add_containment(gfa_line)
29
+ when :P
30
+ add_path(gfa_line)
31
+ else
32
+ raise # this never happens, as already catched by gfa_line init
33
+ end
34
+ return self
35
+ end
36
+
37
+ # Delete elements from the RGFA graph
38
+ # @overload rm(segment)
39
+ # @param segment [String, RGFA::Line::Segment] segment name or instance
40
+ # @overload rm(path)
41
+ # @param path [String, RGFA::Line::Segment] path name or instance
42
+ # @overload rm(link)
43
+ # @param link [RGFA::Line::Link] link
44
+ # @overload rm(containment)
45
+ # @param link [RGFA::Line::Containment] containment
46
+ # @overload rm(:headers)
47
+ # Remove all headers
48
+ # @overload rm(array)
49
+ # Calls {#rm} using each element of the array as argument
50
+ # @param array [Array]
51
+ # @overload rm(method_name, *args)
52
+ # Call a method of RGFA instance, then {#rm} for each returned value
53
+ # @param method_name [Symbol] method to call
54
+ # @param args arguments of the method
55
+ # @return [RGFA] self
56
+ def rm(x, *args)
57
+ if x.kind_of?(RGFA::Line)
58
+ raise ArgumentError,
59
+ "One argument required if first RGFA::Line" if !args.empty?
60
+ case x.record_type
61
+ when :H then raise ArgumentError, "Cannot remove single header lines"
62
+ when :S then delete_segment(x)
63
+ when :P then delete_path(x)
64
+ when :L then delete_link(x)
65
+ when :C then delete_containment(x)
66
+ end
67
+ elsif x.kind_of?(Symbol)
68
+ if @segments.has_key?(x)
69
+ if !args.empty?
70
+ raise ArgumentError, "One arguments required if first segment name"
71
+ end
72
+ delete_segment(x)
73
+ elsif @paths.has_key?(x)
74
+ if !args.empty?
75
+ raise ArgumentError, "One argument required if first path name"
76
+ end
77
+ delete_path(x)
78
+ elsif x == :headers
79
+ if !args.empty?
80
+ raise ArgumentError, "One argument required if first :headers"
81
+ end
82
+ delete_headers
83
+ else
84
+ if respond_to?(x)
85
+ rm(send(x, *args))
86
+ else
87
+ raise ArgumentError, "Cannot remove #{x.inspect}"
88
+ end
89
+ end
90
+ elsif x.kind_of?(String)
91
+ rm(x.to_sym, *args)
92
+ elsif x.kind_of?(Array)
93
+ x.each {|elem| rm(elem, *args)}
94
+ elsif x.nil?
95
+ return self
96
+ else
97
+ raise ArgumentError, "Cannot remove #{x.inspect}"
98
+ end
99
+ return self
100
+ end
101
+
102
+ # Rename a segment or a path
103
+ #
104
+ # @param old_name [String] the name of the segment or path to rename
105
+ # @param new_name [String] the new name for the segment or path
106
+ #
107
+ # @raise[RGFA::DuplicatedLabelError]
108
+ # if +new_name+ is already a segment or path name
109
+ # @return [RGFA] self
110
+ def rename(old_name, new_name)
111
+ old_name = old_name.to_sym
112
+ new_name = new_name.to_sym
113
+ s = segment(old_name)
114
+ pt = nil
115
+ if s.nil?
116
+ pt = path(old_name)
117
+ if pt.nil?
118
+ raise RGFA::LineMissingError,
119
+ "#{old_name} is not a path or segment name"
120
+ end
121
+ end
122
+ if segment(new_name) or path(new_name)
123
+ raise RGFA::DuplicatedLabelError,
124
+ "#{new_name} is already a path or segment name"
125
+ end
126
+ if s
127
+ s.name = new_name
128
+ @segments.delete(old_name)
129
+ @segments[new_name] = s
130
+ else
131
+ pt.path_name = new_name
132
+ @paths.delete(old_name)
133
+ @paths[new_name] = pt
134
+ end
135
+ self
136
+ end
137
+
138
+ private
139
+
140
+ def lines
141
+ headers + segments + links + containments + paths
142
+ end
143
+
144
+ def each_line(&block)
145
+ lines.each(&block)
146
+ end
147
+
148
+ end
149
+
150
+ # Exception raised if a label for segment or path is duplicated
151
+ class RGFA::DuplicatedLabelError < RGFA::Error; end
152
+
153
+ # The error raised by banged line finders if no line respecting the criteria
154
+ # exist in the RGFA
155
+ class RGFA::LineMissingError < RGFA::Error; end
@@ -0,0 +1,242 @@
1
+ require_relative "error"
2
+
3
+ #
4
+ # Methods for the RGFA class, which allow to handle links in the graph.
5
+ #
6
+ module RGFA::Links
7
+
8
+ def add_link(gfa_line)
9
+ gfa_line = gfa_line.to_rgfa_line(validate: @validate)
10
+ gfa_line.normalize!
11
+ l = nil
12
+ if segment(gfa_line.from) and segment(gfa_line.to)
13
+ l = link_from_to(gfa_line.oriented_from,
14
+ gfa_line.oriented_to,
15
+ gfa_line.overlap)
16
+ end
17
+ if l.nil?
18
+ @links << gfa_line
19
+ [:from, :to].each do |dir|
20
+ segment_name = gfa_line.send(dir).to_sym
21
+ orient = gfa_line.send(:"#{dir}_orient").to_sym
22
+ if !@segments.has_key?(segment_name)
23
+ raise RGFA::LineMissingError if @segments_first_order
24
+ @segments[segment_name] =
25
+ RGFA::Line::Segment.new({:name => segment_name},
26
+ virtual: true)
27
+ end
28
+ @segments[segment_name].links[dir][orient] << gfa_line
29
+ gfa_line.send(:"#{dir}=", @segments[segment_name])
30
+ end
31
+ elsif l.virtual?
32
+ l.real!(gfa_line)
33
+ else
34
+ return
35
+ end
36
+ end
37
+ protected :add_link
38
+
39
+ # Deletes a link and all paths depending on it
40
+ #
41
+ # @param l [RGFA::Line::Link] link instance
42
+ # @return [RGFA] self
43
+ def delete_link(l)
44
+ @links.delete(l)
45
+ segment(l.from).links[:from][l.from_orient].delete(l)
46
+ segment(l.to).links[:to][l.to_orient].delete(l)
47
+ l.paths.each {|pt, orient| delete_path(pt)}
48
+ end
49
+
50
+ # Remove all links of a segment end end except that to the other specified
51
+ # segment end.
52
+ # @param segment_end [RGFA::SegmentEnd] the segment end
53
+ # @param other_end [RGFA::SegmentEnd] the other segment end
54
+ # @param conserve_components [Boolean] <i>(defaults to: +false+)</i>
55
+ # Do not remove links if removing them breaks the graph into unconnected
56
+ # components.
57
+ # @return [RGFA] self
58
+ def delete_other_links(segment_end, other_end, conserve_components: false)
59
+ other_end = other_end.to_segment_end
60
+ links_of(segment_end).each do |l|
61
+ if l.other_end(segment_end) != other_end
62
+ if !conserve_components or !cut_link?(l)
63
+ delete_link(l)
64
+ end
65
+ end
66
+ end
67
+ end
68
+
69
+ # All links of the graph
70
+ # @return [Array<RGFA::Line::Link>]
71
+ def links
72
+ @links
73
+ end
74
+
75
+ # Finds links of the specified end of segment.
76
+ #
77
+ # @param [RGFA::SegmentEnd] segment_end a segment end
78
+ #
79
+ # @return [Array<RGFA::Line::Link>] if segment_end[1] == :E,
80
+ # links from sn with from_orient + and to sn with to_orient -
81
+ # @return [Array<RGFA::Line::Link>] if segment_end[1] == :B,
82
+ # links to sn with to_orient + and from sn with from_orient -
83
+ #
84
+ # @note to add or remove links, use the appropriate methods;
85
+ # adding or removing links from the returned array will not work
86
+ def links_of(segment_end)
87
+ segment_end = segment_end.to_segment_end
88
+ s = segment!(segment_end.segment)
89
+ o = segment_end.end_type == :E ? [:+,:-] : [:-,:+]
90
+ s.links[:from][o[0]] + s.links[:to][o[1]]
91
+ end
92
+
93
+ # Finds segment ends connected to the specified segment end.
94
+ #
95
+ # @param [RGFA::SegmentEnd] segment_end a segment end
96
+ #
97
+ # @return [Array<RGFA::SegmentEnd>>] segment ends connected by links
98
+ # to +segment_end+
99
+ def neighbours(segment_end)
100
+ links_of(segment_end).map {|l| l.other_end(segment_end) }
101
+ end
102
+
103
+ # Searches all links between +segment_end1+ and +segment_end2+
104
+ #
105
+ # @!macro [new] two_segment_ends
106
+ # @param segment_end1 [RGFA::SegmentEnd] a segment end
107
+ # @param segment_end2 [RGFA::SegmentEnd] a segment end
108
+ # @return [Array<RGFA::Line::Link>] (possibly empty)
109
+ def links_between(segment_end1, segment_end2)
110
+ segment_end1 = segment_end1.to_segment_end
111
+ segment_end2 = segment_end2.to_segment_end
112
+ links_of(segment_end1).select do |l|
113
+ l.other_end(segment_end1) == segment_end2
114
+ end
115
+ end
116
+
117
+ # @!macro [new] link
118
+ # Searches a link between +segment_end1+ and +segment_end2+
119
+ # @!macro two_segment_ends
120
+ # @return [RGFA::Line::Link] the first link found
121
+ # @return [nil] if no link is found.
122
+ def link(segment_end1, segment_end2)
123
+ segment_end1 = segment_end1.to_segment_end
124
+ segment_end2 = segment_end2.to_segment_end
125
+ links_of(segment_end1).each do |l|
126
+ return l if l.other_end(segment_end1) == segment_end2
127
+ end
128
+ return nil
129
+ end
130
+
131
+ # @!macro link
132
+ # @raise [RGFA::LineMissingError] if no link is found.
133
+ def link!(segment_end1, segment_end2)
134
+ l = link(segment_end1, segment_end2)
135
+ raise RGFA::LineMissingError,
136
+ "No link was found: "+
137
+ "#{segment_end1.to_s} -- "+
138
+ "#{segment_end2.to_s}" if l.nil?
139
+ l
140
+ end
141
+
142
+ # Find links from the segment in the specified orientation
143
+ # (or the equivalent links, i.e. to the segment in opposite orientation).
144
+ #
145
+ # @param [RGFA::OrientedSegment] oriented_segment a segment with orientation
146
+ # @param equivalent [Boolean] return also equivalent links.
147
+ # @return [Array<RGFA::Line::Link>]
148
+ # @note to add or remove links, use the appropriate methods;
149
+ # adding or removing links from the returned array will not work
150
+ def links_from(oriented_segment, equivalent = true)
151
+ oriented_segment = oriented_segment.to_oriented_segment
152
+ s = segment!(oriented_segment.segment)
153
+ retval = s.links[:from][oriented_segment.orient]
154
+ if equivalent
155
+ retval + s.links[:to][oriented_segment.orient_inverted]
156
+ else
157
+ retval
158
+ end
159
+ end
160
+
161
+ # Find links to the segment in the specified orientation
162
+ # (or the equivalent links, i.e. from the segment in opposite orientation).
163
+ #
164
+ # @param [RGFA::OrientedSegment] oriented_segment a segment with orientation
165
+ # @param equivalent [Boolean] return also equivalent links.
166
+ # @return [Array<RGFA::Line::Link>]
167
+ # @note to add or remove links, use the appropriate methods;
168
+ # adding or removing links from the returned array will not work
169
+ def links_to(oriented_segment, equivalent = true)
170
+ oriented_segment = oriented_segment.to_oriented_segment
171
+ s = segment!(oriented_segment.segment)
172
+ retval = s.links[:to][oriented_segment.orient]
173
+ if equivalent
174
+ retval + s.links[:from][oriented_segment.orient_inverted]
175
+ else
176
+ retval
177
+ end
178
+ end
179
+
180
+ # Search all links from a segment S1 in a given orientation
181
+ # to another segment S2 in a given, or the equivalent
182
+ # links from S2 to S1 with inverted orientations.
183
+ #
184
+ # @param [RGFA::OrientedSegment] oriented_segment1 a segment with orientation
185
+ # @param [RGFA::OrientedSegment] oriented_segment2 a segment with orientation
186
+ # @param [RGFA::CIGAR] cigar shall match if not empty/undef
187
+ # @param equivalent [Boolean] return also equivalent links.
188
+ # @return [Array<RGFA::Line::Link>]
189
+ # @note to add or remove links, use the appropriate methods;
190
+ # adding or removing links from the returned array will not work
191
+ def links_from_to(oriented_segment1, oriented_segment2,
192
+ cigar = [], equivalent = true)
193
+ oriented_segment1 = oriented_segment1.to_oriented_segment
194
+ oriented_segment2 = oriented_segment2.to_oriented_segment
195
+ links_from(oriented_segment1, equivalent).select do |l|
196
+ l.compatible?(oriented_segment1, oriented_segment2, cigar, equivalent)
197
+ end
198
+ end
199
+
200
+ # Search the link from a segment S1 in a given orientation
201
+ # to another segment S2 in a given, or the equivalent
202
+ # link from S2 to S1 with inverted orientations.
203
+ #
204
+ # @param [RGFA::OrientedSegment] oriented_segment1 a segment with orientation
205
+ # @param [RGFA::OrientedSegment] oriented_segment2 a segment with orientation
206
+ # @param [RGFA::CIGAR] cigar shall match if not empty/undef
207
+ # @param equivalent [Boolean] return also equivalent links.
208
+ # @return [RGFA::Line::Link] the first link found
209
+ # @return [nil] if no link is found.
210
+ def link_from_to(oriented_segment1, oriented_segment2,
211
+ cigar = [], equivalent = true)
212
+ oriented_segment1 = oriented_segment1.to_oriented_segment
213
+ oriented_segment2 = oriented_segment2.to_oriented_segment
214
+ links_from(oriented_segment1, equivalent).select do |l|
215
+ return l if l.compatible?(oriented_segment1, oriented_segment2,
216
+ cigar, equivalent)
217
+ end
218
+ return nil
219
+ end
220
+
221
+ # Search the link from a segment S1 in a given orientation
222
+ # to another segment S2 in a given, or the equivalent
223
+ # link from S2 to S1 with inverted orientations.
224
+ #
225
+ # @param [RGFA::OrientedSegment] oriented_segment1 a segment with orientation
226
+ # @param [RGFA::OrientedSegment] oriented_segment2 a segment with orientation
227
+ # @param [RGFA::CIGAR] cigar shall match if not empty/undef
228
+ # @param equivalent [Boolean] return also equivalent links.
229
+ # @return [RGFA::Line::Link] the first link found
230
+ # @raise [RGFA::LineMissingError] if no link is found.
231
+ def link_from_to!(oriented_segment1, oriented_segment2,
232
+ cigar = [], equivalent = true)
233
+ l = link_from_to(oriented_segment1, oriented_segment2,
234
+ cigar, equivalent)
235
+ raise RGFA::LineMissingError,
236
+ "No link was found: "+
237
+ "#{oriented_segment1.join(":")} -> "+
238
+ "#{oriented_segment2.join(":")}" if l.nil?
239
+ l
240
+ end
241
+
242
+ end
@@ -0,0 +1,192 @@
1
+ #
2
+ # This class allows to output a message to the log file or STDERR and
3
+ # to keep track of the progress of a method which takes long time to complete.
4
+ #
5
+ # @api private
6
+ #
7
+ class RGFA::Logger
8
+
9
+ # Information about the progress of a computation
10
+ ProgressData = Struct.new(:counter, :units, :partsize,
11
+ :lastpart, :total, :starttime,
12
+ :strlen)
13
+
14
+ # Create a Logger instance
15
+ #
16
+ # @param channel [#puts]
17
+ # where to output (default: STDERR)
18
+ # @param prefix [String]
19
+ # output prefix (default: "#")
20
+ # @param verbose_level [Integer]
21
+ # 0: no logging; >0: the higher, the more logging
22
+ # @return [RGFA::Logger]
23
+ def initialize(verbose_level: 1, channel: STDERR, prefix: "#")
24
+ @progress = false
25
+ if !verbose_level.kind_of?(Integer)
26
+ raise ArgumentError, "verbose_level must be an Integer"
27
+ end
28
+ if !channel.respond_to?(:puts)
29
+ raise TypeError, "channel must provide a puts method"
30
+ end
31
+ @channel = channel
32
+ @pfx = prefix
33
+ @verbose_level = verbose_level
34
+ @data = {}
35
+ end
36
+
37
+ # Output a message
38
+ #
39
+ # @param msg [String] message to output
40
+ # @param min_verbose_level [Integer]
41
+ # @return [void]
42
+ def log(msg, min_verbose_level=1)
43
+ @channel.puts "#@pfx #{msg}" if @verbose_level >= min_verbose_level
44
+ return nil
45
+ end
46
+
47
+ # Enable output from the Logger instance
48
+ #
49
+ # @param part [Float]
50
+ # - part = 0 => output at every call of {RGFA::Logger.progress_log}
51
+ # - 0 < part < 1 => output once per part of the total progress
52
+ # (e.g. 0.001 = log every 0.1% progress)
53
+ # - part = 1 => output only total elapsed time
54
+ # @return [void]
55
+ def enable_progress(part: 0.1)
56
+ if part < 0 or part > 1
57
+ raise ArgumentError, "part must be in range [0..1]"
58
+ end
59
+ @progress = true
60
+ @part = part
61
+ @channel.puts "#@pfx Progress logging enabled" if @verbose_level > 0
62
+ return nil
63
+ end
64
+
65
+ # Disable progress logging
66
+ # @return [void]
67
+ def disable_progress
68
+ @progress = false
69
+ @channel.puts "#@pfx Progress logging disabled" if @verbose_level > 0
70
+ return nil
71
+ end
72
+
73
+ # @!macro progress_init
74
+ # Initialize progress logging for a computation
75
+ # @param symbol [Symbol] a symbol assigned to the computation
76
+ # @param units [String] a string with the name of the units, in plural
77
+ # @param total [Integer] total number of units
78
+ # @param initmsg [String] an optional message to output at the beginning
79
+ # @return [void]
80
+ def progress_init(symbol, units, total, initmsg = nil)
81
+ return nil if !@progress or total == 0
82
+ str = "#@pfx 0.0% #{units} processed"
83
+ @data[symbol] = ProgressData.new(0, units, (@part*total).to_i, 1, total,
84
+ Time.now, str.size)
85
+ @channel.puts "#@pfx #{initmsg}" if initmsg
86
+ @channel.print str if @part != 1
87
+ return nil
88
+ end
89
+
90
+ # @!macro [new] progress_log
91
+ # Updates progress logging for a computation
92
+ # @!macro [new] prlog
93
+ # @param symbol [Symbol] the symbol assigned to the computation at
94
+ # init time
95
+ # @param keyargs [Hash] additional units to display, with their current
96
+ # value (e.g. segments_processed: 10000)
97
+ # @param progress [Integer] how many units were processed
98
+ # @return [void]
99
+ def progress_log(symbol, progress=1, **keyargs)
100
+ return nil if !@progress or @part == 1
101
+ data = @data[symbol]
102
+ return nil if data.nil?
103
+ data.counter += progress
104
+ if data.counter == data.total
105
+ progress_end(symbol)
106
+ elsif data.partsize == 0 or
107
+ (data.counter / data.partsize).to_i > data.lastpart
108
+ return nil if data.partsize == 0 and @part > 0
109
+ # this means total is very small
110
+ data.lastpart = data.counter / data.partsize if data.partsize > 0
111
+ done = data.counter.to_f / data.total
112
+ t = Time.now - data.starttime
113
+ eta = (t / done) - t
114
+ tstr= ("Elapsed: %02dh %02dmin %02ds" % [t/3600, t/60%60, t%60])
115
+ etastr = ("ETA: %02dh %02dmin %02ds" % [eta/3600, eta/60%60, eta%60])
116
+ donestr = "%.1f" % (done*100)
117
+ keystr = ""
118
+ keyargs.each {|k,v| keystr << "; #{k}: #{v}"}
119
+ str = "#@pfx #{donestr}% #{data.units} processed "+
120
+ "[#{tstr}; #{etastr}#{keystr}]"
121
+ if str.size > data.strlen
122
+ data.strlen = str.size
123
+ spacediff = ""
124
+ else
125
+ spacediff = " "*(data.strlen-str.size)
126
+ end
127
+ @channel.print "\r#{str}#{spacediff}"
128
+ @channel.flush
129
+ end
130
+ return nil
131
+ end
132
+
133
+ # @!macro [new] progress_end
134
+ # Completes progress logging for a computation
135
+ # @!macro prlog
136
+ # @return [void]
137
+ def progress_end(symbol, **keyargs)
138
+ return if !@progress
139
+ data = @data[symbol]
140
+ return if data.nil?
141
+ t = Time.now - data.starttime
142
+ tstr= ("Elapsed time: %02dh %02dmin %02ds" % [t/3600, t/60%60, t%60])
143
+ quantity = @part == 1 ? data.total.to_s : "100.0%"
144
+ keystr = ""
145
+ keyargs.each {|k,v| keystr << "; #{k}: #{v}"}
146
+ str = "#@pfx #{quantity} #{data.units} processed [#{tstr}#{keystr}]"
147
+ spacediff = " "*([data.strlen - str.size,0].max)
148
+ @channel.print "\r" if @part != 1
149
+ @channel.puts "#{str}#{spacediff}"
150
+ @channel.flush
151
+ @data.delete(symbol)
152
+ return nil
153
+ end
154
+
155
+ end
156
+
157
+ # Progress logging related-methods for RGFA class
158
+ module RGFA::LoggerSupport
159
+
160
+ # Activate logging of progress
161
+ # @return [RGFA] self
162
+ def enable_progress_logging(part: 0.1, channel: STDERR)
163
+ @progress = RGFA::Logger.new(channel: channel)
164
+ @progress.enable_progress(part: part)
165
+ return self
166
+ end
167
+
168
+ # @!macro progress_init
169
+ # @return [RGFA] self
170
+ # @api private
171
+ def progress_log_init(symbol, units, total, initmsg = nil)
172
+ @progress.progress_init(symbol, units, total, initmsg) if @progress
173
+ return self
174
+ end
175
+
176
+ # @!macro progress_log
177
+ # @return [RGFA] self
178
+ # @api private
179
+ def progress_log(symbol, progress=1, **keyargs)
180
+ @progress.progress_log(symbol, progress) if @progress
181
+ return self
182
+ end
183
+
184
+ # @!macro progress_end
185
+ # @return [RGFA] self
186
+ # @api private
187
+ def progress_log_end(symbol, **keyargs)
188
+ @progress.progress_end(symbol) if @progress
189
+ return self
190
+ end
191
+
192
+ end