File Coverage

lib/Apache/Config/Preproc.pm
Criterion Covered Total %
statement 83 111 74.7
branch 20 38 52.6
condition 3 8 37.5
subroutine 17 20 85.0
pod 3 6 50.0
total 126 183 68.8


line stmt bran cond sub pod time code
1             package Apache::Config::Preproc;
2 24     24   174745 use parent 'Apache::Admin::Config';
  24         217  
  24         158  
3 24     24   487100 use strict;
  24         55  
  24         521  
4 24     24   118 use warnings;
  24         40  
  24         632  
5 24     24   116 use Carp;
  24         39  
  24         1429  
6 24     24   10184 use version 0.77;
  24         49396  
  24         1828  
7              
8             our $VERSION = '1.06';
9              
10             sub import {
11 33     33   867002 my $class = shift;
12 33 50       202 if (defined(my $kw = shift)) {
13 0 0       0 if ($kw eq ':default') {
    0          
14 0         0 install_preproc_default()
15             } elsif ($kw eq ':optimized') {
16 0         0 install_preproc_optimized()
17             } else {
18 0         0 croak "Unrecognized import parameter: $kw"
19             }
20             }
21 33 50       156 if (@_) {
22 0         0 croak "Too many import parameters";
23             }
24 33         33661 $class->SUPER::import();
25             }
26              
27             sub new {
28 31     31 1 168284 my $class = shift;
29 31         71 my $file = shift;
30 31   100     182 my $explist = Apache::Admin::Config::Tree::_get_arg(\@_, '-expand')
31             || [ qw(include) ];
32              
33 31 50       1229 my $self = $class->SUPER::new($file, @_) or return;
34 31         24130 bless $self, $class;
35 31         106 $self->{_filename} = $file;
36 31         100 $self->{_options} = \@_;
37              
38 31         61 eval {
39 31         228 $self->_preproc($explist);
40             };
41 31 100       407 if ($@) {
42 1         3 $Apache::Admin::Config::ERROR = $@;
43 1         8 return;
44             }
45            
46 30         113 return $self;
47             }
48              
49 9     9 1 29 sub filename { shift->{_filename} }
50              
51             sub dequote {
52 28     28 0 170 my ($self, $str) = @_;
53 28 100       174 if ($str =~ s/^"(.*)"$/$1/) {
54 10         42 $str =~ s/\\"/"/g;
55             }
56 28         77 return $str;
57             }
58              
59 22     22 1 30 sub options { @{shift->{_options}} }
  22         109  
