File Coverage

blib/lib/App/hopen/Phases.pm
Criterion Covered Total %
statement 66 74 89.1
branch 13 24 54.1
condition 3 9 33.3
subroutine 18 21 85.7
pod 6 6 100.0
total 106 134 79.1


line stmt bran cond sub pod time code
1             # App::hopen::Phases - definitions of phases
2             package App::hopen::Phases;
3 2     2   17 use Data::Hopen;
  2         4  
  2         110  
4 2     2   12 use strict;
  2         3  
  2         39  
5 2     2   10 use Data::Hopen::Base;
  2         3  
  2         30  
6              
7             our $VERSION = '0.000011';
8              
9 2     2   2512 use parent 'Exporter';
  2         5  
  2         10  
10             our (@EXPORT, @EXPORT_OK, %EXPORT_TAGS);
11             BEGIN {
12 2     2   307 my @normal_export_ok = qw(is_phase is_last_phase phase_idx
13             curr_phase_idx next_phase);
14 2         5 my @hopenfile_export = qw(on);
15              
16 2         6 @EXPORT = qw(@PHASES);
17 2         13 @EXPORT_OK = (@normal_export_ok, @hopenfile_export);
18 2         61 %EXPORT_TAGS = (
19             default => [@EXPORT],
20             all => [@EXPORT, @normal_export_ok],
21             hopenfile => [@hopenfile_export], # Not included in :all!
22             );
23             }
24              
25 2     2   13 use App::hopen::BuildSystemGlobals;
  2         13  
  2         234  
26 2     2   19 use Getargs::Mixed;
  2         7  
  2         124  
27 2     2   1200 use List::MoreUtils qw(first_index);
  2         24731  
  2         14  
