File Coverage

lib/Class/ConfigHash.pm
Criterion Covered Total %
statement 30 34 88.2
branch 13 16 81.2
condition 6 7 85.7
subroutine 5 6 83.3
pod n/a
total 54 63 85.7


line stmt bran cond sub pod time code
1             package Class::ConfigHash;
2             {
3             $Class::ConfigHash::VERSION = '0.001';
4             }
5              
6 2     2   142592 use strict;
  2         5  
  2         69  
7 2     2   10 use warnings;
  2         4  
  2         59  
8 2     2   11 use Carp qw/croak/;
  2         8  
  2         848  
9              
10             =head1 NAME
11              
12             Class::ConfigHash - Lazily turn multi-level hashes of configuration data in to objects with error catching and defaults
13              
14             =head1 VERSION
15              
16             version 0.001
17              
18             =head1 DESCRIPTION
19              
20             Lazily turn multi-level hashes of configuration data in to objects with error catching and defaults
21              
22             =head1 SYNOPSIS
23              
24             my $config = Class::ConfigHash->_new({
25             database => {
26             user => 'rodion',
27             pass => 'bonaparte',
28             options => {
29             city => 'St Petersburg'
30             },
31             },
32             });
33              
34             $config->database->options->city; # St Petersburg
35              
36             # Dies: Can't find 'flags' at [/->database]. Options: [options; pass; user]
37             $config->database->flags;
38              
39             # Won't die, returns undef
40             $config->database->flags({ allow_undef => 1 });
41              
42             # Won't die, returns 'foo'
43             $config->database->flags({ default => 'foo' });
44              
45             # Access the underlying structure
46             $config->database({ raw => 1 })->{'user'} = 'raskolnikov';
47              
48             =head1 METHODS
49              
50             =head2 _new
51              
52             Instantiates a new object. Preceeding underscore to stop collisions on hash
53             keys. Accepts a hashref and an ArrayRef of strings, representing the depth
54             that this hash is found at (defaults to C<['/']>).
55              
56             You will probably never need to specify the depth yourself - instead:
57              
58             my $config = Class::ConfigHash->_new( $hashref );
59              
60             =cut
61              
62             sub _new {
63 7     7   26 my ( $class, $hash, $path ) = @_;
64             # Allow instantiation from existing object
65 7 100       20 $class = ref $class if ref $class;
66             # Default path to be the root
67 7   100     19 $path ||= ['/'];
68              
69 7         74 bless {
70             '_raw' => $hash,
71             'path' => [@$path], # Shallow copy
72             }, $class;
73             }
74              
75             =head2 Auto-created methods generated for each hash key
76              
77             Every other method call tries to lookup the method name as a hashkey.
78              
79             # Logically looks up ->{'configuration'}->{'database'}->{'host'} in wrapped hash
80             my $host = $obj->configuration->database->host;
81              
82             When a key doesn't exist a fatal error with helpful advice is thrown.
83              
84             You can pass in some options as a hashref:
85              
86             C - Boolean - returns the item at the key, rather than attempting to wrap it
87              
88             C - Boolean - returns undef rather than throwing an error if key
89             doesn't exist
90              
91             C - Any value - returns this value rather than throwing an error if key doesn't exist.
92              
93             eg:
94              
95             # Don't get upset if host doesn't exist
96             $obj->configuration->database->host({ allow_undef => 1 })
97              
98             =cut
99              
100             sub AUTOLOAD {
101 12     12   68 my $self = shift;
102 12   100     50 my $options = shift || {};
103              
104 12         14 our $AUTOLOAD;
105 12         17 my $name = $AUTOLOAD;
106 12         47 $name =~ s/.*://; # strip fully-qualified portion
107              
108             # If they're calling methods on the classname, rather than an object
109 12 50       35 unless ( ref($self) ) {
110              
111             # Most common case is they use new() instead of _new(), so catch that
112 0 0       0 if ( $name eq 'new' ) {
113 0         0 croak "The Class::ConfigHash instantiator is called _new(), not new()";
114              
115             # Otherwise just chastise them for their clumsiness
116             } else {
117 0         0 croak "You called [$name] on Config::ConfigHash, rather than an instance of it";
118             }
119             }
120              
121             # In the case where we can't find what they're pointing at
122 12 100       42 unless ( exists $self->{'_raw'}->{$name} ) {
123              
124             # Return the default if one was specified
125 3 100       15 return $options->{'default'} if exists $options->{'default'};
126              
127             # Return undef if they've said that's ok
128 2 100       9 return undef if $options->{'allow_undef'};
129              
130             # Otherwise die, telling the user what they could have used instead
131 1         28 croak( sprintf("Can't find '%s' at [%s]. Options: [%s]",
132             $name,
133 1         37 ( join '->', @{ $self->{'path'} } ),
134 1         2 ( join '; ', sort keys %{ $self->{'_raw'} } )
135             ));
136             }
137              
138             # Grab the item they were after
139 9         18 my $item = $self->{'_raw'}->{$name};
140              
141             # Regardless of its type, if they want the raw version, give it to them
142 9 100       167 return $item if $options->{'raw'};
143              
144             # If they've asked for a value that's a hashref, we create that in to this
145             # class and release.
146 8 100 66     39 return $self->_new( $item, [@{$self->{'path'}}, $name ] )
  6         23  
147             if ref $item && ref $item eq 'HASH';
148              
149             # Otherwise, return whatever we have
150 2         31 return $item;
151              
152             }
153              
154             # Don't want this hitting AUTOLOAD, obv
155 0     0     sub DESTROY {}
156              
157             =head1 SEE ALSO
158              
159             This is pretty similar to L, except it's intended to be simply for
160             configuration hashes, so there's no easy way to set values, there are defaults,
161             and the error message gives you an overview of the different options you might
162             want, and we autobox hashref children.
163              
164             Module inspired by L
165              
166             =head1 AUTHOR
167              
168             Peter Sergeant C - written while working for the excellent
169             L.
170              
171             =cut
172              
173             1;