File Coverage

blib/lib/BioX/Workflow/Command/run/Rules/Rules.pm
Criterion Covered Total %
statement 10 12 83.3
branch n/a
condition n/a
subroutine 4 4 100.0
pod n/a
total 14 16 87.5


line stmt bran cond sub pod time code
1             package BioX::Workflow::Command::run::Rules::Rules;
2              
3 1     1   595 use MooseX::App::Role;
  1         3  
  1         8  
4 1     1   6446 use namespace::autoclean;
  1         2  
  1         67  
5              
6 1     1   73 use Storable qw(dclone);
  1         3  
  1         52  
7 1     1   89 use Data::Merger qw(merger);
  0            
  0            
8             use Data::Walk;
9             use Data::Dumper;
10             use File::Path qw(make_path remove_tree);
11             use Try::Tiny;
12             use Path::Tiny;
13              
14             use BioSAILs::Utils::Traits qw(ArrayRefOfStrs);
15              
16             =head1 Name
17              
18             BioX::Workflow::Command::run::Rules::Rules
19              
20             =head2 Description
21              
22             Role for Rules
23              
24             =cut
25              
26             =head2 Command Line Options
27              
28             =cut
29              
30             option 'select_rules' => (
31             traits => ['Array'],
32             is => 'rw',
33             required => 0,
34             isa => ArrayRefOfStrs,
35             documentation => 'Select rules to process',
36             default => sub { [] },
37             cmd_split => qr/,/,
38             handles => {
39             all_select_rules => 'elements',
40             has_select_rules => 'count',
41             join_select_rules => 'join',
42             },
43             cmd_aliases => ['sr'],
44             );
45              
46             option 'select_after' => (
47             is => 'rw',
48             isa => 'Str',
49             required => 0,
50             predicate => 'has_select_after',
51             clearer => 'clear_select_after',
52             documentation => 'Select rules after and including a particular rule.',
53             cmd_aliases => ['sa'],
54             );
55              
56             option 'select_before' => (
57             is => 'rw',
58             isa => 'Str',
59             required => 0,
60             predicate => 'has_select_before',
61             clearer => 'clear_select_before',
62             documentation => 'Select rules before and including a particular rule.',
63             cmd_aliases => ['sb'],
64             );
65              
66             option 'select_between' => (
67             traits => ['Array'],
68             is => 'rw',
69             isa => ArrayRefOfStrs,
70             documentation => 'select rules to process',
71             cmd_split => qr/,/,
72             required => 0,
73             default => sub { [] },
74             documentation => 'Select sets of rules. Ex: rule1-rule2,rule4-rule5',
75             cmd_aliases => ['sbtwn'],
76             handles => {
77             all_select_between => 'elements',
78             has_select_between => 'count',
79             join_select_between => 'join',
80             },
81             );
82              
83             option 'omit_rules' => (
84             traits => ['Array'],
85             is => 'rw',
86             required => 0,
87             isa => ArrayRefOfStrs,
88             documentation => 'Omit rules to process',
89             default => sub { [] },
90             cmd_split => qr/,/,
91             handles => {
92             all_omit_rules => 'elements',
93             has_omit_rules => 'count',
94             join_omit_rules => 'join',
95             },
96             cmd_aliases => ['or'],
97             );
98              
99             option 'omit_after' => (
100             is => 'rw',
101             isa => 'Str',
102             required => 0,
103             predicate => 'has_omit_after',
104             clearer => 'clear_omit_after',
105             documentation => 'Omit rules after and including a particular rule.',
106             cmd_aliases => ['oa'],
107             );
108              
109             option 'omit_before' => (
110             is => 'rw',
111             isa => 'Str',
112             required => 0,
113             predicate => 'has_omit_before',
114             clearer => 'clear_omit_before',
115             documentation => 'Omit rules before and including a particular rule.',
116             cmd_aliases => ['ob'],
117             );
118              
119             option 'omit_between' => (
120             traits => ['Array'],
121             is => 'rw',
122             isa => ArrayRefOfStrs,
123             documentation => 'omit rules to process',
124             cmd_split => qr/,/,
125             required => 0,
126             default => sub { [] },
127             documentation => 'Omit sets of rules. Ex: rule1-rule2,rule4-rule5',
128             cmd_aliases => ['obtwn'],
129             handles => {
130             all_omit_between => 'elements',
131             has_omit_between => 'count',
132             join_omit_between => 'join',
133             },
134             );
135              
136             option 'select_match' => (
137             traits => ['Array'],
138             is => 'rw',
139             required => 0,
140             isa => ArrayRefOfStrs,
141             documentation => 'Match rules to select',
142             default => sub { [] },
143             cmd_split => qr/,/,
144             handles => {
145             all_select_match => 'elements',
146             has_select_match => 'count',
147             join_select_match => 'join',
148             },
149             cmd_aliases => ['sm'],
150             );
151              
152             option 'omit_match' => (
153             traits => ['Array'],
154             is => 'rw',
155             required => 0,
156             isa => ArrayRefOfStrs,
157             documentation => 'Match rules to omit',
158             default => sub { [] },
159             cmd_split => qr/,/,
160             handles => {
161             all_omit_match => 'elements',
162             has_omit_match => 'count',
163             join_omit_match => 'join',
164             },
165             cmd_aliases => ['om'],
166             );
167              
168             # TODO Change this to rules?
169              
170             has 'rule_keys' => (
171             is => 'rw',
172             isa => 'ArrayRef',
173             default => sub { return [] },
174             );
175              
176             has 'local_rule_keys' => (
177             traits => ['Array'],
178             is => 'rw',
179             isa => 'ArrayRef',
180             default => sub { return [] },
181             handles => {
182             all_local_rule_keys => 'elements',
183             has_local_rule_keys => 'count',
184             },
185             );
186              
187             has 'global_keys' => (
188             traits => ['Array'],
189             is => 'rw',
190             isa => 'ArrayRef',
191             default => sub { return [] },
192             handles => {
193             all_global_keys => 'elements',
194             has_global_keys => 'count',
195             first_index_global_keys => 'first_index',
196             },
197             );
198              
199             has [ 'select_effect', 'omit_effect' ] => (
200             is => 'rw',
201             isa => 'Bool',
202             default => 0,
203             );
204              
205             has 'dummy_sample' => (
206             is => 'rw',
207             isa => 'Str',
208             default => '__DUMMYSAMPLE123456789__'
209             );
210              
211             has 'dummy_iterable' => (
212             is => 'rw',
213             isa => 'Str',
214             default => '__DUMMYITER123456789__'
215             );
216              
217             #This should be in its own role
218             sub iterate_rules {
219             my $self = shift;
220              
221             $self->set_rule_names;
222             my $rules = $self->workflow_data->{rules};
223              
224             $self->filter_rule_keys;
225              
226             foreach my $rule (@$rules) {
227              
228             $self->local_rule($rule);
229             $self->process_rule;
230             $self->p_rule_name( $self->rule_name );
231             $self->p_local_attr( dclone( $self->local_attr ) );
232              
233             }
234              
235             $self->post_process_rules;
236              
237             $self->fh->close();
238             }
239              
240             =head3 filter_rule_keys
241              
242             First option is to use --use_timestamps
243             The user can also override the timestamps with --select_* --omit_*
244              
245             Use the --select_rules and --omit_rules options to choose rules.
246              
247             By default all rules are selected
248              
249             =cut
250              
251             sub filter_rule_keys {
252             my $self = shift;
253              
254             $self->select_rule_keys( dclone( $self->rule_names ) );
255              
256             $self->set_rule_keys('select');
257             $self->set_rule_keys('omit');
258              
259             $self->app_log->info( 'Selected rules:' . "\t"
260             . join( ', ', @{ $self->select_rule_keys } )
261             . "\n" );
262             }
263              
264             =head3 set_rule_names
265              
266             Iterate over the rule names and add them to our array
267              
268             =cut
269              
270             sub set_rule_names {
271             my $self = shift;
272             my $rules = $self->workflow_data->{rules};
273              
274             my @rule_names = map { my ($key) = keys %{$_}; $key } @{$rules};
275             $self->rule_names( \@rule_names );
276             $self->app_log->info( 'Found rules:' . "\t" . join( ', ', @rule_names ) );
277             }
278              
279             #TODO This is confusing change names
280              
281             =head3 set_rule_keys
282              
283             If we have any select_* or select_match, get those rules before we start processing
284              
285             =cut
286              
287             sub set_rule_keys {
288             my $self = shift;
289             my $cond = shift || 'select';
290              
291             my @rules = ();
292             my $rule_exists = 1;
293             my @rule_name_exists = ();
294              
295             my $effect = $cond . '_effect';
296              
297             my ( $has_rules, $has_bf, $has_af, $has_btw, $has_match ) =
298             map { 'has_' . $cond . '_' . $_ }
299             ( 'rules', 'before', 'after', 'between', 'match' );
300              
301             my ( $bf, $af ) = ( $cond . '_before', $cond . '_after' );
302              
303             my ( $btw, $all_rules, $all_matches ) =
304             map { 'all_' . $cond . '_' . $_ } ( 'between', 'rules', 'match' );
305              
306             my ($rule_keys) = ( $cond . '_rule_keys' );
307              
308             if ( $self->$has_rules ) {
309             $self->$effect(1);
310             foreach my $r ( $self->$all_rules ) {
311             if ( $self->first_index_rule_names( sub { $_ eq $r } ) != -1 ) {
312             push( @rules, $r );
313             }
314             else {
315             $self->app_log->warn(
316             "You selected a rule $r that does not exist");
317             $rule_exists = 0;
318             push( @rule_name_exists, $r );
319             }
320             }
321             }
322             elsif ( $self->$has_bf ) {
323             $self->$effect(1);
324             my $index = $self->first_index_rule_names( sub { $_ eq $self->$bf } );
325             if ( $index == -1 ) {
326             $self->app_log->warn( "You "
327             . $cond
328             . "ed a rule "
329             . $self->$bf
330             . " that does not exist" );
331             $rule_exists = 0;
332             push( @rule_name_exists, $self->$bf );
333             }
334             for ( my $x = 0 ; $x <= $index ; $x++ ) {
335             push( @rules, $self->rule_names->[$x] );
336             }
337             }
338             elsif ( $self->$has_af ) {
339             $self->$effect(1);
340             my $index = $self->first_index_rule_names( sub { $_ eq $self->$af } );
341             if ( $index == -1 ) {
342             $self->app_log->warn( "You "
343             . $cond
344             . "ed a rule "
345             . $self->$af
346             . " that does not exist" );
347             $rule_exists = 0;
348             push( @rule_name_exists, $self->$af );
349             }
350             for ( my $x = $index ; $x < $self->has_rule_names ; $x++ ) {
351             push( @rules, $self->rule_names->[$x] );
352             }
353             }
354             elsif ( $self->$has_btw ) {
355             $self->$effect(1);
356             foreach my $rule ( $self->$btw ) {
357             my (@array) = split( '-', $rule );
358              
359             my $index1 =
360             $self->first_index_rule_names( sub { $_ eq $array[0] } );
361             my $index2 =
362             $self->first_index_rule_names( sub { $_ eq $array[1] } );
363              
364             if ( $index1 == -1 || $index2 == -1 ) {
365             $self->app_log->warn( "You "
366             . $cond
367             . "ed a set of rules "
368             . join( ',', $self->$btw )
369             . " that does not exist" );
370             $rule_exists = 0;
371             push( @rule_name_exists, $rule );
372             }
373              
374             for ( my $x = $index1 ; $x <= $index2 ; $x++ ) {
375             push( @rules, $self->rule_names->[$x] );
376             }
377             }
378             }
379             elsif ( $self->$has_match ) {
380             $self->$effect(1);
381             foreach my $match_rule ( $self->$all_matches ) {
382             my @t_rules = $self->grep_rule_names( sub { /$match_rule/ } );
383             map { push( @rules, $_ ) } @t_rules;
384             }
385             }
386              
387             $self->$rule_keys( \@rules ) if @rules;
388              
389             # return ( $rule_exists, @rule_name_exists );
390             }
391              
392             =head3 check_select
393              
394             See if the the current rule_name exists in either select_* or omit_*
395              
396             =cut
397              
398             sub check_select {
399             my $self = shift;
400             my $cond = shift || 'select';
401              
402             my $findex = 'first_index_' . $cond . '_rule_keys';
403             my $index = $self->$findex( sub { $_ eq $self->rule_name } );
404              
405             return 0 if $index == -1;
406             return 1;
407             }
408              
409             =head3 process_rule
410              
411             This function is just a placeholder for the other functions we need to process a rule
412              
413             1. Do a sanity check of the rule - it could be yaml/json friendly but not biox friendly
414             2. Clone the local attr
415             3. Check for carrying indir/outdir INPUT/OUTPUT
416             4. Apply the local attr - Add all the local: keys to our attr
417             5. Get the keys of the rule
418             6. Finally, process the template, or the process: key
419              
420             =cut
421              
422             sub process_rule {
423             my $self = shift;
424              
425             $self->sanity_check_rule;
426              
427             $self->local_attr( dclone( $self->global_attr ) );
428              
429             $self->carry_directives;
430              
431             $self->apply_local_attr;
432              
433             $self->get_keys;
434             $self->template_process;
435             }
436              
437             =head3 sanity_check_rule
438              
439             Check the rule to make sure it only has 1 key
440              
441             =cut
442              
443             #TODO make this into a type Instead
444              
445             sub sanity_check_rule {
446             my $self = shift;
447              
448             my @keys = keys %{ $self->local_rule };
449              
450             # $self->app_log->info("");
451             # $self->app_log->info("Beginning sanity check");
452             if ( $#keys != 0 ) {
453             $self->app_log->fatal(
454             'Sanity check fail: There should be one rule name!');
455             $self->sanity_check_fail;
456             return;
457             }
458              
459             $self->rule_name( $keys[0] );
460              
461             # $self->app_log->info( 'Sanity check on rule ' . $self->rule_name );
462              
463             if ( !exists $self->local_rule->{ $self->rule_name }->{process} ) {
464             $self->app_log->fatal(
465             'Sanity check fail: Rule does not have a process!');
466             $self->sanity_check_fail;
467             return;
468             }
469              
470             if ( !exists $self->local_rule->{ $self->rule_name }->{local} ) {
471             $self->local_rule->{ $self->rule_name }->{local} = [];
472             }
473             else {
474             my $ref = $self->local_rule->{ $self->rule_name }->{local};
475              
476             if ( !ref($ref) eq 'ARRAY' ) {
477             $self->app_log->fatal(
478             'Sanity check fail: Your variable declarations should begin with an array!'
479             );
480             $self->sanity_check_fail;
481             return;
482             }
483             }
484              
485             $self->app_log->info(
486             'Rule: ' . $self->rule_name . ' passes sanity check' );
487             }
488              
489             =head3 template_process
490              
491             Do the actual processing of the rule->process
492              
493             =cut
494              
495             sub template_process {
496             my $self = shift;
497             my $texts = [];
498              
499             #TODO we should not just spit this out as it compare_mtimes
500             #Instead save it as an object
501             #And process the object at the end to account for --auto_deps
502              
503             ##TODO Add back in override_process
504              
505             # $self->local_attr->{_modified} = 0;
506             $self->process_obj->{ $self->rule_name } = {};
507              
508             my $dummy_sample = $self->dummy_sample;
509             my $dummy_texts = $self->check_iterables( $dummy_sample, [] );
510              
511             if ( !$self->local_attr->override_process ) {
512              
513             foreach my $sample ( $self->all_samples ) {
514             foreach my $text ( @{$dummy_texts} ) {
515             my $new_text = $text;
516             $new_text =~ s/$dummy_sample/$sample/g;
517             push( @$texts, $new_text );
518             }
519             }
520             $self->process_obj->{ $self->rule_name }->{text} = $texts;
521             $self->process_obj->{ $self->rule_name }->{run_stats} = $self->local_attr->run_stats;
522             }
523             else {
524             $self->process_obj->{ $self->rule_name }->{text} = $dummy_texts;
525             }
526              
527             $self->process_obj->{ $self->rule_name }->{meta} =
528             $self->write_rule_meta('before_meta');
529             }
530              
531             =head3 use_iterables
532              
533             Check the global and local keys to see if we are using any iterables
534              
535             use_chroms: 1
536             use_chunks: 1
537              
538             =cut
539              
540             sub use_iterables {
541             my $self = shift;
542              
543             my $iter = '';
544             my $use_iter = 0;
545             my @use = ();
546             map {
547             if ( $_ =~ m/^use_/ ) { push( @use, $_ ) }
548             } @{ $self->rule_keys };
549             map {
550             if ( $_ =~ m/^use_/ ) { push( @use, $_ ) }
551             } @{ $self->local_rule_keys };
552              
553             my $use = pop(@use);
554              
555             return 0 if !$use;
556              
557             my $base = $use;
558             $base =~ s/use_//;
559              
560             my $no = 'no_' . $base;
561              
562             return 0 if $self->local_attr->$no;
563              
564             my $elem = $base;
565             $elem =~ s/s$//;
566             my $all = 'all_' . $elem . '_lists';
567              
568             return [ $all, $elem ];
569             }
570              
571             sub check_iterables {
572             my $self = shift;
573             my $sample = shift;
574             my $texts = shift;
575              
576             #First check the global for any lists
577             my $use_iters = $self->use_iterables;
578              
579             # $self->walk_indir_outdir($use_iters);
580              
581             if ( !$use_iters ) {
582             $texts = $self->in_template_process( $sample, $texts );
583             return $texts;
584             }
585              
586             my $all = $use_iters->[0];
587             my $elem = $use_iters->[1];
588              
589             ##TODO This should be a separate function
590             my $dummy_iter = $self->dummy_iterable;
591             $self->local_attr->$elem($dummy_iter);
592              
593             my $dummy_texts = $self->in_template_process( $sample, [] );
594              
595             foreach my $chunk ( $self->local_attr->$all ) {
596             foreach my $text ( @{$dummy_texts} ) {
597             my $new_text = $text;
598             $new_text =~ s/$dummy_iter/$chunk/g;
599             push( @$texts, $new_text );
600             }
601             }
602              
603             return $texts;
604             }
605              
606             sub in_template_process {
607             my $self = shift;
608             my $sample = shift;
609             my $texts = shift;
610              
611             $self->local_attr->sample($sample);
612             $self->sample($sample);
613             my $text = $self->eval_process();
614              
615             # my $log = $self->write_file_log();
616             # $text .= $log;
617             push( @{$texts}, $text ) if $self->print_within_rule;
618              
619             return $texts;
620             }
621              
622             sub walk_attr {
623             my $self = shift;
624              
625             my $attr = dclone( $self->local_attr );
626             $self->check_indir_outdir($attr);
627              
628             $attr->walk_process_data( $self->rule_keys );
629              
630             return $attr;
631             }
632              
633             sub eval_process {
634             my $self = shift;
635              
636             my $attr = $self->walk_attr;
637             $attr->sample( $self->sample ) if $self->has_sample;
638              
639             $self->walk_indir_outdir($attr);
640              
641             my $text = $self->eval_rule($attr);
642             $text = clean_text($text);
643              
644             $self->walk_FILES($attr);
645             $self->clear_files;
646              
647             ##Carry stash when not in template
648             $self->local_attr->stash(dclone($attr->stash));
649              
650             return $text;
651             }
652              
653             =head3 eval_rule
654              
655             Check to see if there is a custom method registered.
656              
657             Otherwise process the template as normal.
658              
659             =cut
660              
661             sub eval_rule {
662             my $self = shift;
663             my $attr = shift;
664              
665             my $process = $self->local_rule->{ $self->rule_name }->{process};
666             my $text;
667              
668             my $eval_rule = 'eval_rule_'.$self->rule_name;
669             if ( $attr->can( $eval_rule ) ) {
670             try {
671             $text = $attr->$eval_rule($process);
672             }
673             catch{
674             $self->app_log->warn('There was a problem evaluating rule. Error is:');
675             $self->app_log->warn($_);
676             };
677             }
678             else {
679             $text = $attr->interpol_directive($process);
680             }
681              
682             return $text;
683             }
684              
685             sub get_global_keys {
686             my $self = shift;
687             my @global_keys = ();
688              
689             map { my ($key) = keys %{$_}; push( @global_keys, $key ) }
690             @{ $self->workflow_data->{global} };
691              
692             $self->global_keys( \@global_keys );
693             }
694              
695             sub get_keys {
696             my $self = shift;
697              
698             my %seen = ();
699             my @local_keys = map { my ($key) = keys %{$_}; $seen{$key} = 1; $key }
700             @{ $self->local_rule->{ $self->rule_name }->{local} };
701              
702             my @global_keys = ();
703             map { my ($key) = keys %{$_}; push( @global_keys, $key ) if !$seen{$key} }
704             @{ $self->workflow_data->{global} };
705              
706             $self->local_rule_keys( dclone( \@local_keys ) );
707              
708             #This should be an object for extending
709             my @special_keys = ( 'indir', 'outdir', 'INPUT', 'OUTPUT' );
710             foreach my $key (@special_keys) {
711             if ( !$seen{$key} ) {
712             unshift( @local_keys, $key );
713             }
714             }
715              
716             map { push( @global_keys, $_ ) } @local_keys;
717              
718             $self->rule_keys( \@global_keys );
719             }
720              
721             ##TODO Write more tests
722             sub walk_indir_outdir {
723             my $self = shift;
724             my $attr = shift;
725              
726             my $text = $attr->interpol_directive( $attr->outdir );
727              
728             # $DB::single = 2;
729             $self->walk_indir_outdir_sample( $attr, $text );
730             }
731              
732             sub walk_indir_outdir_sample {
733             my $self = shift;
734             my $attr = shift;
735             my $text = shift;
736              
737             my $use_iters = $self->use_iterables;
738             my $dummy_sample = $self->dummy_sample;
739              
740             my @samples = @{ $attr->samples } if $attr->has_samples;
741              
742             foreach my $sample ( $attr->all_samples ) {
743             my $new_text = $text;
744             $new_text =~ s/$dummy_sample/$sample/g;
745              
746             if ($use_iters) {
747             $self->walk_indir_outdir_iters( $use_iters, $attr, $new_text );
748             }
749             else {
750             $new_text = path($new_text)->absolute if $attr->coerce_abs_dir;
751             $new_text = path($new_text) if !$attr->coerce_abs_dir;
752             $self->decide_create_outdir( $attr, $new_text );
753             }
754             }
755             }
756              
757             sub walk_indir_outdir_iters {
758             my $self = shift;
759             my $use_iters = shift;
760             my $attr = shift;
761             my $text = shift;
762              
763             return unless $use_iters;
764              
765             my $all = $use_iters->[0];
766             my $elem = $use_iters->[1];
767              
768             my $dummy_iter = $self->dummy_iterable;
769             $attr->$elem($dummy_iter);
770              
771             foreach my $chunk ( $self->local_attr->$all ) {
772             my $new_text = $text;
773             $new_text =~ s/$dummy_iter/$chunk/g;
774             $new_text = path($new_text)->absolute if $attr->coerce_abs_dir;
775             $new_text = path($new_text) if !$attr->coerce_abs_dir;
776             $self->decide_create_outdir( $attr, $new_text );
777             }
778             }
779              
780             sub decide_create_outdir {
781             my $self = shift;
782             my $attr = shift;
783             my $dir = shift;
784              
785             return unless $attr->create_outdir;
786             return unless $dir;
787              
788             try {
789             $dir->mkpath;
790             }
791             catch {
792             $self->app_log->fatal( "We were not able to make the directory.\n\t"
793             . $attr->outdir
794             . "\n\tError: $!" );
795             };
796             }
797              
798             sub clean_text {
799             my $text = shift;
800             my @text = split( "\n", $text );
801             my @new_text = ();
802              
803             foreach my $t (@text) {
804             $t =~ s/^\s+|\s+$//g;
805             if ( $t !~ /^\s*$/ ) {
806             push( @new_text, $t );
807             }
808             }
809              
810             $text = join( "\n", @new_text );
811             return $text;
812             }
813              
814             =head3 print_rule
815              
816             Decide if we print the rule
817              
818             There are 3 main decision trees
819              
820             1. User specifies --select_*
821             2. User specified --omit_*
822             3. User specified --use_timestamps
823              
824             select_* and omit_* take precedence over use_timestamps
825              
826             =cut
827              
828             sub print_rule {
829             my $self = shift;
830             my $print_rule = 1;
831              
832             my $select_index = $self->check_select('select');
833             my $omit_index = $self->check_select('omit');
834              
835             if ( !$select_index ) {
836             $self->app_log->info(
837             'Select rules in place. Skipping rule ' . $self->rule_name );
838             $print_rule = 0;
839             }
840              
841             if ($omit_index) {
842             $self->app_log->info(
843             'Omit rules in place. Skipping rule ' . $self->rule_name );
844             $print_rule = 0;
845             }
846              
847             $self->app_log->info( 'Processing rule ' . $self->rule_name . "\n" )
848             if $print_rule;
849              
850             return $print_rule;
851             }
852              
853             ##This is not necessary without the use_timestamps
854             ##But I will leave it in as a placeholder
855             sub print_within_rule {
856             my $self = shift;
857              
858             #TODO May not need this without use_timestamps
859             my $select_index = $self->check_select('select');
860              
861             return 1;
862             }
863              
864             =head3 check_indir_outdir
865              
866             If by_sample_outdir we pop the last dirname, append {$sample} to the base dir, and then add back on the popped value
867              
868             There are 2 cases we do not do this
869              
870             1. The indir of the first rule
871             2. If the user specifies indir/outdir in the local vars
872              
873             =cut
874              
875             sub check_indir_outdir {
876             my $self = shift;
877             my $attr = shift;
878              
879             # $DB::single = 2;
880             return unless $attr->by_sample_outdir;
881             return unless $self->has_sample;
882             return if $attr->override_process;
883              
884             # If indir/outdir is specified in the local config
885             # then we don't evaluate it
886             my %keys = ();
887             map { $keys{$_} = 1 } @{ $self->local_rule_keys };
888              
889             foreach my $dir ( ( 'indir', 'outdir' ) ) {
890             if ( exists $keys{$dir} ) {
891             next;
892             }
893              
894             if ( $dir eq 'indir' && !$self->has_p_rule_name ) {
895             my $new_dir = File::Spec->catdir( $attr->$dir, '{$sample}' );
896             $attr->$dir($new_dir);
897             next;
898             }
899              
900             my @dirs = File::Spec->splitdir( $attr->$dir );
901             my $last = '';
902             if ($#dirs) {
903             $last = pop(@dirs);
904             }
905              
906             my $base_dir = File::Spec->catdir(@dirs);
907             my $new_dir = File::Spec->catdir( $base_dir, '{$sample}', $last );
908             $attr->$dir($new_dir);
909             }
910              
911             }
912              
913             =head3 carry_directives
914              
915             At the beginning of each rule the previous outdir should be the new indir, and the previous OUTPUT should be the new INPUT
916              
917             Stash should be carried over
918              
919             Outdir should be global_attr->outdir/rule_name
920              
921             =cut
922              
923             sub carry_directives {
924             my $self = shift;
925              
926             # $DB::single = 2;
927             $self->local_attr->outdir(
928             $self->global_attr->outdir . '/' . $self->rule_name );
929              
930             return unless $self->has_p_rule_name;
931              
932             $self->local_attr->indir( dclone( $self->p_local_attr->outdir ) );
933              
934             if ( $self->p_local_attr->has_OUTPUT ) {
935             if ( ref( $self->p_local_attr->OUTPUT ) ) {
936             $self->local_attr->INPUT( dclone( $self->p_local_attr->OUTPUT ) );
937             }
938             else {
939             $self->local_attr->INPUT( $self->p_local_attr->OUTPUT );
940             }
941             }
942              
943             $self->local_attr->stash( dclone( $self->p_local_attr->stash ) );
944             }
945              
946             sub sanity_check_fail {
947             my $self = shift;
948              
949             my $rule_example = <<EOF;
950             global:
951             - indir: data/raw
952             - outdir: data/processed
953             - sample_rule: (sample.*)$
954             - by_sample_outdir: 1
955             - find_sample_bydir: 1
956             - copy1:
957             local:
958             - indir: '{\$self->my_dir}'
959             - INPUT: '{\$self->indir}/{\$sample}.csv'
960             - HPC:
961             - mem: '40GB'
962             - walltime: '40GB'
963             process: |
964             echo 'MyDir on {\$self->my_dir}'
965             echo 'Indir on {\$self->indir}'
966             echo 'Outdir on {\$self->outdir}'
967             echo 'INPUT on {\$self->INPUT}'
968             EOF
969             $self->app_log->fatal('Skipping this rule.');
970             $self->app_log->fatal(
971             'Here is an example workflow. For more information please see biox-workflow.pl new --help.'
972             );
973             $self->app_log->fatal($rule_example);
974             }
975              
976             1;