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   2617 use warnings;
  8         17  
  8         257  
3 8     8   39 use strict;
  8         21  
  8         183  
4 8     8   54 use Carp qw/ confess /;
  8         14  
  8         488  
5              
6             our $VERSION = '0.002';
7              
8 8     8   47 use base 'Test::Smoke::ObjectBase';
  8         22  
  8         2957  
9              
10 8     8   64 use Cwd 'abs_path';
  8         16  
  8         348  
11 8     8   6457 use Getopt::Long qw/:config pass_through/;
  8         96066  
  8         57  
12 8     8   4226 use Test::Smoke::App::AppOption;
  8         25  
  8         282  
13 8     8   3342 use Test::Smoke::App::AppOptionCollection;
  8         23  
  8         294  
14 8     8   3426 use Test::Smoke::LogMixin;
  8         23  
  8         385  
15 8     8   3707 use Test::Smoke::Util::Serialise qw/serialise/;
  8         23  
  8         16794  
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 995 my $class = shift;
89 26         143 my %args = @_;
90              
91 26         211 my $struct = {
92             _main_options => [],
93             _general_options => [],
94             _special_options => {},
95             _final_options => {},
96             };
97              
98 26         154 for my $known (keys %$struct) {
99 104         454 (my $key = $known) =~ s/^_//;
100 104 100       394 $struct->{$known} = delete $args{$key} if exists $args{$key};
101             }
102              
103 26         148 my $self = bless $struct, $class;
104              
105 26         279 $self->process_options();
106 26         167 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 66 my $class = shift;
117 26         159 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 74 my $class = shift;
132 26         144 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 150 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 127 my $self = shift;
190              
191 26         327 $self->{_opt_collection} = Test::Smoke::App::AppOptionCollection->new();
192              
193 26         281 $self->_pre_process_options();
194 26         288 $self->_get_options();
195 26         55244 $self->_post_process_options();
196              
197 26         80 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 3925 my $self = shift;
232 818         1598 my ($option) = @_;
233              
234 818         2933 my $opts = $self->final_options;
235 818 100       2013 if (exists $opts->{$option}) {
236 817         1086 my $is_main = grep $_->name eq $option, @{$self->main_options};
  817         2666  
237 817 100       1747 return $opts->{$option} if $is_main;
238              
239 778         1043 my $is_general = grep $_->name eq $option, @{$self->general_options};
  778         2497  
240 778 100       4957 return $opts->{$option} if $is_general;
241              
242 60         99 for my $mainopt (@{$self->main_options}) {
  60         219  
243 86         289 my $type = $opts->{$mainopt->name};
244 86         299 my $specials = $self->special_options->{$type};
245 86         333 my $is_special = grep $_->name eq $option, @$specials;
246 86 100       393 return $opts->{$option} if $is_special;
247             }
248              
249 1         100 confess("Option '$option' is not valid.");
250             }
251 1         214 confess("Invalid option '$option'");
252             }
253              
254             sub _find_option {
255 453     453   670 my $self = shift;
256 453         745 my ($option) = @_;
257              
258 453         591 my ($oo) = grep $_->name eq $option, @{$self->main_options};
  453         1493  
259 453 100       1014 return $oo if $oo;
260              
261 432         569 ($oo) = grep $_->name eq $option, @{$self->general_options};
  432         1406  
262 432 100       1105 return $oo if $oo;
263              
264 38         84 for my $mo (@{$self->main_options}) {
  38         150  
265 62         247 my $type = $self->final_options->{$mo->name};
266 62         263 my $specials = $self->special_options->{$type};
267 62         254 ($oo) = grep $_->name eq $option, @$specials;
268 62 100       194 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 751 my $self = shift;
292              
293 40         69 my %options;
294 40         101 for my $opt (@{$self->main_options}) {
  40         165  
295 31         146 my $type = $self->option($opt->name);
296 31         122 $options{$opt->name} = $type;
297 31         139 my $specials = $self->special_options->{$type};
298 31         86 for my $opt (@$specials) {
299 62         220 $options{$opt->name} = $self->option($opt->name);
300             }
301             }
302             # collect all general options
303 40         90 for my $opt (@{ $self->general_options }) {
  40         176  
304 631 100       2125 next if $opt->name =~ /^(?:help|show_config)$/;
305 551         1981 $options{$opt->name} = $self->option($opt->name);
306             }
307              
308 40         537 return %options;
309             }
310              
311             sub _pre_process_options {
312 26     26   91 my $self = shift;
313              
314 26         62 unshift @{$self->general_options}, $self->configfile_option;
  26         270  
315 26         87 push @{$self->general_options}, $self->show_config_option;
  26         146  
316 26         74 push @{$self->general_options}, $self->verbose_option;
  26         140  
317 26         80 for my $opt (@{$self->general_options}) {
  26         137  
318 422         1745 $self->opt_collection->add($opt);
319             }
320              
321 26         96 for my $opt (sort {$a->name cmp $b->name} @{$self->main_options}) {
  9         48  
  26         210  
322 21         116 $self->opt_collection->add_helptext("\n");
323 21         105 $self->opt_collection->add($opt);
324 21         60 for my $special (sort {lc($a) cmp lc($b)} @{$opt->allow}) {
  68         158  
  21         92  
325 68         267 $self->opt_collection->add_helptext(
326             sprintf("\nOptions for '%s':\n", $special)
327             );
328 68         301 my $specials = $self->special_options->{$special};
329 68         193 for my $thisopt (@$specials) {
330 164         593 $self->opt_collection->add($thisopt);
331             }
332             }
333             }
334              
335 26         165 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   580 print "Usage: $0 [options]\n\n$helptext";
341 1         4 exit(0);
342             },
343 26         361 helptext => 'This message.',
344             );
345 26         85 push @{$self->general_options}, $help_option;
  26         168  
