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   2504 use warnings;
  8         19  
  8         282  
3 8     8   43 use strict;
  8         23  
  8         181  
4 8     8   53 use Carp qw/ confess /;
  8         17  
  8         490  
5              
6             our $VERSION = '0.002';
7              
8 8     8   53 use base 'Test::Smoke::ObjectBase';
  8         13  
  8         2901  
9              
10 8     8   72 use Cwd 'abs_path';
  8         16  
  8         349  
11 8     8   6145 use Getopt::Long qw/:config pass_through/;
  8         96211  
  8         45  
12 8     8   4174 use Test::Smoke::App::AppOption;
  8         23  
  8         233  
13 8     8   3370 use Test::Smoke::App::AppOptionCollection;
  8         21  
  8         214  
14 8     8   3414 use Test::Smoke::LogMixin;
  8         25  
  8         366  
15 8     8   3562 use Test::Smoke::Util::Serialise qw/serialise/;
  8         22  
  8         16181  
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 978 my $class = shift;
89 26         158 my %args = @_;
90              
91 26         218 my $struct = {
92             _main_options => [],
93             _general_options => [],
94             _special_options => {},
95             _final_options => {},
96             };
97              
98 26         165 for my $known (keys %$struct) {
99 104         425 (my $key = $known) =~ s/^_//;
100 104 100       402 $struct->{$known} = delete $args{$key} if exists $args{$key};
101             }
102              
103 26         141 my $self = bless $struct, $class;
104              
105 26         501 $self->process_options();
106 26         146 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 83 my $class = shift;
117 26         121 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 90 my $class = shift;
132 26         158 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 142 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 99 my $self = shift;
190              
191 26         352 $self->{_opt_collection} = Test::Smoke::App::AppOptionCollection->new();
192              
193 26         258 $self->_pre_process_options();
194 26         296 $self->_get_options();
195 26         53250 $self->_post_process_options();
196              
197 26         63 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 803     803 1 4390 my $self = shift;
232 803         1580 my ($option) = @_;
233              
234 803         2932 my $opts = $self->final_options;
235 803 100       1920 if (exists $opts->{$option}) {
236 802         1047 my $is_main = grep $_->name eq $option, @{$self->main_options};
  802         2641  
237 802 100       1692 return $opts->{$option} if $is_main;
238              
239 763         1021 my $is_general = grep $_->name eq $option, @{$self->general_options};
  763         2409  
240 763 100       4642 return $opts->{$option} if $is_general;
241              
242 60         107 for my $mainopt (@{$self->main_options}) {
  60         193  
243 86         318 my $type = $opts->{$mainopt->name};
244 86         307 my $specials = $self->special_options->{$type};
245 86         390 my $is_special = grep $_->name eq $option, @$specials;
246 86 100       380 return $opts->{$option} if $is_special;
247             }
248              
249 1         96 confess("Option '$option' is not valid.");
250             }
251 1         193 confess("Invalid option '$option'");
252             }
253              
254             sub _find_option {
255 442     442   772 my $self = shift;
256 442         762 my ($option) = @_;
257              
258 442         594 my ($oo) = grep $_->name eq $option, @{$self->main_options};
  442         1412  
259 442 100       909 return $oo if $oo;
260              
261 421         589 ($oo) = grep $_->name eq $option, @{$self->general_options};
  421         1323  
262 421 100       1116 return $oo if $oo;
263              
264 38         67 for my $mo (@{$self->main_options}) {
  38         154  
265 62         270 my $type = $self->final_options->{$mo->name};
266 62         246 my $specials = $self->special_options->{$type};
267 62         255 ($oo) = grep $_->name eq $option, @$specials;
268 62 100       204 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 753 my $self = shift;
292              
293 40         73 my %options;
294 40         66 for my $opt (@{$self->main_options}) {
  40         169  
295 31         137 my $type = $self->option($opt->name);
296 31         122 $options{$opt->name} = $type;
297 31         138 my $specials = $self->special_options->{$type};
298 31         94 for my $opt (@$specials) {
299 62         210 $options{$opt->name} = $self->option($opt->name);
300             }
301             }
302             # collect all general options
303 40         73 for my $opt (@{ $self->general_options }) {
  40         145  
304 616 100       2030 next if $opt->name =~ /^(?:help|show_config)$/;
305 536         1866 $options{$opt->name} = $self->option($opt->name);
306             }
307              
308 40         542 return %options;
309             }
310              
311             sub _pre_process_options {
312 26     26   82 my $self = shift;
313              
314 26         57 unshift @{$self->general_options}, $self->configfile_option;
  26         251  
315 26         94 push @{$self->general_options}, $self->show_config_option;
  26         218  
316 26         76 push @{$self->general_options}, $self->verbose_option;
  26         140  
317 26         79 for my $opt (@{$self->general_options}) {
  26         188  
318 411         1590 $self->opt_collection->add($opt);
319             }
320              
321 26         78 for my $opt (sort {$a->name cmp $b->name} @{$self->main_options}) {
  9         70  
  26         247  
322 21         121 $self->opt_collection->add_helptext("\n");
323 21         102 $self->opt_collection->add($opt);
324 21         69 for my $special (sort {lc($a) cmp lc($b)} @{$opt->allow}) {
  68         189  
  21         105  
325 68         289 $self->opt_collection->add_helptext(
326             sprintf("\nOptions for '%s':\n", $special)
327             );
328 68         330 my $specials = $self->special_options->{$special};
329 68         237 for my $thisopt (@$specials) {
330 164         600 $self->opt_collection->add($thisopt);
331             }
332             }
333             }
334              
335 26         158 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   552 print "Usage: $0 [options]\n\n$helptext";
341 1         7 exit(0);
342             },
343 26         357 helptext => 'This message.',
344             );
345 26         87 push @{$self->general_options}, $help_option;
  26         136  
