File Coverage

blib/lib/App/Fasops/Command/concat.pm
Criterion Covered Total %
statement 84 89 94.3
branch 18 26 69.2
condition 8 12 66.6
subroutine 12 12 100.0
pod 6 6 100.0
total 128 145 88.2


line stmt bran cond sub pod time code
1             package App::Fasops::Command::concat;
2 20     20   13475 use strict;
  20         54  
  20         622  
3 20     20   125 use warnings;
  20         56  
  20         509  
4 20     20   114 use autodie;
  20         41  
  20         111  
5              
6 20     20   104543 use App::Fasops -command;
  20         49  
  20         213  
7 20     20   6875 use App::RL::Common;
  20         47  
  20         592  
8 20     20   119 use App::Fasops::Common;
  20         39  
  20         21564  
9              
10             sub abstract {
11 2     2 1 52 return 'concatenate sequence pieces in blocked fasta files';
12             }
13              
14             sub opt_spec {
15             return (
16 7     7 1 49 [ "outfile|o=s", "Output filename. [stdout] for screen" ],
17             [ "total|t=i", "Stop when exceed this length", { default => 10_000_000, }, ],
18             [ "relaxed", "output relaxed phylip instead of fasta" ],
19             { show_defaults => 1, }
20             );
21             }
22              
23             sub usage_desc {
24 7     7 1 59523 return "fasops concat [options] ";
25             }
26              
27             sub description {
28 1     1 1 999 my $desc;
29 1         8 $desc .= ucfirst(abstract) . ".\n";
30 1         4 $desc .= <<'MARKDOWN';
31              
32             * is the path to blocked fasta file, .fas.gz is supported
33             * infile == stdin means reading from STDIN
34             * is a file with a list of names to keep, one per line
35             * Names in the output file will following the order in
36              
37             MARKDOWN
38              
39 1         4 return $desc;
40             }
41              
42             sub validate_args {
43 6     6 1 6611 my ( $self, $opt, $args ) = @_;
44              
45 6 100       11 if ( @{$args} != 2 ) {
  6         22  
46 2         5 my $message = "This command need two input files.\n\tIt found";
47 2         3 $message .= sprintf " [%s]", $_ for @{$args};
  2         9  
48 2         6 $message .= ".\n";
49 2         13 $self->usage_error($message);
50             }
51 4         9 for ( @{$args} ) {
  4         11  
52 7 50       205 next if lc $_ eq "stdin";
53 7 100       28 if ( !Path::Tiny::path($_)->is_file ) {
54 1         134 $self->usage_error("The input file [$_] doesn't exist.");
55             }
56             }
57              
58 3 50       148 if ( !exists $opt->{outfile} ) {
59             $opt->{outfile} = Path::Tiny::path( $args->[0] )->absolute
60 0 0       0 . ( $opt->{relaxed} ? ".concat.phy" : ".concat.fasta" );
61             }
62             }
63              
64             sub execute {
65 3     3 1 22 my ( $self, $opt, $args ) = @_;
66              
67 3         6 my @names = @{ App::RL::Common::read_names( $args->[1] ) };
  3         17  
68              
69 3         9313 my $in_fh;
70 3 50       14 if ( lc $args->[0] eq "stdin" ) {
71 0         0 $in_fh = *STDIN{IO};
72             }
73             else {
74 3         14 $in_fh = IO::Zlib->new( $args->[0], "rb" );
75             }
76              
77 3         4123 my $out_fh;
78 3 50       12 if ( lc( $opt->{outfile} ) eq "stdout" ) {
79 3         8 $out_fh = *STDOUT{IO};
80             }
81             else {
82 0         0 open $out_fh, ">", $opt->{outfile};
83             }
84              
85 3         9 my $all_seq_of = { map { $_ => "" } @names };
  6         24  
86             {
87 3         8 my $content = ''; # content of one block
  3         7  
88 3         5 BLOCK: while (1) {
89 74 100 66     314 last if $in_fh->eof and $content eq '';
90 72         3086 my $line = '';
91 72 50       254 if ( !$in_fh->eof ) {
92 72         2917 $line = $in_fh->getline;
93             }
94 72 100 66     8245 if ( ( $line eq '' or $line =~ /^\s+$/ ) and $content ne '' ) {
      66        
95 8         31 my $info_of = App::Fasops::Common::parse_block($content);
96 8         15 $content = '';
97              
98 8         13 my $first_name = ( keys %{$info_of} )[0];
  8         31  
99 8         217 my $align_length = length $info_of->{$first_name}{seq};
100              
101 8         93 for my $name (@names) {
102 16 50       146 if ( exists $info_of->{$name} ) {
103 16         101 $all_seq_of->{$name} .= $info_of->{$name}{seq};
104             }
105             else {
106             # fill absent names with ------
107 0         0 $all_seq_of->{$name} .= '-' x $align_length;
108             }
109             }
110              
111 8 100 66     186 if ( $opt->{total} and $opt->{total} < length $all_seq_of->{ $names[0] } ) {
112 1         12 last BLOCK;
113             }
114             }
115             else {
116 64         128 $content .= $line;
117             }
118             }
119             }
120              
121 3         328 my $all_seq_length = length $all_seq_of->{ $names[0] };
122 3 100       10 if ( $opt->{relaxed} ) {
123 1         4 print {$out_fh} scalar @names, " $all_seq_length\n";
  1         8  
124 1         19 for my $name (@names) {
125 2         17 print {$out_fh} "$name ";
  2         9  
126 2         26 print {$out_fh} $all_seq_of->{$name}, "\n";
  2         8  
127             }
128             }
129             else {
130 2         7 for my $name (@names) {
131 4         43 print {$out_fh} ">$name\n";
  4         22  
132 4         61 print {$out_fh} $all_seq_of->{$name}, "\n";
  4         27  
133             }
134             }
135              
136 3         60 close $out_fh;
137 0           $in_fh->close;
138             }
139              
140             1;