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   185309 use parent 'Apache::Admin::Config';
  24         205  
  24         157  
3 24     24   520058 use strict;
  24         61  
  24         577  
4 24     24   119 use warnings;
  24         46  
  24         661  
5 24     24   134 use Carp;
  24         45  
  24         1553  
6 24     24   11944 use version 0.77;
  24         53287  
  24         2000  
7              
8             our $VERSION = '1.07';
9              
10             sub import {
11 33     33   912906 my $class = shift;
12 33 50       205 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       160 if (@_) {
22 0         0 croak "Too many import parameters";
23             }
24 33         35835 $class->SUPER::import();
25             }
26              
27             sub new {
28 31     31 1 185760 my $class = shift;
29 31         81 my $file = shift;
30 31   100     198 my $explist = Apache::Admin::Config::Tree::_get_arg(\@_, '-expand')
31             || [ qw(include) ];
32              
33 31 50       1255 my $self = $class->SUPER::new($file, @_) or return;
34 31         25282 bless $self, $class;
35 31         116 $self->{_filename} = $file;
36 31         102 $self->{_options} = \@_;
37              
38 31         67 eval {
39 31         239 $self->_preproc($explist);
40             };
41 31 100       497 if ($@) {
42 1         2 $Apache::Admin::Config::ERROR = $@;
43 1         10 return;
44             }
45            
46 30         143 return $self;
47             }
48              
49 9     9 1 32 sub filename { shift->{_filename} }
50              
51             sub dequote {
52 28     28 0 156 my ($self, $str) = @_;
53 28 100       195 if ($str =~ s/^"(.*)"$/$1/) {
54 10         30 $str =~ s/\\"/"/g;
55             }
56 28         80 return $str;
57             }
58              
59 22     22 1 28 sub options { @{shift->{_options}} }
  22         119  
60              
61             sub _preproc {
62 31     31   100 my ($self, $explist) = @_;
63              
64             $self->_preproc_section($self,
65             [ map {
66 31         109 my ($mod,@arg);
  41         337  
67 41 100       198 if (ref($_) eq 'HASH') {
    50          
68 3         28 ($mod,my $ref) = each %$_;
69 3         11 @arg = @$ref;
70             } elsif (ref($_) eq 'ARRAY') {
71 0         0 @arg = @$_;
72 0         0 $mod = shift @arg;
73             } else {
74 38         170 $mod = $_;
75             }
76 41         119 $mod = 'Apache::Config::Preproc::'.$mod;
77 41         250 (my $file = $mod) =~ s|::|/|g;
78 41         14622 require $file . '.pm';
79 41         352 $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   394 my ($self, $section, $modlist) = @_;
148              
149 240 50       430 return unless @$modlist;
150              
151 240         624 $_->begin_section($section) foreach (@$modlist);
152             OUTER:
153 240         749 for (my $i = 0; defined(my $d = $section->get_nth($i)); ) {
154 736         1048 foreach my $mod (@$modlist) {
155 2037 100       6030 if ($mod->expand($d, \my @repl)) {
156 229         1220 $section->replace_inplace($i, @repl);
157 229         1465 next OUTER;
158             }
159 1807 100       5003 if ($d->type eq 'section') {
160 209         857 $self->_preproc_section_optimized($d, $modlist);
161             }
162             }
163 506         2307 $i++;
164             }
165 239         647 $_->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   76 my $self = shift;
186 31 100       401 unless ($self->can('_preproc_section_internal')) {
187 24 50 0     496 if ((version->parse($Apache::Admin::Config::VERSION) == version->parse('0.95')
      33        
188             || (exists($self->{children}) && ref($self->{tree}{children}) eq 'ARRAY'))) {
189 24         93 install_preproc_optimized()
190             } else {
191 0         0 install_preproc_default()
192             }
193             }
194 31         536 $self->_preproc_section_internal(@_);
195             }
196              
197             sub install_preproc_optimized {
198 24     24   47158 no warnings 'once';
  24         2096  
  24         6338  
199             *{Apache::Admin::Config::Tree::get_nth} = sub {
200 975     975   4314 my ($self, $n) = @_;
201 975 100       1018 if ($n < @{$self->{children}}) {
  975         2505  
202 736         1789 return $self->{children}[$n];
203             }
204             return undef
205 239     24 0 495 };
  24         206  
206             *{Apache::Admin::Config::Tree::replace_inplace} = sub {
207 229     229   1958 my ($self, $n, @items) = @_;
208 229         593 splice @{$self->{children}}, $n, 1,
209 229         354 map { $_->{parent} = $self; $_ } @items;
  234         351  
  234         572  
210 24         127 };
211              
212 24         129 *{_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__