346 26         130 $self->opt_collection->add($help_option);
347              
348 26         58 %{$self->{_dft_options}} = %{$self->opt_collection->options_with_default};
  26         315  
  26         148  
349             }
350              
351             sub _get_options {
352 26     26   60 my $self = shift;
353              
354 26         49 %{$self->{_cli_options}} = %{$self->opt_collection->options_for_cli};
  26         108  
  26         129  
355              
356 26         161 @{$self->{_ARGV}} = @{$self->{_ARGV_EXTRA}} = @ARGV;
  26         99  
  26         229  
357              
358 26         364 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         3434 @{ $self->opt_collection->options_list },
  26         144  
363             );
364             }
365              
366             sub _post_process_options {
367 26     26   69 my $self = shift;
368              
369 26         179 $self->_obtain_config_file;
370 26         78 %{$self->final_options} = %{$self->cli_options};
  26         216  
  26         123  
371              
372             # now combine with configfile
373             $self->final_options(
374             {
375 26         129 %{$self->opt_collection->all_options},
376 26         497 %{$self->dft_options},
377 26         190 %{$self->from_configfile},
378 26         109 %{$self->cli_options},
  26         183  
379             }
380             );
381 26         186 my @errors;
382 26         203 my %check_options = $self->options;
383 26         230 for my $opt (keys %check_options) {
384 442         1000 my $oo = $self->_find_option($opt);
385 442         1498 my $value = $self->final_options->{$opt};
386 442 100       1017 $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 442 100       1384 ) if !$oo->allowed($self->final_options->{$opt});
394             }
395 26 100       143 if (@errors) {
396 1         5 print "$_\n" for @errors;
397 1         4 exit(1);
398             }
399              
400 26 100       114 if ($self->final_options->{show_config}) {
401 1         7 print "Show configuration requested:\n";
402 1         3 print
403             sprintf(
404             " %-20s| %s\n",
405             'Option',
406             'Value'
407             );
408 1         4 print "----------------------+--------------------------------------------\n";
409 1         4 my %options = $self->options;
410 1         5 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   5 my $self = shift;
421 3         8 my ($option_name) = @_;
422              
423 3         9 return serialise( $self->option($option_name) );
424             }
425              
426             sub _obtain_config_file {
427 26     26   59 my $self = shift;
428 26         110 $self->{_from_configfile} = {};
429 26         75 $self->{_configfile_error} = undef;
430              
431 26         151 my $cf_name = $self->cli_options->{'configfile'};
432 26 100       105 return if !$cf_name;
433              
434 4 100       81 if (!-f $cf_name) {
435 3         10 for my $ext (qw/_config .config/) {
436 5 100       75 if (-f "${cf_name}${ext}") { $cf_name .= $ext; last }
  2         16  
  2         5  
437             }
438             }
439              
440 4 100       40 if (-f $cf_name) {
441 3         66 my $abs_cf = $self->cli_options->{'configfile'} = abs_path($cf_name);
442              
443             # Read the config-file in a localized environment
444 3         7 our $conf;
445 3         3 local $conf;
446 3         7 delete $INC{$abs_cf};
447 3         8 eval { local $^W; require $abs_cf };
  3         12  
  3         760  
448 3         18 $self->{_configfile_error} = $@;
449 3 100       5 %{$self->from_configfile} = %{ $conf || {} };
  3         26  
  3         17  
450 3         9 delete $INC{$abs_cf};
451             $self->from_configfile->{verbose} = delete($self->from_configfile->{v})
452 3 100       29 if exists $self->from_configfile->{v};
453             }
454             else {
455 1         13 $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