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