346 26         136 $self->opt_collection->add($help_option);
347              
348 26         85 %{$self->{_dft_options}} = %{$self->opt_collection->options_with_default};
  26         248  
  26         129  
349             }
350              
351             sub _get_options {
352 26     26   62 my $self = shift;
353              
354 26         58 %{$self->{_cli_options}} = %{$self->opt_collection->options_for_cli};
  26         169  
  26         131  
355              
356 26         147 @{$self->{_ARGV}} = @{$self->{_ARGV_EXTRA}} = @ARGV;
  26         111  
  26         204  
357              
358 26         379 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         3547 @{ $self->opt_collection->options_list },
  26         148  
363             );
364             }
365              
366             sub _post_process_options {
367 26     26   100 my $self = shift;
368              
369 26         184 $self->_obtain_config_file;
370 26         77 %{$self->final_options} = %{$self->cli_options};
  26         169  
  26         140  
371              
372             # now combine with configfile
373             $self->final_options(
374             {
375 26         129 %{$self->opt_collection->all_options},
376 26         255 %{$self->dft_options},
377 26         188 %{$self->from_configfile},
378 26         86 %{$self->cli_options},
  26         112  
379             }
380             );
381 26         158 my @errors;
382 26         158 my %check_options = $self->options;
383 26         209 for my $opt (keys %check_options) {
384 453         1187 my $oo = $self->_find_option($opt);
385 453         1593 my $value = $self->final_options->{$opt};
386 453 100       1094 $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       1470 ) if !$oo->allowed($self->final_options->{$opt});
394             }
395 26 100       154 if (@errors) {
396 1         6 print "$_\n" for @errors;
397 1         9 exit(1);
398             }
399              
400 26 100       130 if ($self->final_options->{show_config}) {
401 1         12 print "Show configuration requested:\n";
402 1         3 print
403             sprintf(
404             " %-20s| %s\n",
405             'Option',
406             'Value'
407             );
408 1         3 print "----------------------+--------------------------------------------\n";
409 1         3 my %options = $self->options;
410 1         14 for my $opt (sort keys %options) {
411 2   100     7 printf " %-20s| %s\n",
412             $opt,
413             $self->_show_option_value($opt) || '?';
414             }
415 1         5 exit(0);
416             }
417             }
418              
419             sub _show_option_value {
420 3     3   9 my $self = shift;
421 3         8 my ($option_name) = @_;
422              
423 3         7 return serialise( $self->option($option_name) );
424             }
425              
426             sub _obtain_config_file {
427 26     26   72 my $self = shift;
428 26         119 $self->{_from_configfile} = {};
429 26         77 $self->{_configfile_error} = undef;
430              
431 26         183 my $cf_name = $self->cli_options->{'configfile'};
432 26 100       150 return if !$cf_name;
433              
434 4 100       62 if (!-f $cf_name) {
435 3         21 for my $ext (qw/_config .config/) {
436 5 100       73 if (-f "${cf_name}${ext}") { $cf_name .= $ext; last }
  2         15  
  2         5  
437             }
438             }
439              
440 4 100       38 if (-f $cf_name) {
441 3         67 my $abs_cf = $self->cli_options->{'configfile'} = abs_path($cf_name);
442              
443             # Read the config-file in a localized environment
444 3         6 our $conf;
445 3         6 local $conf;
446 3         6 delete $INC{$abs_cf};
447 3         4 eval { local $^W; require $abs_cf };
  3         13  
  3         729  
448 3         17 $self->{_configfile_error} = $@;
449 3 100       6 %{$self->from_configfile} = %{ $conf || {} };
  3         25  
  3         17  
450 3         20 delete $INC{$abs_cf};
451             $self->from_configfile->{verbose} = delete($self->from_configfile->{v})
452 3 100       21 if exists $self->from_configfile->{v};
453             }
454             else {
455 1         7 $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