File Coverage

blib/lib/App/Fasops/Command/consensus.pm
Criterion Covered Total %
statement 47 112 41.9
branch 4 28 14.2
condition 0 9 0.0
subroutine 14 17 82.3
pod 6 7 85.7
total 71 173 41.0


line stmt bran cond sub pod time code
1             package App::Fasops::Command::consensus;
2 20     20   14006 use strict;
  20         56  
  20         614  
3 20     20   110 use warnings;
  20         48  
  20         560  
4 20     20   110 use autodie;
  20         37  
  20         110  
5              
6 20     20   119612 use MCE;
  20         713939  
  20         132  
7 20     20   13734 use MCE::Flow Sereal => 1;
  20         67296  
  20         150  
8 20     20   16498 use MCE::Candy;
  20         22610  
  20         139  
9              
10 20     20   851 use App::Fasops -command;
  20         58  
  20         309  
11 20     20   9047 use App::RL::Common;
  20         44  
  20         488  
12 20     20   119 use App::Fasops::Common;
  20         47  
  20         25849  
13              
14             sub abstract {
15 2     2 1 100 return 'create consensus from blocked fasta file';
16             }
17              
18             sub opt_spec {
19             return (
20 3     3 1 27 [ "outfile|o=s", "output filename. [stdout] for screen" ],
21             [ "outgroup", "has outgroup at the end of blocks", ],
22             [ "cname", "the name of consensus", { default => "consensus" }, ],
23             [ "parallel|p=i", "run in parallel mode", { default => 1 }, ],
24             { show_defaults => 1, }
25             );
26             }
27              
28             sub usage_desc {
29 3     3 1 40835 return "fasops consensus [options] ";
30             }
31              
32             sub description {
33 1     1 1 1102 my $desc;
34 1         5 $desc .= ucfirst(abstract) . ".\n";
35 1         3 $desc .= <<'MARKDOWN';
36              
37             * are paths to blocked fasta files, .fas.gz is supported
38             * infile == stdin means reading from STDIN
39             * `poa` is used for creating consensus sequences
40              
41             MARKDOWN
42              
43 1         4 return $desc;
44             }
45              
46             sub validate_args {
47 2     2 1 2263 my ( $self, $opt, $args ) = @_;
48              
49 2 100       4 if ( @{$args} != 1 ) {
  2         9  
50 1         3 my $message = "This command need one input file.\n\tIt found";
51 1         2 $message .= sprintf " [%s]", $_ for @{$args};
  1         4  
52 1         3 $message .= ".\n";
53 1         11 $self->usage_error($message);
54             }
55 1         4 for ( @{$args} ) {
  1         3  
56 1 50       5 next if lc $_ eq "stdin";
57 1 50       7 if ( !Path::Tiny::path($_)->is_file ) {
58 1         121 $self->usage_error("The input file [$_] doesn't exist.");
59             }
60             }
61              
62 0 0         if ( !exists $opt->{outfile} ) {
63 0           $opt->{outfile} = Path::Tiny::path( $args->[0] )->absolute . ".fas";
64             }
65             }
66              
67             sub execute {
68 0     0 1   my ( $self, $opt, $args ) = @_;
69              
70 0           my $in_fh;
71 0 0         if ( lc $args->[0] eq "stdin" ) {
72 0           $in_fh = *STDIN{IO};
73             }
74             else {
75 0           $in_fh = IO::Zlib->new( $args->[0], "rb" );
76             }
77              
78 0           my $out_fh;
79 0 0         if ( lc( $opt->{outfile} ) eq "stdout" ) {
80 0           $out_fh = *STDOUT{IO};
81             }
82             else {
83 0           open $out_fh, ">", $opt->{outfile};
84             }
85              
86 0           my @infos; # collect blocks for parallelly refining
87 0           my $content = ''; # content of one block
88 0           while (1) {
89 0 0 0       last if $in_fh->eof and $content eq '';
90 0           my $line = '';
91 0 0         if ( !$in_fh->eof ) {
92 0           $line = $in_fh->getline;
93             }
94 0 0 0       if ( ( $line eq '' or $line =~ /^\s+$/ ) and $content ne '' ) {
      0        
95 0           my $info_of = App::Fasops::Common::parse_block( $content, 1 );
96 0           $content = '';
97              
98 0 0         if ( $opt->{parallel} >= 2 ) {
99 0           push @infos, $info_of;
100             }
101             else {
102 0           my $out_string = proc_block( $info_of, $opt );
103 0           print {$out_fh} $out_string;
  0            
104             }
105             }
106             else {
107 0           $content .= $line;
108             }
109             }
110 0           $in_fh->close;
111              
112 0 0         if ( $opt->{parallel} >= 2 ) {
113             my $worker = sub {
114 0     0     my ( $self, $chunk_ref, $chunk_id ) = @_;
115              
116 0           my $info_of = $chunk_ref->[0];
117 0           my $out_string = proc_block( $info_of, $opt );
118              
119             # preserving output order
120 0           MCE->gather( $chunk_id, $out_string );
121 0           };
122              
123             MCE::Flow::init {
124             chunk_size => 1,
125             max_workers => $opt->{parallel},
126 0           gather => MCE::Candy::out_iter_fh($out_fh),
127             };
128 0           mce_flow $worker, \@infos;
129 0           MCE::Flow::finish;
130             }
131              
132 0           close $out_fh;
133             }
134              
135             sub proc_block {
136 0     0 0   my $info_of = shift;
137 0           my $opt = shift;
138              
139 0           tie my %info_out, "Tie::IxHash";
140             {
141 0           my @keys = keys %{$info_of};
  0            
  0            
142              
143 0           my $outgroup;
144 0 0         if ( $opt->{outgroup} ) {
145 0           $outgroup = pop @keys;
146             }
147              
148             # copy prev info
149 0           $info_out{ $opt->{cname} } = $info_of->{ $keys[0] };
150 0 0         if ( $opt->{outgroup} ) {
151 0           $info_out{$outgroup} = $info_of->{$outgroup};
152             }
153              
154             # create consensus
155 0           my $seq_refs = [];
156 0           for my $key (@keys) {
157 0           push @{$seq_refs}, $info_of->{$key}{seq};
  0            
158             }
159 0           my $cseq = App::Fasops::Common::poa_consensus( $seq_refs, $opt->{msa} );
160              
161             # update info
162 0 0         if ( defined $info_out{ $opt->{cname} }->{name} ) {
163 0           $info_out{ $opt->{cname} }->{name} = $opt->{cname};
164             }
165             else {
166 0           $info_out{ $opt->{cname} }->{chr} = $opt->{cname};
167             }
168 0           $info_out{ $opt->{cname} }->{seq} = $cseq;
169             }
170              
171 0           my $out_string;
172              
173 0           for my $key ( keys %info_out ) {
174 0           $out_string .= sprintf ">%s\n", App::RL::Common::encode_header( $info_out{$key} );
175 0           $out_string .= sprintf "%s\n", $info_out{$key}->{seq};
176             }
177 0           $out_string .= "\n";
178              
179 0           return $out_string;
180             }
181              
182             1;