File Coverage

lib/Badger/Config.pm
Criterion Covered Total %
statement 63 75 84.0
branch 24 34 70.5
condition 5 10 50.0
subroutine 9 11 81.8
pod 3 8 37.5
total 104 138 75.3


line stmt bran cond sub pod time code
1             #========================================================================
2             #
3             # Badger::Config
4             #
5             # DESCRIPTION
6             # A central configuration module.
7             #
8             # AUTHOR
9             # Andy Wardley
10             #
11             #========================================================================
12              
13             package Badger::Config;
14              
15 70     70   2710 use Badger::Debug ':dump debugf';
  70         125  
  70         475  
16             use Badger::Class
17 70         976 version => 0.01,
18             debug => 0,
19             import => 'class',
20             base => 'Badger::Prototype',
21             utils => 'blessed numlike extend',
22             constants => 'HASH ARRAY CODE DELIMITER',
23             auto_can => 'can_configure',
24             alias => {
25             init => \&init_config,
26             },
27             messages => {
28             get => 'Cannot fetch configuration item <1>.<2> (<1> is <3>)',
29             no_config => 'No configuration data found for %s',
30 70     70   459 };
  70         119  
31              
32              
33              
34             sub init_config {
35 9     9 0 20 my ($self, $config) = @_;
36 9   100     60 my $data = $self->{ data } = $config->{ data } || { %$config };
37 9         26 my $class = $self->class;
38              
39             # merge all $ITEMS in package variables with those listed in
40             # $config->{ items } and all other $config keys.
41             my $items = $class->list_vars(
42 9         38 ITEMS => delete($config->{ items }), keys %$data
43             );
44              
45 9         13 if (DEBUG) {
46             $self->debug("[$self] $class ITEMS: ", $self->dump_data($items));
47             $self->debug("[$self] $class DATA: ", $self->dump_data($data));
48             }
49              
50             # store hash lookup table marking valid items
51             $items = $self->{ item } = {
52 25         47 map { $_ => 1 }
53             keys %$data,
54 9         61 map { split DELIMITER }
  12         45  
55             @$items
56             };
57              
58             # load up all the configuration items from package variables
59             #
60             # TODO: We need different init rules here with fallbacks. This should
61             # be merged in with the code in Badger::Class::Config, or rather B:C:C
62             # should define a config schema.
63 9         34 foreach my $item (keys %$items) {
64 14 100       27 next if exists $data->{ $item };
65 3   33     16 $data->{ $item } = $config->{ $item }
66             || $class->any_var( uc $item );
67 3         6 $self->debug("config set $item => ", $data->{ $item }, "\n") if DEBUG;
68             }
69              
70 9         13 if (DEBUG) {
71             $self->debug("config items: ", $self->dump_data($self->{ item }));
72             $self->debug("config data: ", $self->dump_data($self->{ data }));
73             }
74              
75 9         18 return $self;
76             }
77              
78              
79             sub get {
80 25     25 1 78 my $self = shift->prototype;
81 25 100       45 my @names = map { ref $_ eq ARRAY ? @$_ : split /\./ } @_;
  28         110  
82 25         40 my $name = shift @names;
83              
84 25         26 $self->debug(
85             "get: [",
86             join('].[', $name, @names),
87             "]"
88             ) if DEBUG;
89              
90             # fetch the head item
91 25         48 my $data = $self->head($name);
92              
93 25 100       51 if (! defined $data) {
94 10         29 return $self->decline_msg(
95             no_config => $name
96             );
97             }
98              
99             return @names
100 15 100       54 ? $self->dot($name, $data, \@names)
101             : $data;
102             }
103              
104             sub dot {
105 9     9 0 17 my ($self, $name, $data, $dots) = @_;
106 9         16 my @done = ($name);
107 9         13 my ($dot, $last, $method);
108              
109 9         9 $self->debug(
110             "dot: [",
111             join('].[', $name, @$dots),
112             "]"
113             ) if DEBUG;
114              
115              
116             # resolve any dotted paths after the head
117 9         18 foreach $dot (@$dots) {
118             # call any function reference to return a value
119 20 100       44 if (ref $data eq CODE) {
120 3         7 $data = $data->();
121             }
122              
123             CHECK: {
124 20 100       33 if (ref $data eq HASH) {
  20 100       53  
    50          
125 13         23 $data = $data->{ $dot };
126 13         18 last CHECK;
127             }
128             elsif (ref $data eq ARRAY) {
129 5 50       23 if (numlike $dot) {
130 5         8 $data = $data->[$dot];
131 5         8 last CHECK;
132             }
133             # else vmethods?
134             }
135             elsif (blessed $data) {
136 2 50       15 if ($method = $data->can($dot)) {
137 2         4 $data = $method->($dot);
138 2         24 last CHECK;
139             }
140             }
141 0         0 return $self->decline_msg(
142             no_config => join('.', @done, $dot)
143             );
144             }
145              
146 20 50       35 if (! defined $data) {
147 0         0 return $self->decline_msg(
148             no_config => join('.', @done, $dot)
149             );
150             }
151 20         32 push(@done, $dot);
152             }
153              
154 9         54 return $data;
155             }
156              
157             sub head {
158 25     25 0 36 my ($self, $name) = @_;
159             # subclasses can do something more complicated
160 25         80 return $self->{ data }->{ $name };
161             }
162              
163             sub set {
164 0     0 1 0 my $self = shift->prototype;
165 0         0 my $name = shift;
166 0 0       0 my $data = @_ == 1 ? shift : { @_ };
167 0         0 $self->{ data }->{ $name } = $data;
168 0   0     0 $self->{ item }->{ $name } ||= 1;
169 0         0 return $data;
170             }
171              
172             sub data {
173 0     0 0 0 my $self = shift->prototype;
174 0         0 my $data = $self->{ data };
175 0 0       0 extend($data, @_) if @_;
176 0         0 return $data;
177             }
178              
179             sub can_configure {
180 6     6 1 14 my ($self, $name) = @_;
181              
182 6 50       13 $self = $self->prototype unless ref $self;
183              
184 6         6 $self->debug("can_configure($name)") if DEBUG;
185              
186             return
187 6 100 66     18 unless $name && $self->has_item($name);
188              
189             return sub {
190 7 50   7   27 return @_ > 1
191             ? shift->set( $name => @_ ) # set
192             : shift->get( $name );
193 5         35 };
194             }
195              
196             sub has_item {
197 6     6 0 17 my $self = shift->prototype;
198 6         9 my $name = shift;
199 6         11 my $item = $self->{ item }->{ $name };
200 6 100       13 if (defined $item) {
201             # A 1/0 entry in the item tells us if an item categorically does or
202             # doesn't exist in the config data set (or allowable set - it might
203             # be a valid configuration option that simply hasn't been set yet)
204 5         17 return $item;
205             }
206             else {
207             # Otherwise the existence (or not) of an item in the data set is
208             # enough to satisfy us one way or another
209 1         8 return exists $self->{ data }->{ $name };
210             }
211             }
212              
213              
214             1;
215              
216             __END__