File Coverage

blib/lib/BioX/Workflow/Command/run/Rules/Rules.pm
Criterion Covered Total %
statement 7 9 77.7
branch n/a
condition n/a
subroutine 3 3 100.0
pod n/a
total 10 12 83.3


line stmt bran cond sub pod time code
1             package BioX::Workflow::Command::run::Rules::Rules;
2              
3 1     1   537 use MooseX::App::Role;
  1         3  
  1         9  
4 1     1   6766 use Storable qw(dclone);
  1         2  
  1         52  
5 1     1   131 use Data::Merger qw(merger);
  0            
  0            
6             use Data::Walk;
7             use Data::Dumper;
8             use File::Path qw(make_path remove_tree);
9             use Try::Tiny;
10             use Path::Tiny;
11              
12             with 'BioX::Workflow::Command::Utils::Files::TrackChanges';
13             use BioX::Workflow::Command::Utils::Traits qw(ArrayRefOfStrs);
14              
15             =head1 Name
16              
17             BioX::Workflow::Command::run::Utils::Rules
18              
19             =head2 Description
20              
21             Role for Rules
22              
23             =cut
24              
25             =head2 Command Line Options
26              
27             =cut
28              
29             option 'select_rules' => (
30             traits => ['Array'],
31             is => 'rw',
32             required => 0,
33             isa => ArrayRefOfStrs,
34             documentation => 'Select rules to process',
35             default => sub { [] },
36             cmd_split => qr/,/,
37             handles => {
38             all_select_rules => 'elements',
39             has_select_rules => 'count',
40             join_select_rules => 'join',
41             },
42             cmd_aliases => ['sr'],
43             );
44              
45             option 'select_after' => (
46             is => 'rw',
47             isa => 'Str',
48             required => 0,
49             predicate => 'has_select_after',
50             clearer => 'clear_select_after',
51             documentation => 'Select rules after and including a particular rule.',
52             cmd_aliases => ['sa'],
53             );
54              
55             option 'select_before' => (
56             is => 'rw',
57             isa => 'Str',
58             required => 0,
59             predicate => 'has_select_before',
60             clearer => 'clear_select_before',
61             documentation => 'Select rules before and including a particular rule.',
62             cmd_aliases => ['sb'],
63             );
64              
65             option 'select_between' => (
66             traits => ['Array'],
67             is => 'rw',
68             isa => ArrayRefOfStrs,
69             documentation => 'select rules to process',
70             cmd_split => qr/,/,
71             required => 0,
72             default => sub { [] },
73             documentation => 'Select sets of rules. Ex: rule1-rule2,rule4-rule5',
74             cmd_aliases => ['sbtwn'],
75             handles => {
76             all_select_between => 'elements',
77             has_select_between => 'count',
78             join_select_between => 'join',
79             },
80             );
81              
82             option 'omit_rules' => (
83             traits => ['Array'],
84             is => 'rw',
85             required => 0,
86             isa => ArrayRefOfStrs,
87             documentation => 'Omit rules to process',
88             default => sub { [] },
89             cmd_split => qr/,/,
90             handles => {
91             all_omit_rules => 'elements',
92             has_omit_rules => 'count',
93             join_omit_rules => 'join',
94             },
95             cmd_aliases => ['or'],
96             );
97              
98             option 'omit_after' => (
99             is => 'rw',
100             isa => 'Str',
101             required => 0,
102             predicate => 'has_omit_after',
103             clearer => 'clear_omit_after',
104             documentation => 'Omit rules after and including a particular rule.',
105             cmd_aliases => ['oa'],
106             );
107              
108             option 'omit_before' => (
109             is => 'rw',
110             isa => 'Str',
111             required => 0,
112             predicate => 'has_omit_before',
113             clearer => 'clear_omit_before',
114             documentation => 'Omit rules before and including a particular rule.',
115             cmd_aliases => ['ob'],
116             );
117              
118             option 'omit_between' => (
119             traits => ['Array'],
120             is => 'rw',
121             isa => ArrayRefOfStrs,
122             documentation => 'omit rules to process',
123             cmd_split => qr/,/,
124             required => 0,
125             default => sub { [] },
126             documentation => 'Omit sets of rules. Ex: rule1-rule2,rule4-rule5',
127             cmd_aliases => ['obtwn'],
128             handles => {
129             all_omit_between => 'elements',
130             has_omit_between => 'count',
131             join_omit_between => 'join',
132             },
133             );
134              
135             option 'select_match' => (
136             traits => ['Array'],
137             is => 'rw',
138             required => 0,
139             isa => ArrayRefOfStrs,
140             documentation => 'Match rules to select',
141             default => sub { [] },
142             cmd_split => qr/,/,
143             handles => {
144             all_select_match => 'elements',
145             has_select_match => 'count',
146             join_select_match => 'join',
147             },
148             cmd_aliases => ['sm'],
149             );
150              
151             option 'omit_match' => (
152             traits => ['Array'],
153             is => 'rw',
154             required => 0,
155             isa => ArrayRefOfStrs,
156             documentation => 'Match rules to omit',
157             default => sub { [] },
158             cmd_split => qr/,/,
159             handles => {
160             all_omit_match => 'elements',
161             has_omit_match => 'count',
162             join_omit_match => 'join',
163             },
164             cmd_aliases => ['om'],
165             );
166              
167             # TODO Change this to rules?
168              
169             has 'rule_keys' => (
170             is => 'rw',
171             isa => 'ArrayRef',
172             default => sub { return [] },
173             );
174              
175             has 'local_rule_keys' => (
176             traits => ['Array'],
177             is => 'rw',
178             isa => 'ArrayRef',
179             default => sub { return [] },
180             handles => {
181             all_local_rule_keys => 'elements',
182             has_local_rule_keys => 'count',
183             },
184             );
185              
186             has 'global_keys' => (
187             traits => ['Array'],
188             is => 'rw',
189             isa => 'ArrayRef',
190             default => sub { return [] },
191             handles => {
192             all_global_keys => 'elements',
193             has_global_keys => 'count',
194             first_index_global_keys => 'first_index',
195             },
196             );
197              
198             has [ 'select_effect', 'omit_effect' ] => (
199             is => 'rw',
200             isa => 'Bool',
201             default => 0,
202             );
203              
204             has 'dummy_sample' => (
205             is => 'rw',
206             isa => 'Str',
207             default => '__DUMMYSAMPLE123456789__'
208             );
209              
210             has 'dummy_iterable' => (
211             is => 'rw',
212             isa => 'Str',
213             default => '__DUMMYITER123456789__'
214             );
215              
216             #This should be in its own role
217             sub iterate_rules {
218             my $self = shift;
219              
220             $self->set_rule_names;
221             my $rules = $self->workflow_data->{rules};
222              
223             $self->filter_rule_keys;
224              
225             foreach my $rule (@$rules) {
226              
227             $self->local_rule($rule);
228             $self->process_rule;
229             $self->p_rule_name( $self->rule_name );
230             $self->p_local_attr( dclone( $self->local_attr ) );
231              
232             }
233              
234             $self->post_process_rules;
235              
236             $self->fh->close();
237             }
238              
239             =head3 filter_rule_keys
240              
241             First option is to use --use_timestamps
242             The user can also override the timestamps with --select_* --omit_*
243              
244             Use the --select_rules and --omit_rules options to choose rules.
245              
246             By default all rules are selected
247              
248             =cut
249              
250             sub filter_rule_keys {
251             my $self = shift;
252              
253             # if ( !$self->use_timestamps ) {
254             $self->select_rule_keys( dclone( $self->rule_names ) );
255              
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             # unless $self->use_timestamps;
265             # $self->app_log->info( 'Using timestamps ... ' . 'Rules to process TBA' )
266             # if $self->use_timestamps;
267             }
268              
269             =head3 set_rule_names
270              
271             Iterate over the rule names and add them to our array
272              
273             =cut
274              
275             sub set_rule_names {
276             my $self = shift;
277             my $rules = $self->workflow_data->{rules};
278              
279             my @rule_names = map { my ($key) = keys %{$_}; $key } @{$rules};
280             $self->rule_names( \@rule_names );
281             $self->app_log->info( 'Found rules:' . "\t" . join( ', ', @rule_names ) );
282             }
283              
284             #TODO This is confusing change names
285              
286             =head3 set_rule_keys
287              
288             If we have any select_* or select_match, get those rules before we start processing
289              
290             =cut
291              
292             sub set_rule_keys {
293             my $self = shift;
294             my $cond = shift || 'select';
295              
296             my @rules = ();
297             my $rule_exists = 1;
298             my @rule_name_exists = ();
299              
300             my $effect = $cond . '_effect';
301              
302             my ( $has_rules, $has_bf, $has_af, $has_btw, $has_match ) =
303             map { 'has_' . $cond . '_' . $_ }
304             ( 'rules', 'before', 'after', 'between', 'match' );
305              
306             my ( $bf, $af ) = ( $cond . '_before', $cond . '_after' );
307              
308             my ( $btw, $all_rules, $all_matches ) =
309             map { 'all_' . $cond . '_' . $_ } ( 'between', 'rules', 'match' );
310              
311             my ($rule_keys) = ( $cond . '_rule_keys' );
312              
313             if ( $self->$has_rules ) {
314             $self->$effect(1);
315             foreach my $r ( $self->$all_rules ) {
316             if ( $self->first_index_rule_names( sub { $_ eq $r } ) != -1 ) {
317             push( @rules, $r );
318             }
319             else {
320             $self->app_log->warn(
321             "You selected a rule $r that does not exist");
322             $rule_exists = 0;
323             push( @rule_name_exists, $r );
324             }
325             }
326             }
327             elsif ( $self->$has_bf ) {
328             $self->$effect(1);
329             my $index = $self->first_index_rule_names( sub { $_ eq $self->$bf } );
330             if ( $index == -1 ) {
331             $self->app_log->warn( "You "
332             . $cond
333             . "ed a rule "
334             . $self->$bf
335             . " that does not exist" );
336             $rule_exists = 0;
337             push( @rule_name_exists, $self->$bf );
338             }
339             for ( my $x = 0 ; $x <= $index ; $x++ ) {
340             push( @rules, $self->rule_names->[$x] );
341             }
342             }
343             elsif ( $self->$has_af ) {
344             $self->$effect(1);
345             my $index = $self->first_index_rule_names( sub { $_ eq $self->$af } );
346             if ( $index == -1 ) {
347             $self->app_log->warn( "You "
348             . $cond
349             . "ed a rule "
350             . $self->$af
351             . " that does not exist" );
352             $rule_exists = 0;
353             push( @rule_name_exists, $self->$af );
354             }
355             for ( my $x = $index ; $x < $self->has_rule_names ; $x++ ) {
356             push( @rules, $self->rule_names->[$x] );
357             }
358             }
359             elsif ( $self->$has_btw ) {
360             $self->$effect(1);
361             foreach my $rule ( $self->$btw ) {
362             my (@array) = split( '-', $rule );
363              
364             my $index1 =
365             $self->first_index_rule_names( sub { $_ eq $array[0] } );
366             my $index2 =
367             $self->first_index_rule_names( sub { $_ eq $array[1] } );
368              
369             if ( $index1 == -1 || $index2 == -1 ) {
370             $self->app_log->warn( "You "
371             . $cond
372             . "ed a set of rules "
373             . join( ',', $self->$btw )
374             . " that does not exist" );
375             $rule_exists = 0;
376             push( @rule_name_exists, $rule );
377             }
378              
379             for ( my $x = $index1 ; $x <= $index2 ; $x++ ) {
380             push( @rules, $self->rule_names->[$x] );
381             }
382             }
383             }
384             elsif ( $self->$has_match ) {
385             $self->$effect(1);
386             foreach my $match_rule ( $self->$all_matches ) {
387             my @t_rules = $self->grep_rule_names( sub { /$match_rule/ } );
388             map { push( @rules, $_ ) } @t_rules;
389             }
390             }
391              
392             $self->$rule_keys( \@rules ) if @rules;
393              
394             # return ( $rule_exists, @rule_name_exists );
395             }
396              
397             =head3 check_select
398              
399             See if the the current rule_name exists in either select_* or omit_*
400              
401             =cut
402              
403             sub check_select {
404             my $self = shift;
405             my $cond = shift || 'select';
406              
407             my $findex = 'first_index_' . $cond . '_rule_keys';
408             my $index = $self->$findex( sub { $_ eq $self->rule_name } );
409              
410             return 0 if $index == -1;
411             return 1;
412             }
413              
414             =head3 process_rule
415              
416             This function is just a placeholder for the other functions we need to process a rule
417              
418             1. Do a sanity check of the rule - it could be yaml/json friendly but not biox friendly
419             2. Clone the local attr
420             3. Check for carrying indir/outdir INPUT/OUTPUT
421             4. Apply the local attr - Add all the local: keys to our attr
422             5. Get the keys of the rule
423             6. Finally, process the template, or the process: key
424              
425             =cut
426              
427             sub process_rule {
428             my $self = shift;
429              
430             $self->sanity_check_rule;
431              
432             $self->local_attr( dclone( $self->global_attr ) );
433              
434             $self->carry_directives;
435              
436             $self->apply_local_attr;
437              
438             $self->get_keys;
439             $self->template_process;
440             }
441              
442             =head3 sanity_check_rule
443              
444             Check the rule to make sure it only has 1 key
445              
446             =cut
447              
448             #TODO make this into a type Instead
449              
450             sub sanity_check_rule {
451             my $self = shift;
452              
453             my @keys = keys %{ $self->local_rule };
454              
455             # $self->app_log->info("");
456             # $self->app_log->info("Beginning sanity check");
457             if ( $#keys != 0 ) {
458             $self->app_log->fatal(
459             'Sanity check fail: There should be one rule name!');
460             $self->sanity_check_fail;
461             return;
462             }
463              
464             $self->rule_name( $keys[0] );
465              
466             # $self->app_log->info( 'Sanity check on rule ' . $self->rule_name );
467              
468             if ( !exists $self->local_rule->{ $self->rule_name }->{process} ) {
469             $self->app_log->fatal(
470             'Sanity check fail: Rule does not have a process!');
471             $self->sanity_check_fail;
472             return;
473             }
474              
475             if ( !exists $self->local_rule->{ $self->rule_name }->{local} ) {
476             $self->local_rule->{ $self->rule_name }->{local} = [];
477             }
478             else {
479             my $ref = $self->local_rule->{ $self->rule_name }->{local};
480              
481             if ( !ref($ref) eq 'ARRAY' ) {
482             $self->app_log->fatal(
483             'Sanity check fail: Your variable declarations should begin with an array!'
484             );
485             $self->sanity_check_fail;
486             return;
487             }
488             }
489              
490             $self->app_log->info(
491             'Rule: ' . $self->rule_name . ' passes sanity check' );
492             }
493              
494             =head3 template_process
495              
496             Do the actual processing of the rule->process
497              
498             =cut
499              
500             sub template_process {
501             my $self = shift;
502             my $texts = [];
503              
504             #TODO we should not just spit this out as it compare_mtimes
505             #Instead save it as an object
506             #And process the object at the end to account for --auto_deps
507              
508             ##TODO Add back in override_process
509              
510             # $self->local_attr->{_modified} = 0;
511             $self->process_obj->{ $self->rule_name } = {};
512              
513             my $dummy_sample = $self->dummy_sample;
514             my $dummy_texts = $self->check_iterables( $dummy_sample, [] );
515              
516             if ( !$self->local_attr->override_process ) {
517              
518             foreach my $sample ( $self->all_samples ) {
519             foreach my $text ( @{$dummy_texts} ) {
520             my $new_text = $text;
521             $new_text =~ s/$dummy_sample/$sample/g;
522             push( @$texts, $new_text );
523             }
524             }
525             $self->process_obj->{ $self->rule_name }->{text} = $texts;
526             }
527             else {
528             $self->process_obj->{ $self->rule_name }->{text} = $dummy_texts;
529             }
530              
531             $self->process_obj->{ $self->rule_name }->{meta} =
532             $self->write_rule_meta('before_meta');
533             }
534              
535             =head3 use_iterables
536              
537             Check the global and local keys to see if we are using any iterables
538              
539             use_chroms: 1
540             use_chunks: 1
541              
542             =cut
543              
544             sub use_iterables {
545             my $self = shift;
546              
547             my $iter = '';
548             my $use_iter = 0;
549             my @use = ();
550             map {
551             if ( $_ =~ m/^use_/ ) { push( @use, $_ ) }
552             } @{ $self->rule_keys };
553             map {
554             if ( $_ =~ m/^use_/ ) { push( @use, $_ ) }
555             } @{ $self->local_rule_keys };
556              
557             my $use = pop(@use);
558              
559             return 0 if !$use;
560              
561             my $base = $use;
562             $base =~ s/use_//;
563              
564             my $no = 'no_' . $base;
565              
566             return 0 if $self->local_attr->$no;
567              
568             my $elem = $base;
569             $elem =~ s/s$//;
570             my $all = 'all_' . $elem . '_lists';
571              
572             return [ $all, $elem ];
573             }
574              
575             sub check_iterables {
576             my $self = shift;
577             my $sample = shift;
578             my $texts = shift;
579              
580             #First check the global for any lists
581             my $use_iters = $self->use_iterables;
582              
583             # $self->walk_indir_outdir($use_iters);
584              
585             if ( !$use_iters ) {
586             $texts = $self->in_template_process( $sample, $texts );
587             return $texts;
588             }
589              
590             my $all = $use_iters->[0];
591             my $elem = $use_iters->[1];
592              
593             ##TODO This should be a separate function
594             my $dummy_iter = $self->dummy_iterable;
595             $self->local_attr->$elem($dummy_iter);
596              
597             my $dummy_texts = $self->in_template_process( $sample, [] );
598              
599             foreach my $chunk ( $self->local_attr->$all ) {
600             foreach my $text ( @{$dummy_texts} ) {
601             my $new_text = $text;
602             $new_text =~ s/$dummy_iter/$chunk/g;
603             push( @$texts, $new_text );
604             }
605             }
606              
607             return $texts;
608             }
609              
610             sub in_template_process {
611             my $self = shift;
612             my $sample = shift;
613             my $texts = shift;
614              
615             $self->local_attr->sample($sample);
616             $self->sample($sample);
617             my $text = $self->eval_process();
618              
619             # my $log = $self->write_file_log();
620             # $text .= $log;
621             push( @{$texts}, $text ) if $self->print_within_rule;
622              
623             return $texts;
624             }
625              
626             sub walk_attr {
627             my $self = shift;
628              
629             my $attr = dclone( $self->local_attr );
630             $self->check_indir_outdir($attr);
631              
632             $attr->walk_process_data( $self->rule_keys );
633              
634             return $attr;
635             }
636              
637             sub eval_process {
638             my $self = shift;
639              
640             my $attr = $self->walk_attr;
641             $attr->sample( $self->sample ) if $self->has_sample;
642              
643             $self->walk_indir_outdir($attr);
644              
645             my $text = $self->eval_rule($attr);
646             $text = clean_text($text);
647              
648             $self->walk_FILES($attr);
649             $self->clear_files;
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;