File Coverage

blib/lib/Package/Configure.pm
Criterion Covered Total %
statement 111 217 51.1
branch 37 100 37.0
condition 0 4 0.0
subroutine 20 23 86.9
pod 8 8 100.0
total 176 352 50.0


line stmt bran cond sub pod time code
1             package Package::Configure;
2              
3 1     1   40225 use strict;
  1         2  
  1         36  
4 1     1   4 use Carp qw(confess);
  1         2  
  1         48  
5 1     1   5 use Data::Dumper;
  1         1  
  1         107  
6 1     1   1390 use Getopt::Long;
  1         12492  
  1         8  
7 1     1   2999 use SelfLoader;
  1         10954  
  1         54  
8 1     1   1226 use Term::ANSIColor;
  1         11002  
  1         114  
9 1     1   1192 use Text::ParseWords;
  1         1398  
  1         70  
10 1     1   10506 use Text::Wrap;
  1         9323  
  1         72  
11              
12             #can't use it here b/c it may not be installed when Package-Tools is
13             #installed, so we require it later.
14             #use Config::IniFiles;
15              
16 1     1   9 use vars qw($AUTOLOAD);
  1         2  
  1         37  
17              
18 1     1   15 use constant CACHE => 'pkg_config.cache';
  1         2  
  1         63  
19 1     1   4 use constant TEMPLATE => 'pkg_config.in';
  1         2  
  1         337  