60              
61             sub _preproc {
62 31     31   90 my ($self, $explist) = @_;
63              
64             $self->_preproc_section($self,
65             [ map {
66 31         102 my ($mod,@arg);
  41         299  
67 41 100       194 if (ref($_) eq 'HASH') {
    50          
68 3         19 ($mod,my $ref) = each %$_;
69 3         9 @arg = @$ref;
70             } elsif (ref($_) eq 'ARRAY') {
71 0         0 @arg = @$_;
72 0         0 $mod = shift @arg;
73             } else {
74 38         157 $mod = $_;
75             }
76 41         118 $mod = 'Apache::Config::Preproc::'.$mod;
77 41         236 (my $file = $mod) =~ s|::|/|g;
78 41         12594 require $file . '.pm';
79 41         326 $mod->new($self, @arg)
80             } @$explist ]);
81             }
82              
83             # As of version 0.95, the Apache::Admin::Config package provides no
84             # methods for iterating over all configuration file statements, excepting
85             # the select method with the -which => N argument, which returns Nth
86             # statement or undef if N is out of range. This method has two drawbacks:
87             #
88             # 1. It iterates over entire statement tree no matter what arguments are
89             # given (see Apache/Admin/Config.pm, lines 417-439)
90             # 2. It makes unnecessary memory allocations (ibid., line 437).
91             # 3. When N is out of range, the following warning is emitted
92             # in -w mode:
93             # Use of uninitialized value $_[0] in string at
94             # /usr/share/perl5/overload.pm line 119
95             # That's because it unreferences the undefined value and passes it
96             # to the overload::StrVal method (ibid., line 443).
97             #
98             # This means that time complexity of the code below is O(N**2). This is
99             # further aggravated by the fact that no method is provided for inline
100             # modification of the source tree, except for the 'add' method, which again
101             # iterates over entire tree in order to locate the element, after which
102             # the new one should be inserted.
103             #
104             # Thus, the following default implementation of the _preproc_section function
105             # is highly inefficient:
106              
107             sub _preproc_section_default {
108 0     0   0 my ($self, $section, $modlist) = @_;
109              
110 0 0       0 return unless @$modlist;
111              
112 0         0 $_->begin_section($section) foreach (@$modlist);
113             OUTER:
114 0         0 for (my $i = 0;
115             defined(my $d = do {
116             local $SIG{__WARN__} = sub {
117 0     0   0 my $msg = shift;
118 0 0       0 warn "$msg" unless $msg =~ /uninitialized/;
119 0         0 };
120 0         0 $section->select(-which => $i) }); ) {
121 0         0 foreach my $mod (@$modlist) {
122 0 0       0 if ($mod->expand($d, \my @repl)) {
123 0         0 my $prev = $d;
124 0         0 foreach my $r (@repl) {
125 0         0 $prev = $section->add($r, -after => $prev);
126             }
127 0         0 $d->unlink;
128 0         0 next OUTER;
129             }
130 0 0       0 if ($d->type eq 'section') {
131 0         0 $self->_preproc_section_default($d, $modlist);
132             }
133             }
134 0         0 $i++;
135             }
136 0         0 $_->end_section($section) foreach (@$modlist);
137             }
138              
139             # In an attempt to fix the above problems I resort to a kludgy solution,
140             # which directly modifies the Apache::Admin::Config::Tree namespace
141             # and defines two missing functions in it: get_nth(N), which returns
142             # the Nth statement or undef if N is greater than the source tree
143             # length, and replace_inplace(N, A), which replaces the Nth statement
144             # with statements from the array A. With these two methods at hand,
145             # the following implementation is used:
146             sub _preproc_section_optimized {
147 240     240   374 my ($self, $section, $modlist) = @_;
148              
149 240 50       431 return unless @$modlist;
150              
151 240         606 $_->begin_section($section) foreach (@$modlist);
152             OUTER:
153 240         684 for (my $i = 0; defined(my $d = $section->get_nth($i)); ) {
154 736         995 foreach my $mod (@$modlist) {
155 2037 100       5978 if ($mod->expand($d, \my @repl)) {
156 229         1136 $section->replace_inplace($i, @repl);
157 229         1455 next OUTER;
158             }
159 1807 100       5080 if ($d->type eq 'section') {
160 209         859 $self->_preproc_section_optimized($d, $modlist);
161             }
162             }
163 506         2285 $i++;
164             }
165 239         657 $_->end_section($section) foreach (@$modlist);
166             }
167              
168             # The _preproc_section method upon its first invocation selects the
169             # right implementation to use. If the version of the Apache::Admin::Config
170             # module is 0.95 or if the object has attribute {tree}{children} and it is
171             # a list reference, the function installs the two new methods in the
172             # Apache::Admin::Config::Tree namespace and selects the optimized
173             # implementation. Otherwise, the default implementation is used.
174             #
175             # The decision can be forced when requiring the module. To select the
176             # default implementation, do
177             #
178             # use Apache::Config::Preproc qw(:default);
179             #
180             # To select the optimized implementation:
181             #
182             # use Apache::Config::Preproc qw(:optimized);
183             #
184             sub _preproc_section {
185 31     31   70 my $self = shift;
186 31 100       325 unless ($self->can('_preproc_section_internal')) {
187 24 50 0     471 if ((version->parse($Apache::Admin::Config::VERSION) == version->parse('0.95')
      33        
188             || (exists($self->{children}) && ref($self->{tree}{children}) eq 'ARRAY'))) {
189 24         89 install_preproc_optimized()
190             } else {
191 0         0 install_preproc_default()
192             }
193             }
194 31         185 $self->_preproc_section_internal(@_);
195             }
196              
197             sub install_preproc_optimized {
198 24     24   42297 no warnings 'once';
  24         1859  
  24         7251  
199             *{Apache::Admin::Config::Tree::get_nth} = sub {
200 975     975   4209 my ($self, $n) = @_;
201 975 100       1008 if ($n < @{$self->{children}}) {
  975         2426  
202 736         1755 return $self->{children}[$n];
203             }
204             return undef
205 239     24 0 493 };
  24         184  
206             *{Apache::Admin::Config::Tree::replace_inplace} = sub {
207 229     229   1921 my ($self, $n, @items) = @_;
208 229         636 splice @{$self->{children}}, $n, 1,
209 229         288 map { $_->{parent} = $self; $_ } @items;
  234         353  
  234         571  
210 24         127 };
211              
212 24         126 *{_preproc_section_internal} = \&_preproc_section_optimized;
213             }
214              
215             sub install_preproc_default {
216 0     0 0   *{_preproc_section_internal} = \&_preproc_section_default;
217             }
218              
219             1;
220             __END__