File Coverage

blib/lib/App/Fasops/Command/slice.pm
Criterion Covered Total %
statement 108 118 91.5
branch 22 34 64.7
condition 7 9 77.7
subroutine 12 12 100.0
pod 6 6 100.0
total 155 179 86.5


line stmt bran cond sub pod time code
1             package App::Fasops::Command::slice;
2 20     20   13787 use strict;
  20         49  
  20         613  
3 20     20   111 use warnings;
  20         49  
  20         494  
4 20     20   104 use autodie;
  20         41  
  20         119  
5              
6 20     20   104566 use App::Fasops -command;
  20         56  
  20         206  
7 20     20   6672 use App::RL::Common;
  20         42  
  20         565  
8 20     20   124 use App::Fasops::Common;
  20         39  
  20         27827  
9              
10             sub abstract {
11 2     2 1 47 return 'extract alignment slices from a blocked fasta';
12             }
13              
14             sub opt_spec {
15             return (
16 5     5 1 36 [ "outfile|o=s", "Output filename. [stdout] for screen." ],
17             [ "name|n=s", "According to this species. Default is the first one." ],
18             [ "length|l=i", "the threshold of alignment length", { default => 1 } ],
19             { show_defaults => 1, }
20             );
21             }
22              
23             sub usage_desc {
24 5     5 1 48746 return "fasops slice [options] ";
25             }
26              
27             sub description {
28 1     1 1 949 my $desc;
29 1         4 $desc .= ucfirst(abstract) . ".\n";
30 1         3 $desc .= <<'MARKDOWN';
31              
32             * are paths to axt files, .axt.gz is supported
33             * infile == stdin means reading from STDIN
34             * is a App::RL dump
35              
36             MARKDOWN
37              
38 1         4 return $desc;
39             }
40              
41             sub validate_args {
42 4     4 1 4427 my ( $self, $opt, $args ) = @_;
43              
44 4 100       9 if ( @{$args} != 2 ) {
  4         14  
45 2         4 my $message = "This command need two input files.\n\tIt found";
46 2         3 $message .= sprintf " [%s]", $_ for @{$args};
  2         10  
47 2         5 $message .= ".\n";
48 2         13 $self->usage_error($message);
49             }
50 2         5 for ( @{$args} ) {
  2         6  
51 3 50       69 next if lc $_ eq "stdin";
52 3 100       13 if ( !Path::Tiny::path($_)->is_file ) {
53 1         109 $self->usage_error("The input file [$_] doesn't exist.");
54             }
55             }
56              
57 1 50       50 if ( !exists $opt->{outfile} ) {
58             $opt->{outfile}
59 0         0 = Path::Tiny::path( $args->[0] )->absolute . ".slice.fas";
60             }
61             }
62              
63             sub execute {
64 1     1 1 7 my ( $self, $opt, $args ) = @_;
65              
66 1         3 my $in_fh;
67 1 50       6 if ( lc $args->[0] eq "stdin" ) {
68 0         0 $in_fh = *STDIN{IO};
69             }
70             else {
71 1         11 $in_fh = IO::Zlib->new( $args->[0], "rb" );
72             }
73              
74 1         1893 my $out_fh;
75 1 50       5 if ( lc( $opt->{outfile} ) eq "stdout" ) {
76 1         3 $out_fh = *STDOUT{IO};
77             }
78             else {
79 0         0 open $out_fh, ">", $opt->{outfile};
80             }
81              
82 1         7 my $set_single
83             = App::RL::Common::runlist2set( YAML::Syck::LoadFile( $args->[1] ) );
84              
85             {
86 1         487 my $content = ''; # content of one block
  1         3  
87 1         2 while (1) {
88 8 100 100     104 last if $in_fh->eof and $content eq '';
89 7         495 my $line = '';
90 7 100       24 if ( !$in_fh->eof ) {
91 6         252 $line = $in_fh->getline;
92             }
93 7 100 66     1267 if ( ( $line eq '' or $line =~ /^\s+$/ ) and $content ne '' ) {
      66        
94 1         6 my $info_of = App::Fasops::Common::parse_block($content);
95 1         3 $content = '';
96              
97             # set $opt->{name} to the first one of the first block
98 1 50       5 if ( !defined $opt->{name} ) {
99 0         0 ( $opt->{name} ) = keys %{$info_of};
  0         0  
100             }
101              
102             # target name
103 1         3 my $name = $opt->{name};
104              
105             # basic target information
106 1         6 my $chr_name = $info_of->{$name}{chr};
107 1         14 my $chr_strand = $info_of->{$name}{strand};
108 1         12 my $chr_start = $info_of->{$name}{start};
109 1         11 my $chr_end = $info_of->{$name}{end};
110              
111             # chr present
112 1 50       13 next unless exists $set_single->{$chr_name};
113 1 50       4 next if $set_single->{$chr_name}->is_empty;
114              
115             # has intersect
116 1         19 my $i_chr_intspan;
117             {
118 1         2 my AlignDB::IntSpan $slice_set = $set_single->{$chr_name};
  1         2  
119 1         3 my $align_chr_set = AlignDB::IntSpan->new;
120 1         13 $align_chr_set->add_pair( $chr_start, $chr_end );
121 1         62 $i_chr_intspan = $slice_set->intersect($align_chr_set);
122             }
123 1 50       422 next if $i_chr_intspan->is_empty;
124              
125             # print YAML::Syck::Dump {
126             # name => $name,
127             # chr_name => $chr_name,
128             # chr_strand => $chr_strand,
129             # chr_start => $chr_start,
130             # chr_end => $chr_end,
131             # i_chr_intspan => $i_chr_intspan->runlist,
132             # };
133              
134             # target sequence intspan
135 1         20 my $target_seq_intspan = App::Fasops::Common::seq_intspan( $info_of->{$name}{seq} );
136              
137             # every sequence intspans
138 1         6 my %seq_intspan_of;
139 1         3 for my $key ( keys %{$info_of} ) {
  1         11  
140             $seq_intspan_of{$key}
141 3         54 = App::Fasops::Common::seq_intspan( $info_of->{$key}{seq} );
142             }
143              
144             # all indel regions
145 1         8 my $indel_intspan = AlignDB::IntSpan->new;
146 1         17 for my $key ( keys %{$info_of} ) {
  1         54  
147             $indel_intspan->add(
148 3         9626 App::Fasops::Common::indel_intspan( $info_of->{$key}{seq} ) );
149             }
150              
151             # there may be more than one subslice intersect this alignment
152 1         2594 my @sub_slices;
153 1         6 for my AlignDB::IntSpan $ss_chr_intspan ( $i_chr_intspan->sets ) {
154              
155             # chr positions to align positions
156 1         197 my $ss_start
157             = App::Fasops::Common::chr_to_align( $target_seq_intspan,
158             $ss_chr_intspan->min, $chr_start, $chr_strand );
159 1         7 my $ss_end
160             = App::Fasops::Common::chr_to_align( $target_seq_intspan,
161             $ss_chr_intspan->max, $chr_start, $chr_strand );
162 1 50       5 next if $ss_start >= $ss_end;
163              
164 1         4 my $ss_intspan = AlignDB::IntSpan->new;
165 1         14 $ss_intspan->add_pair( $ss_start, $ss_end );
166              
167             # borders of subslice inside a indel
168 1 50       67 if ( $indel_intspan->contains($ss_start) ) {
169 0         0 my $indel_island = $indel_intspan->find_islands($ss_start);
170 0         0 $ss_intspan->remove($indel_island);
171             }
172 1 50       65 if ( $indel_intspan->contains($ss_end) ) {
173 0         0 my $indel_island = $indel_intspan->find_islands($ss_end);
174 0         0 $ss_intspan->remove($indel_island);
175             }
176 1 50       54 next if $ss_intspan->size <= $opt->{length};
177 1         24 push @sub_slices, $ss_intspan;
178             }
179              
180             # write heasers and sequences
181 1         4 for my AlignDB::IntSpan $sub_slice (@sub_slices) {
182 1         4 my $ss_start = $sub_slice->min;
183 1         24 my $ss_end = $sub_slice->max;
184              
185 1         19 for my $key ( keys %{$info_of} ) {
  1         11  
186             my $key_start = App::Fasops::Common::align_to_chr(
187             $seq_intspan_of{$key}, $ss_start,
188             $info_of->{$key}{start},
189             $info_of->{$key}{strand}
190 3         115 );
191             my $key_end = App::Fasops::Common::align_to_chr(
192             $seq_intspan_of{$key}, $ss_end,
193             $info_of->{$key}{start},
194             $info_of->{$key}{strand}
195 3         15 );
196             my $ss_info = {
197             name => $info_of->{$key}{name},
198             chr => $info_of->{$key}{chr},
199             strand => $info_of->{$key}{strand},
200 3         15 start => $key_start,
201             end => $key_end,
202             };
203 3         91 printf {$out_fh} ">%s\n", App::RL::Common::encode_header($ss_info);
  3         25  
204 3         15 printf {$out_fh} "%s\n",
205 3         256 substr( $info_of->{$key}{seq}, $ss_start - 1, $ss_end - $ss_start + 1 );
206             }
207 1         31 print {$out_fh} "\n";
  1         5  
208             }
209              
210             }
211             else {
212 6         48 $content .= $line;
213             }
214             }
215             }
216 1         235 close $out_fh;
217 0           $in_fh->close;
218             }
219              
220             1;