File Coverage

blib/lib/Test/Smoke/App/Base.pm
Criterion Covered Total %
statement 194 195 99.4
branch 38 38 100.0
condition 2 2 100.0
subroutine 24 24 100.0
pod 7 7 100.0
total 265 266 99.6


line stmt bran cond sub pod time code
1             package Test::Smoke::App::Base;
2 8     8   2030 use warnings;
  8         15  
  8         212  
3 8     8   36 use strict;
  8         12  
  8         150  
4 8     8   47 use Carp qw/ confess /;
  8         12  
  8         390  
5              
6             our $VERSION = '0.002';
7              
8 8     8   39 use base 'Test::Smoke::ObjectBase';
  8         12  
  8         2277  
9              
10 8     8   45 use Cwd 'abs_path';
  8         13  
  8         281  
11 8     8   4749 use Getopt::Long qw/:config pass_through/;
  8         77454  
  8         39  
12 8     8   3512 use Test::Smoke::App::AppOption;
  8         16  
  8         188  
13 8     8   2747 use Test::Smoke::App::AppOptionCollection;
  8         17  
  8         186  
14 8     8   3025 use Test::Smoke::LogMixin;
  8         19  
  8         322  
15 8     8   2978 use Test::Smoke::Util::Serialise qw/serialise/;
  8         19  
  8         13793  
