File Coverage

blib/lib/Config/Options.pm
Criterion Covered Total %
statement 151 179 84.3
branch 41 68 60.2
condition 14 22 63.6
subroutine 19 21 90.4
pod 9 9 100.0
total 234 299 78.2


line stmt bran cond sub pod time code
1             package Config::Options;
2             our $VERSION = 0.08;
3             # Copyright (c) 2007 Edward Allen III. All rights reserved.
4             #
5             ## This program is free software; you can redistribute it and/or
6             ## modify it under the terms of the Artistic License, distributed
7             ## with Perl.
8             #
9              
10             =pod
11              
12             =head1 NAME
13              
14             Config::Options - module to provide a configuration hash with option to read from file.
15              
16             =head1 SYNOPSIS
17              
18             use Config::Options;
19              
20             my $options = Config::Options->new({ verbose => 1, optionb => 2, mood => "sardonic" });
21              
22             # Access option as a hash...
23             print "My mode is ", $options->{mood}, "\n";
24              
25             # Merge a hash of options...
26             $options->options({ optionc => 5, style => "poor"});
27              
28             # Merge options from file
29              
30             $options->options("optionfile", $ENV{HOME} . "/.myoptions.conf");
31             $options->fromfile_perl();
32              
33              
34             =head1 AUTHOR
35              
36             Edward Allen, ealleniii _at_ cpan _dot_ org
37              
38             =head1 DESCRIPTION
39              
40             The motivation for this module was to provide an option hash with a little bit of brains. It's
41             pretty simple and used mainly by other modules I have written.
42              
43             =cut
44              
45 1     1   86551 use strict;
  1         1  
  1         35  
46 1     1   10091 use Data::Dumper;
  1         19222  
  1         90  
47 1     1   11 use Carp;
  1         8  
  1         65  
48 1     1   7 use Scalar::Util;
  1         2  
  1         37  
49 1     1   5 use Config;
  1         2  
  1         2117  
