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