File Coverage

blib/lib/Config/Extend/MySQL.pm
Criterion Covered Total %
statement 72 85 84.7
branch 26 38 68.4
condition 9 13 69.2
subroutine 14 18 77.7
pod 1 1 100.0
total 122 155 78.7


line stmt bran cond sub pod time code
1             package Config::Extend::MySQL;
2 2     2   5739 use 5.006; # read the CAVEATS if you really want a version that works on 5.5
  2         7  
  2         77  
3 2     2   11 use strict;
  2         7  
  2         73  
4 2     2   20 use warnings;
  2         3  
  2         54  
5 2     2   10 use Carp;
  2         3  
  2         159  
6 2     2   12 use File::Basename qw(dirname);
  2         4  
  2         153  
7 2     2   738 use File::Spec::Functions qw(catfile rel2abs);
  2         721  
  2         336  
8 2     2   2272 use File::Read;
  2         38386  
  2         16  
9 2     2   1246 use UNIVERSAL::require;
  2         1414  
  2         21  
10              
11              
12             {
13 2     2   53 no strict "vars";
  2         5  
  2         83  
14             $VERSION = '0.05';
15             }
16              
17 2     2   11 use constant USE_IO_STRING => $] <= 5.008;
  2         4  
  2         2137  
18              
19             my %skip;
20              
21              
22             =head1 NAME
23              
24             Config::Extend::MySQL - Extend your favourite .INI parser module to read MySQL configuration file
25              
26             =head1 VERSION
27              
28             Version 0.05
29              
30             =head1 SYNOPSIS
31              
32             use Config::Extend::MySQL;
33              
34             # read MySQL config using Config::IniFiles
35             my $config = Config::Extend::MySQL->new({ from => $file, using => "Config::IniFiles" });
36              
37             # read MySQL config using Config::Tiny
38             my $config = Config::Extend::MySQL->new({ from => $file, using => "Config::Tiny" });
39              
40             # use the resulting object as you usually do
41             ...
42              
43              
44             =head1 DESCRIPTION
45              
46             This module extends other C modules so they can read MySQL
47             configuration files. It works by slurping and preprocessing the files
48             before letting your favourite C module parse the result.
49              
50             Currently supported modules are C, C
51             and C.
52              
53             =head2 Rationale
54              
55             This module was written out of a need of reading MySQL configuration
56             files from random machines. At first, the author thought they were just
57             classical C<.INI> files, but soon discovered that they include additional
58             features like C and C, and bare boolean options,
59             which without surprise make most common modules choke or die.
60              
61             Hence this module which simply slurps all the files, recursing though the
62             C and C directives, inlining their content in
63             memory, and transforms the bare boolean options into explicitly assigned
64             options.
65              
66             As to why this module extends other modules instead of being on its own,
67             it's because the author was too lazy to think of yet another API and
68             preferred to use the modules he already know. And given he use several
69             of them, depending on the context, it was just as easy to avoid being
70             too thighly coupled to a particular module.
71              
72              
73             =head1 METHODS
74              
75             =head2 new()
76              
77             Create and return an object
78              
79             B
80              
81             my $config = Config::Extend::MySQL->new({ from => $file, using => $module });
82              
83             B
84              
85             =over
86              
87             =item *
88              
89             C - the path to the main MySQL configuration file
90              
91             =item *
92              
93             C - the module name to use as backend for parsing the configuration file
94              
95             =back
96              
97             B
98              
99             # read MySQL config using Config::IniFiles
100             my $config = Config::Extend::MySQL->new({ from => $file, using => "Config::IniFiles" });
101             # $config ISA Config::Extend::MySQL, ISA Config::IniFiles
102              
103             # read MySQL config using Config::Tiny
104             my $config = Config::Extend::MySQL->new({ from => $file, using => "Config::Tiny" });
105             # $config ISA Config::Extend::MySQL, ISA Config::Tiny
106              
107             =cut
108              
109             sub new {
110 16     16 1 35301 my ($class, $args) = @_;
111              
112 16 100       242 croak "error: Arguments must be given as a hash reference"
113             unless ref $args eq "HASH";
114 15 100       194 croak "error: Missing required argument 'from'"
115             unless exists $args->{from};
116 14 100 100     545 croak "error: Empty argument 'from'"
117             unless defined $args->{from} and length $args->{from};
118              
119             # check that the file exists and contains something
120 12         22 my $file = $args->{from};
121 12 100       645 croak "fatal: No such file '$file'" unless -f $file;
122 11 100 50     300 carp "warning: File '$file' is empty" and return if -s _ == 0;
123              
124             # read the file and resolve the MySQL-isms
125 10         26 my $content = __read_config(file => $file);
126              
127 10         20 my $fh = undef;
128 10         12 if (USE_IO_STRING) {
129             require IO::String;
130             $fh = IO::String->new(\$content);
131             }
132             else {
133 10 50   1   142 open($fh, "<", \$content)
  1         11  
  1         1  
  1         8  
134             or croak "fatal: Can't read in-memory buffer: $!";
135             }
136              
137             # create the object using the given Config:: module
138 10 50       1860 my $backend = defined $args->{using} ? $args->{using} : "Config::Tiny";
139 10 100       89 $backend->require or croak "fatal: Can't load module $args->{using}: $@";
140 9         376 @Config::Extend::MySQL::ISA = ($backend);
141 9 50       31 my $self = __new_from($backend, $fh, \$content)
142             or croak "fatal: Backend module failed to parse '$file'";
143 9         977 bless $self, $class;
144              
145             # store the names to skip when reading directories
146 9         21 my @skip_names = qw(. .. CVS);
147 9         30 @skip{@skip_names} = (1) x @skip_names;
148              
149 9         54 return $self
150             }
151              
152              
153             sub __new_from {
154 9     9   15 my ($backend, $fh, $content_r) = @_;
155              
156 9 50 33     65 if ($backend eq "Config::IniFiles") {
    50          
    50          
    50          
157 0     0   0 local $SIG{__WARN__} = sub {}; # avoid a warning from stat() on this $fh
  0         0  
158 0     0   0 local *IO::String::FILENO = sub { -1 };
  0         0  
159 0         0 return Config::IniFiles->new(-file => $fh)
160             }
161             elsif ($backend eq "Config::Format::Ini") {
162 0     0   0 local $SIG{__WARN__} = sub {}; # avoid "slurp redefined" warning
  0         0  
163 0     0   0 local *Config::Format::Ini::slurp = sub { return ${$_[0]} };
  0         0  
  0         0  
164 0         0 return Config::Format::Ini::read_ini($content_r)
165             }
166             elsif ($backend eq "Config::Simple") {
167             # can't get Config::Simple to play nicely because it want to
168             # seek() and flock() the filehandle. seek() works on in-memory
169             # filehandles, but flock() doesn't, and can't be faked/replaced
170              
171             #my $obj = Config::Simple->new(syntax => "ini");
172             #$obj->{_DATA} = $obj->parse_ini_file($fh);
173             #return $obj
174              
175 0         0 return Config::Simple->new($fh)
176             }
177             elsif ($backend eq "Config::Tiny" or $backend eq "Config::INI::Reader") {
178 9         35 return $backend->read_string($$content_r)
179             }
180             }
181              
182              
183             sub __read_config {
184 21     21   266 my ($what, $path) = @_;
185 21         30 my $content = "";
186 21         31 my $opts = {}; #{ err_mode => "quiet" };
187              
188 21 100       54 if ($what eq "file") {
    50          
189 19         871 my $base_dir = dirname($path);
190 19         65 $content = read_file($opts, $path);
191              
192             # handle single param (without value)
193 19         4123 $content =~ s{^ \s* (\w+ (?:-\w+)* ) \s* $}{$1 = yes}xgm;
194              
195             # handle includes
196 19         72 $content =~ s{^ \s* !include(dir)? \s+ (.+) \s* $}
197 5   100     39 { __read_config($1 || "file", rel2abs($2, $base_dir)) }xgme;
198             }
199             elsif ($what eq "dir") {
200 2 50       124 opendir(my $dirh, $path) or return "";
201              
202 2         38 while (my $file = readdir($dirh)) {
203             # skip invisible files and directories we shouldn't
204             # recurse into, like ../ or CVS/
205 10 100 66     84 next if $skip{$file} or index($file, ".") == 0;
206              
207 6         41 my $filepath = catfile($path, $file);
208              
209 6 50       151 if (-f $filepath) {
    0          
210 6         19 $content .= __read_config(file => $filepath)
211             }
212             elsif (-d _) {
213 0         0 $content .= __read_config(dir => $filepath)
214             }
215             }
216              
217 2         28 closedir($dirh);
218             }
219              
220 21         125 return $content
221             }
222              
223              
224             =head1 DIAGNOSTICS
225              
226             =over
227              
228             =item C
229              
230             B<(E)> As the message says, the arguments must be given to the
231             function or method as a hash reference.
232              
233             =item C
234              
235             B<(F)> The backend module was unable to parse the given file.
236             See L<"CAVEATS"> for some hints.
237              
238             =item C
239              
240             B<(F)> The backend module could not be loaded.
241              
242             =item C
243              
244             B<(F)> This should not happen.
245              
246             =item C
247              
248             B<(E)> The given argument was empty, but a value is required.
249              
250             =item C
251              
252             B<(W)> The file is empty.
253              
254             =item C
255              
256             B<(E)> You forgot to supply a mandatory argument.
257              
258             =item C
259              
260             B<(F)> The given path does not point to an existing file.
261              
262             =back
263              
264              
265             =head1 CAVEATS
266              
267             The different supported modules don't parse C<.INI> files exactly the
268             same ways, and have different behaviours:
269              
270             =over
271              
272             =item *
273              
274             C doesn't want to create an object from an empty file.
275              
276             =item *
277              
278             C by default doesn't allow the pound sign (C<#>)
279             for beginning comments.
280              
281             =item *
282              
283             when assigning the same option twice, C replaces the old
284             value with the new one, C appends it with a newline.
285              
286             =back
287              
288             And probably many more.
289              
290             Also note that in order to keep the code simple, this module wants
291             Perl 5.6 or newer. However, a patch to make it work on Perl 5.5.3 is
292             included in the distribution (F).
293              
294              
295             =head1 SEE ALSO
296              
297             L
298              
299             L
300              
301             L
302              
303              
304             =head1 AUTHOR
305              
306             SEbastien Aperghis-Tramoni, C<< >>
307              
308             =head1 BUGS
309              
310             Please report any bugs or feature requests
311             to C, or through the web interface
312             at L.
313             I will be notified, and then you'll automatically be notified of
314             progress on your bug as I make changes.
315              
316              
317             =head1 SUPPORT
318              
319             You can find documentation for this module with the perldoc command.
320              
321             perldoc Config::Extend::MySQL
322              
323              
324             You can also look for information at:
325              
326             =over 4
327              
328             =item * RT: CPAN's request tracker
329              
330             L
331              
332             =item * AnnoCPAN: Annotated CPAN documentation
333              
334             L
335              
336             =item * CPAN Ratings
337              
338             L
339              
340             =item * Search CPAN
341              
342             L
343              
344             =back
345              
346              
347             =head1 COPYRIGHT & LICENSE
348              
349             Copyright 2008 SEbastien Aperghis-Tramoni, all rights reserved.
350              
351             This program is free software; you can redistribute it and/or modify it
352             under the same terms as Perl itself.
353              
354              
355             =cut
356              
357             1; # End of Config::Extend::MySQL