File Coverage

blib/lib/App/Framework/Base.pm
Criterion Covered Total %
statement 127 138 92.0
branch 87 102 85.2
condition 10 12 83.3
subroutine 8 8 100.0
pod 3 3 100.0
total 235 263 89.3


line stmt bran cond sub pod time code
1             package App::Framework::Base ;
2              
3             =head1 NAME
4              
5             App::Framework::Base - Application feature
6              
7             =head1 SYNOPSIS
8              
9             use App::Framework::Base ;
10              
11              
12             =head1 DESCRIPTION
13              
14             Base object for all application objects (core/extensions/features etc)
15              
16             B
17              
18             =cut
19              
20 29     29   104 use strict ;
  29         30  
  29         631  
21 29     29   88 use Carp ;
  29         32  
  29         1596  
22              
23             our $VERSION = "1.100" ;
24              
25             #============================================================================================
26             # USES
27             #============================================================================================
28 29     29   12013 use App::Framework::Base::Object::ErrorHandle ;
  29         53  
  29         33060  
29              
30             #============================================================================================
31             # OBJECT HIERARCHY
32             #============================================================================================
33             our @ISA = qw(App::Framework::Base::Object::ErrorHandle) ;
34              
35             #============================================================================================
36             # GLOBALS
37             #============================================================================================
38              
39             our $PRIORITY_CORE = 10 ;
40             our $PRIORITY_SYSTEM = 100 ;
41             our $PRIORITY_USER = 1000 ;
42             our $PRIORITY_DEFAULT = 32767 ;
43              
44             our $class_debug = 0 ;
45              
46             =head2 FIELDS
47              
48             The following fields should be defined either in the call to 'new()', as part of a 'set()' call, or called by their accessor method
49             (which is the same name as the field):
50              
51              
52             =over 4
53              
54             =item B - list of required modules
55              
56             ARRAY ref of a list of module names that are required to be loaded by this object.
57              
58             =item B - list of which modules have been loaded
59              
60             HASH containing the modules loaded (used as key), with the value set to 1 if the module loaded ok; 0 otherwise
61              
62             =item B - all required modules are ok
63              
64             Flag that is set if all required modules loaded correctly
65              
66             =back
67              
68             =cut
69              
70             my %FIELDS = (
71             'priority' => $PRIORITY_DEFAULT,
72             'requires' => [],
73            
74             'loaded' => {}, # list of which modules have been loaded
75             'requires_ok' => 0, # all required modules are ok
76             );
77              
78             #============================================================================================
79              
80             =head2 CONSTRUCTOR
81              
82             =over 4
83              
84             =cut
85              
86             #============================================================================================
87              
88             =item B< new([%args]) >
89              
90             Create a new feature.
91              
92             The %args are specified as they would be in the B method.
93              
94             =cut
95              
96             sub new
97             {
98 157     157 1 758 my ($obj, %args) = @_ ;
99              
100 157   33     811 my $class = ref($obj) || $obj ;
101              
102 157 100       1513 print "App::Framework::Base->new() class=$class\n" if $class_debug ;
103              
104             # Create object
105 157         1047 my $this = $class->SUPER::new(%args) ;
106              
107             ## Check for any required modules
108 157         213 my $ok = 1 ;
109 157         220 my %loaded ;
110 157         173 foreach my $module (@{$this->requires})
  157         3113  
111             {
112 2     1   138 eval "package $class; use $module;" ;
  1     1   5  
  1         2  
  1         48  
  1         10  
  1         4  
  1         46  
113 2 50       3 if ($@)
114             {
115 0         0 $loaded{$module} = 0 ;
116 0         0 $ok = 0 ;
117             }
118             else
119             {
120 2         7 $loaded{$module} = 1 ;
121             }
122             }
123 157         3033 $this->requires_ok($ok) ;
124 157         2930 $this->loaded(\%loaded) ;
125              
126             ## First check that all required modules loaded correcly
127 157 50       2837 if (!$this->requires_ok)
128             {
129 0         0 my $loaded_href = $class->loaded ;
130 0         0 my $failed_modules = join ', ', grep {$loaded_href->{$_}} keys %$loaded_href ;
  0         0  
131 0         0 $this->throw_fatal("Failed to load: $failed_modules") ;
132             }
133              
134 157 100       932 print "App::Framework::Base->new() - END\n" if $class_debug ;
135              
136 157         515 return($this) ;
137             }
138              
139              
140              
141             #============================================================================================
142              
143             =back
144              
145             =head2 CLASS METHODS
146              
147             =over 4
148              
149             =cut
150              
151             #============================================================================================
152              
153             #-----------------------------------------------------------------------------
154              
155             =item B< init_class([%args]) >
156              
157             Initialises the object class variables.
158              
159             =cut
160              
161             sub init_class
162             {
163 157     157 1 256 my $class = shift ;
164 157         473 my (%args) = @_ ;
165              
166             # Add extra fields
167 157         470 $class->add_fields(\%FIELDS, \%args) ;
168              
169             # init class
170 157         786 $class->SUPER::init_class(%args) ;
171              
172             }
173              
174             #----------------------------------------------------------------------------
175              
176             =item B
177              
178             Processes all of the HASH values, replacing any variables with their contents. The variable
179             values are taken from the ARRAY ref I<$vars_aref>, which is an array of hashes. Each hash
180             containing variable name / variable value pairs.
181              
182             The HASH values being expanded can be either scalar, or an ARRAY ref. In the case of the ARRAY ref each
183             ARRAY entry must be a scalar (e.g. an array of file lines).
184              
185             =cut
186              
187             sub expand_keys
188             {
189 86     86 1 111 my $class = shift ;
190 86         116 my ($hash_ref, $vars_aref, $_state_href, $_to_expand) = @_ ;
191              
192 86 100       504 print "expand_keys($hash_ref, $vars_aref)\n" if $class_debug;
193 86 100       224 $class->prt_data("vars=", $vars_aref, "hash=", $hash_ref) if $class_debug ;
194              
195 86 100       378 my %to_expand = $_to_expand ? (%$_to_expand) : (%$hash_ref) ;
196 86 100       194 if (!$_state_href)
197             {
198             ## Top-level
199 78         84 my %data_ref ;
200            
201             # create state HASH
202 78         102 $_state_href = {} ;
203            
204             # scan through hash looking for variables
205 78         111 %to_expand = () ;
206 78         236 foreach my $key (keys %$hash_ref)
207             {
208 251         170 my @vals ;
209 251 100       510 if (ref($hash_ref->{$key}) eq 'ARRAY')
    50          
210             {
211 75         67 @vals = @{$hash_ref->{$key}} ;
  75         218  
212             }
213             elsif (!ref($hash_ref->{$key}))
214             {
215 176         194 push @vals, $hash_ref->{$key} ;
216             }
217            
218             ## Set up state - provide a level of indirection so that we can handle the case where multiple keys point to the same data
219 251   100     423 my $ref = $hash_ref->{$key} || '' ;
220 251 100 100     785 if ($ref && exists($data_ref{"$ref"}))
221             {
222 39 100       77 print " + already seen data for key=$key\n" if $class_debug>=2;
223             # already got created a state for this data, point to it
224 39         79 $_state_href->{$key} = $data_ref{"$ref"} ;
225             }
226             else
227             {
228 212 100       322 print " + new state key=$key\n" if $class_debug>=2;
229 212         196 my $state = 'expanded' ;
230 212         231 $_state_href->{$key} = \$state ;
231             }
232              
233             # save data reference
234 251 100       600 $data_ref{"$ref"} = $_state_href->{$key} if $ref ;
235            
236 251 100       362 print " + check for expansion...\n" if $class_debug>=2;
237 251         230 foreach my $val (@vals)
238             {
239 1420 100       1505 next unless $val ;
240              
241 1160 100       1293 print " + + val=$val\n" if $class_debug>=2;
242              
243 1160 100       1641 if (index($val, '$') >= 0)
244             {
245 35 100       61 print " + + + needs expanding\n" if $class_debug>=2;
246 35         45 $to_expand{$key}++ ;
247 35         26 ${$_state_href->{$key}} = 'to_expand' ;
  35         49  
248 35         56 last ;
249             }
250             }
251             }
252             }
253              
254 86 100       206 $class->prt_data("to expand=", \%to_expand) if $class_debug;
255              
256 86 100       165 $class->prt_data("Hash=", $hash_ref) if $class_debug;
257              
258             ## Expand them
259 86         623 foreach my $key (keys %to_expand)
260             {
261 43 100       79 print " # Key=$key State=${$_state_href->{$key}}\n" if $class_debug;
  20         48  
262            
263             # skip if not valid (if called recursively with a variable that is not in the hash)
264 43 50       107 next unless exists($hash_ref->{$key}) ;
265              
266             # Do replacement iff required
267 43 100       34 next if ${$_state_href->{$key}} eq 'expanded' ;
  43         113  
268              
269 28         30 my @vals ;
270 28 100       64 if (ref($hash_ref->{$key}) eq 'ARRAY')
    50          
271             {
272 7         7 foreach my $val (@{$hash_ref->{$key}})
  7         11  
273             {
274 76         62 push @vals, \$val ;
275             }
276             }
277             elsif (!ref($hash_ref->{$key}))
278             {
279 21         30 push @vals, \$hash_ref->{$key} ;
280             }
281            
282             # mark as expanding
283 28         28 ${$_state_href->{$key}} = 'expanding' ;
  28         38  
284              
285 28 100       68 $class->prt_data("Vals to expand=", \@vals) if $class_debug;
286              
287             #use re 'debugcolor' ;
288              
289 28         38 foreach my $val_ref (@vals)
290             {
291              
292 97 100       178 print " # Expand \"$$val_ref\" ...\n" if $class_debug;
293              
294 97         361 $$val_ref =~ s{
295             (?:
296             [\\\$]\$ # escaped dollar
297             \{{0,1} # optional brace
298             (\w+) # find a "word" and store it in $1
299             \}{0,1} # optional brace
300             )
301             |
302             (?:
303             \$ # find a literal dollar sign
304             \{{0,1} # optional brace
305             (\w+) # find a "word" and store it in $1
306             \}{0,1} # optional brace
307             )
308             }{
309 68         63 my $prefix = '' ;
310 68         102 my ($escaped, $var) = ($1, $2) ;
311            
312 68   100     185 $escaped ||= '' ;
313 68   100     124 $var ||= '' ;
314            
315 68 100       187 print " # esc=\"$escaped\", prefix=\"$prefix\", var=\"$var\"\n" if $class_debug;
316            
317 68         50 my $replace='' ;
318 68 100       83 if ($escaped)
319             {
320 16         12 $prefix = '$' ;
321 16         14 $replace = $escaped ;
322 16 50       39 print " ## escaped prefix=$prefix replace=$replace\n" if $class_debug;
323 16 50       33 print " ## DONE\n" if $class_debug;
324             }
325             else
326             {
327             ## use current HASH values before vars
328 52 100       101 if (defined $hash_ref->{$var})
329             {
330 30 100       78 print " ## var=$var current state=${$_state_href->{$var}}\n" if $class_debug;
  13         29  
331 30 100       23 if (${$_state_href->{$var}} eq 'to_expand')
  30         57  
332             {
333 8 100       23 print " ## var=$var call expand..\n" if $class_debug;
334             # go expand it first
335 8         87 $class->expand_keys($hash_ref, $vars_aref, $_state_href, {$var => 1}) ;
336             }
337 30 50       31 if (${$_state_href->{$var}} eq 'expanded')
  30         63  
338             {
339 30 100       68 print " ## var=$var already expanded\n" if $class_debug;
340 30         33 $replace = $hash_ref->{$var}; # expand variable
341 30 50       55 $replace = join("\n", @{$hash_ref->{$var}}) if (ref($hash_ref->{$var}) eq 'ARRAY') ;
  0         0  
342             }
343             }
344 52 100       104 print " ## var=$var can replace from hash=$replace\n" if $class_debug;
345            
346             ## If not found, use vars
347 52 100       70 if (!$replace)
348             {
349             ## use vars
350 22         22 foreach my $href (@$vars_aref)
351             {
352 46 100       67 if (defined $href->{$var})
353             {
354 22         21 $replace = $href->{$var}; # expand variable
355 22 50       33 $replace = join("\n", @{$hash_ref->{$var}}) if (ref($href->{$var}) eq 'ARRAY') ;
  0         0  
356 22 100       45 print " ## found var=$var replace=$replace\n" if $class_debug;
357 22         24 last ;
358             }
359             }
360             }
361 52 100       94 print " ## var=$var can replace now=$replace\n" if $class_debug;
362              
363 52 50       72 if (!$replace)
364             {
365 0         0 $replace = "" ;
366 0 0       0 print " ## no replacement\n" if $class_debug;
367 0 0       0 print " ## DONE\n" if $class_debug;
368             }
369             }
370            
371 68 100       140 print " ## ALL DONE $key: $escaped$var = \"$prefix$replace\"\n\n" if $class_debug;
372 68         201 "$prefix$replace" ;
373             }egxm; ## NOTE: /m is for multiline anchors; /s is for multiline dots
374             }
375              
376 28 100       68 $class->prt_data("Hash now=", $hash_ref) if $class_debug>=2;
377              
378             # mark as expanded
379 28         21 ${$_state_href->{$key}} = 'expanded' ;
  28         42  
380              
381 28 100       104 $class->prt_data("State now=", $_state_href) if $class_debug>=2;
382             }
383             }
384              
385              
386              
387             ##============================================================================================
388             #
389             #=back
390             #
391             #=head2 OBJECT DATA METHODS
392             #
393             #=over 4
394             #
395             #=cut
396             #
397             ##============================================================================================
398              
399              
400             ##============================================================================================
401             #
402             #=back
403             #
404             #=head2 OBJECT METHODS
405             #
406             #=over 4
407             #
408             #=cut
409             #
410             ##============================================================================================
411              
412              
413             #============================================================================================
414             #
415             # PRIVATE
416             #
417             #============================================================================================
418              
419             # ============================================================================================
420             # END OF PACKAGE
421              
422             =back
423              
424             =head1 DIAGNOSTICS
425              
426             Setting the debug flag to level 1 prints out (to STDOUT) some debug messages, setting it to level 2 prints out more verbose messages.
427              
428             =head1 AUTHOR
429              
430             Steve Price C<< >>
431              
432             =head1 BUGS
433              
434             None that I know of!
435              
436             =cut
437              
438              
439             1;
440              
441             __END__