28              
29             # Docs {{{1
30              
31             =head1 NAME
32              
33             Data::Hopen::Phases - Definitions and routines for hopen phases
34              
35             =head1 SYNOPSIS
36              
37             Definition of hopen phases. Phase names are case-insensitive. The canonical
38             form has only the first letter capitalized.
39              
40             Phase names may only contain ASCII letters, digits, or underscore. The first
41             character of a phase may not be a digit. This is so they can be used as
42             identifiers if necessary.
43              
44             This package also defines a special export tag, C<:hopenfile>, for use when
45             running hopen files. The wrapper code in L<Data::Hopen::App> uses this
46             tag. Hopen files themselves do not need to use this tag.
47              
48             The names C<first>, C<start>, C<last>, and C<end> are reserved.
49              
50             =head1 VARIABLES
51              
52             =head2 @PHASES
53              
54             The phases we know about, in order.
55              
56             =head1 FUNCTIONS
57              
58             =cut
59              
60             # }}}1
61              
62             # Phases are case-insensitive.
63 2     2   3262 our @PHASES; BEGIN { @PHASES = ('Check', 'Gen'); }
64             # *** This is where the default phase ($PHASES[0]) is set ***
65             # TODO? be more sophisticated about this :)
66              
67             # Internal function to regularize phase names.
68             sub _clean {
69 30 50   30   86 my $test_phase = shift or croak 'Need a phase name';
70 30         67 $test_phase = lc $test_phase;
71 30 50 33     145 $test_phase = $PHASES[0]
72             if $test_phase eq 'first' or $test_phase eq 'start';
73 30 50 33     129 $test_phase = $PHASES[$#PHASES]
74             if $test_phase eq 'last' or $test_phase eq 'end';
75 30         135 return lc($test_phase);
76             } #_clean()
77              
78             =head2 is_phase
79              
80             Return truthy if the given argument is the name of a phase we know about.
81              
82             =cut
83              
84             sub is_phase {
85 0 0   0 1 0 my $test_phase = shift or croak 'Need a phase name';
86 0         0 $test_phase = _clean($test_phase);
87 0     0   0 my $curr_idx = first_index { lc($_) eq $test_phase } @PHASES;
  0         0  
88 0         0 return $curr_idx+1; # -1 => falsy; all others => truthy
89             } #is_phase()
90              
91             =head2 is_last_phase
92              
93             Return truthy if the argument is the name of the last phase.
94             If no argument is given, checks the current phase
95             (L<App::hopen::BuildSystemGlobals/$Phase>).
96              
97             =cut
98              
99 4   33 4 1 27 sub is_last_phase { _clean(shift // $Phase) eq lc($PHASES[$#PHASES]) }
100              
101             =head2 phase_idx
102              
103             Get the index of the phase given as a parameter.
104             Returns undef if none. Phases are case-insensitive.
105              
106             =cut
107              
108             sub phase_idx {
109 18 50   18 1 53 my $test_phase = shift or croak 'Need a phase name';
110 18         51 $test_phase = _clean($test_phase);
111 18     24   167 my $curr_idx = first_index { lc($_) eq $test_phase } @PHASES;
  24         71  
112 18 50       159 return $curr_idx<0 ? undef : $curr_idx;
113             } #phase_idx()
114              
115             =head2 curr_phase_idx
116              
117             Get the index of the current phase.
118              
119             =cut
120              
121 4     4 1 11 sub curr_phase_idx { phase_idx $Phase }
122              
123             =head2 next_phase
124              
125             Get the phase after the given on. Returns undef if the argument
126             is the last phase. Dies if the argument is not a phase.
127              
128             =cut
129              
130             sub next_phase {
131 4 50   4 1 20 my $test_phase = shift or croak 'Need a phase name';
132 4         13 $test_phase = _clean($test_phase);
133 4         15 my $curr_idx = phase_idx $test_phase;
134 4 50       15 die "$test_phase is not a phase I know about" unless defined($curr_idx);
135 4 100       31 return undef if $curr_idx == $#PHASES; # Last one
136              
137 2         61 return $PHASES[$curr_idx+1];
138             } #next_phase()
139              
140             =head1 ROUTINES FOR USE IN HOPEN FILES
141              
142             These are exported if the tag C<:hopenfile> is given on the C<use> line.
143              
144             =head2 on
145              
146             Take a given action only in a specified phase. Usage examples:
147              
148             on check => { foo => 42 }; # Just return the given hashref
149             on gen => 1337; # Returns { Gen => 1337 }
150             on check => sub { return { foo => 1337 } };
151             # Call the given sub and return its return value.
152              
153             This is designed for use within a hopen file.
154             See L<Data::Hopen::App/_run_phase> for the execution environment C<on()> is
155             designed to run in.
156              
157             When run as part of a hopen file, C<on()> will skip the rest of the file if it
158             runs. For example:
159              
160             say "Hello, world!"; # This always runs
161             on check => { answer => $answer }; # This runs during the Check phase
162             on gen => { done => true }; # This runs during the Gen phase
163             say "Phase was neither Check nor Gen"; # Doesn't run in Check or Gen
164              
165             TODO support C<< on '!last' => ... >> or similar to take action when not in
166             the given phase.
167              
168             =cut
169              
170             sub on {
171 4     4 1 15 my $caller = caller;
172 4         32 my (%args) = parameters([qw(phase value)], @_);
173              
174 4         367 my $which_phase = _clean($args{phase});
175 4         11 my $val = $args{value};
176              
177 4         11 my $which_idx = phase_idx($which_phase);
178 4 100       15 return if $which_idx != curr_phase_idx;
179              
180             # We are in the correct phase. Take appropriate action.
181             # However, don't change our own return value.
182 2         5 my $result;
183 2 50       24 if(ref $val eq 'CODE') {
    50          
184 0         0 $result = &$val;
185             } elsif(ref $val eq 'HASH') {
186 2         4 $result = $val; # TODO? clone?
187             } else {
188 0         0 $result = {$PHASES[$which_idx] => $val};
189             }
190              
191             # Stash the value for the caller.
192             {
193 2     2   18 no strict 'refs';
  2         5  
  2         219  
  2         6  
194 2         5 ${ $caller . "::__R_on_result" } = $result;
  2         11  
195             }
196              
197             # Done --- skip the rest of the hopen file if we're in one.
198 2     0   17 hlog { 'Done with script for phase ``' . $args{phase} . "''" } 3;
  0         0  
199 2         18 eval {
200 2     2   14 no warnings 'exiting';
  2         4  
  2         173  
201 2         12 last __R_DO;
202             };
203             } #on()
204              
205             1;
206             __END__
207             # vi: set fdm=marker: #