File Coverage

blib/lib/Bio/SFF/Reader.pm
Criterion Covered Total %
statement 71 73 97.2
branch 7 12 58.3
condition 1 3 33.3
subroutine 16 16 100.0
pod n/a
total 95 104 91.3


line stmt bran cond sub pod time code
1             package Bio::SFF::Reader;
2             {
3             $Bio::SFF::Reader::VERSION = '0.007';
4             }
5              
6 1     1   12936 use Moo::Role;
  1         3  
  1         8  
7              
8 1     1   1090 use Bio::SFF::Entry;
  1         3  
  1         60  
9 1     1   10704 use Bio::SFF::Header;
  1         5  
  1         108  
10 1     1   14 use Carp qw/croak/;
  1         2  
  1         69  
11 1     1   5 use Config;
  1         1  
  1         37  
12 1     1   6 use Const::Fast;
  1         1  
  1         9  
13 1     1   76 use Fcntl qw/SEEK_SET/;
  1         2  
  1         121  
14 1     1   2726 use FileHandle;
  1         13926  
  1         8  
15 1     1   495 use Scalar::Util qw/reftype/;
  1         2  
  1         10525  
16              
17             const my $padding_to => 8;
18             const my $index_header => 8;
19             const my $header_size => 31;
20             const my $entry_header_size => 4;
21             const my $idx_off_type => ($] >= 5.010 && $Config{use64bitint} ? 'Q>' : 'x[N]N');
22             const my $size_of_flowgram_value => 2;
23             const my $uses_number_of_bases => 3;
24              
25             requires '_has_index';
26              
27             sub _roundup {
28 134     134   4306 my $number = shift;
29 134         248 my $remain = $number % $padding_to;
30 134 100       734 return $number + ($remain ? $padding_to - $remain : 0);
31             }
32              
33             has _fh => (
34             is => 'ro',
35             required => 1,
36             init_arg => 'file',
37             isa => sub {
38             reftype($_[0]) eq 'GLOB';
39             },
40             coerce => sub {
41             my $val = shift;
42             return $val if ref $val and reftype($val) eq 'GLOB';
43             open my $fh, '<:raw', $val or croak "Could open file $val: $!";
44             return $fh;
45             }
46             );
47              
48             has header => (
49             is => 'ro',
50             init_arg => undef,
51             builder => '_build_header',
52             lazy => 1,
53             );
54              
55             ## no critic (Subroutines::ProhibitUnusedPrivateSubroutines,Subroutines::ProhibitBuiltinHomonyms)
56             sub _build_header {
57 7     7   737 my $self = shift;
58 7         31 my $templ = "a4N $idx_off_type N2n3C";
59 7         1141 my ($magic, $version, $index_offset, $index_length, $number_of_reads, $header_length, $key_length, $number_of_flows_per_read, $flowgram_format_code) = unpack $templ, $self->_read_bytes($header_size);
60 7         65 my ($flow_chars, $key_sequence) = unpack sprintf('a%da%d', $number_of_flows_per_read, $key_length), $self->_read_bytes($header_length - $header_size);
61              
62 7         42 my $header = Bio::SFF::Header->new(
63             magic => $magic,
64             version => $version,
65             index_offset => $index_offset,
66             index_length => _roundup($index_length),
67             number_of_reads => $number_of_reads,
68             header_length => _roundup($header_length),
69             number_of_flows_per_read => $number_of_flows_per_read,
70             flowgram_format_code => $flowgram_format_code,
71             flow_chars => $flow_chars,
72             key_sequences => $key_sequence,
73             );
74              
75 7         595 return $header;
76             }
77              
78             for my $method (qw/number_of_reads number_of_flows_per_read index_offset index_length/) {
79             has "_$method" => (
80             is => 'ro',
81             init_arg => undef,
82             default => sub {
83             my $self = shift;
84             return $self->header->$method;
85             },
86             lazy => 1,
87             );
88             }
89              
90             sub _read_bytes {
91 379     379   687 my ($self, $num) = @_;
92 379         533 my $buffer;
93 379 50       2521 croak "Could not read SFF file: $!" if not defined read $self->_fh, $buffer, $num;
94 379         5209 return $buffer;
95             }
96              
97             my $read_template = 'Nnnnn a%d';
98             my @header_keys = qw/clip_qual_left clip_qual_right clip_adaptor_left clip_adaptor_right name/;
99              
100             sub _read_entry {
101 120     120   209 my $self = shift;
102 120         183 my %entry;
103 120         350 @entry{qw/read_header_length name_length/} = unpack 'nn', $self->_read_bytes($entry_header_size);
104 120         754 (my ($number_of_bases), @entry{@header_keys}) = unpack sprintf($read_template, $entry{name_length}), $self->_read_bytes($entry{read_header_length} - $entry_header_size);
105              
106 120         585 my $data_template = sprintf 'a%da%da%da%d', $size_of_flowgram_value * $self->_number_of_flows_per_read, ($number_of_bases) x $uses_number_of_bases;
107 120         7413 my $data_length = _roundup($size_of_flowgram_value * $self->_number_of_flows_per_read + $uses_number_of_bases * $number_of_bases);
108 120         365 @entry{qw/flowgram_values flow_index_per_base bases quality_scores/} = unpack $data_template, $self->_read_bytes($data_length);
109 120         5979 return Bio::SFF::Entry->new(\%entry);
110             }
111              
112             has _index_info => (
113             is => 'ro',
114             init_arg => undef,
115             builder => '_build_index_info',
116             lazy => 1,
117             );
118              
119             sub _build_index_info {
120 1     1   605 my $self = shift;
121 1         8 my ($index_offset, $index_length) = ($self->header->index_offset, $self->header->index_length);
122 1 50 33     47 return if !$index_offset || !$index_length;
123            
124 1         22 my $tell = $self->_fh->tell;
125 1         22 $self->_fh->seek($index_offset, SEEK_SET);
126 1         23 my ($magic_number) = unpack 'A8', $self->_read_bytes($index_header);
127 1         6 $self->_fh->seek($tell, SEEK_SET);
128 1         20 return $magic_number;
129             }
130              
131             has manifest => (
132             is => 'ro',
133             init_arg => undef,
134             builder => '_build_manifest',
135             lazy => 1,
136             );
137              
138             sub _build_manifest {
139 1     1   641 my $self = shift;
140 1 50       13 return $self->_index->manifest if $self->_has_index;
141 1         7 my $magic_number = $self->_index_info;
142 1 50       6 if ($magic_number eq '.mft1.00') {
143 1         6 my ($index_offset, $index_length) = ($self->_index_offset, $self->_index_length);
144 1         40 my $tell = $self->_fh->tell;
145 1         11 $self->_fh->seek($index_offset + $index_header, SEEK_SET);
146 1         132 my $xml = $self->_read_manifest($magic_number);
147 1         7 $self->_fh->seek($tell, SEEK_SET);
148 1         25 return $xml;
149             }
150 0         0 return;
151             }
152              
153             sub _read_manifest {
154 2     2   6 my ($self, $magic_number) = @_;
155 2         8 my $xmldata_head = $self->_read_bytes($index_header);
156 2 50       8 if ( $magic_number eq '.mft1.00') {
157 2         10 my ($xml_size, $data_size) = unpack 'NN', $xmldata_head;
158 2         6 return $self->_read_bytes($xml_size);
159             }
160 0           return;
161             }
162              
163             1;
164              
165             #ABSTRACT: An SFF reader role
166              
167             __END__