File Coverage

blib/lib/BioX/Seq/Stream.pm
Criterion Covered Total %
statement 106 106 100.0
branch 45 52 88.4
condition 1 2 50.0
subroutine 20 20 100.0
pod 2 2 100.0
total 174 182 96.1


line stmt bran cond sub pod time code
1             package BioX::Seq::Stream;
2              
3 2     2   7363 use 5.012;
  2         9  
4 2     2   16 use strict;
  2         6  
  2         58  
5 2     2   13 use warnings;
  2         5  
  2         180  
6              
7 2     2   901 use IPC::Cmd qw/can_run/;
  2         83453  
  2         196  
8 2     2   20 use Scalar::Util qw/blessed openhandle/;
  2         4  
  2         152  
9 2     2   14 use BioX::Seq;
  2         4  
  2         77  
10 2     2   23 use POSIX qw/ceil/;
  2         3  
  2         19  
11 2     2   1224 use Cwd qw/abs_path/;
  2         3  
  2         114  
12 2     2   11 use File::Basename qw/fileparse/;
  2         3  
  2         431  
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 2     2   20 use constant MAGIC_GZIP => pack('C3', 0x1f, 0x8b, 0x08);
  2         9  
  2         191  
24 2     2   12 use constant MAGIC_DSRC => pack('C2', 0xaa, 0x02);
  2         3  
  2         117  
25 2     2   9 use constant MAGIC_BZIP => 'BZh';
  2         3  
  2         87  
26 2     2   11 use constant MAGIC_FQZC => '.fqz';
  2         25  
  2         148  
27 2     2   14 use constant MAGIC_BAM => pack('C4', 0x42, 0x41, 0x4d, 0x01);
  2         3  
  2         109  
28 2     2   9 use constant MAGIC_2BIT => pack('C4', 0x1a, 0x41, 0x27, 0x43);
  2         4  
  2         104  
29 2     2   9 use constant MAGIC_ZSTD => pack('C4', 0x28, 0xB5, 0x2F, 0xFD);
  2         3  
  2         145  
30 2     2   11 use constant MAGIC_XZ => pack('C6', 0xfd, 0x37, 0x7a, 0x58, 0x5a, 0x00);
  2         3  
  2         2558  
31              
32             sub new {
33              
34 25     25 1 10451 my ($class,$fn, %args) = @_;
35              
36 25         89 my $self = bless {} => $class;
37              
38             # 'fast' mode turns off parser sanity-checking in places
39 25 100       167 if ($args{fast}) {
40 1         10 $self->fast( $args{fast} );
41             }
42              
43 25 100       132 if (defined $fn) {
44              
45 24         119 my $fh = openhandle($fn); # can pass filehandle too;
46 24 100       86 if (! defined $fh) { # otherwise assume filename
47            
48             #if passed a filename, try to determine if compressed
49 22 100       1370 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         623 read( $fh, my $magic, 6 );
57             #binmode($fh, $old_layers);
58              
59             #check for compression and open stream if found
60 21 100       257 if (substr($magic,0,3) eq MAGIC_GZIP) {
    100          
    100          
    100          
    100          
    100          
61 5         73 close $fh;
62 5 100       19 if (! defined $GZIP_BIN) {
63             # fall back on Perl-based method (but can be SLOOOOOW!)
64 1         1151 require IO::Uncompress::Gunzip;
65 1         73682 $fh = IO::Uncompress::Gunzip->new($fn, MultiStream => 1);
66             }
67             else {
68 4 100       24613 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         56 close $fh;
74 3 100       26 if (! defined $BZIP_BIN) {
75             # fall back on Perl-based method (but can be SLOOOOOW!)
76 2         1315 require IO::Uncompress::Bunzip2;
77 2         7847 $fh = IO::Uncompress::Bunzip2->new($fn, MultiStream => 1);
78             }
79             else {
80 1 50       8573 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       38 die "no zstd backend found\n" if (! defined $ZSTD_BIN);
86 1         17 close $fh;
87 1 50       9373 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       45 die "no dsrc backend found\n" if (! defined $DSRC_BIN);
92 1         29 close $fh;
93 1 50       6736 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       49 die "no fqz backend found\n" if (! defined $FQZC_BIN);
98 1         28 close $fh;
99 1 50       5101 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       9 die "no xz backend found\n" if (! defined $XZ_BIN);
104 1         20 close $fh;
105 1 50       7622 open $fh, '-|', $XZ_BIN, '-dc', $fn
106             or die "Error opening xz stream: $!\n";
107             }
108             else {
109 6         62 seek($fh,0,0);
110             }
111              
112             }
113 15         7545 $self->{fh} = $fh;
114              
115             }
116             else {
117 1         5 $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         181 $self->_guess_format;
125              
126 14         81 $self->_init;
127              
128 12         418 return $self;
129              
130             }
131              
132             sub fast {
133              
134 1     1 1 5 my ($self, $bool) = @_;
135 1   50     10 $self->{fast} = $bool // 1;
136              
137             }
138              
139             sub _guess_format {
140              
141 16     16   93 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         10812 my $r = (read $self->{fh}, $self->{buffer}, 2);
146 16 100       371 die "failed to read initial bytes" if ($r != 2);
147              
148 15         1525 my $search_path = abs_path(__FILE__);
149 15         268 $search_path =~ s/\.pm$//i;
150 15         62 my @matched;
151 15         6737 for my $module ( glob "$search_path/*.pm" ) {
152 45         2242 my ($name,$path,$suff) = fileparse($module, qr/\.pm/i);
153 45         259 my $classname = blessed($self) . "::$name";
154 45         7651 eval "require $classname";
155 45 100       479 if ($classname->_check_type($self)) {
156 14         54 push @matched, $classname;
157             }
158             }
159              
160 15 100       105 die "Failed to guess filetype\n" if (scalar(@matched) < 1);
161             # uncoverable branch true
162 14 50       63 die "Multiple filetypes matched\n" if (scalar(@matched) > 1);
163              
164 14         945 eval "require $matched[0]";
165 14         86 bless $self => $matched[0];
166              
167             }
168              
169              
170             1;
171              
172              
173             __END__