50              
51             =pod
52              
53             =head1 METHODS
54              
55             =over 4
56              
57             =item new()
58              
59             Create new options hash. Pass it a hash ref to start with. Please note that this reference
60             is copied, not blessed.
61              
62             my $options = Config::Options->new({hash_of_startup_options});
63              
64             =cut
65              
66             sub new {
67 1     1 1 24 my $class = shift;
68 1 50       17 if ($Config{useithreads}) {
69 0         0 require Config::Options::Threaded;
70 0         0 return Config::Options::Threaded->new(@_);
71             }
72             else {
73 1         7 return $class->_new(@_);
74             }
75             }
76              
77             sub _new {
78 1     1   2 my $class = shift;
79 1         2 my $self = {};
80 1         3 bless $self, $class;
81 1         5 $self->options(@_);
82             }
83              
84             =item clone()
85              
86             Creates a clone of options object.
87              
88             my $newoptions = $options->clone();
89              
90             =cut
91              
92             sub clone {
93 0     0 1 0 my $self = shift;
94 0         0 my $clone = {%$self};
95 0         0 bless $clone, ref $self;
96 0         0 return $clone;
97             }
98              
99             =item options()
100              
101             This is a utility function for accessing options. If passed a hashref, merges it.
102             If passed a scalar, returns the value. If passed two scalars, sets the option.
103              
104             my $optionsb = $options->options; # Duplicates option file. Not very usefull.
105             $options->options($hashref); # Same as $options->merge($hashref);
106             my $value = $options->options("key") # Return option value.
107             $options->options("key", "value") # Set an option.
108              
109             =cut
110              
111             sub options {
112 5     5 1 673 my $self = shift;
113 5         8 my $option = shift;
114 5 100       16 if ( ref $option ) {
    50          
115 2         25 return $self->merge($option);
116             }
117             elsif ($option) {
118 3         5 my $value = shift;
119 3 50       7 if ( defined $value ) {
120 0         0 $self->_setoption($option, $value);
121 0         0 $self->{$option} = $value;
122             }
123 3         14 return $self->{$option};
124             }
125 0         0 return $self;
126             }
127              
128              
129             =item merge()
130              
131             Takes a hashref as argument and merges with current options.
132              
133             $options->merge($hashref);
134              
135              
136             =cut
137              
138             sub merge {
139 2     2 1 3 my $self = shift;
140 2         3 my $option = shift;
141 2 50       9 return unless ( ref $option );
142 2         4 while ( my ( $k, $v ) = each %{$option} ) {
  7         23  
143 5         14 $self->_setoption($k, $v);
144             }
145 2         8 return $self;
146             }
147              
148             # Safely set an option
149             sub _setoption {
150 5     5   6 my $self = shift;
151 5         7 my ($key, $value) = @_;
152 5         9 my $new = $value;
153 5 100       13 if (ref $value) {
154 2         9 $new = $self->_copyref($value);
155             }
156 5         11 $self->{$key} = $new;
157 5         14 return $value;
158             }
159              
160             sub _newhash {
161 2     2   4 return {};
162             }
163              
164             sub _newarray {
165 1     1   3 return [];
166             }
167              
168              
169             # Created a shared copy of a (potentially unshared) reference
170             sub _copyref {
171 3     3   5 my $self = shift;
172 3         5 my $in = shift;
173 3   100     14 my $haveseen = shift || [];
174 3   100     13 my $depth = shift || 0;
175 3 50       9 if (++$depth > 20) {
176 0         0 carp "More than 20 deep on nested reference. Is this a loop?";
177 0         0 return $in;
178             }
179 3         5 my $seen = [ @{$haveseen} ];
  3         6  
180 3 50       4 foreach (@{$seen}) { if(Scalar::Util::refaddr($in) == $_) { carp "Attempt to create circular reference!"; return $in } }
  3         8  
  1         7  
  0         0  
  0         0  
181 3         5 push @{$seen}, Scalar::Util::refaddr($in);
  3         11  
182 3 100       15 if (Scalar::Util::reftype($in) eq "HASH") {
    50          
    0          
183 2         7 my $out = $self->_newhash();
184 2         3 while (my ($k, $v) = each %{$in}) {
  7         22  
185 5 100       9 if (ref $v) {
186 1         7 $out->{$k} = $self->_copyref($v, $seen, $depth);
187             }
188             else {
189 4         11 $out->{$k} = $v;
190             }
191             }
192 2         6 return $out;
193             }
194             elsif (Scalar::Util::reftype($in) eq "ARRAY") {
195 1         4 my $out = $self->_newarray();
196 1         2 foreach my $v (@{$in}) {
  1         3  
197 1 50       3 if (ref $v) {
198 0         0 push @{$out}, $self->_copyref($v, $seen, $depth);
  0         0  
199             }
200             else {
201 1         2 push @{$out}, $v;
  1         5  
202             }
203             }
204 1         4 return $out;
205             }
206             elsif (ref $in) {
207 0         0 croak "Attempt to copy unsupported reference type: " . (ref $in);
208             }
209             else {
210 0         0 return $in;
211             }
212             }
213              
214             # If $from and $to are both refs of same type, merge. Otherwise $to replaces $from.
215             #
216             sub _mergerefs {
217 21     21   28 my $self = shift;
218 21         25 my $from = shift;
219 21         19 my $to = shift;
220 21   100     61 my $haveseen = shift || [];
221 21   100     49 my $depth = shift || 0;
222 21 50       61 if (++$depth > 20) {
223 0         0 carp "More than 20 deep on nested reference. Is this a loop?";
224 0         0 return $to;
225             }
226 21 50       67 if (Scalar::Util::refaddr($from) == Scalar::Util::refaddr($to)) {
227 0         0 croak "Do NOT try to merge two identical references!"
228             }
229 21         24 my $seen = [ @{$haveseen} ];
  21         46  
230 21 100       25 foreach (@{$seen}) { if(Scalar::Util::refaddr($from) == $_) { carp "Attempt to create circular reference!"; return $to } }
  21         40  
  42         123  
  1         215  
  1         192  
231 20         24 push @{$seen}, Scalar::Util::refaddr($from), Scalar::Util::refaddr($to);
  20         66  
232 20 50 33     94 return unless ((ref $from) && (ref $to));
233 20 50       111 if (Scalar::Util::reftype($from) eq Scalar::Util::reftype($to)) {
234 20 100       55 if (Scalar::Util::reftype($from) eq "HASH") {
    50          
235 17         22 while (my ($k, $v) = each %{$from} ) {
  70         204  
236 53 100       91 if (exists $to->{$k}) {
237 51 50       92 if (defined $v) {
238 51 100       76 if (ref $v) {
239 15         44 $self->_mergerefs($from->{$k}, $to->{$k}, $seen, $depth)
240             }
241             else {
242 36         83 $to->{$k} = $v;
243             }
244             }
245             }
246             else {
247 2 50       7 if (ref $v) {
248 0         0 $to->{$k} = $self->_copyref($v, $seen, $depth);
249             }
250             else {
251 2         5 $to->{$k} = $v;
252             }
253             }
254             }
255             }
256             elsif (Scalar::Util::reftype($from) eq "ARRAY") {
257 3         3 foreach my $v (@{$from}) {
  3         7  
258 3 50       8 if (ref $v) {
259 0         0 push @{$to}, $self->_copyref($v, $seen, $depth);
  0         0  
260             }
261             else {
262 3         4 push @{$to}, $v;
  3         12  
263             }
264             }
265             }
266             }
267             else {
268 0         0 $to = $self->_copyref($from, $seen, $depth);
269             }
270 20         62 return $to;
271             }
272              
273              
274             =item deepmerge()
275              
276             Same as merge, except when a value is a hash or array reference. For example:
277              
278             my $options = Config::Options->new({ moods => [ qw(happy sad angry) ] });
279             $options->deepmerge({ moods => [ qw(sardonic twisted) ] });
280              
281             print join(" ", @{$options->{moods}}), "\n";
282              
283             The above outputs:
284              
285             happy sad angry sardonic twisted
286              
287             =cut
288              
289             sub deepmerge {
290 6     6 1 1706 my $self = shift;
291 6         8 my $option = shift;
292 6         16 $self->_mergerefs($option, $self);
293             }
294              
295             =pod
296              
297             =item tofile_perl()
298              
299             This is used to store options to a file. The file is actually a perl program that
300             returns a hash. By default uses option 'optionfile' as filename, or value passed as argument.
301              
302             If 'optionfile' is an array, then uses LAST option in array as default.
303              
304             $options->tofile_perl("/path/to/optionfile");
305              
306             =cut
307              
308             sub tofile_perl {
309 1     1 1 6 my $self = shift;
310 1   33     7 my $filename = shift || $self->options("optionfile");
311 1         1 my $file;
312 1 50       4 if ( ref $filename ) {
313 1         2 $file = $filename->[-1];
314             }
315             else {
316 0         0 $file = $filename;
317             }
318 1         3 local *OUT;
319 1 50       78 open( OUT, ">", $file ) or croak "Can't open option file: $file for write: $!";
320 1         4 my $data = $self->serialize();
321 1         120 print OUT $data;
322 1 50       60 close(OUT) or croak "Error closing file: ${file}: $!";
323 1         8 return $self;
324             }
325              
326             =pod
327              
328             =item fromfile_perl()
329              
330             This is used to retreive options from a file. The optionfile is actually a perl program that
331             returns a hash. By default uses option 'optionfile' as filename if none is passed.
332              
333             If 'optionfile' is an array, reads all option files in order.
334              
335             Non-existant files are ignored.
336              
337             Please note that values for this are cached.
338              
339             $options->fromfile_perl("/path/to/optionfile");
340              
341             =cut
342              
343             sub fromfile_perl {
344 2     2 1 4 my $self = shift;
345 2   33     14 my $filename = shift || $self->options("optionfile");
346 2         4 my @files = ();
347 2 50       7 if ( ref $filename eq "ARRAY" ) {
348 2         3 push @files, @{$filename};
  2         5  
349             }
350             else {
351 0         0 push @files, $filename;
352             }
353 2         4 my $n = 0;
354 2         4 foreach my $f ( @files ) {
355 3 50       100 if ( -e $f ) {
356 3 50 33     17 if ( ( exists $self->{verbose} ) && ( $self->{verbose} ) ) {
357 3         218 print STDERR "Loading options from $f\n";
358             }
359 3         11 local *IN;
360 3         4 my $sub = "";
361 3 50       120 open( IN, $f ) or croak "Couldn't open option file $f: $!";
362 3         42 while () {
363 54         129 $sub .= $_;
364             }
365 3         83 close(IN);
366 3         14 my $o = $self->deserialize( $sub, "Options File: $f" );
367 3 50       17 $o && $n++;
368             }
369             }
370 2         16 return $n;
371             }
372              
373             =pod
374              
375             =item deserialize($data, $source)
376              
377             Takes a scalar as argument and evals it, then merges option. If second option is given uses this in error message if the eval fails.
378              
379             my $options = $options->deserialize($scalar, $source);
380              
381             =cut
382              
383             sub deserialize {
384 4     4 1 547 my $self = shift;
385 4         7 my $data = shift;
386 4   100     15 my $source = shift || "Scalar";
387 4         372 my $o = eval $data;
388 4 50       103 if ($@) { croak "Can't process ${source}: $@" }
  0         0  
389             else {
390 4         11 $self->deepmerge($o);
391 4         25 return $self;
392             }
393             }
394              
395             =pod
396              
397             =item serialize()
398              
399             Output optons hash as a scalar using Data::Dumper.
400              
401             my $scalar = $options->serialize();
402              
403             =cut
404              
405             sub serialize {
406 2     2 1 4 my $self = shift;
407 2         3 my $d = Data::Dumper->new( [ { %{$self} } ] );
  2         28  
408 2         74 return $d->Purity(1)->Terse(1)->Deepcopy(1)->Dump;
409             }
410              
411             =item del($key)
412              
413             Removes $key from options.
414              
415             =cut
416              
417 0     0     sub DESTROY {
418             }
419              
420             =back
421              
422             =head1 BUGS
423              
424             =over 4
425              
426             =item Deepmerge does a poor job at recogniaing recursive loops.
427              
428             For example, $options->deepmerge($options) will really screw things up. As protection, will only loop 20 deep.
429              
430             =item fromfile_perl provides tainted data.
431              
432             Since it comes from an external file, the data is considered tainted.
433              
434             =back
435              
436             =head1 SEE ALSO
437              
438             L
439              
440             =head1 LICENSE
441              
442             This program is free software; you can redistribute it and/or
443             modify it under the terms of the Artistic License, distributed
444             with Perl.
445              
446             =head1 COPYRIGHT
447              
448             Copyright (c) 2007 Edward Allen III. Some rights reserved.
449              
450              
451              
452             =cut
453              
454             1;