File Coverage

blib/lib/BioX/Workflow.pm
Criterion Covered Total %
statement 194 238 81.5
branch 46 80 57.5
condition 2 6 33.3
subroutine 34 36 94.4
pod 14 19 73.6
total 290 379 76.5


line stmt bran cond sub pod time code
1             package BioX::Workflow;
2              
3 2     2   509301 use 5.008_005;
  2         6  
4             our $VERSION = '1.0.2';
5              
6 2     2   482 use Moose;
  2         309587  
  2         12  
7              
8 2     2   8791 use File::Path qw(make_path remove_tree);
  2         4  
  2         135  
9 2     2   8 use Cwd qw(abs_path getcwd);
  2         2  
  2         78  
10 2     2   631 use Data::Dumper;
  2         4995  
  2         89  
11 2     2   1437 use List::Compare;
  2         27835  
  2         65  
12 2     2   782 use YAML::XS 'LoadFile';
  2         3858  
  2         85  
13 2     2   774 use Config::Any;
  2         12142  
  2         65  
14              
15 2     2   12 use Data::Dumper;
  2         3  
  2         90  
16 2     2   23 use Class::Load ':all';
  2         2  
  2         247  
17 2     2   804 use IO::File;
  2         12430  
  2         179  
18 2     2   1156 use Interpolation E => 'eval';
  2         6291  
  2         10  
19 2     2   1162 use Text::Template qw(fill_in_file fill_in_string);
  2         4590  
  2         110  
20 2     2   948 use Data::Pairs;
  2         3009  
  2         75  
21 2     2   1049 use Storable qw(dclone);
  2         4686  
  2         113  
22 2     2   853 use MooseX::Types::Path::Tiny qw/Path Paths AbsPath/;
  2         264672  
  2         14  
23              
24             extends 'BioX::Wrapper';
25              
26             with 'BioX::Workflow::Samples';
27             with 'BioX::Workflow::Debug';
28             with 'BioX::Workflow::SpecialVars';
29             with 'BioX::Workflow::StructureOutput';
30             with 'BioX::Workflow::WriteMeta';
31             with 'BioX::Workflow::Rules';
32              
33             with 'MooseX::Getopt::Usage';
34             with 'MooseX::Getopt::Usage::Role::Man';
35             with 'MooseX::SimpleConfig';
36             with 'MooseX::Object::Pluggable';
37              
38 2     2   5199 use MooseX::FileAttribute;
  2         499715  
  2         9  