16              
17             =head1 NAME
18              
19             Test::Smoke::App::Base - Baseclass for Test::Smoke::App::* applications.
20              
21             =head1 SYNOPSIS
22              
23             package Test::Smoke::App::Mailer;
24             use base 'Test::Smoke::App::Base';
25             sub run {...}
26              
27             =head1 DESCRIPTION
28              
29             use Test::Smoke::App::Mailer;
30             my $mailer = Test::Smoke::App::Mailer->new(
31             main_options => [
32             Test::Smoke::App::AppOption->new(
33             name => 'mailer',
34             option => '=s',
35             allow => [qw/MIME::lite sendmail/],
36             helptext => "Mailsystem to use for sendig reports.",
37             ),
38             ],
39             genral_options => [
40             Test::Smoke::AppOption->new(
41             name => 'ddir',
42             option => '=s',
43             helptxt => "Smoke Destination Directory.",
44             ),
45              
46             ],
47             special_options => {
48             'MIME::Lite' => [
49             mserver(),
50             msport(),
51             msuser(),
52             mspass(),
53             ],
54             'sendmail' => [],
55             },
56             );
57              
58             $mailer->run();
59              
60             =head2 Test::Smoke::App->new(%arguments)
61              
62             =head3 Arguments
63              
64             Named:
65              
66             =over
67              
68             =item main_options => $list_of_test_smoke_appoptions
69              
70             =item general_options => $list_of_test_smoke_appoptions
71              
72             These options are always valid.
73              
74             =item special_options => $hashref
75              
76             This is a hashref with the values of the C-array, that hold a list of
77             L.
78              
79             =back
80              
81             =head3 Exceptions
82              
83             None.
84              
85             =cut
86              
87             sub new {
88 26     26 1 816 my $class = shift;
89 26         105 my %args = @_;
90              
91 26         175 my $struct = {
92             _main_options => [],
93             _general_options => [],
94             _special_options => {},
95             _final_options => {},
96             };
97              
98 26         132 for my $known (keys %$struct) {
99 104         341 (my $key = $known) =~ s/^_//;
100 104 100       287 $struct->{$known} = delete $args{$key} if exists $args{$key};
101             }
102              
103 26         88 my $self = bless $struct, $class;
104              
105 26         207 $self->process_options();
106 26         105 return $self;
107             }
108              
109             =head2 Test::Smoke::App::Base->configfile_option()
110              
111             Returns a L for 'configfile'.
112              
113             =cut
114              
115             sub configfile_option {
116 26     26 1 60 my $class = shift;
117 26         112 return Test::Smoke::App::AppOption->new(
118             name => 'configfile',
119             option => 'config|c=s',
120             helptext => "Set the name/prefix of the configfile\n",
121             );
122             }
123              
124             =head2 Test::Smoke::App::Base->verbose_option()
125              
126             Returns a L for 'verbose'.
127              
128             =cut
129              
130             sub verbose_option {
131 26     26 1 58 my $class = shift;
132 26         102 return Test::Smoke::App::AppOption->new(
133             name => 'verbose',
134             option => 'v=i',
135             allow => [0, 1, 2],
136             default => 0,
137             helptext => 'Set verbosity level.',
138             );
139             }
140              
141             =head2 $app->show_config_option
142              
143             =cut
144              
145             sub show_config_option {
146 26     26 1 105 return Test::Smoke::App::AppOption->new(
147             name => 'show_config',
148             option => 'show-config',
149             helptext => "Show all about config vars.",
150             );
151             }
152              
153             =head2 $app->process_options()
154              
155             This process constists of three (3) steps:
156              
157             =over
158              
159             =item 1. pre_process_options
160              
161             This step organizes the options in a AppOptionCollection.
162              
163             =item 2. get_options
164              
165             This step processes the arguments passed on the command line.
166              
167             =item 3. post_process_options
168              
169             This step integrates the arguments, their coded-defaults, config-file values
170             and command-line overrides.
171              
172             =back
173              
174             =head3 Arguments
175              
176             None.
177              
178             =head3 Returns
179              
180             The object-instance.
181              
182             =head3 Exceptions
183              
184             None.
185              
186             =cut
187              
188             sub process_options {
189 26     26 1 96 my $self = shift;
190              
191 26         276 $self->{_opt_collection} = Test::Smoke::App::AppOptionCollection->new();
192              
193 26         186 $self->_pre_process_options();
194 26         205 $self->_get_options();
195 26         43053 $self->_post_process_options();
196              
197 26         59 return $self;
198             }
199              
200             =head2 $app->option($option)
201              
202             Return the value of an option.
203              
204             =head3 Arguments
205              
206             Positional.
207              
208             =over
209              
210             =item $option_name
211              
212             =back
213              
214             =head3 Returns
215              
216             The value of that option if applicable.
217              
218             =head3 Exceptions
219              
220             =over
221              
222             =item B $type)>
223              
224             =item B
225              
226             =back
227              
228             =cut
229              
230             sub option {
231 818     818 1 3273 my $self = shift;
232 818         1267 my ($option) = @_;
233              
234 818         2359 my $opts = $self->final_options;
235 818 100       1630 if (exists $opts->{$option}) {
236 817         903 my $is_main = grep $_->name eq $option, @{$self->main_options};
  817         2086  
237 817 100       1384 return $opts->{$option} if $is_main;
238              
239 778         836 my $is_general = grep $_->name eq $option, @{$self->general_options};
  778         2058  
240 778 100       3698 return $opts->{$option} if $is_general;
241              
242 60         70 for my $mainopt (@{$self->main_options}) {
  60         162  
243 86         220 my $type = $opts->{$mainopt->name};
244 86         227 my $specials = $self->special_options->{$type};
245 86         297 my $is_special = grep $_->name eq $option, @$specials;
246 86 100       329 return $opts->{$option} if $is_special;
247             }
248              
249 1         78 confess("Option '$option' is not valid.");
250             }
251 1         152 confess("Invalid option '$option'");
252             }
253              
254             sub _find_option {
255 453     453   664 my $self = shift;
256 453         650 my ($option) = @_;
257              
258 453         501 my ($oo) = grep $_->name eq $option, @{$self->main_options};
  453         1190  
259 453 100       738 return $oo if $oo;
260              
261 432         492 ($oo) = grep $_->name eq $option, @{$self->general_options};
  432         1103  
262 432 100       884 return $oo if $oo;
263              
264 38         62 for my $mo (@{$self->main_options}) {
  38         107  
265 62         190 my $type = $self->final_options->{$mo->name};
266 62         216 my $specials = $self->special_options->{$type};
267 62         204 ($oo) = grep $_->name eq $option, @$specials;
268 62 100       157 return $oo if $oo;
269             }
270              
271 0         0 return;
272             }
273              
274             =head2 $app->options()
275              
276             =head3 Arguments
277              
278             None.
279              
280             =head3 Returns
281              
282             A hash (list) of all options that apply to this instance of the app.
283              
284             =head3 Exceptions
285              
286             None.
287              
288             =cut
289              
290             sub options {
291 40     40 1 582 my $self = shift;
292              
293 40         57 my %options;
294 40         64 for my $opt (@{$self->main_options}) {
  40         137  
295 31         101 my $type = $self->option($opt->name);
296 31         104 $options{$opt->name} = $type;
297 31         103 my $specials = $self->special_options->{$type};
298 31         65 for my $opt (@$specials) {
299 62         166 $options{$opt->name} = $self->option($opt->name);
300             }
301             }
302             # collect all general options
303 40         72 for my $opt (@{ $self->general_options }) {
  40         135  
304 631 100       1654 next if $opt->name =~ /^(?:help|show_config)$/;
305 551         1507 $options{$opt->name} = $self->option($opt->name);
306             }
307              
308 40         453 return %options;
309             }
310              
311             sub _pre_process_options {
312 26     26   61 my $self = shift;
313              
314 26         53 unshift @{$self->general_options}, $self->configfile_option;
  26         236  
315 26         54 push @{$self->general_options}, $self->show_config_option;
  26         121  
316 26         49 push @{$self->general_options}, $self->verbose_option;
  26         120  
317 26         47 for my $opt (@{$self->general_options}) {
  26         103  
318 422         1363 $self->opt_collection->add($opt);
319             }
320              
321 26         53 for my $opt (sort {$a->name cmp $b->name} @{$self->main_options}) {
  9         41  
  26         186  
322 21         93 $self->opt_collection->add_helptext("\n");
323 21         70 $self->opt_collection->add($opt);
324 21         42 for my $special (sort {lc($a) cmp lc($b)} @{$opt->allow}) {
  68         134  
  21         81  
325 68         246 $self->opt_collection->add_helptext(
326             sprintf("\nOptions for '%s':\n", $special)
327             );
328 68         259 my $specials = $self->special_options->{$special};
329 68         157 for my $thisopt (@$specials) {
330 164         507 $self->opt_collection->add($thisopt);
331             }
332             }
333             }
334              
335 26         175 my $helptext = $self->opt_collection->helptext;
336             my $help_option = Test::Smoke::App::AppOption->new(
337             name => 'help',
338             option => 'h',
339             default => sub {
340 1     1   443 print "Usage: $0 [options]\n\n$helptext";
341 1         3 exit(0);
342             },
343 26         297 helptext => 'This message.',
344             );
345 26         52 push @{$self->general_options}, $help_option;
  26         133  
346 26         95 $self->opt_collection->add($help_option);
347              
348 26         51 %{$self->{_dft_options}} = %{$self->opt_collection->options_with_default};
  26         214  
  26         106  
349             }
350              
351             sub _get_options {
352 26     26   49 my $self = shift;
353              
354 26         47 %{$self->{_cli_options}} = %{$self->opt_collection->options_for_cli};
  26         146  
  26         115  
355              
356 26         141 @{$self->{_ARGV}} = @{$self->{_ARGV_EXTRA}} = @ARGV;
  26         100  
  26         156  
357              
358 26         290 my $parser = Getopt::Long::Parser->new(config => [qw/no_ignore_case passthrough/]);
359             $parser->getoptionsfromarray(
360             $self->{_ARGV_EXTRA},
361             $self->cli_options,
362 26         2752 @{ $self->opt_collection->options_list },
  26         106  
363             );
364             }
365              
366             sub _post_process_options {
367 26     26   55 my $self = shift;
368              
369 26         135 $self->_obtain_config_file;
370 26         41 %{$self->final_options} = %{$self->cli_options};
  26         142  
  26         93  
371              
372             # now combine with configfile
373             $self->final_options(
374             {
375 26         94 %{$self->opt_collection->all_options},
376 26         183 %{$self->dft_options},
377 26         159 %{$self->from_configfile},
378 26         58 %{$self->cli_options},
  26         89  
379             }
380             );
381 26         125 my @errors;
382 26         131 my %check_options = $self->options;
383 26         155 for my $opt (keys %check_options) {
384 453         950 my $oo = $self->_find_option($opt);
385 453         1244 my $value = $self->final_options->{$opt};
386 453 100       833 $value = '' if !defined $value;
387             push(
388             @errors,
389             sprintf(
390             "Invalid value '%s' for option '%s'",
391             $self->_show_option_value($opt), $opt
392             )
393 453 100       1160 ) if !$oo->allowed($self->final_options->{$opt});
394             }
395 26 100       113 if (@errors) {
396 1         4 print "$_\n" for @errors;
397 1         4 exit(1);
398             }
399              
400 26 100       107 if ($self->final_options->{show_config}) {
401 1         4 print "Show configuration requested:\n";
402 1         3 print
403             sprintf(
404             " %-20s| %s\n",
405             'Option',
406             'Value'
407             );
408 1         2 print "----------------------+--------------------------------------------\n";
409 1         3 my %options = $self->options;
410 1         4 for my $opt (sort keys %options) {
411 2   100     6 printf " %-20s| %s\n",
412             $opt,
413             $self->_show_option_value($opt) || '?';
414             }
415 1         4 exit(0);
416             }
417             }
418              
419             sub _show_option_value {
420 3     3   4 my $self = shift;
421 3         5 my ($option_name) = @_;
422              
423 3         7 return serialise( $self->option($option_name) );
424             }
425              
426             sub _obtain_config_file {
427 26     26   44 my $self = shift;
428 26         87 $self->{_from_configfile} = {};
429 26         58 $self->{_configfile_error} = undef;
430              
431 26         137 my $cf_name = $self->cli_options->{'configfile'};
432 26 100       80 return if !$cf_name;
433              
434 4 100       54 if (!-f $cf_name) {
435 3         8 for my $ext (qw/_config .config/) {
436 5 100       56 if (-f "${cf_name}${ext}") { $cf_name .= $ext; last }
  2         5  
  2         4  
437             }
438             }
439              
440 4 100       33 if (-f $cf_name) {
441 3         55 my $abs_cf = $self->cli_options->{'configfile'} = abs_path($cf_name);
442              
443             # Read the config-file in a localized environment
444 3         3 our $conf;
445 3         5 local $conf;
446 3         4 delete $INC{$abs_cf};
447 3         6 eval { local $^W; require $abs_cf };
  3         9  
  3         543  
448 3         13 $self->{_configfile_error} = $@;
449 3 100       5 %{$self->from_configfile} = %{ $conf || {} };
  3         22  
  3         13  
450 3         7 delete $INC{$abs_cf};
451             $self->from_configfile->{verbose} = delete($self->from_configfile->{v})
452 3 100       10 if exists $self->from_configfile->{v};
453             }
454             else {
455 1         5 $self->{_configfile_error} = "Could not find a configfile for '$cf_name'.";
456             }
457             }
458              
459             1;
460              
461             =head1 COPYRIGHT
462              
463             (c) 2002-2013, Abe Timmerman All rights reserved.
464              
465             With contributions from Jarkko Hietaniemi, Merijn Brand, Campo
466             Weijerman, Alan Burlison, Allen Smith, Alain Barbet, Dominic Dunlop,
467             Rich Rauenzahn, David Cantrell.
468              
469             This library is free software; you can redistribute it and/or modify
470             it under the same terms as Perl itself.
471              
472             See:
473              
474             =over 4
475              
476             =item * L
477              
478             =item * L
479              
480             =back
481              
482             This program is distributed in the hope that it will be useful,
483             but WITHOUT ANY WARRANTY; without even the implied warranty of
484             MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
485              
486             =cut