20              
21             =head1 SYNOPSIS
22              
23             my $config = Package::Configure->new();
24             my $value1 = $config->setting1(); #get
25             $config->setting1('a new value for setting 1'); #set
26              
27             =head1 DESCRIPTION
28              
29             Package::Configure - Access package configuration values
30             from command-line options (Getopt::Long style), previously specified
31             cached settings, or default values. This package is a kindred spirit to
32             the GNU automake and autoconf tools.
33              
34             When a Package::Configure object is instantiated, the following
35             happens:
36              
37             1. A. If F exists, load it into L accessor as a
38             Config::IniFiles object.
39             B. Otherwise, if F exists, load that.
40             C. Otherwise, load nothing.
41              
42             2. If a configuration file was loaded, process commandline arguments
43             Using Getopt::Long, overriding configuration setings with those provided
44             from Getopt::Long.
45              
46             3. A. If C<--help> was given as a Makefile.PL argument, render the configuration
47             as a usage document to STDOUT and exit(0).
48              
49             -otherwise-
50              
51             B. If a configuration file was loaded, and C<--interactive> was given as a
52             Makefile.PL argument, query the user on STDOUT/STDIN for new configuration
53             values.
54              
55             4. Variable values may also be accessed using C<$config-Emy_setting_name()>
56             to get the current value, or C<$config-Emy_setting_name('a new value')> to
57             update the value of the variable.
58              
59             5. When the object is destroyed (by falling out of scope, being undefined, etc),
60             the current state of the object is written to F.
61              
62             =head1 CONFIGURATION FILES pkg_config.in AND pkg_config.cache
63              
64             The configuration files are in INI format, and are parsed using Config::IniFiles.
65             You should be familiar with the INI format and L.
66              
67             =head2 RESERVED VARIABLES
68              
69             These variables have a built-in function and are reserved for use by
70             Package::Configure.
71              
72             * help
73             * interactive
74              
75             Run C for a display of what parameters are available, and
76             C for an interactive query for values of said
77             parameters.
78              
79             =head2 DECLARING CONFIGURATION VARIABLES
80              
81             Package::Configure recognizes variables in the following INI sections:
82              
83             for single value parameters:
84              
85             * [option integer]
86             * [option float]
87             * [option string]
88             * [option dir]
89             * [option file]
90              
91             for multi value parameters:
92              
93             * [option integers]
94             * [option floats]
95             * [option strings]
96             * [option dirs]
97             * [option files]
98              
99             Comments on sections/parameters are recognized and displayed when F is
100             run with the C<--help> option.
101              
102             Typechecking is performed on the integer, float, dir, and file sections, see
103             L.
104              
105             for scripts:
106              
107             * [PL_FILES]
108             * [EXE_FILES]
109              
110             thes sections are special -- they are passed to ExtUtils::MakeMaker to
111             determine which scripts are processed at make-time (PL_FILES), and which are installed
112             (EXE_FILES). See L for details on how that system works.
113              
114             =head2 SETTING VARIABLE VALUES
115              
116             See L
117              
118             Default values can be set in F, as well as collected from the
119             command-line using Getopt::Long-style options, or with interactive question/answer.
120              
121             The Getopt::Long parameters available are created dynamically from the variable names
122             in F or F (preferred if present).
123              
124             =head3 EDITING CONFIGURATION FILE
125              
126             See L for a description of the configuration file format.
127              
128             =head3 COMMAND-LINE OPTIONS
129              
130             For a script called F, valid executions of the script might be:
131              
132             C
133              
134             C
135              
136             Argument names are identical to those in F or F.
137              
138             =head3 INTERACTIVE QUERY
139              
140             A few runs of C might look like the following:
141              
142             [14:38]aday@asti:~/cvsroot/Package-Tools> /usr/bin/perl ./script.pl --interactive
143             color - what is your favorite color? (currently: "blue")? red
144             number - what is your favorite number? (currently: "2")? 9
145              
146             [14:38]aday@asti:~/cvsroot/Package-Tools> /usr/bin/perl ./script.pl --interactive
147             color - what is your favorite color? (currently: "red")? yellow
148             number - what is your favorite number? (currently: "9")? 8
149              
150             [14:38]aday@asti:~/cvsroot/Package-Tools> /usr/bin/perl ./script.pl --interactive --color 6 --number orange
151             Value "orange" invalid for option number (number expected)
152             color - what is your favorite color? (currently: "6")? orange
153             number - what is your favorite number? (currently: "8")? 6
154              
155             =head1 AUTHOR
156              
157             Allen Day, Eallenday@ucla.eduE
158              
159             =cut
160              
161             =head1 METHODS
162              
163             =cut
164              
165             =head2 AUTOLOAD()
166              
167             FIXME internal method, undocumented
168              
169             =cut
170              
171             sub AUTOLOAD {
172 3     3   1155 my $self = shift;
173 3         4 my $val = shift;
174              
175             #return undef unless $self && $self->ini();
176              
177 3         5 my $symbol = $AUTOLOAD;
178 3         4 my $sub = $symbol;
179 3         21 $sub =~ s/^.+::([\w]+?)$/$1/;
180              
181 3         6 my $sect = undef;
182 3         4 my $i = 0;
183 3         8 foreach my $section ($self->ini()->Sections){
184 33 100       84 if(grep {$_ eq $sub} $self->ini()->Parameters($section)){
  36         539  
185 3         4 $sect = $section;
186 3         6 $i++;
187             }
188              
189             }
190              
191 3 50       11 if($i == 0){
    50          
192 0         0 die "no such parameter or method '$sub'";
193             } elsif($i == 1){
194 1     1   6 no strict 'refs';
  1         2  
  1         2605  
195              
196             *$symbol = sub {
197 7     7   2076 my($self,@val) = @_;
198 7 100       15 if(@val){
199 2         6 return $self->ini()->setval($sect,$sub,@val);
200             } else {
201 5         12 return $self->ini()->val($sect,$sub);
202             }
203 3         28 };
204              
205 3         10 return $self->$sub(@_);
206             } else {
207 0         0 warn "parameters in multiple ($i) sections named $sub, use
208             \$install->config->ini()->val('section',$sub)
209             \$install->config->ini()->setval('section,$sub,\@newvals)
210             for access";
211 0         0 return undef;
212             }
213              
214 0         0 return undef;
215             }
216              
217             =head2 new()
218              
219             Usage : $config = Package::Configure->new();
220             Function: constructs a new object, reads variables and their default/cached
221             values from state files F and F.
222             Also handles command-line arguments by delegating to Getopt::Long.
223             Returns : a Package::Configure object
224             Args : none.
225              
226             =cut
227              
228             sub new {
229 1     1 1 15 my($class,%arg) = @_;
230              
231 1         4 my $self = bless {}, $class;
232              
233 1         2 my $ini;
234              
235 1 50       5 if(!$arg{bootstrap}){
236 1         1604 require Config::IniFiles;
237 1 50       28697 if (-f CACHE) {
    0          
238 1         13 $ini = Config::IniFiles->new( -file => CACHE );
239 1         6953 print STDERR colored("\rusing cached configuration values from ".CACHE,'cyan')."\n";
240             } elsif (-f TEMPLATE) {
241 0         0 $ini = Config::IniFiles->new( -file => TEMPLATE );
242 0         0 print STDERR colored("\rusing default configuration values from ".TEMPLATE,'cyan')."\n";
243             } else {
244             #no config file
245 0         0 $ini = Config::IniFiles->new();
246             }
247              
248 1 50       201 if(!$ini){
249 0         0 print STDERR colored('config parse failed: '.join(' ',@Config::IniFiles::errors),'red')."\n";
250 0         0 exit(1);
251             }
252              
253 1         7 $self->ini($ini);
254              
255             #override defaults and cache with command-line args
256 1         4 $self->process_options();
257              
258             #query user interactively
259 1 50       6 $self->process_interactive() if $self->interactive();
260              
261             #validate parameters
262 1         4 $self->validate_configuration();
263             }
264              
265 1 50       22 $self->ini()->WriteConfig(CACHE) if $self->ini();
266              
267 1         1701 return $self;
268             }
269              
270             =head2 validate_type()
271              
272             Usage : $obj->validate_type('type','thing_to_check');
273             Function: attempts to validate a value as a particular type
274             valid values for argument 1 are: integer, float, string, dir, file.
275             Returns : 1 on success
276             Args : anonymous list:
277             argument 1: type to validate against
278             argument 2: value to validate
279              
280             =cut
281              
282             sub validate_type {
283 0     0 1 0 my ($self,$type,$val) = @_;
284              
285 0 0       0 if($type eq 'integer') { return 1 if $val =~ /^-?\d+$/ }
  0 0       0  
    0          
    0          
    0          
    0          
286 0 0       0 elsif($type eq 'float') { return 1 if $val =~ /^-?\d*\.?\d*$/ }
287 0         0 elsif($type eq 'string') { return 1 }
288 0 0       0 elsif($type eq 'dir') { return 1 if -d $val }
289 0 0       0 elsif($type eq 'file') { return 1 if -f $val }
290              
291 0         0 return 0;
292             }
293              
294              
295             =head2 validate_configuration()
296              
297             Usage : $obj->validate_configuration();
298             Function: internal method. attempts to validate values
299             from the configuration file by calling L
300             on each.
301             Returns : n/a
302             Args : none
303              
304             =cut
305              
306             sub validate_configuration {
307 1     1 1 3 my ($self) = @_;
308              
309 1         3 my $cfg = $self->ini;
310              
311 1         5 foreach my $section ( $cfg->GroupMembers('option') ) {
312 10         164 foreach my $param ($cfg->Parameters("option $section")){
313 0         0 my $die = 0;
314              
315             #single
316 0 0       0 if($section !~ /s$/){
317 0         0 my $val = val("option $section",$param);
318 0         0 my $type = $section;
319 0         0 $type =~ s/option //;
320 0 0       0 $die++ unless $self->validate_type($type,$val);
321             }
322              
323             #plural
324             else {
325 0         0 my @val = val("option $section",$param);
326 0         0 foreach my $val (@val){
327 0         0 my $type = $section;
328 0         0 $type =~ s/option //;
329 0         0 $type =~ s/s$//;
330 0 0       0 $die++ unless $self->validate_type($type,$val);
331             }
332             }
333              
334             #did the param(s) validate?
335 0 0       0 if($die){
336 0         0 $section =~ s/option //;
337 0         0 die "[option $section] $param: value is not a valid '$section'";
338             }
339             }
340             }
341             }
342              
343             =head2 process_interactive()
344              
345             Usage : $obj->process_interactive();
346             Function: iterates over [option *] and [EXE_FILES] sections from
347             configuration file and prompts user for new values. values
348             are validated using L before being
349             accepted. lists of values are accepted and split using
350             L
351             Returns : n/a
352             Args : none
353              
354             =cut
355              
356             sub process_interactive {
357 0     0 1 0 my ($self) = @_;
358              
359 0         0 my $ask = qq(\r%s [%s] - %s (currently: "%s")? );
360              
361 0         0 foreach my $section ( $self->ini()->Sections ){
362 0 0       0 next unless $section =~ /^option/;
363 0         0 foreach my $param ( $self->ini()->Parameters($section) ){
364 0         0 my $type = $section;
365 0         0 $type =~ s/^option //;
366              
367 0         0 my $comment = join('', map{s/^#//;$_} $self->ini()->GetParameterComment($section,$param));
  0         0  
  0         0  
368              
369 0         0 print sprintf($ask,
370             $param,
371             $type,
372             $comment,
373             $self->ini()->val($section,$param)
374             );
375 0         0 my $response = <>;
376 0         0 chomp $response;
377              
378 0 0       0 if($response eq ''){
379 0         0 print colored("\ryou didn't respond, skipping. this may break the build",'red')."\n";
380 0         0 next;
381             }
382              
383 0         0 my $valid = 1;
384             #single
385 0 0       0 if($type !~ /s$/){
386 0 0       0 if(!$self->validate_type($type,$response)){
387 0         0 $valid = 0;
388             } else {
389             #commit it
390 0         0 $self->ini()->setval($section,$param,$response);
391             }
392             }
393             #plural
394             else {
395 0         0 $type =~ s/s$//;
396 0         0 my @response = shellwords($response);
397 0         0 foreach my $response (@response) {
398 0 0       0 if(!$self->validate_type($type,$response)){
399 0         0 $valid = 0;
400 0         0 last;
401             } else {
402 0         0 $self->ini()->setval($section,$param,@response);
403             }
404             }
405 0 0       0 if($valid == 1) {
406             #commit it
407 0         0 $self->ini()->setval($section,$param,@response);
408             }
409             }
410 0 0       0 if(!$valid){
411 0         0 print colored("\rinvalid value(s), try again",'red')."\n";
412 0         0 redo;
413             }
414             }
415             }
416              
417 0         0 $ask = qq(\rinstall %s - %s [Y/n]? );
418              
419 0         0 foreach my $exe ( $self->ini()->Parameters('EXE_FILES') ){
420 0         0 my $target = $exe;
421              
422 0         0 $target =~ s/\.PLS?$//;
423              
424 0         0 my $comment = join('', map{s/^#//;$_} $self->ini()->GetParameterComment('EXE_FILES',$exe));
  0         0  
  0         0  
425              
426 0         0 print sprintf($ask,
427             $target,
428             $comment,
429             $self->ini()->val('EXE_FILES',$exe)
430             );
431 0         0 my $response = <>;
432 0         0 chomp $response;
433              
434 0 0       0 if($response !~ /^n/i){
435 0         0 $self->ini()->setval('EXE_FILES',$exe,'yes')
436             } else {
437 0         0 $self->ini()->setval('EXE_FILES',$exe,'no')
438             }
439             }
440             }
441              
442              
443             =head2 process_options()
444              
445             Usage : $config->process_options();
446             Function: Internal method that processes command-line options.
447              
448             =cut
449              
450             sub process_options {
451 1     1 1 3 my $self = shift;
452              
453 1         3 my $cfg = $self->ini();
454              
455 1         4 my %slot = ();
456 1         1 my %sect = ();
457 1         2 my @protos = ();
458              
459             #hardcode in --help
460 1         2 $slot{help} = undef;
461 1         2 push @protos, 'help!';
462 1         20 $slot{interactive} = undef;
463 1         3 push @protos, 'interactive!';
464              
465 1         4 foreach my $section ($cfg->GroupMembers('option')) {
466 10         42 foreach my $param ($cfg->Parameters($section)) {
467 11         132 $sect{$param} = $section;
468 11         22 $slot{$param} = undef;
469              
470             #single
471 11 100       46 if($section eq 'option integer') { push @protos, "$param=i" }
  2 100       6  
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    50          
472 1         3 elsif($section eq 'option float') { push @protos, "$param=f" }
473 1         3 elsif($section eq 'option file') { push @protos, "$param=s" }
474 1         4 elsif($section eq 'option dir') { push @protos, "$param=s" }
475 1         9 elsif($section eq 'option string') { push @protos, "$param=s" }
476              
477             #plural
478 1         4 elsif($section eq 'option integers'){ push @protos, "$param=i@" }
479 1         3 elsif($section eq 'option floats') { push @protos, "$param=f@" }
480 1         4 elsif($section eq 'option files') { push @protos, "$param=s@" }
481 1         3 elsif($section eq 'option dirs') { push @protos, "$param=s@" }
482 1         4 elsif($section eq 'option strings') { push @protos, "$param=s@" }
483             }
484             }
485              
486 1         6 GetOptions(\%slot,@protos);
487              
488             #if help requested, give it and bail out
489 1 50       772 if($slot{help}){
490 0         0 $self->show_help();
491 0         0 exit 0;
492             }
493              
494             #if interactive requested, set a flag to do a query later
495 1 50       4 if($slot{interactive}){
496 0         0 $self->interactive(1);
497             }
498              
499             #handle setteds
500 1         4 foreach my $k (keys %slot){
501 13 50       25 next unless defined($slot{$k});
502 0 0       0 if(ref($slot{$k}) eq 'ARRAY'){
503 0         0 $cfg->setval($sect{$k},$k,@{ $slot{$k} });
  0         0  
504             } else {
505 0         0 $cfg->setval($sect{$k},$k,$slot{$k});
506             }
507             }
508             }
509              
510             =head2 show_help()
511              
512             Usage : $obj->show_help();
513             Function: render configuration file sections/parameters with
514             descriptions to STDOUT. program exits and call does
515             not return.
516             Returns : exit code on program termination
517             Args : exits 0 (success)
518              
519             =cut
520              
521             sub show_help {
522 0     0 1 0 my ($self) = @_;
523              
524 0         0 my $i = 4;
525              
526 0         0 print "Usage: $0 [options]\n";
527 0         0 print "Available options, organized by section:\n\n";
528              
529 0         0 foreach my $section ($self->ini->Sections()){
530 0 0       0 next unless $section =~ /^option/;
531 0 0       0 next unless $self->ini->Parameters($section);
532              
533 0         0 my $comment = join(' ', map {s/^#+//; $_} $self->ini->GetSectionComment($section));
  0         0  
  0         0  
534 0   0     0 $comment ||= 'no description for this section';
535 0         0 print( (' ' x $i)."[$section] $comment\n" );
536              
537 0         0 $i += 4;
538              
539 0         0 foreach my $param ($self->ini->Parameters($section)){
540 0         0 my $comment = join(' ', map {s/^#+//; $_} $self->ini->GetParameterComment($section,$param));
  0         0  
  0         0  
541 0   0     0 $comment ||= 'no description for this parameter';
542 0         0 print( (' ' x $i).'--'.$param." : $comment\n" );
543             }
544              
545 0         0 $i -= 4;
546              
547 0         0 print "\n";
548             }
549             }
550              
551             =head2 ini()
552              
553             Usage : $obj->ini($newval)
554             Function: holds a Config::IniFiles instance
555             Returns : value of ini (a scalar)
556             Args : on set, new value (a scalar or undef, optional)
557              
558              
559             =cut
560              
561             sub ini {
562 50     50 1 59 my($self,$val) = @_;
563 50 100       102 $self->{'ini'} = $val if defined($val);
564 50         180 return $self->{'ini'};
565             }
566              
567             =head2 interactive()
568              
569             Usage : $obj->interactive($newval)
570             Function: flag for whether or not the user should be interactively
571             queried for configuration values.
572             Returns : value of interactive (a scalar)
573             Args : on set, new value (a scalar or undef, optional)
574              
575              
576             =cut
577              
578             sub interactive {
579 1     1 1 1 my($self,$val) = @_;
580 1 50       5 $self->{'interactive'} = $val if defined($val);
581 1         4 return $self->{'interactive'};
582             }
583              
584             =head2 DESTROY()
585              
586             called when the object is destroyed. writes object's variables' states
587             to F to be read at next instantiation.
588              
589             =cut
590              
591             sub DESTROY {
592 1     1   364 my $self = shift;
593 1 50       2 $self->ini->WriteConfig(CACHE) if $self->ini();
594             }
595              
596             1;