39              
40             # For pretty man pages!
41              
42             $ENV{TERM} = 'xterm-256color';
43              
44             =encoding utf-7
45              
46             =head1 NAME
47              
48             BioX::Workflow - A very opinionated template based workflow writer.
49              
50             =head1 SYNOPSIS
51              
52             Most of the functionality can be accessed through the biox-workflow.pl script.
53              
54             biox-workflow.pl --workflow /path/to/workflow.yml
55              
56             This module was written with Bioinformatics workflows in mind, but should be extensible to any sort of workflow or pipeline.
57              
58             =head1 Usage
59              
60             Please check out the full Usage Docs at L<BioX::Workflow::Usage>
61              
62             =head1 In Code Documenation
63              
64             You shouldn't really need to look here unless you have some reason to do some serious hacking.
65              
66             =head2 Attributes
67              
68             Moose attributes. Technically any of these can be changed, but may break everything.
69              
70             =head3 comment_char
71              
72             This should really be in BioX::Wrapper
73              
74             =cut
75              
76             has '+comment_char' => (
77             predicate => 'has_comment_char',
78             clearer => 'clear_comment_char',
79             );
80              
81             =head3 workflow
82              
83             Path to workflow workflow. This must be a YAML file.
84              
85             =cut
86              
87             has_file 'workflow' => (
88             is => 'rw',
89             required => 1,
90             must_exist => 1,
91             documentation => q{Your configuration workflow file.},
92             );
93              
94             =head3 rule_based
95              
96             This is the default. The outer loop are the rules, not the samples
97              
98             =cut
99              
100             has 'rule_based' => (
101             is => 'rw',
102             isa => 'Bool',
103             default => 1,
104             );
105              
106             =head3 sample_based
107              
108             Default Value. The outer loop is samples, not rules. Must be set in your global values or on the command line --sample_based 1
109              
110             If you ever have resample: 1 in your config you should NOT set this value to true!
111              
112             =cut
113              
114             has 'sample_based' => (
115             is => 'rw',
116             isa => 'Bool',
117             default => 0,
118             );
119              
120             =head2 stash
121              
122             This isn't ever used in the code. Its just there incase you want to persist objects across rules
123              
124             It uses Moose::Meta::Attribute::Native::Trait::Hash and supports all the methods.
125              
126             set_stash => 'set',
127             get_stash => 'get',
128             has_no_stash => 'is_empty',
129             num_stashs => 'count',
130             delete_stash => 'delete',
131             stash_pairs => 'kv',
132              
133             =cut
134              
135             has 'stash' => (
136             is => 'rw',
137             isa => 'HashRef',
138             traits => ['Hash'],
139             default => sub { {} },
140             handles => {
141             set_stash => 'set',
142             get_stash => 'get',
143             has_no_stash => 'is_empty',
144             num_stashs => 'count',
145             delete_stash => 'delete',
146             stash_pairs => 'kv',
147             },
148             );
149              
150             =head2 plugins
151              
152             Load plugins as an opt
153              
154             =cut
155              
156             has 'plugins' => (
157             is => 'rw',
158             isa => 'ArrayRef',
159             default => sub { [] },
160             );
161              
162             =head3 No GetOpt Here
163              
164             =cut
165              
166             has 'yaml' => (
167             traits => ['NoGetopt'],
168             is => 'rw',
169             );
170              
171             =head3 attr
172              
173             attributes read in from runtime
174              
175             =cut
176              
177             has 'attr' => (
178             traits => ['NoGetopt'],
179             is => 'rw',
180             isa => 'Data::Pairs',
181             );
182              
183             =head3 global_attr
184              
185             Attributes defined in the global section of the yaml file
186              
187             =cut
188              
189             has 'global_attr' => (
190             traits => ['NoGetopt'],
191             is => 'rw',
192             isa => 'Data::Pairs',
193             lazy => 1,
194             default => sub {
195             my $self = shift;
196              
197             my $n = Data::Pairs->new(
198             [ { resample => $self->resample },
199             { wait => $self->wait },
200             { auto_input => $self->auto_input },
201             { coerce_paths => $self->coerce_paths },
202             { auto_name => $self->auto_name },
203             { indir => $self->indir },
204             { outdir => $self->outdir },
205             { min => $self->min },
206             { override_process => $self->override_process },
207             { rule_based => $self->rule_based },
208             { verbose => $self->verbose },
209             { create_outdir => $self->create_outdir },
210             ]
211             );
212             return $n;
213             }
214             );
215              
216             =head3 local_attr
217              
218             Attributes defined in the rules->rulename->local section of the yaml file
219              
220             =cut
221              
222             has 'local_attr' => (
223             traits => ['NoGetopt'],
224             is => 'rw',
225             isa => 'Data::Pairs',
226             );
227              
228             =head3 local_rule
229              
230             =cut
231              
232             has 'local_rule' => (
233             traits => ['NoGetopt'],
234             is => 'rw',
235             isa => 'HashRef'
236             );
237              
238             =head3 process
239              
240             Our bash string
241              
242             bowtie2 -p 12 -I {$sample}.fastq -O {$sample}.bam
243              
244             =cut
245              
246             has 'process' => (
247             traits => ['NoGetopt'],
248             is => 'rw',
249             isa => 'Str',
250             );
251              
252             =head3 key
253              
254             Name of the rule
255              
256             =cut
257              
258             has 'key' => (
259             traits => ['NoGetopt'],
260             is => 'rw',
261             isa => 'Str',
262             );
263              
264             =head3 pkey
265              
266             Name of the previous rule
267              
268             =cut
269              
270             has 'pkey' => (
271             traits => ['NoGetopt'],
272             is => 'rw',
273             isa => 'Str|Undef',
274             predicate => 'has_pkey'
275             );
276              
277             =head2 Subroutines
278              
279             Subroutines can also be overriden and/or extended in the usual Moose fashion.
280              
281             =head3 run
282              
283             Starting point.
284              
285             =cut
286              
287             sub run {
288 0     0 1 0 my ($self) = shift;
289              
290 0         0 print "#!/bin/bash\n\n";
291              
292 0         0 $self->print_opts;
293              
294 0         0 $self->init_things;
295              
296 0         0 $self->write_workflow_meta('start');
297              
298 0         0 $self->write_pipeline;
299              
300 0         0 $self->write_workflow_meta('end');
301             }
302              
303             =head3 init_things
304              
305             Load the workflow, additional classes, and plugins
306              
307             Initialize the global_attr, make the global outdir, and find samples
308              
309             =cut
310              
311             sub init_things {
312 6     6 1 7796 my $self = shift;
313              
314 6         197 $self->key('global');
315              
316 6         19 $self->workflow_load;
317 6         29 $self->class_load;
318 6         21 $self->plugin_load;
319              
320             #Darn you data pairs and your shallow copies!
321 6         17 $self->init_global_attr;
322              
323 6         1899 $self->make_outdir;
324 6         586 $self->get_samples;
325              
326 6         33 $self->save_env;
327             }
328              
329             =head2 workflow_load
330              
331             use Config::Any to load configuration files - yaml, json, etc
332              
333             =cut
334              
335             sub workflow_load {
336 6     6 1 8 my $self = shift;
337              
338 6         54 my $cfg = Config::Any->load_files(
339             { files => [ $self->workflow ], use_ext => 1 } );
340              
341 6         24393 for (@$cfg) {
342 6         17 my ( $filename, $config ) = %$_;
343 6         203 $self->yaml($config);
344             }
345             }
346              
347             =head3 plugin_load
348              
349             Load plugins defined in yaml or on command line with --plugins with MooseX::Object::Pluggable
350              
351             =cut
352              
353             sub plugin_load {
354 6     6 1 12 my ($self) = shift;
355              
356 6         9 my $plugins = [];
357 6 50       123 if ( $self->yaml->{plugins} ) {
    50          
358 0         0 $plugins = $self->yaml->{plugins};
359             }
360             elsif ( $self->plugins ) {
361 6         119 $plugins = $self->plugins;
362             }
363             else {
364 0         0 return;
365             }
366              
367 6         18 foreach my $m (@$plugins) {
368 0         0 $self->load_plugin($m);
369             }
370             }
371              
372             =head3 class_load
373              
374             Load classes defined in yaml with Class::Load
375              
376             =cut
377              
378             sub class_load {
379 6     6 1 11 my ($self) = shift;
380              
381 6 50       134 return unless $self->yaml->{use};
382              
383 0         0 my $modules = $self->yaml->{use};
384              
385 0         0 foreach my $m (@$modules) {
386 0         0 load_class($m);
387             }
388             }
389              
390             =head3 make_template
391              
392             Make the template for interpolating strings
393              
394             =cut
395              
396             sub make_template {
397 698     698 1 612 my ( $self, $input ) = @_;
398              
399 698         2335 my $template = Text::Template->new(
400             TYPE => 'STRING',
401             SOURCE => "$E{$input}",
402             );
403              
404             #SOURCE => "$input",
405 698         47749 return $template;
406             }
407              
408             =head2 init_global_attr
409              
410             Add our global key from config file to the global_attr, and then to attr
411              
412             Deprecated: set_global_yaml
413              
414             =cut
415              
416             sub init_global_attr {
417 6     6 1 8 my $self = shift;
418              
419 6 50       116 return unless exists $self->yaml->{global};
420              
421 6         117 my $aref = $self->yaml->{global};
422 6         12 for my $a (@$aref) {
423 24         21 while ( my ( $key, $value ) = each( %{$a} ) ) {
  48         1108  
424 24         550 $self->global_attr->set( $key => $value );
425             }
426             }
427              
428 6         137 $self->attr( dclone( $self->global_attr ) );
429 6         21 $self->create_attr;
430             }
431              
432             =head3 create_attr
433              
434             Add attributes to $self-> namespace
435              
436             =cut
437              
438             sub create_attr {
439 15     15 1 32 my ($self) = shift;
440              
441 15         102 my $meta = __PACKAGE__->meta;
442              
443 15         376 $meta->make_mutable;
444              
445 15         12075 my %seen = ();
446              
447 15         83 for my $attr ( $meta->get_all_attributes ) {
448 758         2891 $seen{ $attr->name } = 1;
449             }
450              
451             # Data Pairs is so much prettier
452 15         459 my @keys = $self->attr->get_keys();
453              
454 15         515 foreach my $k (@keys) {
455 220         4498 my ($v) = $self->attr->get_values($k);
456              
457 220 100       9086 if ( !exists $seen{$k} ) {
458 3 100       16 if ( $k =~ m/_dir$/ ) {
459 1 50       27 if ( $self->coerce_paths ) {
460 1         7 $meta->add_attribute(
461             $k => (
462             is => 'rw',
463             isa => AbsPath,
464             coerce => 1,
465             predicate => "has_$k",
466             clearer => "clear_$k"
467             )
468             );
469             }
470             else {
471 0         0 $meta->add_attribute(
472             $k => (
473             is => 'rw',
474             isa => AbsPath,
475             coerce => 0,
476             predicate => "has_$k",
477             clearer => "clear_$k"
478             )
479             );
480             }
481             }
482             else {
483 2         13 $meta->add_attribute(
484             $k => (
485             is => 'rw',
486             predicate => "has_$k",
487             clearer => "clear_$k"
488             )
489             );
490             }
491             }
492 220 50       26265 $self->$k($v) if defined $v;
493             }
494              
495 15         257 $meta->make_immutable;
496             }
497              
498             =head3 eval_attr
499              
500             Evaluate the keys for variables using Text::Template
501             {$sample} -> SampleA
502             {$self->indir} -> data/raw (or the indir of the rule)
503              
504             If variables are themselves hashes/array refs, leave them alone
505              
506             =cut
507              
508             sub eval_attr {
509 54     54 1 50 my $self = shift;
510 54         52 my $sample = shift;
511              
512 54         1158 my @keys = $self->attr->get_keys();
513              
514 54         1533 foreach my $k (@keys) {
515 815 50       1297 next unless $k;
516              
517 815         17466 my ($v) = $self->attr->get_values($k);
518 815 100       36538 next unless $v;
519              
520             #If its an array or hash reference leave it alone
521 653 50 33     2363 if ( ref($v) eq 'ARRAY' || ref($v) eq 'HASH' ) {
522 0         0 $self->$k($v);
523 0         0 next;
524             }
525              
526             #Otherwise its a string
527 653         1004 my $template = $self->make_template($v);
528 653         523 my $text;
529 653 100       869 if ($sample) {
530 545         1549 $text = $template->fill_in(
531             HASH => { self => \$self, sample => $sample } );
532             }
533             else {
534 108         275 $text = $template->fill_in( HASH => { self => \$self } );
535             }
536              
537 653         123570 $self->$k($text);
538             }
539              
540 54 50       2257 $self->make_outdir if $self->create_outdir;
541             }
542              
543             =head2 clear_attr
544              
545             After each rule is processe clear the $self->attr
546              
547             =cut
548              
549             sub clear_attr {
550 0     0 1 0 my $self = shift;
551              
552 0         0 my @keys = $self->attr->get_keys();
553              
554 0         0 foreach my $k (@keys) {
555 0         0 my ($v) = $self->attr->get_values($k);
556 0 0       0 next unless $v;
557              
558 0         0 my $clear = "clear_$k";
559 0         0 $self->$clear;
560             }
561             }
562              
563             sub write_pipeline {
564 3     3 0 21 my ($self) = shift;
565              
566             #Min and Sample_Based Mode will break with --resample
567 3 50       85 if ( $self->min ) {
    50          
    50          
568 0         0 $self->write_min_files;
569 0         0 $self->process_rules;
570             }
571             elsif ( $self->sample_based ) {
572              
573             #Store the samples
574 0         0 my $sample_store = $self->samples;
575 0         0 foreach my $sample (@$sample_store) {
576 0         0 $self->samples( [$sample] );
577 0         0 $self->process_rules;
578             }
579             }
580             elsif ( $self->rule_based ) {
581 3         10 $self->process_rules;
582             }
583             else {
584 0         0 die print "Workflow must be rule based or sample based!\n";
585             }
586             }
587              
588             sub process_rules {
589 3     3 0 5 my $self = shift;
590              
591 3         5 my $process;
592 3         66 $process = $self->yaml->{rules};
593              
594 3 50       10 die print "Where are the rules?\n" unless $process;
595 3 50       11 die unless ref($process) eq 'ARRAY';
596              
597 3         4 foreach my $p ( @{$process} ) {
  3         7  
598 9 50       23 next unless $p;
599 9 50       227 if ( $self->number_rules ) {
600 0         0 my @keys = keys %{$p};
  0         0  
601 0         0 my $result = sprintf( "%04d", $self->counter_rules );
602 0         0 my $newkey = $keys[0];
603 0         0 $newkey = $result . '-' . $newkey;
604 0         0 $p->{$newkey} = dclone( $p->{ $keys[0] } );
605 0         0 delete $p->{ $keys[0] };
606             }
607 9         213 $self->local_rule($p);
608 9         22 $self->dothings;
609 9         300 $self->inc_counter_rules;
610             }
611             }
612              
613             sub dothings {
614 9     9 0 19 my ($self) = shift;
615              
616 9         27 $self->check_keys;
617              
618 9         29 $self->init_process_vars;
619              
620 9 50       517 return if $self->check_rules;
621              
622 9         215 $self->process( $self->local_rule->{ $self->key }->{process} );
623              
624 9         50 $self->write_rule_meta('before_meta');
625              
626 9         38 $self->write_process();
627              
628 9         44 $self->write_rule_meta('after_meta');
629              
630 9         27 $self->clear_process_attr;
631              
632 9 50       491 $self->indir( $self->outdir . "/" . $self->pkey ) if $self->auto_name;
633             }
634              
635             =head2 check_keys
636              
637             There should be one key and one key only!
638              
639             =cut
640              
641             sub check_keys {
642 9     9 1 11 my $self = shift;
643 9         10 my @keys = keys %{ $self->local_rule };
  9         214  
644              
645 9 50       31 if ( $#keys > 0 ) {
    50          
646 0         0 die print
647             "We have a problem! There should only be one key. Please see the documentation!\n";
648             }
649             elsif ( !@keys ) {
650 0         0 die print "There are no rules. Please see the documenation.\n";
651             }
652             else {
653 9         197 $self->key( $keys[0] );
654             }
655              
656 9 50       188 if ( !exists $self->local_rule->{ $self->key }->{process} ) {
657 0         0 die print "There is no process key! Dying...\n";
658             }
659             }
660              
661             =head2 clear_process_attr
662              
663             Clear the process attr
664              
665             Deprecated: clear_process_vars
666              
667             =cut
668              
669             sub clear_process_attr {
670 9     9 1 11 my $self = shift;
671              
672 9         248 $self->attr->clear;
673 9         299 $self->local_attr->clear;
674              
675 9         51 $self->add_attr('global_attr');
676              
677 9         358 $self->eval_attr;
678             }
679              
680             =head2 init_process_vars
681              
682             Initialize the process vars
683              
684             =cut
685              
686             sub init_process_vars {
687 9     9 1 12 my $self = shift;
688              
689 9 50       213 if ( $self->auto_name ) {
690 9         241 $self->outdir( $self->outdir . "/" . $self->key );
691 9 100       302 $self->make_outdir() unless $self->by_sample_outdir;
692             }
693              
694 9 50 33     798 if ( exists $self->local_rule->{ $self->key }->{override_process}
695             && $self->local_rule->{ $self->key }->{override_process} == 1 )
696             {
697 0         0 $self->override_process(1);
698             }
699             else {
700 9         217 $self->override_process(0);
701             }
702              
703 9         56 $self->local_attr( Data::Pairs->new( [] ) );
704 9 100       195 if ( exists $self->local_rule->{ $self->key }->{local} ) {
705             $self->local_attr(
706             Data::Pairs->new(
707             dclone( $self->local_rule->{ $self->key }->{local} )
708 2         43 )
709             );
710             }
711              
712             #Make sure these aren't reset to global
713             ##YAY FOR TESTS
714 9 50       187 $self->local_attr->set( 'outdir' => $self->outdir )
715             unless $self->local_attr->exists('outdir');
716 9 50       336 $self->local_attr->set( 'indir' => $self->indir )
717             unless $self->local_attr->exists('indir');
718              
719 9         164 $self->add_attr('local_attr');
720 9         365 $self->create_attr;
721 9 50       2903 $self->get_samples if $self->resample;
722              
723             #Why did I have this in write rule meta?
724 9 50       232 if ( $self->auto_input ) {
725 9 50       259 $self->local_attr->set( 'OUTPUT' => $self->OUTPUT )
726             if $self->has_OUTPUT;
727 9 50       221 $self->local_attr->set(
728             'INPUT' => $self->global_attr->get_values('INPUT') )
729             if $self->global_attr->exists('INPUT');
730             }
731             }
732              
733             =head2 add_attr
734              
735             Add the local attr onto the global attr
736              
737             =cut
738              
739             sub add_attr {
740 18     18 1 29 my $self = shift;
741 18         20 my $type = shift;
742              
743 18         437 my @keys = $self->$type->get_keys();
744              
745 18         404 foreach my $key (@keys) {
746 156 50       3918 next unless $key;
747              
748 156         3386 my ($v) = $self->$type->get_values($key);
749 156         9043 $self->attr->set( $key => $v );
750             }
751             }
752              
753             sub write_process {
754 9     9 0 14 my ($self) = @_;
755              
756 9         43 $self->save_env;
757              
758 9 50       215 if ( !$self->override_process ) {
759 9         10 foreach my $sample ( @{ $self->samples } ) {
  9         216  
760 45         1037 $self->sample($sample);
761 45 100       1049 $self->process_by_sample_outdir($sample)
762             if $self->by_sample_outdir;
763 45         1482 $self->eval_attr($sample);
764 45         1377 my $data = { self => \$self, sample => $sample };
765 45         99 $self->process_template($data);
766 45         147 $self->reset_special_vars;
767             }
768             }
769             else {
770 0         0 $self->eval_attr;
771 0         0 my $data = { self => \$self };
772 0         0 $self->process_template($data);
773             }
774              
775 9 50       209 print "\nwait\n" if $self->wait;
776              
777 9         45 $self->OUTPUT_to_INPUT;
778              
779 9         198 $self->pkey( $self->key );
780             }
781              
782             sub process_template {
783 45     45 0 50 my ( $self, $data ) = @_;
784              
785 45         1101 my $template = $self->make_template( $self->process );
786 45         118 $template->fill_in( HASH => $data, OUTPUT => \*STDOUT );
787              
788 45         4484 $DB::single = 2;
789 45         48 $DB::single = 2;
790              
791 45         470 print "\n\n";
792             }
793              
794             __PACKAGE__->meta->make_immutable;
795              
796             1;
797              
798             __END__
799              
800              
801             =head1 DESCRIPTION
802              
803             BioX::Workflow - A very opinionated template based workflow writer.
804              
805             =head1 AUTHOR
806              
807             Jillian Rowe E<lt>jillian.e.rowe@gmail.comE<gt>
808              
809             =head1 Acknowledgements
810              
811             Before version 0.03
812              
813             This module was originally developed at and for Weill Cornell Medical
814             College in Qatar within ITS Advanced Computing Team. With approval from
815             WCMC-Q, this information was generalized and put on github, for which
816             the authors would like to express their gratitude.
817              
818             As of version 0.03:
819              
820             This modules continuing development is supported
821             by NYU Abu Dhabi in the Center for Genomics and
822             Systems Biology. With approval from NYUAD, this
823             information was generalized and put on bitbucket,
824             for which the authors would like to express their
825             gratitude.
826              
827              
828             =head1 COPYRIGHT
829              
830             Copyright 2015- Weill Cornell Medical College in Qatar
831              
832             =head1 LICENSE
833              
834             This library is free software; you can redistribute it and/or modify
835             it under the same terms as Perl itself.
836              
837             =head1 SEE ALSO
838              
839             =cut