File Coverage

blib/lib/BioX/Seq/Stream.pm
Criterion Covered Total %
statement 106 106 100.0
branch 46 52 90.3
condition 1 2 50.0
subroutine 20 20 100.0
pod 2 2 100.0
total 175 182 96.7


line stmt bran cond sub pod time code
1             package BioX::Seq::Stream;
2              
3 1     1   489 use 5.012;
  1         4  
4 1     1   5 use strict;
  1         2  
  1         19  
5 1     1   5 use warnings;
  1         1  
  1         26  
6              
7 1     1   5 use IPC::Cmd qw/can_run/;
  1         1  
  1         45  
8 1     1   6 use Scalar::Util qw/blessed openhandle/;
  1         2  
  1         63  
9 1     1   7 use BioX::Seq;
  1         1  
  1         21  
10 1     1   6 use POSIX qw/ceil/;
  1         1  
  1         8  
11 1     1   465 use Cwd qw/abs_path/;
  1         2  
  1         49  
12 1     1   6 use File::Basename qw/fileparse/;
  1         2  
  1         175  
13              
14             # define or search for binary locations
15             # if these are not available
16             our $GZIP_BIN = can_run('pigz') // can_run('gzip');
17             our $BZIP_BIN = can_run('pbzip2') // can_run('bzip2');
18             our $ZSTD_BIN = can_run('pzstd') // can_run('zstd');
19             our $DSRC_BIN = can_run('dsrc2') // can_run('dsrc');
20             our $FQZC_BIN = can_run('fqz_comp');
21             our $XZ_BIN = can_run('xz');
22              
23 1     1   6 use constant MAGIC_GZIP => pack('C3', 0x1f, 0x8b, 0x08);
  1         10  
  1         78  
24 1     1   7 use constant MAGIC_DSRC => pack('C2', 0xaa, 0x02);
  1         2  
  1         47  
25 1     1   5 use constant MAGIC_BZIP => 'BZh';
  1         2  
  1         57  
26 1     1   6 use constant MAGIC_FQZC => '.fqz';
  1         2  
  1         64  
27 1     1   6 use constant MAGIC_BAM => pack('C4', 0x42, 0x41, 0x4d, 0x01);
  1         2  
  1         51  
28 1     1   5 use constant MAGIC_2BIT => pack('C4', 0x1a, 0x41, 0x27, 0x43);
  1         11  
  1         64  
29 1     1   6 use constant MAGIC_ZSTD => pack('C4', 0x28, 0xB5, 0x2F, 0xFD);
  1         3  
  1         50  
30 1     1   5 use constant MAGIC_XZ => pack('C6', 0xfd, 0x37, 0x7a, 0x58, 0x5a, 0x00);
  1         2  
  1         965  
