File Coverage

blib/lib/OpenInteract/Config/GlobalOverride.pm
Criterion Covered Total %
statement 9 142 6.3
branch 0 78 0.0
condition 0 19 0.0
subroutine 3 13 23.0
pod 0 5 0.0
total 12 257 4.6


line stmt bran cond sub pod time code
1             package OpenInteract::Config::GlobalOverride;
2              
3             # $Id: GlobalOverride.pm,v 1.8 2002/04/25 12:36:36 lachoy Exp $
4              
5 1     1   5 use strict;
  1         1  
  1         27  
6 1     1   5 use OpenInteract::Config;
  1         2  
  1         28  
7              
8 1     1   4 use constant DEBUG => 0;
  1         1  
  1         2118  
9              
10             ########################################
11             # CLASS METHODS
12              
13             sub new {
14 0     0 0   my ( $class, $params ) = @_;
15 0           my $self = bless( {}, $class );
16 0           DEBUG && warn "Creating new override object\n";
17 0           return $self->_read_rules( $params );
18             }
19              
20              
21             sub break_key {
22 0     0 0   my ( $class, $key ) = @_;
23 0 0         unless ( $key ) {
24 0           die "Given rule does not have key, not processing\n";
25             }
26 0           return split /\./, $key;
27             }
28              
29              
30             ########################################
31             # OBJECT METHODS
32              
33             # \%params should have either 'filename' or 'content' defined.
34             # We're not using our INI reader because a) this is simpler and b) we
35             # need to allow multiple actions per key
36              
37             sub _read_rules {
38 0     0     my ( $self, $params ) = @_;
39 0           my ( $lines );
40 0 0         if ( $params->{filename} ) {
    0          
41 0           $lines = OpenInteract::Config->read_file( $params->{filename} );
42             }
43             elsif ( $params->{content} ) {
44 0           $lines = [ split /\n/, $params->{content} ];
45             }
46             else {
47 0           die "Cannot read override rules without 'filename' or ",
48             "'content' being defined\n";
49             }
50              
51 0           my @rules = ();
52 0           my ( $current_section, $current_rule );
53 0           for ( @{ $lines } ) {
  0            
54 0           chomp;
55 0           s/\r//g;
56 0 0         next if ( /^\s*$/ );
57 0 0         next if ( /^\s*\#/ );
58 0           s/\s+$//;
59 0           s/^\s+//;
60              
61             # Encountered a key -- if we have a section/rule saved, stick
62             # that into our rule list and reset the section.
63              
64 0 0         if ( /^\s*\[\s*(\S|\S.*\S)\s*\]\s*$/) {
65 0 0 0       if ( $current_section and $current_rule ) {
66 0           push @rules, $current_rule;
67             }
68 0           $current_section = $1;
69 0           $current_rule = { key => $current_section };
70 0           next;
71             }
72              
73             # Otherwise, we should have a key/value pair. If a value
74             # already exists for that key, make it an arrayref. (Future:
75             # make all values arrayrefs)
76              
77 0           my ( $param, $value ) = /^\s*([^=]+?)\s*=\s*(.*)\s*$/;
78 0           my $existing = $current_rule->{ $param };
79 0 0 0       if ( $existing and ref $existing eq 'ARRAY' ) {
    0          
80 0           push @{ $current_rule->{ $param } }, $value;
  0            
81             }
82             elsif ( $existing ) {
83 0           $current_rule->{ $param } = [ $existing, $value ];
84             }
85             else {
86 0           $current_rule->{ $param } = $value;
87             }
88             }
89              
90             # Stick the last rule into our rule list and set into the object
91              
92 0           push @rules, $current_rule;
93 0           $self->rules( \@rules );
94              
95 0           return $self;
96             }
97              
98              
99             # Get/set for the override rules
100              
101             sub rules {
102 0     0 0   my ( $self, $rules ) = @_;
103 0 0         if ( $rules ) { $self->{_rules} = $rules; }
  0            
104 0           return $self->{_rules};
105             }
106              
107              
108             # Get an arrayref of override keys.
109              
110             sub override_keys {
111 0     0 0   my ( $self ) = @_;
112 0           return [ map { $_->{key} } @{ $self->{_rules} } ];
  0            
  0            
113             }
114              
115              
116             # Main method: apply the set of override rules to a passed-in
117             # configuration
118              
119             sub apply_rules {
120 0     0 0   my ( $self, $config ) = @_;
121 0           foreach my $rule ( @{ $self->rules } ) {
  0            
122 0 0 0       next unless ( ref $rule eq 'HASH' and keys %{ $rule } );
  0            
123              
124             # For the processors: put the key inside the rule and ensure
125             # that 'value' is always an arrayref
126              
127 0 0         $rule->{value} = ( ref $rule->{value} eq 'ARRAY' )
128             ? $rule->{value}
129             : [ $rule->{value} ];
130              
131             # Process this rule
132              
133 0 0         if ( $rule->{action} eq 'add' ) {
    0          
    0          
134 0           $self->_key_iterate( $rule, $config,
135             { last_key => \&_add_action,
136             autovifify => 'yes' } );
137             }
138             elsif ( $rule->{action} eq 'remove' ) {
139 0           $self->_key_iterate( $rule, $config,
140             { last_key => \&_remove_action,
141             autovivify => 'no' } );
142             }
143             elsif ( $rule->{action} eq 'replace' ) {
144 0 0         unless ( $rule->{replace} ) {
145 0           die "Rule 'replace' for the key [$rule->{key}] ",
146             "must have a value for the 'replace' key.\n";
147             }
148 0           $self->_key_iterate( $rule, $config,
149             { last_key => \&_replace_action,
150             autovifify => 'no' } );
151             }
152             }
153             }
154              
155              
156             # Split apart the key in $rule->{key} and traverse $config; once we've
157             # reached the last key (where we should do something), execute the
158             # callback passed in $params->{last_key}. Caller should also specify
159             # whether we should autovifify keys as we traverse $config if a key
160             # isn't found -- 'yes' we should, 'no' we die.
161              
162             sub _key_iterate {
163 0     0     my ( $self, $rule, $config, $params ) = @_;
164              
165 0           my @keys = $self->break_key( $rule->{key} );
166 0 0         unless ( scalar @keys ) {
167 0           die "No keys found from [$rule->{key}]\n";
168             }
169              
170 0           my $item = $config;
171 0           my $num_keys = scalar @keys;
172              
173 0           for ( my $i = 0; $i < $num_keys; $i++ ) {
174 0           my $key = $keys[ $i ];
175              
176             # If the top-level key doesn't exist and there's more than one
177             # key then we don't do anything. This means we shouldn't
178             # autovivify top-level configuration items.
179              
180 0 0 0       if ( $i == 0 and $num_keys > 1 and ! $item->{ $key } ) {
      0        
181 0           DEBUG && warn "Skipping [$rule->{ $key }] since the top level ",
182             "doesn't exist and there are [$num_keys] keys\n";
183 0           last;
184             }
185              
186             # Run the last key action
187              
188 0 0         if ( $i == $num_keys - 1 ) {
189 0           $params->{last_key}->( $rule, $item, $key );
190 0           next;
191             }
192              
193             # Otherwise climb down...
194              
195             # if we're supposed to autovivify, create the key to climb
196             # down, otherwise die
197              
198 0 0         unless ( $item->{ $key } ) {
199 0 0         if ( $params->{autovifify} eq 'yes' ) {
200 0           $item->{ $key } = {};
201             }
202             else {
203 0           die "The key specified in '$rule->{action}' for ",
204             "[$rule->{key}] must already exist. (Nothing ",
205             "for [$key])\n";
206             }
207             }
208 0           $item = $item->{ $key };
209             }
210             }
211              
212              
213             # Action to execute when we find the last key for an 'add'
214              
215             sub _add_action {
216 0     0     my ( $rule, $item, $key ) = @_;
217 0           DEBUG && warn "Adding to [$key]: ", join( ', ', @{ $rule->{value} } ), "\n";
218 0           my $type = $rule->{type};
219 0 0         unless ( $type ) {
220 0 0         $type = 'list' if ( ref $item->{ $key } eq 'ARRAY' );
221 0 0         $type = 'hash' if ( ref $item->{ $key } eq 'HASH' );
222             }
223              
224 0 0         unless ( $item->{ $key } ) {
225 0 0         $item->{ $key } = [] if ( $type eq 'list' );
226             }
227              
228 0 0         if ( $type eq 'list' ) {
229 0 0         unless ( ref $item->{ $key } eq 'ARRAY' ) {
230 0 0         $item->{ $key } = ( defined $item->{ $key } )
231             ? [ $item->{ $key } ] : [];
232             }
233 0   0       my $queue = $rule->{queue} || 'back';
234 0 0         if ( $queue eq 'front' ) {
235 0           unshift @{ $item->{ $key } }, @{ $rule->{value} };
  0            
  0            
236             }
237             else {
238 0           push @{ $item->{ $key } }, @{ $rule->{value} };
  0            
  0            
239             }
240             }
241             else {
242 0           $item->{ $key } = $rule->{value}[0];
243             }
244             }
245              
246              
247             # Action to execute when we find the last key for a 'remove'
248              
249             sub _remove_action {
250 0     0     my ( $rule, $item, $key ) = @_;
251 0           DEBUG && warn "Removing from [$key]: ", join( ', ', @{ $rule->{value} } ), "\n";
252 0 0         unless ( $item->{ $key } ) {
253 0           delete $item->{ $key };
254 0           return;
255             }
256 0           my $type = $rule->{type};
257 0 0         unless ( $type ) {
258 0 0         $type = 'list' if ( ref $item->{ $key } eq 'ARRAY' );
259 0 0         $type = 'hash' if ( ref $item->{ $key } eq 'HASH' );
260 0   0       $type ||= 'scalar';
261             }
262              
263             # If there are no values, just delete the key entirely
264              
265 0 0         unless ( $rule->{value}[0] ) {
266 0           delete $item->{ $key };
267 0           return;
268             }
269              
270             # Otherwise cycle through the values and do the right thing
271              
272 0           foreach my $value ( @{ $rule->{value} } ) {
  0            
273 0 0         if ( $type eq 'list' ) {
    0          
274 0           $item->{ $key } = [ grep { $_ ne $value }
  0            
275 0           @{ $item->{ $key } } ];
276             }
277             elsif ( $type eq 'hash' ) {
278 0           delete $item->{ $key }{ $value };
279             }
280             else {
281 0           delete $item->{ $key };
282             }
283             }
284             }
285              
286             # Action to execute when we find the last key for a 'replace'
287              
288             sub _replace_action {
289 0     0     my ( $rule, $item, $key ) = @_;
290 0 0         unless ( ref $item->{ $key } eq 'ARRAY' ) {
291 0           die "The rule 'replace' can only be applied to lists. ",
292             "The value in the key [$rule->{key}] is not a list.\n";
293             }
294             DEBUG && warn "Replacing from [$key]: [$rule->{replace}] with [",
295             join( ', ', @{ $rule->{value} } ), "] with existing ",
296 0           "values [", join( ', ', @{ $item->{ $key } } ), "]\n";
297 0           my @new_list = ();
298 0           foreach my $existing ( @{ $item->{ $key } } ) {
  0            
299 0 0         if ( $existing eq $rule->{replace} ) {
300 0           push @new_list, @{ $rule->{value} };
  0            
301             }
302             else {
303 0           push @new_list, $existing;
304             }
305             }
306 0           DEBUG && warn "Resulting replaced values [",
307             join( ', ', @new_list ), "]\n";
308 0           $item->{ $key } = \@new_list;
309             }
310              
311             1;
312              
313             __END__