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   413 use 5.012;
  1         4  
4 1     1   4 use strict;
  1         2  
  1         15  
5 1     1   3 use warnings;
  1         2  
  1         33  
6              
7 1     1   5 use IPC::Cmd qw/can_run/;
  1         1  
  1         38  
8 1     1   5 use Scalar::Util qw/blessed openhandle/;
  1         2  
  1         53  
9 1     1   5 use BioX::Seq;
  1         1  
  1         17  
10 1     1   4 use POSIX qw/ceil/;
  1         2  
  1         6  
11 1     1   385 use Cwd qw/abs_path/;
  1         1  
  1         39  
12 1     1   6 use File::Basename qw/fileparse/;
  1         1  
  1         154  
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   5 use constant MAGIC_GZIP => pack('C3', 0x1f, 0x8b, 0x08);
  1         8  
  1         97  
24 1     1   6 use constant MAGIC_DSRC => pack('C2', 0xaa, 0x02);
  1         1  
  1         38  
25 1     1   4 use constant MAGIC_BZIP => 'BZh';
  1         2  
  1         52  
26 1     1   5 use constant MAGIC_FQZC => '.fqz';
  1         2  
  1         50  
27 1     1   5 use constant MAGIC_BAM => pack('C4', 0x42, 0x41, 0x4d, 0x01);
  1         1  
  1         42  
28 1     1   4 use constant MAGIC_2BIT => pack('C4', 0x1a, 0x41, 0x27, 0x43);
  1         10  
  1         53  
29 1     1   5 use constant MAGIC_ZSTD => pack('C4', 0x28, 0xB5, 0x2F, 0xFD);
  1         1  
  1         41  
30 1     1   5 use constant MAGIC_XZ => pack('C6', 0xfd, 0x37, 0x7a, 0x58, 0x5a, 0x00);
  1         1  
  1         869  
31              
32             sub new {
33              
34 25     25 1 5909 my ($class,$fn, %args) = @_;
35              
36 25         57 my $self = bless {} => $class;
37              
38             # 'fast' mode turns off parser sanity-checking in places
39 25 100       79 if ($args{fast}) {
40 1         3 $self->fast( $args{fast} );
41             }
42              
43 25 100       52 if (defined $fn) {
44              
45 24         52 my $fh = openhandle($fn); # can pass filehandle too;
46 24 100       68 if (! defined $fh) { # otherwise assume filename
47            
48             #if passed a filename, try to determine if compressed
49 22 100       824 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         400 read( $fh, my $magic, 6 );
57             #binmode($fh, $old_layers);
58              
59             #check for compression and open stream if found
60 21 100       158 if (substr($magic,0,3) eq MAGIC_GZIP) {
    100          
    100          
    100          
    100          
    100          
61 5         59 close $fh;
62 5 100       16 if (! defined $GZIP_BIN) {
63             # fall back on Perl-based method (but can be SLOOOOOW!)
64 1         662 require IO::Uncompress::Gunzip;
65 1         31215 $fh = IO::Uncompress::Gunzip->new($fn, MultiStream => 1);
66             }
67             else {
68 4 100       8077 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         44 close $fh;
74 3 100       17 if (! defined $BZIP_BIN) {
75             # fall back on Perl-based method (but can be SLOOOOOW!)
76 1         598 require IO::Uncompress::Bunzip2;
77 1         4027 $fh = IO::Uncompress::Bunzip2->new($fn, MultiStream => 1);
78             }
79             else {
80 2 100       3684 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       26 die "no zstd backend found\n" if (! defined $ZSTD_BIN);
86 1         13 close $fh;
87 1 50       2495 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       41 die "no dsrc backend found\n" if (! defined $DSRC_BIN);
92 1         20 close $fh;
93 1 50       2989 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       39 die "no fqz backend found\n" if (! defined $FQZC_BIN);
98 1         18 close $fh;
99 1 50       2977 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       5 die "no xz backend found\n" if (! defined $XZ_BIN);
104 1         13 close $fh;
105 1 50       1708 open $fh, '-|', "$XZ_BIN -dc $fn"
106             or die "Error opening xz stream: $!\n";
107             }
108             else {
109 6         59 seek($fh,0,0);
110             }
111              
112             }
113 15         3071 $self->{fh} = $fh;
114              
115             }
116             else {
117 1         3 $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         129 $self->_guess_format;
125              
126 14         53 $self->_init;
127              
128 12         335 return $self;
129              
130             }
131              
132             sub fast {
133              
134 1     1 1 6 my ($self, $bool) = @_;
135 1   50     35 $self->{fast} = $bool // 1;
136              
137             }
138              
139             sub _guess_format {
140              
141 16     16   45 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         3394 my $r = (read $self->{fh}, $self->{buffer}, 2);
146 16 100       153 die "failed to read initial bytes" if ($r != 2);
147              
148 15         918 my $search_path = abs_path(__FILE__);
149 15         187 $search_path =~ s/\.pm$//i;
150 15         35 my @matched;
151 15         1294 for my $module ( glob "$search_path/*.pm" ) {
152 45         1507 my ($name,$path,$suff) = fileparse($module, qr/\.pm/i);
153 45         244 my $classname = blessed($self) . "::$name";
154 45         2463 eval "require $classname";
155 45 100       303 if ($classname->_check_type($self)) {
156 14         50 push @matched, $classname;
157             }
158             }
159              
160 15 100       64 die "Failed to guess filetype\n" if (scalar(@matched) < 1);
161             # uncoverable branch true
162 14 50       24 die "Multiple filetypes matched\n" if (scalar(@matched) > 1);
163              
164 14         535 eval "require $matched[0]";
165 14         49 bless $self => $matched[0];
166              
167             }
168              
169              
170             1;
171              
172              
173             __END__