31              
32             sub new {
33              
34 25     25 1 7669 my ($class,$fn, %args) = @_;
35              
36 25         85 my $self = bless {} => $class;
37              
38             # 'fast' mode turns off parser sanity-checking in places
39 25 100       110 if ($args{fast}) {
40 1         12 $self->fast( $args{fast} );
41             }
42              
43 25 100       63 if (defined $fn) {
44              
45 24         66 my $fh = openhandle($fn); # can pass filehandle too;
46 24 100       55 if (! defined $fh) { # otherwise assume filename
47            
48             #if passed a filename, try to determine if compressed
49 22 100       974 open $fh, '<', $fn or die "Error opening $fn for reading\n";
50              
51             #read first six bytes as raw
52             #this causes a memory leak as opened filehandles are not properly
53             #closed again. Should work without setting binary mode anyway.
54             #my $old_layers = join '', map {":$_"} PerlIO::get_layers($fh);
55             #binmode($fh);
56 21         466 read( $fh, my $magic, 6 );
57             #binmode($fh, $old_layers);
58              
59             #check for compression and open stream if found
60 21 100       264 if (substr($magic,0,3) eq MAGIC_GZIP) {
    100          
    100          
    100          
    100          
    100          
61 5         71 close $fh;
62 5 100       20 if (! defined $GZIP_BIN) {
63             # fall back on Perl-based method (but can be SLOOOOOW!)
64 1         1846 require IO::Uncompress::Gunzip;
65 1         37953 $fh = IO::Uncompress::Gunzip->new($fn, MultiStream => 1);
66             }
67             else {
68 4 100       13007 open $fh, '-|', $GZIP_BIN, '-dc', $fn
69             or die "Error opening gzip stream: $!\n";
70             }
71             }
72             elsif (substr($magic,0,3) eq MAGIC_BZIP) {
73 3         49 close $fh;
74 3 100       19 if (! defined $BZIP_BIN) {
75             # fall back on Perl-based method (but can be SLOOOOOW!)
76 1         2886 require IO::Uncompress::Bunzip2;
77 1         9093 $fh = IO::Uncompress::Bunzip2->new($fn, MultiStream => 1);
78             }
79             else {
80 2 100       8997 open $fh, '-|', $BZIP_BIN, '-dc', $fn
81             or die "Error opening bzip2 stream: $!\n";
82             }
83             }
84             elsif (substr($magic,0,4) eq MAGIC_ZSTD) {
85 2 100       31 die "no zstd backend found\n" if (! defined $ZSTD_BIN);
86 1         14 close $fh;
87 1 50       3831 open $fh, '-|', $ZSTD_BIN, '-dc', $fn
88             or die "Error opening zstd stream: $!\n";
89             }
90             elsif (substr($magic,0,2) eq MAGIC_DSRC) {
91 2 100       48 die "no dsrc backend found\n" if (! defined $DSRC_BIN);
92 1         25 close $fh;
93 1 50       3475 open $fh, '-|', $DSRC_BIN, 'd', '-s', $fn
94             or die "Error opening dsrc stream: $!\n";
95             }
96             elsif (substr($magic,0,4) eq MAGIC_FQZC) {
97 2 100       67 die "no fqz backend found\n" if (! defined $FQZC_BIN);
98 1         28 close $fh;
99 1 50       3917 open $fh, '-|', $FQZC_BIN, '-d', $fn
100             or die "Error opening fqz_comp stream: $!\n";
101             }
102             elsif (substr($magic,0,6) eq MAGIC_XZ) {
103 1 50       6 die "no xz backend found\n" if (! defined $XZ_BIN);
104 1         18 close $fh;
105 1 50       5450 open $fh, '-|', $XZ_BIN, '-dc', $fn
106             or die "Error opening xz stream: $!\n";
107             }
108             else {
109 6         69 seek($fh,0,0);
110             }
111              
112             }
113 15         4524 $self->{fh} = $fh;
114              
115             }
116             else {
117 1         4 $self->{fh} = \*STDIN;
118             }
119              
120             # handle files coming from different platforms
121             #my @layers = PerlIO::get_layers($self->{fh});
122             #binmode($self->{fh},':unix:stdio:crlf');
123              
124 16         160 $self->_guess_format;
125              
126 14         64 $self->_init;
127              
128 12         407 return $self;
129              
130             }
131              
132             sub fast {
133              
134 1     1 1 3 my ($self, $bool) = @_;
135 1   50     38 $self->{fast} = $bool // 1;
136              
137             }
138              
139             sub _guess_format {
140              
141 16     16   59 my ($self) = @_;
142              
143             # Filetype guessing must be based on first two bytes (or less)
144             # which are stored in an object buffer
145 16         6750 my $r = (read $self->{fh}, $self->{buffer}, 2);
146 16 100       216 die "failed to read initial bytes" if ($r != 2);
147              
148 15         1171 my $search_path = abs_path(__FILE__);
149 15         246 $search_path =~ s/\.pm$//i;
150 15         42 my @matched;
151 15         1711 for my $module ( glob "$search_path/*.pm" ) {
152 45         1872 my ($name,$path,$suff) = fileparse($module, qr/\.pm/i);
153 45         312 my $classname = blessed($self) . "::$name";
154 45         3240 eval "require $classname";
155 45 100       393 if ($classname->_check_type($self)) {
156 14         49 push @matched, $classname;
157             }
158             }
159              
160 15 100       78 die "Failed to guess filetype\n" if (scalar(@matched) < 1);
161             # uncoverable branch true
162 14 50       33 die "Multiple filetypes matched\n" if (scalar(@matched) > 1);
163              
164 14         676 eval "require $matched[0]";
165 14         62 bless $self => $matched[0];
166              
167             }
168              
169              
170             1;
171              
172              
173             __END__