[svn-inject] Installing original source of libconfig-merge-perl (1.01)
Nicholas Bamber
13 years ago
0 | use strict; | |
1 | use warnings; | |
2 | use Module::Build; | |
3 | ||
4 | my $builder = Module::Build->new( | |
5 | module_name => 'Config::Merge', | |
6 | license => 'perl', | |
7 | dist_author => q{Clinton Gormley <drtech@cpan.org>}, | |
8 | dist_version_from => 'lib/Config/Merge.pm', | |
9 | build_requires => { 'Test::More' => 0, }, | |
10 | create_makefile_pl => 'traditional', | |
11 | requires => { | |
12 | 'File::Spec' => 0, | |
13 | 'Storable' => 0, | |
14 | 'Config::Any' => '0.07', | |
15 | }, | |
16 | recommends => { | |
17 | 'YAML::Syck' => 0, | |
18 | 'YAML' => 0, | |
19 | 'JSON::XS' => 0, | |
20 | 'JSON' => 0, | |
21 | 'Config::Tiny' => 0, | |
22 | 'XML::Simple' => 0, | |
23 | 'Config::General' => 0 | |
24 | }, | |
25 | meta_merge => { | |
26 | resources => { | |
27 | bugtracker => | |
28 | 'http://github.com/clintongormley/ConfigMerge/issues', | |
29 | repository => 'http://github.com/clintongormley/ConfigMerge' | |
30 | } | |
31 | }, | |
32 | ); | |
33 | ||
34 | $builder->create_build_script(); | |
35 |
0 | 1.01 12 Oct 2010 Removed dependency on Data::Alias - thanks to Graham Barr | |
1 | for the patch. | |
2 | Restructured the module to use the standard CPAN layout | |
3 | with lib/, and switched to Module::Build | |
4 | ||
5 | 1.00 22 Oct 2007 First release of Config::Merge to CPAN. | |
6 | Replaces Config::Loader | |
7 |
0 | ||
1 | Build.PL | |
2 | Changes | |
3 | examples/advanced.pl | |
4 | examples/advanced/app-(dev1).yaml | |
5 | examples/advanced/app-(dev2).yaml | |
6 | examples/advanced/app.yaml | |
7 | examples/browser.pl | |
8 | examples/config_dev/app.yaml | |
9 | examples/config_dev/app/db.yaml | |
10 | examples/config_dev/app/html_filter.yaml | |
11 | examples/config_dev/forms/search.yaml | |
12 | examples/config_dev/forms/user/edit.yaml | |
13 | examples/config_dev/forms/user/forgotten.yaml | |
14 | examples/config_dev/forms/user/password.yaml | |
15 | examples/config_dev/local.yaml | |
16 | examples/config_prod/app.yaml | |
17 | examples/config_prod/app/db.yaml | |
18 | examples/config_prod/app/html_filter.yaml | |
19 | examples/config_prod/forms/search.yaml | |
20 | examples/config_prod/forms/user/edit.yaml | |
21 | examples/config_prod/forms/user/forgotten.yaml | |
22 | examples/config_prod/forms/user/password.yaml | |
23 | examples/func.pl | |
24 | examples/oo.pl | |
25 | examples/README | |
26 | lib/Config/Merge.pm | |
27 | lib/Config/Merge/Perl.pm | |
28 | Makefile.PL | |
29 | MANIFEST This list of files | |
30 | README | |
31 | t/10_oo.t | |
32 | t/20_functional.t | |
33 | t/30_errors.t | |
34 | t/40_advanced.t | |
35 | t/80_pod.t | |
36 | t/81_pod_coverage.t | |
37 | t/data/array/local.pl | |
38 | t/data/array/main.pl | |
39 | t/data/bad/db.pl | |
40 | t/data/empty/.dummy | |
41 | t/data/errors/array_delete_int/local.pl | |
42 | t/data/errors/array_delete_int/main.pl | |
43 | t/data/errors/array_delete_ref/local.pl | |
44 | t/data/errors/array_delete_ref/main.pl | |
45 | t/data/errors/array_insert_int/local.pl | |
46 | t/data/errors/array_insert_int/main.pl | |
47 | t/data/errors/array_insert_ref/local.pl | |
48 | t/data/errors/array_insert_ref/main.pl | |
49 | t/data/errors/array_merge/local.pl | |
50 | t/data/errors/array_merge/main.pl | |
51 | t/data/load_as/dir-(local)/file.pl | |
52 | t/data/load_as/dir/file.pl | |
53 | t/data/load_as/file-(local).pl | |
54 | t/data/load_as/file.pl | |
55 | t/data/load_as/sub/dir-(aaa)/test.pl | |
56 | t/data/load_as/sub/dir-(bbb)/test.pl | |
57 | t/data/load_as/sub/dir/test.pl | |
58 | t/data/load_as/sub/test-(aaa).pl | |
59 | t/data/load_as/sub/test-(bbb).pl | |
60 | t/data/load_as/sub/test.pl | |
61 | t/data/local/email-(aaa).pl | |
62 | t/data/local/email-(bbb).pl | |
63 | t/data/local/email.pl | |
64 | t/data/local/main.pl | |
65 | t/data/local/override.pl | |
66 | t/data/perl/global.perl | |
67 | t/data/perlmulti/global.perl | |
68 | t/data/perlmulti/global/db.pl | |
69 | t/data/perlmulti/global/db2.pl | |
70 | t/data/perlmulti/global/db3.pl | |
71 | t/data/perlmulti/global/local.pl | |
72 | t/data/perlmulti/local.perl | |
73 | t/data/skip/main/skip_1.pl | |
74 | t/data/skip/main/skip_2.pl | |
75 | t/data/skip/main/skip_not.pl | |
76 | t/data/skip/skip_1/test.pl | |
77 | t/data/skip/skip_2/test.pl | |
78 | t/data/skip/skip_not/test.pl | |
79 | META.yml |
0 | --- | |
1 | abstract: "load a configuration directory tree containing\nYAML, JSON, XML, Perl, INI or Config::General files" | |
2 | author: | |
3 | - 'Clinton Gormley <drtech@cpan.org>' | |
4 | build_requires: | |
5 | Test::More: 0 | |
6 | configure_requires: | |
7 | Module::Build: 0.36 | |
8 | generated_by: 'Module::Build version 0.3603' | |
9 | license: perl | |
10 | meta-spec: | |
11 | url: http://module-build.sourceforge.net/META-spec-v1.4.html | |
12 | version: 1.4 | |
13 | name: Config-Merge | |
14 | provides: | |
15 | Config::Merge: | |
16 | file: lib/Config/Merge.pm | |
17 | version: 1.01 | |
18 | Config::Merge::Perl: | |
19 | file: lib/Config/Merge/Perl.pm | |
20 | recommends: | |
21 | Config::General: 0 | |
22 | Config::Tiny: 0 | |
23 | JSON: 0 | |
24 | JSON::XS: 0 | |
25 | XML::Simple: 0 | |
26 | YAML: 0 | |
27 | YAML::Syck: 0 | |
28 | requires: | |
29 | Config::Any: 0.07 | |
30 | File::Spec: 0 | |
31 | Storable: 0 | |
32 | resources: | |
33 | bugtracker: http://github.com/clintongormley/ConfigMerge/issues | |
34 | license: http://dev.perl.org/licenses/ | |
35 | repository: http://github.com/clintongormley/ConfigMerge | |
36 | version: 1.01 |
0 | # Note: this file was auto-generated by Module::Build::Compat version 0.3603 | |
1 | use ExtUtils::MakeMaker; | |
2 | WriteMakefile | |
3 | ( | |
4 | 'NAME' => 'Config::Merge', | |
5 | 'VERSION_FROM' => 'lib/Config/Merge.pm', | |
6 | 'PREREQ_PM' => { | |
7 | 'Config::Any' => '0.07', | |
8 | 'File::Spec' => 0, | |
9 | 'Storable' => 0, | |
10 | 'Test::More' => 0 | |
11 | }, | |
12 | 'INSTALLDIRS' => 'site', | |
13 | 'EXE_FILES' => [], | |
14 | 'PL_FILES' => {} | |
15 | ) | |
16 | ; |
0 | NAME | |
1 | Config::Merge - load a configuration directory tree containing YAML, | |
2 | JSON, XML, Perl, INI or Config::General files | |
3 | ||
4 | SYNOPSIS | |
5 | OO style | |
6 | ------------------------------------------------------- | |
7 | use Config::Merge(); | |
8 | ||
9 | my $config = Config::Merge->new('/path/to/config'); | |
10 | ||
11 | @hosts = $config->('db.hosts.session'); | |
12 | $hosts_ref = $config->('db.hosts.session'); | |
13 | @cloned_hosts = $config->clone('db.hosts.session'); | |
14 | ------------------------------------------------------- | |
15 | ||
16 | OR | |
17 | ||
18 | Functional style | |
19 | ------------------------------------------------------- | |
20 | # On startup | |
21 | use Config::Merge('My::Config' => '/path/to/config'); | |
22 | ||
23 | # Then, in any module where you want to use the config | |
24 | package My::Module; | |
25 | use My::Config; | |
26 | ||
27 | @hosts = C('db.hosts.sesssion'); | |
28 | $hosts_ref = C('db.hosts.sesssion'); | |
29 | @cloned_hosts = My::Config::clone('db.hosts.session'); | |
30 | $config = My::Config::object; | |
31 | ------------------------------------------------------- | |
32 | ||
33 | ADVANCED USAGE | |
34 | ||
35 | OO style | |
36 | ------------------------------------------------------- | |
37 | my $config = Config::Merge->new( | |
38 | path => '/path/to/config', | |
39 | skip => sub {} | regex | {} , | |
40 | is_local => sub {} | regex | {} , | |
41 | load_as => sub {} | regex , | |
42 | sort => sub {} , | |
43 | debug => 1 | 0 | |
44 | ); | |
45 | ------------------------------------------------------- | |
46 | ||
47 | Functional style | |
48 | ------------------------------------------------------- | |
49 | use Config::Merge( | |
50 | 'My::Config' => '/path/to/config', | |
51 | { | |
52 | skip => sub {} | regex | {} , | |
53 | is_local => sub {} | regex | {} , | |
54 | load_as => sub {} | regex , | |
55 | sort => sub {} , | |
56 | debug => 1 | 0 | |
57 | } | |
58 | ); | |
59 | ||
60 | # Also, you can subclass these: | |
61 | ||
62 | package My::Config; | |
63 | sub skip { | |
64 | ... | |
65 | } | |
66 | ||
67 | ------------------------------------------------------- | |
68 | ||
69 | DESCRIPTION | |
70 | Config::Merge is a configuration module which has six goals: | |
71 | ||
72 | * Flexible storage | |
73 | Store all configuration in your format(s) of choice (YAML, JSON, | |
74 | INI, XML, Perl, Config::General / Apache-style config) broken down | |
75 | into individual files in a configuration directory tree, for easy | |
76 | maintenance. See "CONFIG TREE LAYOUT" | |
77 | ||
78 | * Flexible access | |
79 | Provide a simple, easy to read, concise way of accessing the | |
80 | configuration values (similar to Template). See "ACCESSING CONFIG | |
81 | DATA" | |
82 | ||
83 | * Minimal maintenance | |
84 | Specify the location of the configuration files only once per | |
85 | application, so that it requires minimal effort to relocate. See | |
86 | "USING Config::Merge" | |
87 | ||
88 | * Easy to alter development environment | |
89 | Provide a way for overriding configuration values on a development | |
90 | machine, so that differences between the dev environment and the | |
91 | live environment do not get copied over accidentally. See | |
92 | "OVERRIDING CONFIG LOCALLY" | |
93 | ||
94 | * Minimise memory use | |
95 | Load all config at startup so that (eg in the mod_perl environment) | |
96 | the data is shared between all child processes. See "MINIMISING | |
97 | MEMORY USE" | |
98 | ||
99 | * Flexible implementation | |
100 | You may want to use a different schema for your configuration files, | |
101 | so you can pass in (or subclass) methods for determining how your | |
102 | files are merged. See "ADVANCED USAGE". | |
103 | ||
104 | USING "Config::Merge" | |
105 | There are two ways to use "Config::Merge": | |
106 | ||
107 | OO STYLE | |
108 | use Config::Merge(); | |
109 | my $config = Config::Merge->new('/path/to/config'); | |
110 | ||
111 | @hosts = $config->('db.hosts.session'); | |
112 | $hosts_ref = $config->('db.hosts.session'); | |
113 | @cloned_hosts = $config->clone('db.hosts.session'); | |
114 | ||
115 | Also, see "ADVANCED USAGE". | |
116 | ||
117 | YOUR OWN CONFIG CLASS (functional style) | |
118 | The following code: | |
119 | ||
120 | # On startup | |
121 | use Config::Merge('My::Config' => '/path/to/config'); | |
122 | ||
123 | * auto-generates the class "My::Config" | |
124 | ||
125 | * loads the configuration data in '/path/to/config' | |
126 | ||
127 | * creates the subs "My::Config::C", "My::Config::clone" and | |
128 | "My::Config::object". | |
129 | ||
130 | Then when you want your application to have access to your | |
131 | configuration data, you add this (eg in your class "My::Module"): | |
132 | ||
133 | package My::Module; | |
134 | use My::Config; # Note, no () | |
135 | ||
136 | This exports the sub "C" into your current package, which allows you | |
137 | to access your configuation data as follows: | |
138 | ||
139 | @hosts = C('db.hosts.sesssion'); | |
140 | $hosts_ref = C('db.hosts.sesssion'); | |
141 | @cloned_hosts = My::Config::clone('db.hosts.session'); | |
142 | $config = My::Config::object; | |
143 | ||
144 | CONFIG TREE LAYOUT | |
145 | Config::Merge reads the data from any number (and type) of config files | |
146 | stored in a directory tree. File names and directory names are used as | |
147 | keys in the configuration hash. | |
148 | ||
149 | It uses file extensions to decide what type of data the file contains, | |
150 | so: | |
151 | ||
152 | YAML : .yaml .yml | |
153 | JSON : .json .jsn | |
154 | XML : .xml | |
155 | INI : .ini | |
156 | Perl : .perl .pl | |
157 | Config::General : .conf .cnf | |
158 | ||
159 | When loading your config data, Config::Merge starts at the directory | |
160 | specified at startup (see "USING Config::Merge") and looks through all | |
161 | the sub-directories for files ending in one of the above extensions. | |
162 | ||
163 | The name of the file or subdirectory is used as the first key. So: | |
164 | ||
165 | global/ | |
166 | db.yaml: | |
167 | username : admin | |
168 | hosts: | |
169 | - host1 | |
170 | - host2 | |
171 | password: | |
172 | host1: password1 | |
173 | host2: password2 | |
174 | ||
175 | would be loaded as : | |
176 | ||
177 | $Config = { | |
178 | global => { | |
179 | db => { | |
180 | username => 'admin', | |
181 | password => { host1 => 'password1', host2 => 'password2'}, | |
182 | hosts => ['host1','host2'], | |
183 | } | |
184 | } | |
185 | } | |
186 | ||
187 | Subdirectories are processed before the current directory, so you can | |
188 | have a directory and a config file with the same name, and the values | |
189 | will be merged into a single hash, so for instance, you can have: | |
190 | ||
191 | confdir: | |
192 | syndication/ | |
193 | --data_types/ | |
194 | --traffic.yaml | |
195 | --headlines.yaml | |
196 | --data_types.ini | |
197 | syndication.conf | |
198 | ||
199 | The config items in syndication.conf will be added to (or overwrite) the | |
200 | items loaded into the syndication namespace via the subdirectory called | |
201 | syndication. | |
202 | ||
203 | OVERRIDING CONFIG LOCALLY | |
204 | The situation often arises where it is necessary to specify different | |
205 | config values on different machines. For instance, the database host on | |
206 | a dev machine may be different from the host on the live application. | |
207 | Also, see "ADVANCED USAGE" which provides you with other means to merge | |
208 | local data. | |
209 | ||
210 | Instead of changing this data during dev and then having to remember to | |
211 | change it back before putting the new code live, we have a mechanism for | |
212 | overriding config locally in a "local.*" file and then, as long as that | |
213 | file never gets uploaded to live, you are protected. | |
214 | ||
215 | You can put a file called "local.*" (where * is any of the recognised | |
216 | extensions) in any sub-directory, and the data in this file will be | |
217 | merged with the existing data. | |
218 | ||
219 | Just make sure that the "local.*" files are never checked into your live | |
220 | code. | |
221 | ||
222 | For instance, if we have: | |
223 | ||
224 | confdir: | |
225 | db.yaml | |
226 | local.yaml | |
227 | ||
228 | and db.yaml has : | |
229 | ||
230 | connections: | |
231 | default_settings: | |
232 | host: localhost | |
233 | table: abc | |
234 | password: 123 | |
235 | ||
236 | And in local.yaml: | |
237 | ||
238 | db: | |
239 | connections: | |
240 | default_settings: | |
241 | password: 456 | |
242 | ||
243 | the resulting configuration will look like this: | |
244 | ||
245 | db: | |
246 | connections: | |
247 | default_settings: | |
248 | host: localhost | |
249 | table: abc | |
250 | password: 456 | |
251 | ||
252 | ACCESSING CONFIG DATA | |
253 | All configuration data is loaded into a single hash, eg: | |
254 | ||
255 | $config = { | |
256 | db => { | |
257 | hosts => { | |
258 | session => ['host1','host2','host3'], | |
259 | images => ['host1','host2','host3'], | |
260 | etc... | |
261 | } | |
262 | } | |
263 | } | |
264 | ||
265 | If you want to access it via standard Perl dereferences, you can just | |
266 | ask for the hash: | |
267 | ||
268 | OO: | |
269 | $data_ref = $config->(); | |
270 | $hosts_ref = $data_ref->{db}{hosts}{session}; | |
271 | $host_1 = $data_ref->{db}{hosts}{session}[0]; | |
272 | ||
273 | Functional: | |
274 | $data_ref = C(); | |
275 | $hosts_ref = $data_ref->{db}{hosts}{session}; | |
276 | $host_1 = $data_ref->{db}{hosts}{session}[0]; | |
277 | ||
278 | However, "Config::Merge" also provides an easy to read dot-notation in | |
279 | the style of Template Toolkit: "('key1.key2.keyn')". | |
280 | ||
281 | A key can be the key of a hash or the index of an array. The return | |
282 | value is context sensitive, so if called in list context, a hash ref or | |
283 | array ref will be dereferenced. | |
284 | ||
285 | OO: | |
286 | @hosts = $config->('db.hosts.session'); | |
287 | $hosts_ref = $config->('db.hosts.session'); | |
288 | $host_1 = $config->('db.hosts.session.0'); | |
289 | ||
290 | Functional: | |
291 | @hosts = C('db.hosts.session'); | |
292 | $hosts_ref = C('db.hosts.session'); | |
293 | $host_1 = C('db.hosts.session.0'); | |
294 | ||
295 | These lookups are memo'ised, so lookups are fast. | |
296 | ||
297 | If the specified key is not found, then an error is thrown. | |
298 | ||
299 | MINIMISING MEMORY USE | |
300 | The more configuration data you load, the more memory you use. In order | |
301 | to keep the memory use as low as possible for mod_perl (or other forking | |
302 | applications), the configuration data should be loaded at startup in the | |
303 | parent process. | |
304 | ||
305 | As long as the data is never changed by the children, the configuration | |
306 | hash will be stored in shared memory, rather than there being a separate | |
307 | copy in each child process. | |
308 | ||
309 | (See | |
310 | <http://search.cpan.org/~pgollucci/mod_perl-2.0.3/docs/user/performance/ | |
311 | mpm.pod>) | |
312 | ||
313 | METHODS | |
314 | "new()" | |
315 | $conf = Config::Merge->new($config_dir); | |
316 | ||
317 | new() instantiates a config object, loads the config from the | |
318 | directory specified, and returns the object. | |
319 | ||
320 | "C()" | |
321 | $val = $config->C('key1.key2.keyn'); | |
322 | $val = $config->C('key1.key2.keyn',$hash_ref); | |
323 | ||
324 | "Config::Merge" objects are overloaded so that this also works: | |
325 | ||
326 | $val = $config->('key1.key2.keyn'); | |
327 | $val = $config->('key1.key2.keyn',$hash_ref); | |
328 | ||
329 | Or, if used in the functional style (see "USING Config::Merge"): | |
330 | ||
331 | $val = C('key1.key2.keyn'); | |
332 | $val = C('key1.key2.keyn',$hash_ref); | |
333 | ||
334 | "key1" etc can be keys in a hash, or indexes of an array. | |
335 | ||
336 | "C('key1.key2.keyn')" returns everything from "keyn" down, so you | |
337 | can use the return value just as you would any normal Perl variable. | |
338 | ||
339 | The return values are context-sensitive, so if called in list | |
340 | context, an array ref or hash ref will be returned as lists. Scalar | |
341 | values, code refs, regexes and blessed objects will always be | |
342 | returned as themselves. | |
343 | ||
344 | So for example: | |
345 | ||
346 | $password = C('database.main.password'); | |
347 | $regex = C('database.main.password_regex'); | |
348 | ||
349 | @countries = C('lists.countries'); | |
350 | $countries_array_ref = C('lists.countries'); | |
351 | ||
352 | etc | |
353 | ||
354 | If called with a hash ref as the second parameter, then that hash | |
355 | ref will be examined, rather than the $config data. | |
356 | ||
357 | "clone()" | |
358 | This works exactly the same way as "C()" but it performs a deep | |
359 | clone of the data before returning it. | |
360 | ||
361 | This means that the returned data can be changed without affecting | |
362 | the data stored in the $conf object; | |
363 | ||
364 | The data is deep cloned, using Storable, so the bigger the data, the | |
365 | more performance hit. That said, Storable's dclone is very fast. | |
366 | ||
367 | "register_loader()" | |
368 | Config::Merge->register_loader( 'Config::Merge::XYZ'); | |
369 | ||
370 | Config::Merge->register_loader( 'Config::Merge::XYZ' => 'xyz','xxx'); | |
371 | ||
372 | By default, "Config::Merge" uses the "Config::Any" plugins to | |
373 | support YAML, JSON, INI, XML, Perl and Config::General configuration | |
374 | files, using the standard file extensions to recognise the file | |
375 | type. (See "CONFIG TREE LAYOUT"). | |
376 | ||
377 | If you would like to change the handler for an extension (eg, you | |
378 | want ".conf" and ".cnf" files to be treated as YAML), do the | |
379 | following: | |
380 | ||
381 | Config::Merge->register_loader ('Config::Any::YAML' => 'conf', 'cnf'); | |
382 | ||
383 | If you would like to add a new config style, then your module should | |
384 | have two methods: "extensions()" (which returns a list of the | |
385 | extensions it handles), and "load()" which accepts the name of the | |
386 | file to load, and returns a hash ref containing the data in the | |
387 | file. See Config::Any for details. | |
388 | ||
389 | Alternatively, you can specify the extensions when you load it: | |
390 | ||
391 | Config::Merge->register_loader ('My::Merge' => 'conf', 'cnf'); | |
392 | ||
393 | "load_config()" | |
394 | $config->load_config(); | |
395 | ||
396 | Will reload the config files located in the directory specified at | |
397 | object creation (see "new()"). | |
398 | ||
399 | BEWARE : If you are using this in a mod_perl environment, you will | |
400 | lose the benefit of shared memory by calling this in a child process | |
401 | - each child will have its own copy of the data. See "MINIMISING | |
402 | MEMORY USE". | |
403 | ||
404 | Returns the config hash ref. | |
405 | ||
406 | "clear_cache()" | |
407 | $config->clear_cache(); | |
408 | ||
409 | Config data is generally not supposed to be changed at runtime. | |
410 | However, if you do make changes, you may get inconsisten results, | |
411 | because lookups are cached. | |
412 | ||
413 | For instance: | |
414 | ||
415 | print $config->C('db.hosts.session'); # Caches this lookup | |
416 | > "host1 host2 host3" | |
417 | ||
418 | $data = $config->C('db.hosts'); | |
419 | $data->{session} = 123; | |
420 | ||
421 | print $config->C('db.hosts.session'); # uses cached value | |
422 | > "host1 host2 host3" | |
423 | ||
424 | $config->clear_cache(); | |
425 | print $config->C('db.hosts.session'); # uses actual value | |
426 | > "123" | |
427 | ||
428 | "import()" | |
429 | "import()" will normally be called automatically when you "use | |
430 | Config::Merge". However, you may want to do this: | |
431 | ||
432 | use Config::Merge(); | |
433 | Config::Merge->register_loader('My::Plugin' => 'ext'); | |
434 | Config::Merge->import('My::Config' => '/path/to/config/dir'); | |
435 | ||
436 | If called with two params: $config_class and $config_dir, it | |
437 | generates the new class (which inherits from Config::Merge) | |
438 | specified in $config_class, creates a new object of that class and | |
439 | creates 4 subs: | |
440 | ||
441 | "C()" | |
442 | As a function: | |
443 | C('keys...') | |
444 | ||
445 | is the equivalent of: | |
446 | $config->C('keys...'); | |
447 | ||
448 | "clone()" | |
449 | As a function: | |
450 | clone('keys...') | |
451 | ||
452 | is the equivalent of: | |
453 | $config->clone('keys...'); | |
454 | ||
455 | "object()" | |
456 | $config = My::Config->object(); | |
457 | ||
458 | Returns the $config object, | |
459 | ||
460 | "import()" | |
461 | When you use your generated config class, it exports the "C()" | |
462 | sub into your package: | |
463 | ||
464 | use My::Config; | |
465 | $hosts = C('db.hosts.session'); | |
466 | ||
467 | ADVANCED USAGE | |
468 | The items in the section allow you to customise how Config::Merge loads | |
469 | your data. You may never need them. | |
470 | ||
471 | You can: | |
472 | ||
473 | * Override array values | |
474 | ||
475 | * Skip the loading of parts of your config tree | |
476 | ||
477 | * Specify which files / dirs are local | |
478 | ||
479 | * Specify how to translate a file / dir name into a key | |
480 | ||
481 | * Change order in which files are loaded | |
482 | ||
483 | * See debug output | |
484 | ||
485 | Overriding array values | |
486 | Overriding hash values is easy, however arrays are more complex. it | |
487 | may be simpler to copy and paste and edit the array you want to | |
488 | change locally. | |
489 | ||
490 | However, if your array is too long, and you want to make small | |
491 | changes, then you can use the following: | |
492 | ||
493 | In the main config: | |
494 | ||
495 | { | |
496 | cron => [qw( job1 job2 job3 job4)] | |
497 | } | |
498 | ||
499 | In the local file | |
500 | ||
501 | { | |
502 | cron => { | |
503 | '3' => 'newjob4', # changes 'job4' -> 'newjob4' | |
504 | ||
505 | '!' => { # signals an array override | |
506 | ||
507 | '-' => [1], # deletes 'job2' | |
508 | ||
509 | '+' => ['job5'], # appends 'job5' | |
510 | ||
511 | OR '+' => { # inserts 'job3a' after 'job3' | |
512 | 2 => 'job3a' | |
513 | } | |
514 | } | |
515 | } | |
516 | ||
517 | * The override has to be a hash, with at least this structure "{ | |
518 | '!' => {} }" to signal an array override | |
519 | ||
520 | * Any other keys with integers are treated as indexes and are used | |
521 | to change the value at that index in the original array | |
522 | ||
523 | * The '-' key should contain an array ref, with the indexes of the | |
524 | elements to remove from the array. | |
525 | ||
526 | * If the '+' key contains an array ref, then its contents are | |
527 | appended to the original array. | |
528 | ||
529 | * If the '+' key contains a hash ref, then each value is inserted | |
530 | into the original array at the index given in the key | |
531 | ||
532 | * Indexes are zero based, just as in Perl. | |
533 | ||
534 | "skip()" | |
535 | $c = Config::Merge->new( | |
536 | path => '/path/to/config', | |
537 | skip => qr/regex/, | |
538 | | [ qr/regex1/, qr/regex2/...] | |
539 | | { name1 => 1, name2 => 2} | |
540 | | sub {} | |
541 | ); | |
542 | ||
543 | "skip()" allows you to skip the loading of parts of your config | |
544 | tree. For instance, if you don't need a list of cron jobs when | |
545 | running your web server, you can skip it. | |
546 | ||
547 | The decision is made based on the path to that value, eg | |
548 | 'app.db.hosts' rather than on filenames. Also, the check is only | |
549 | performed for each new directory or filename - it doesn't check the | |
550 | data within each file. | |
551 | ||
552 | To use "skip()", you can either subclass it, or pass in a parameter | |
553 | to new: | |
554 | ||
555 | "qr/regex/" or "[qr/regex1/, qr/regex2]" | |
556 | Each regex will be checked against the key path, and if it | |
557 | matches then the loading of that tree will be skipped | |
558 | ||
559 | "{key_path => 1}" | |
560 | If the key path exists in the hash, then loading will be skipped | |
561 | ||
562 | "sub {}" or subclassed "skip" | |
563 | sub { | |
564 | my ($self,$key_path) = @_; | |
565 | ...make decision... | |
566 | return 1 | 0; | |
567 | } | |
568 | ||
569 | "is_local()" | |
570 | $c = Config::Merge->new( | |
571 | path => '/path/to/config', | |
572 | is_local => qr/regex/, | |
573 | | [ qr/regex1/, qr/regex2/...] | |
574 | | { name1 => 1, name2 => 2} | |
575 | | sub {} | |
576 | ); | |
577 | ||
578 | "is_local()" indicates whether a file or dir should be considered | |
579 | part of the main config (and thus loaded normally) or part of the | |
580 | local config (and thus merged into the main config). | |
581 | ||
582 | The decision is made based on the name of the file / dir, without | |
583 | any extension. | |
584 | ||
585 | To use "is_local()", you can either subclass it, or pass in a | |
586 | parameter to new: | |
587 | ||
588 | "qr/regex/" or "[qr/regex1/, qr/regex2]" | |
589 | Each regex will be checked against the file/dir name, and if it | |
590 | matches then that tree will be merged | |
591 | ||
592 | "{filename => 1, dirname => 1}" | |
593 | If the file/dir name exists in the hash, then that tree will be | |
594 | merged | |
595 | ||
596 | "sub {}" or subclassed "is_local" | |
597 | sub { | |
598 | my ($self,$name) = @_; | |
599 | ...make decision... | |
600 | return 1 | 0; | |
601 | } | |
602 | ||
603 | See "EXAMPLE USING is_local() AND load_as()". | |
604 | ||
605 | "load_as()" | |
606 | $c = Config::Merge->new( | |
607 | path => '/path/to/config', | |
608 | load_as => qr/(regex)/, | |
609 | | sub {} | |
610 | ); | |
611 | ||
612 | "load_as()" returns the name of the key to use when loading the file | |
613 | / dir. By default, it returns the $name for main config files, or '' | |
614 | for local files. | |
615 | ||
616 | The decision is made based on the name of the file / dir, without | |
617 | any extension. | |
618 | ||
619 | If "load_as()" returns an empty string, then each key in the | |
620 | file/tree is merged separately. This is how the "local.*" files work | |
621 | by default. See "OVERRIDING CONFIG LOCALLY". | |
622 | ||
623 | For instance: | |
624 | ||
625 | main.yaml: | |
626 | key1: value | |
627 | key2: value | |
628 | ||
629 | db.yaml: | |
630 | key3: value | |
631 | key4: value | |
632 | ||
633 | local.yaml: | |
634 | main: | |
635 | key1: new_value | |
636 | db: | |
637 | key4: new_value | |
638 | ||
639 | To use "load_as()", you can either subclass it, or pass in a | |
640 | parameter to new: | |
641 | ||
642 | "qr/(regex)/" | |
643 | The regex will be checked against the file/dir name, and if it | |
644 | matches then it returns the string captured in the regex, | |
645 | otherwise it returns the original name. | |
646 | ||
647 | "sub {}" or subclassed "is_local" | |
648 | sub { | |
649 | my ($self,$name,$is_local) = @_; | |
650 | ...make decision... | |
651 | return 'string'; # string is used as the keyname | |
652 | return ''; # acts like local.* (see above) | |
653 | return undef; # don't load this file/dir | |
654 | } | |
655 | ||
656 | Also, see "EXAMPLE USING is_local() AND load_as()". | |
657 | ||
658 | EXAMPLE USING "is_local()" AND "load_as()" | |
659 | For instance, instead of using "local.*" files, you may want to keep | |
660 | versioned copies of local configs for different machines, and so | |
661 | use: | |
662 | ||
663 | app.yaml | |
664 | app-(dev1.domain.com).yaml | |
665 | app-(dev2.domain.com).yaml | |
666 | ||
667 | You would implement this as follows: | |
668 | ||
669 | my $config = Config::Merge->new( | |
670 | path => '/path/to/config', | |
671 | ||
672 | # If matches 'xxx-(yyy)' | |
673 | is_local => sub { | |
674 | my ( $self, $name ) = @_; | |
675 | return $name=~/- [(] .+ [)]/x ? 1 : 0; | |
676 | }, | |
677 | ||
678 | # If local and matches 'xxx-(hostname)', return xxx | |
679 | load_as => sub { | |
680 | my ( $self, $name, $is_local ) = @_; | |
681 | if ($is_local) { | |
682 | if ( $name=~/(.*) - [(] ($hostname) [)] /x ) { | |
683 | return $1; | |
684 | } | |
685 | return undef; | |
686 | } | |
687 | return $name; | |
688 | } | |
689 | ); | |
690 | ||
691 | See "examples/advanced.pl" for a working illustration. | |
692 | ||
693 | "sort()" | |
694 | $c = Config::Merge->new( | |
695 | path => '/path/to/config', | |
696 | sort => sub {} | |
697 | ); | |
698 | ||
699 | By default, directory entries are sorted alphabetically, with | |
700 | directories before filenames. | |
701 | ||
702 | This would be the order for these directory entries: | |
703 | ||
704 | api/ | |
705 | api-(dev1)/ | |
706 | api.yaml | |
707 | api-(dev1).yaml | |
708 | ||
709 | To override this, you can subclass "sort()" or pass it in as a | |
710 | parameter to new: | |
711 | ||
712 | sub { | |
713 | my ($self,$names_array_ref) = @_ | |
714 | ...sort... | |
715 | return $names_array_ref; | |
716 | } | |
717 | ||
718 | "debug()" | |
719 | my $config = Config::Merge->new( | |
720 | path => '/path/to/config', | |
721 | debug => 1 | 0 | |
722 | ); | |
723 | ||
724 | If "debug" is true, then Config::Merge prints out an explanation of | |
725 | what it is doing on STDERR. | |
726 | ||
727 | SEE ALSO | |
728 | Storable, Config::Any, Config::Any::YAML, Config::Any::JSON, | |
729 | Config::Any::INI, Config::Any::XML, Config::Any::General | |
730 | ||
731 | THANKS | |
732 | Thanks to Hasanuddin Tamir [HASANT] for vacating the Config::Merge | |
733 | namespace, which allowed me to rename Config::Loader to the more | |
734 | meaningful Config::Merge. | |
735 | ||
736 | His version of Config::Merge can be found in | |
737 | <http://backpan.cpan.org/modules/by-authors/id/H/HA/HASANT/>. | |
738 | ||
739 | Thanks to Joel Bernstein and Brian Cassidy for the interface to the | |
740 | various configuration modules. Also to Ewan Edwards for his suggestions | |
741 | about how to make Config::Merge more flexible. | |
742 | ||
743 | BUGS | |
744 | No bugs have been reported. | |
745 | ||
746 | Please report any bugs or feature requests to | |
747 | bug-config-loader@rt.cpan.org, or through the web interface at | |
748 | <http://rt.cpan.org>. | |
749 | ||
750 | AUTHOR | |
751 | Clinton Gormley, <clinton@traveljury.com> | |
752 | ||
753 | COPYRIGHT | |
754 | Copyright (C) 2007 by Clinton Gormley | |
755 | ||
756 | LICENSE | |
757 | This library is free software; you can redistribute it and/or modify it | |
758 | under the same terms as Perl itself, either Perl version 5.8.7 or, at | |
759 | your option, any later version of Perl 5 you may have available. | |
760 |
0 | Examples for Config::Merge | |
1 | =========================== | |
2 | ||
3 | There are four examples of the usage of Config::Merge: | |
4 | ||
5 | 1) 'oo.pl' and 'func.pl' illustrate the two ways of using Config::Merge | |
6 | They dump the config data corresponding to the path you type in. | |
7 | ||
8 | To run them, try this: | |
9 | ||
10 | perl oo.pl | |
11 | perl oo.pl app.images | |
12 | perl oo.pl app.html_filter.BanAllBut | |
13 | perl oo.pl app.html_filter.BanAllBut.2 | |
14 | ||
15 | OR | |
16 | ||
17 | perl func.pl | |
18 | perl func.pl app.images | |
19 | perl func.pl app.html_filter.BanAllBut | |
20 | perl func.pl app.html_filter.BanAllBut.2 | |
21 | ||
22 | You need to have YAML::Syck or YAML installed to use these examples | |
23 | ||
24 | 2) 'browser.pl' allows you to compare the data between a production environment | |
25 | and a development environment. The only difference between the data is | |
26 | the 'local.yaml' file in 'config_dev/local.yaml' | |
27 | ||
28 | To try it, type: | |
29 | perl browser.pl | |
30 | OR perl browser.pl debug | |
31 | ||
32 | Then enter the path to the data you want (eg 'app.images.path') | |
33 | or you can just press Enter to start. | |
34 | ||
35 | You need to have Term::ReadLine and YAML::Syck or YAML installed to use | |
36 | this example | |
37 | ||
38 | ||
39 | 3) 'advanced.pl' illustrates how to define your own methods for is_local() | |
40 | and load_as(). It shows the same config data for the production server | |
41 | and for the machines 'dev1' and 'dev2' | |
42 | ||
43 | perl advanced.pl | |
44 | OR perl advanced.pl debug | |
45 |
0 | # Local overrides for machine 'dev1' | |
1 | ||
2 | --- | |
3 | root: /home/user/www/EXAMPLE | |
4 | db: | |
5 | host: DEV1.domain.com | |
6 | utf8: ON | |
7 | cron: | |
8 | '!' : | |
9 | '-' : | |
10 | - 2 | |
11 | '+' : | |
12 | - JOB5 | |
13 | 1 : JOB1CHANGE |
0 | # Local overrides for machine 'dev2' | |
1 | --- | |
2 | db: | |
3 | password: EASY | |
4 | ||
5 | cron: | |
6 | '!': | |
7 | '+': | |
8 | 1 : JOB1NEW | |
9 | 5 : JOB5NEW |
0 | # Global config file | |
1 | ||
2 | --- | |
3 | root: /srv/www/sites/example | |
4 | db: | |
5 | host: db.domain.com | |
6 | username: prod | |
7 | password: secret | |
8 | ||
9 | cron: | |
10 | - job1 | |
11 | - job2 | |
12 | - job3 | |
13 | - job4 |
0 | #!/usr/bin/perl | |
1 | use strict; | |
2 | use warnings; | |
3 | use blib; | |
4 | use Config::Merge(); | |
5 | use File::Spec(); | |
6 | ||
7 | eval { require YAML::Syck; YAML::Syck->import(); 1 } | |
8 | or eval { require YAML; YAML->import(); 1 } | |
9 | or die "ERROR: " | |
10 | . "YAML::Syck or YAML needs to be installed to use this example\n\n"; | |
11 | ||
12 | my $debug = shift @ARGV; | |
13 | ||
14 | foreach my $hostname qw(main dev1 dev2) { | |
15 | my $config = Config::Merge->new( | |
16 | path => get_path('advanced'), | |
17 | debug => $debug, | |
18 | ||
19 | # If matches 'xxx-(yyy)' | |
20 | is_local => sub { | |
21 | my ($self,$name) = @_; | |
22 | return $name=~/- [(] .+ [)]/x ? 1 : 0; | |
23 | }, | |
24 | ||
25 | # If local and matches 'xxx-(hostname)', return xxx | |
26 | load_as => sub { | |
27 | my ($self,$name,$is_local) = @_; | |
28 | if ($is_local) { | |
29 | if ($name=~/(.*) - [(] ($hostname) [)] /x) { | |
30 | return $1; | |
31 | } | |
32 | return undef; | |
33 | } | |
34 | return $name; | |
35 | } | |
36 | ); | |
37 | print "\nCONFIG FOR $hostname:\n".Dump(scalar $config->C()); | |
38 | } | |
39 | ||
40 | #=================================== | |
41 | sub get_path { | |
42 | #=================================== | |
43 | my ($vol,$path) = File::Spec->splitpath( | |
44 | File::Spec->rel2abs($0) | |
45 | ); | |
46 | $path = File::Spec->catdir( | |
47 | File::Spec->splitdir($path), | |
48 | ,@_ | |
49 | ); | |
50 | return File::Spec->catpath($vol,$path,''); | |
51 | } |
0 | #!/usr/bin/perl | |
1 | use strict; | |
2 | use warnings; | |
3 | use blib; | |
4 | use Config::Merge(); | |
5 | use File::Spec(); | |
6 | ||
7 | init(); | |
8 | ||
9 | my $debug = shift @ARGV; | |
10 | ||
11 | my $prod = Config::Merge->new( path => get_path('config_prod') , debug => $debug); | |
12 | my $dev = Config::Merge->new( path => get_path('config_dev') , debug => $debug); | |
13 | ||
14 | my $term = Term::ReadLine->new('Browser'); | |
15 | ||
16 | while ( defined( my $path = $term->readline("Enter path:") ) ) { | |
17 | last if $path eq 'q'; | |
18 | $term->addhistory($path); | |
19 | print "\n"; | |
20 | dump_vals( 'Production Config', $prod, $path ); | |
21 | dump_vals( 'Dev Config', $dev, $path ); | |
22 | } | |
23 | ||
24 | #=================================== | |
25 | sub dump_vals { | |
26 | #=================================== | |
27 | my ( $title, $config, $path ) = @_; | |
28 | print "$title\n" . ( '-' x length($title) ) . "\n "; | |
29 | my $vals = eval { scalar $config->($path) }; | |
30 | if ($@) { | |
31 | print " -- PATH NOT FOUND --"; | |
32 | } | |
33 | else { | |
34 | if ( my $ref = ref $vals ) { | |
35 | if ( $ref eq 'ARRAY' ) { | |
36 | print "ARRAY: " . join( ', ', @$vals ); | |
37 | } | |
38 | elsif ( $ref eq 'HASH' ) { | |
39 | print "HASH KEYS: " . join( ', ', keys %$vals ); | |
40 | } | |
41 | else { | |
42 | print "$ref"; | |
43 | } | |
44 | } | |
45 | else { | |
46 | print "SCALAR: $vals"; | |
47 | } | |
48 | } | |
49 | print "\n\n"; | |
50 | } | |
51 | ||
52 | #=================================== | |
53 | sub get_path { | |
54 | #=================================== | |
55 | my ($vol,$path) = File::Spec->splitpath( | |
56 | File::Spec->rel2abs($0) | |
57 | ); | |
58 | $path = File::Spec->catdir( | |
59 | File::Spec->splitdir($path), | |
60 | ,@_ | |
61 | ); | |
62 | return File::Spec->catpath($vol,$path,''); | |
63 | } | |
64 | ||
65 | #=================================== | |
66 | sub init { | |
67 | #=================================== | |
68 | ||
69 | print <<USAGE; | |
70 | ||
71 | This browser allows you to compare an example configuration tree | |
72 | for production and development environments. The only difference is | |
73 | that development has a 'local.yaml' file. | |
74 | ||
75 | Type in the path of the value you would like to see, eg: app.images.path | |
76 | Try just pressing Enter to start. | |
77 | ||
78 | 'q' to quit | |
79 | ||
80 | USAGE | |
81 | ||
82 | eval { require Term::ReadLine } | |
83 | or die "ERROR: " | |
84 | . "Term::ReadLine needs to be installed to use this example browser\n\n"; | |
85 | ||
86 | eval { require YAML::Syck } | |
87 | or eval { require YAML } | |
88 | or die "ERROR: " | |
89 | . "YAML::Syck or YAML needs to be installed to use this example browser\n\n" | |
90 | ; | |
91 | ||
92 | } |
0 | # Config file for database connections | |
1 | ||
2 | --- | |
3 | inactive_timeout: 30 | |
4 | connections: | |
5 | default_settings: | |
6 | driver: DBI:mysql | |
7 | host: localhost | |
8 | database: example | |
9 | character_set: utf8 | |
10 | time_zone: +0:00 | |
11 | username: iannounce | |
12 | password: SeCrEt | |
13 | attributes: | |
14 | RaiseError: 1 | |
15 | PrintError: 0 | |
16 | AutoCommit: 1 | |
17 | params: | |
18 | mysql_connect_timeout: 5 | |
19 | mysql_client_found_rows: 1 |
0 | AllowHref: 0 | |
1 | AllowSrc: 1 | |
2 | AllowRelURL: 1 | |
3 | BanAllBut: | |
4 | - blockquote | |
5 | - br | |
6 | - div | |
7 | - em | |
8 | - h1 | |
9 | - h2 | |
10 | - h3 | |
11 | - hr | |
12 | - ol | |
13 | - p | |
14 | - span | |
15 | - strong | |
16 | - ul | |
17 | - li | |
18 | - img |
0 | # Global config file | |
1 | ||
2 | --- | |
3 | #======================================================================= | |
4 | root: /srv/www/sites/example | |
5 | #======================================================================= | |
6 | ||
7 | #======================================================================= | |
8 | webserver: | |
9 | #======================================================================= | |
10 | virtual_hosts: | |
11 | - sites: example1 example2 | |
12 | http: *:80 | |
13 | https: *:443 | |
14 | aliases: | |
15 | example1.co.uk: example1 | |
16 | example2.co.uk: examples | |
17 | options: | |
18 | name_virtual_host: 0 | |
19 | listen: 0 | |
20 | ||
21 | #======================================================================= | |
22 | feed_parser: | |
23 | #======================================================================= | |
24 | incoming_dir: cron/feed/incoming | |
25 | working_dir: cron/feed/working | |
26 | archive_dir: cron/feed/archive | |
27 | log_dir: cron/feed | |
28 | send_user_emails: 0 | |
29 | ||
30 | #======================================================================= | |
31 | images: | |
32 | #======================================================================= | |
33 | path: /var/images/ | |
34 | autolevels: 1 | |
35 | jpeg: | |
36 | jpegquality: 80 | |
37 | large: | |
38 | X: 600 | |
39 | Y: 600 | |
40 | medium: | |
41 | X: 160 | |
42 | Y: 160 | |
43 | min_ratio: 0.5 | |
44 | small: | |
45 | X: 100 | |
46 | Y: 100 | |
47 | min_ratio: 0.5 | |
48 | ||
49 | #======================================================================= | |
50 | upload: | |
51 | #======================================================================= | |
52 | max_size: | |
53 | total: 3500000 | |
54 | file: 2500000 | |
55 | types: | |
56 | - image/bmp | |
57 | - image/gif | |
58 | - image/jpef | |
59 | - image/jpeg | |
60 | - image/pjpeg | |
61 | - image/png | |
62 | - image/pnm | |
63 | - image/raw | |
64 | - image/rgb | |
65 | - image/tga | |
66 | - image/tiff | |
67 | path: /var/images/temp/ | |
68 | expiry: 900 | |
69 | ||
70 | #======================================================================= | |
71 | search: | |
72 | #======================================================================= | |
73 | rows: 10 | |
74 | cache_pages: 3 | |
75 | show_pages: 9 | |
76 | expiry: 300 | |
77 | ||
78 | #======================================================================= | |
79 | accounting: | |
80 | #======================================================================= | |
81 | vat_rate: 0.175 | |
82 | email: | |
83 | to: john@example.com | |
84 | from: jack@example.com | |
85 | text: | | |
86 | Please find attached this month's invoice for | |
87 | hosting, plus a spreadsheet containing | |
88 | a breakdown of your announcements. | |
89 | ||
90 | temp_dir: cron/accounts/temp/ | |
91 | archive_dir: cron/accounts/archive/ |
0 | name: search | |
1 | fields: | |
2 | - keywords: | |
3 | name: | |
4 | en: Look for | |
5 | type: text | |
6 | min_length: 3 | |
7 | max_length: 150 | |
8 | - type: | |
9 | name: | |
10 | en: Search in | |
11 | type: list | |
12 | display: select | |
13 | options: [] | |
14 | default: 'all_notices' | |
15 | - source: | |
16 | name: | |
17 | en: Publication | |
18 | type: list | |
19 | display: select | |
20 | options: '' | |
21 | default: '' | |
22 | - date_limit: | |
23 | type: list | |
24 | display: select | |
25 | options: date_limit | |
26 | default: 0 |
0 | name: user_edit | |
1 | fields: | |
2 | - email: | |
3 | name: | |
4 | en: Email address | |
5 | required: 1 | |
6 | default: | |
7 | type: text | |
8 | checks: email | |
9 | max_length: 255 | |
10 | - firstname: | |
11 | name: | |
12 | en: First name | |
13 | required: 1 | |
14 | type: text | |
15 | checks: name | |
16 | max_length: 100 | |
17 | - surname: | |
18 | name: | |
19 | en: Last name | |
20 | required: 1 | |
21 | type: text | |
22 | checks: name | |
23 | max_length: 100 |
0 | name: login | |
1 | fields: | |
2 | - email: | |
3 | name: | |
4 | en: Email address | |
5 | required: 1 | |
6 | default: | |
7 | type: text | |
8 | checks: email | |
9 | max_length: 255 |
0 | name: user_password | |
1 | dependencies: | |
2 | - equal: password password2 | |
3 | fields: | |
4 | - password: | |
5 | name: | |
6 | en: Password | |
7 | required: 1 | |
8 | type: password | |
9 | checks: | |
10 | min_length: 5 | |
11 | max_length: 25 | |
12 | - password2: | |
13 | name: | |
14 | en: Confirm password | |
15 | required: 1 | |
16 | type: password | |
17 | checks: | |
18 | min_length: 5 | |
19 | max_length: 25 |
0 | #======================================================================= | |
1 | app: | |
2 | #======================================================================= | |
3 | webserver: | |
4 | virtual_hosts: | |
5 | - sites: example1 | |
6 | http: *:80 | |
7 | https: *:443 | |
8 | aliases: | |
9 | localhost: example1 | |
10 | options: | |
11 | name_virtual_host: 0 | |
12 | listen: 0 | |
13 | db: | |
14 | connections: | |
15 | default_settings: | |
16 | host: localhost | |
17 | password: LoCal | |
18 | ||
19 | images: | |
20 | path: /srv/www/sites/example/images/ | |
21 | upload: | |
22 | path: /srv/www/sites/example/images/temp/ |
0 | # Config file for database connections | |
1 | ||
2 | --- | |
3 | inactive_timeout: 30 | |
4 | connections: | |
5 | default_settings: | |
6 | driver: DBI:mysql | |
7 | host: localhost | |
8 | database: example | |
9 | character_set: utf8 | |
10 | time_zone: +0:00 | |
11 | username: iannounce | |
12 | password: SeCrEt | |
13 | attributes: | |
14 | RaiseError: 1 | |
15 | PrintError: 0 | |
16 | AutoCommit: 1 | |
17 | params: | |
18 | mysql_connect_timeout: 5 | |
19 | mysql_client_found_rows: 1 |
0 | AllowHref: 0 | |
1 | AllowSrc: 1 | |
2 | AllowRelURL: 1 | |
3 | BanAllBut: | |
4 | - blockquote | |
5 | - br | |
6 | - div | |
7 | - em | |
8 | - h1 | |
9 | - h2 | |
10 | - h3 | |
11 | - hr | |
12 | - ol | |
13 | - p | |
14 | - span | |
15 | - strong | |
16 | - ul | |
17 | - li | |
18 | - img |
0 | # Global config file | |
1 | ||
2 | --- | |
3 | #======================================================================= | |
4 | root: /srv/www/sites/example | |
5 | #======================================================================= | |
6 | ||
7 | #======================================================================= | |
8 | webserver: | |
9 | #======================================================================= | |
10 | virtual_hosts: | |
11 | - sites: example1 example2 | |
12 | http: *:80 | |
13 | https: *:443 | |
14 | aliases: | |
15 | example1.co.uk: example1 | |
16 | example2.co.uk: examples | |
17 | options: | |
18 | name_virtual_host: 0 | |
19 | listen: 0 | |
20 | ||
21 | #======================================================================= | |
22 | feed_parser: | |
23 | #======================================================================= | |
24 | incoming_dir: cron/feed/incoming | |
25 | working_dir: cron/feed/working | |
26 | archive_dir: cron/feed/archive | |
27 | log_dir: cron/feed | |
28 | send_user_emails: 0 | |
29 | ||
30 | #======================================================================= | |
31 | images: | |
32 | #======================================================================= | |
33 | path: /var/images/ | |
34 | autolevels: 1 | |
35 | jpeg: | |
36 | jpegquality: 80 | |
37 | large: | |
38 | X: 600 | |
39 | Y: 600 | |
40 | medium: | |
41 | X: 160 | |
42 | Y: 160 | |
43 | min_ratio: 0.5 | |
44 | small: | |
45 | X: 100 | |
46 | Y: 100 | |
47 | min_ratio: 0.5 | |
48 | ||
49 | #======================================================================= | |
50 | upload: | |
51 | #======================================================================= | |
52 | max_size: | |
53 | total: 3500000 | |
54 | file: 2500000 | |
55 | types: | |
56 | - image/bmp | |
57 | - image/gif | |
58 | - image/jpef | |
59 | - image/jpeg | |
60 | - image/pjpeg | |
61 | - image/png | |
62 | - image/pnm | |
63 | - image/raw | |
64 | - image/rgb | |
65 | - image/tga | |
66 | - image/tiff | |
67 | path: /var/images/temp/ | |
68 | expiry: 900 | |
69 | ||
70 | #======================================================================= | |
71 | search: | |
72 | #======================================================================= | |
73 | rows: 10 | |
74 | cache_pages: 3 | |
75 | show_pages: 9 | |
76 | expiry: 300 | |
77 | ||
78 | #======================================================================= | |
79 | accounting: | |
80 | #======================================================================= | |
81 | vat_rate: 0.175 | |
82 | email: | |
83 | to: john@example.com | |
84 | from: jack@example.com | |
85 | text: | | |
86 | Please find attached this month's invoice for | |
87 | hosting, plus a spreadsheet containing | |
88 | a breakdown of your announcements. | |
89 | ||
90 | temp_dir: cron/accounts/temp/ | |
91 | archive_dir: cron/accounts/archive/ |
0 | name: search | |
1 | fields: | |
2 | - keywords: | |
3 | name: | |
4 | en: Look for | |
5 | type: text | |
6 | min_length: 3 | |
7 | max_length: 150 | |
8 | - type: | |
9 | name: | |
10 | en: Search in | |
11 | type: list | |
12 | display: select | |
13 | options: [] | |
14 | default: 'all_notices' | |
15 | - source: | |
16 | name: | |
17 | en: Publication | |
18 | type: list | |
19 | display: select | |
20 | options: '' | |
21 | default: '' | |
22 | - date_limit: | |
23 | type: list | |
24 | display: select | |
25 | options: date_limit | |
26 | default: 0 |
0 | name: user_edit | |
1 | fields: | |
2 | - email: | |
3 | name: | |
4 | en: Email address | |
5 | required: 1 | |
6 | default: | |
7 | type: text | |
8 | checks: email | |
9 | max_length: 255 | |
10 | - firstname: | |
11 | name: | |
12 | en: First name | |
13 | required: 1 | |
14 | type: text | |
15 | checks: name | |
16 | max_length: 100 | |
17 | - surname: | |
18 | name: | |
19 | en: Last name | |
20 | required: 1 | |
21 | type: text | |
22 | checks: name | |
23 | max_length: 100 |
0 | name: login | |
1 | fields: | |
2 | - email: | |
3 | name: | |
4 | en: Email address | |
5 | required: 1 | |
6 | default: | |
7 | type: text | |
8 | checks: email | |
9 | max_length: 255 |
0 | name: user_password | |
1 | dependencies: | |
2 | - equal: password password2 | |
3 | fields: | |
4 | - password: | |
5 | name: | |
6 | en: Password | |
7 | required: 1 | |
8 | type: password | |
9 | checks: | |
10 | min_length: 5 | |
11 | max_length: 25 | |
12 | - password2: | |
13 | name: | |
14 | en: Confirm password | |
15 | required: 1 | |
16 | type: password | |
17 | checks: | |
18 | min_length: 5 | |
19 | max_length: 25 |
0 | #!/usr/bin/perl | |
1 | use strict; | |
2 | use warnings; | |
3 | use blib; | |
4 | use File::Spec(); | |
5 | my $dir; | |
6 | ||
7 | # Checks for YAML::Syck or YAML | |
8 | # and finds the path for the config dir | |
9 | BEGIN { | |
10 | eval { require YAML::Syck; YAML::Syck->import(); 1 } | |
11 | or eval { require YAML; YAML->import(); 1 } | |
12 | or die "ERROR: " | |
13 | . "YAML::Syck or YAML needs to be installed to use this example\n\n"; | |
14 | ||
15 | my ($vol,$path) = File::Spec->splitpath( | |
16 | File::Spec->rel2abs($0) | |
17 | ); | |
18 | $path = File::Spec->catdir( | |
19 | File::Spec->splitdir($path), | |
20 | ,'config_dev' | |
21 | ); | |
22 | $dir = File::Spec->catpath($vol,$path,''); | |
23 | } | |
24 | ||
25 | # Set up the class My::Config | |
26 | # Normally this would happen in a startup file | |
27 | use Config::Merge( 'My::Config' => $dir ); | |
28 | ||
29 | # Import the sub My::Config::C | |
30 | # You'd put this line into every module which needs access to the config data | |
31 | use My::Config; | |
32 | ||
33 | my $path = shift @ARGV || ''; | |
34 | my $data = C($path); | |
35 | ||
36 | print Dump($data); | |
37 |
0 | #!/usr/bin/perl | |
1 | use strict; | |
2 | use warnings; | |
3 | use blib; | |
4 | use Config::Merge(); | |
5 | use File::Spec(); | |
6 | ||
7 | eval { require YAML::Syck; YAML::Syck->import(); 1 } | |
8 | or eval { require YAML; YAML->import(); 1 } | |
9 | or die "ERROR: " | |
10 | . "YAML::Syck or YAML needs to be installed to use this example\n\n"; | |
11 | ||
12 | my $config = Config::Merge->new( get_path('config_dev') ); | |
13 | my $path = shift @ARGV || ''; | |
14 | ||
15 | my $data = $config->($path); | |
16 | ||
17 | print Dump($data); | |
18 | ||
19 | #=================================== | |
20 | sub get_path { | |
21 | #=================================== | |
22 | my ($vol,$path) = File::Spec->splitpath( | |
23 | File::Spec->rel2abs($0) | |
24 | ); | |
25 | $path = File::Spec->catdir( | |
26 | File::Spec->splitdir($path), | |
27 | ,@_ | |
28 | ); | |
29 | return File::Spec->catpath($vol,$path,''); | |
30 | } |
0 | package Config::Merge::Perl; | |
1 | ||
2 | use strict; | |
3 | use warnings FATAL => 'all', NONFATAL => 'redefine'; | |
4 | ||
5 | =head1 NAME | |
6 | ||
7 | Config::Merge::Perl - Load Perl config files | |
8 | ||
9 | =head1 DESCRIPTION | |
10 | ||
11 | Loads Perl files. Example: | |
12 | ||
13 | { | |
14 | name => 'TestApp', | |
15 | 'Controller::Foo' => { | |
16 | foo => 'bar' | |
17 | }, | |
18 | 'Model::Baz' => { | |
19 | qux => 'xyzzy' | |
20 | } | |
21 | } | |
22 | ||
23 | Any error/warning in the file will throw a fatal error. | |
24 | ||
25 | =head1 METHODS | |
26 | ||
27 | =head2 extensions( ) | |
28 | ||
29 | return an array of valid extensions (C<pl>, C<perl>). | |
30 | ||
31 | =cut | |
32 | ||
33 | sub extensions {qw( pl perl );} | |
34 | ||
35 | =head2 load( $file ) | |
36 | ||
37 | Attempts to load C<$file> as a Perl file. | |
38 | ||
39 | =cut | |
40 | ||
41 | ||
42 | sub load { | |
43 | my $class = shift; | |
44 | my $file = shift; | |
45 | delete $INC{$file}; | |
46 | local ($^W) = 1; | |
47 | local $SIG{__WARN__} = sub { die $_[0]}; | |
48 | require $file; | |
49 | } | |
50 | ||
51 | =head1 SEE ALSO | |
52 | ||
53 | L<Config::Merge> | |
54 | ||
55 | =head1 THANKS | |
56 | ||
57 | Thanks to Joel Bernstein and Brian Cassidy for the original Config::Any::Perl | |
58 | module | |
59 | ||
60 | =head1 BUGS | |
61 | ||
62 | None known | |
63 | ||
64 | =head1 AUTHOR | |
65 | ||
66 | Clinton Gormley, E<lt>clinton@traveljury.comE<gt> | |
67 | ||
68 | =head1 COPYRIGHT | |
69 | ||
70 | Copyright (C) 2007 by Clinton Gormley | |
71 | ||
72 | =cut | |
73 | ||
74 | =head1 LICENSE | |
75 | ||
76 | This library is free software; you can redistribute it and/or modify | |
77 | it under the same terms as Perl itself, either Perl version 5.8.7 or, | |
78 | at your option, any later version of Perl 5 you may have available. | |
79 | ||
80 | ||
81 | =cut | |
82 | ||
83 | 1;⏎ |
0 | package Config::Merge; | |
1 | ||
2 | use strict; | |
3 | use warnings FATAL => 'all', NONFATAL => 'redefine'; | |
4 | ||
5 | use File::Spec(); | |
6 | use Storable(); | |
7 | use overload ( | |
8 | '&{}' => sub { | |
9 | my $self = shift; | |
10 | return sub { $self->C(@_) } | |
11 | }, | |
12 | 'fallback' => 1 | |
13 | ); | |
14 | ||
15 | use vars qw($VERSION); | |
16 | $VERSION = '1.01'; | |
17 | ||
18 | =head1 NAME | |
19 | ||
20 | Config::Merge - load a configuration directory tree containing | |
21 | YAML, JSON, XML, Perl, INI or Config::General files | |
22 | ||
23 | =head1 SYNOPSIS | |
24 | ||
25 | OO style | |
26 | ------------------------------------------------------- | |
27 | use Config::Merge(); | |
28 | ||
29 | my $config = Config::Merge->new('/path/to/config'); | |
30 | ||
31 | @hosts = $config->('db.hosts.session'); | |
32 | $hosts_ref = $config->('db.hosts.session'); | |
33 | @cloned_hosts = $config->clone('db.hosts.session'); | |
34 | ------------------------------------------------------- | |
35 | ||
36 | OR | |
37 | ||
38 | Functional style | |
39 | ------------------------------------------------------- | |
40 | # On startup | |
41 | use Config::Merge('My::Config' => '/path/to/config'); | |
42 | ||
43 | ||
44 | # Then, in any module where you want to use the config | |
45 | package My::Module; | |
46 | use My::Config; | |
47 | ||
48 | @hosts = C('db.hosts.sesssion'); | |
49 | $hosts_ref = C('db.hosts.sesssion'); | |
50 | @cloned_hosts = My::Config::clone('db.hosts.session'); | |
51 | $config = My::Config::object; | |
52 | ------------------------------------------------------- | |
53 | ||
54 | ADVANCED USAGE | |
55 | ||
56 | OO style | |
57 | ------------------------------------------------------- | |
58 | my $config = Config::Merge->new( | |
59 | path => '/path/to/config', | |
60 | skip => sub {} | regex | {} , | |
61 | is_local => sub {} | regex | {} , | |
62 | load_as => sub {} | regex , | |
63 | sort => sub {} , | |
64 | debug => 1 | 0 | |
65 | ); | |
66 | ------------------------------------------------------- | |
67 | ||
68 | Functional style | |
69 | ------------------------------------------------------- | |
70 | use Config::Merge( | |
71 | 'My::Config' => '/path/to/config', | |
72 | { | |
73 | skip => sub {} | regex | {} , | |
74 | is_local => sub {} | regex | {} , | |
75 | load_as => sub {} | regex , | |
76 | sort => sub {} , | |
77 | debug => 1 | 0 | |
78 | } | |
79 | ); | |
80 | ||
81 | # Also, you can subclass these: | |
82 | ||
83 | package My::Config; | |
84 | sub skip { | |
85 | ... | |
86 | } | |
87 | ||
88 | ------------------------------------------------------- | |
89 | ||
90 | =head1 DESCRIPTION | |
91 | ||
92 | Config::Merge is a configuration module which has six goals: | |
93 | ||
94 | =over | |
95 | ||
96 | =item * Flexible storage | |
97 | ||
98 | Store all configuration in your format(s) of choice (YAML, JSON, INI, XML, Perl, | |
99 | Config::General / Apache-style config) broken down into individual files in | |
100 | a configuration directory tree, for easy maintenance. | |
101 | See L</"CONFIG TREE LAYOUT"> | |
102 | ||
103 | =item * Flexible access | |
104 | ||
105 | Provide a simple, easy to read, concise way of accessing the configuration | |
106 | values (similar to L<Template>). See L</"ACCESSING CONFIG DATA"> | |
107 | ||
108 | =item * Minimal maintenance | |
109 | ||
110 | Specify the location of the configuration files only once per | |
111 | application, so that it requires minimal effort to relocate. | |
112 | See L</"USING Config::Merge"> | |
113 | ||
114 | =item * Easy to alter development environment | |
115 | ||
116 | Provide a way for overriding configuration values on a development | |
117 | machine, so that differences between the dev environment and | |
118 | the live environment do not get copied over accidentally. | |
119 | See L</"OVERRIDING CONFIG LOCALLY"> | |
120 | ||
121 | =item * Minimise memory use | |
122 | ||
123 | Load all config at startup so that (eg in the mod_perl environment) the | |
124 | data is shared between all child processes. See L</"MINIMISING MEMORY USE"> | |
125 | ||
126 | =item * Flexible implementation | |
127 | ||
128 | You may want to use a different schema for your configuration files, | |
129 | so you can pass in (or subclass) methods for determining how your | |
130 | files are merged. See L</"ADVANCED USAGE">. | |
131 | ||
132 | =back | |
133 | ||
134 | =head1 USING C<Config::Merge> | |
135 | ||
136 | There are two ways to use C<Config::Merge>: | |
137 | ||
138 | =over | |
139 | ||
140 | =item OO STYLE | |
141 | ||
142 | use Config::Merge(); | |
143 | my $config = Config::Merge->new('/path/to/config'); | |
144 | ||
145 | @hosts = $config->('db.hosts.session'); | |
146 | $hosts_ref = $config->('db.hosts.session'); | |
147 | @cloned_hosts = $config->clone('db.hosts.session'); | |
148 | ||
149 | Also, see L</"ADVANCED USAGE">. | |
150 | ||
151 | =item YOUR OWN CONFIG CLASS (functional style) | |
152 | ||
153 | The following code: | |
154 | ||
155 | # On startup | |
156 | use Config::Merge('My::Config' => '/path/to/config'); | |
157 | ||
158 | =over | |
159 | ||
160 | =item * | |
161 | ||
162 | auto-generates the class C<My::Config> | |
163 | ||
164 | =item * | |
165 | ||
166 | loads the configuration data in C<'/path/to/config'> | |
167 | ||
168 | =item * | |
169 | ||
170 | creates the subs C<My::Config::C>, C<My::Config::clone> | |
171 | and C<My::Config::object>. | |
172 | ||
173 | =back | |
174 | ||
175 | Then when you want your application to have access to your configuration data, | |
176 | you add this (eg in your class C<My::Module>): | |
177 | ||
178 | package My::Module; | |
179 | use My::Config; # Note, no () | |
180 | ||
181 | This exports the sub C<C> into your current package, which allows you to | |
182 | access your configuation data as follows: | |
183 | ||
184 | @hosts = C('db.hosts.sesssion'); | |
185 | $hosts_ref = C('db.hosts.sesssion'); | |
186 | @cloned_hosts = My::Config::clone('db.hosts.session'); | |
187 | $config = My::Config::object; | |
188 | ||
189 | =back | |
190 | ||
191 | =head1 CONFIG TREE LAYOUT | |
192 | ||
193 | Config::Merge reads the data from any number (and type) of config files | |
194 | stored in a directory tree. File names and directory names are used as keys in | |
195 | the configuration hash. | |
196 | ||
197 | It uses file extensions to decide what type of data the file contains, so: | |
198 | ||
199 | YAML : .yaml .yml | |
200 | JSON : .json .jsn | |
201 | XML : .xml | |
202 | INI : .ini | |
203 | Perl : .perl .pl | |
204 | Config::General : .conf .cnf | |
205 | ||
206 | When loading your config data, Config::Merge starts at the directory | |
207 | specified at startup (see L</"USING Config::Merge">) and looks | |
208 | through all the sub-directories for files ending in one of the above | |
209 | extensions. | |
210 | ||
211 | The name of the file or subdirectory is used as the first key. So: | |
212 | ||
213 | global/ | |
214 | db.yaml: | |
215 | username : admin | |
216 | hosts: | |
217 | - host1 | |
218 | - host2 | |
219 | password: | |
220 | host1: password1 | |
221 | host2: password2 | |
222 | ||
223 | would be loaded as : | |
224 | ||
225 | $Config = { | |
226 | global => { | |
227 | db => { | |
228 | username => 'admin', | |
229 | password => { host1 => 'password1', host2 => 'password2'}, | |
230 | hosts => ['host1','host2'], | |
231 | } | |
232 | } | |
233 | } | |
234 | ||
235 | Subdirectories are processed before the current directory, so | |
236 | you can have a directory and a config file with the same name, | |
237 | and the values will be merged into a single hash, so for | |
238 | instance, you can have: | |
239 | ||
240 | confdir: | |
241 | syndication/ | |
242 | --data_types/ | |
243 | --traffic.yaml | |
244 | --headlines.yaml | |
245 | --data_types.ini | |
246 | syndication.conf | |
247 | ||
248 | The config items in syndication.conf will be added to (or overwrite) | |
249 | the items loaded into the syndication namespace via the subdirectory | |
250 | called syndication. | |
251 | ||
252 | =head1 OVERRIDING CONFIG LOCALLY | |
253 | ||
254 | The situation often arises where it is necessary to specify | |
255 | different config values on different machines. For instance, | |
256 | the database host on a dev machine may be different from the host | |
257 | on the live application. Also, see L</"ADVANCED USAGE"> which | |
258 | provides you with other means to merge local data. | |
259 | ||
260 | Instead of changing this data during dev and then having to remember | |
261 | to change it back before putting the new code live, we have a mechanism | |
262 | for overriding config locally in a C<local.*> file and then, as long as | |
263 | that file never gets uploaded to live, you are protected. | |
264 | ||
265 | You can put a file called C<local.*> (where * is any of the recognised | |
266 | extensions) in any sub-directory, and | |
267 | the data in this file will be merged with the existing data. | |
268 | ||
269 | Just make sure that the C<local.*> files are never checked into your live | |
270 | code. | |
271 | ||
272 | For instance, if we have: | |
273 | ||
274 | confdir: | |
275 | db.yaml | |
276 | local.yaml | |
277 | ||
278 | and db.yaml has : | |
279 | ||
280 | connections: | |
281 | default_settings: | |
282 | host: localhost | |
283 | table: abc | |
284 | password: 123 | |
285 | ||
286 | And in local.yaml: | |
287 | ||
288 | db: | |
289 | connections: | |
290 | default_settings: | |
291 | password: 456 | |
292 | ||
293 | the resulting configuration will look like this: | |
294 | ||
295 | db: | |
296 | connections: | |
297 | default_settings: | |
298 | host: localhost | |
299 | table: abc | |
300 | password: 456 | |
301 | ||
302 | =head1 ACCESSING CONFIG DATA | |
303 | ||
304 | All configuration data is loaded into a single hash, eg: | |
305 | ||
306 | $config = { | |
307 | db => { | |
308 | hosts => { | |
309 | session => ['host1','host2','host3'], | |
310 | images => ['host1','host2','host3'], | |
311 | etc... | |
312 | } | |
313 | } | |
314 | } | |
315 | ||
316 | ||
317 | If you want to access it via standard Perl dereferences, you can just ask | |
318 | for the hash: | |
319 | ||
320 | OO: | |
321 | $data_ref = $config->(); | |
322 | $hosts_ref = $data_ref->{db}{hosts}{session}; | |
323 | $host_1 = $data_ref->{db}{hosts}{session}[0]; | |
324 | ||
325 | Functional: | |
326 | $data_ref = C(); | |
327 | $hosts_ref = $data_ref->{db}{hosts}{session}; | |
328 | $host_1 = $data_ref->{db}{hosts}{session}[0]; | |
329 | ||
330 | However, C<Config::Merge> also provides an easy to read dot-notation in the | |
331 | style of Template Toolkit: C<('key1.key2.keyn')>. | |
332 | ||
333 | A key can be the key of a hash or the index of an array. The return value is | |
334 | context sensitive, so if called in list context, a hash ref or array ref will | |
335 | be dereferenced. | |
336 | ||
337 | OO: | |
338 | @hosts = $config->('db.hosts.session'); | |
339 | $hosts_ref = $config->('db.hosts.session'); | |
340 | $host_1 = $config->('db.hosts.session.0'); | |
341 | ||
342 | Functional: | |
343 | @hosts = C('db.hosts.session'); | |
344 | $hosts_ref = C('db.hosts.session'); | |
345 | $host_1 = C('db.hosts.session.0'); | |
346 | ||
347 | These lookups are memo'ised, so lookups are fast. | |
348 | ||
349 | If the specified key is not found, then an error is thrown. | |
350 | ||
351 | =head1 MINIMISING MEMORY USE | |
352 | ||
353 | The more configuration data you load, the more memory you use. In order to | |
354 | keep the memory use as low as possible for mod_perl (or other forking | |
355 | applications), the configuration data should be loaded at startup in the | |
356 | parent process. | |
357 | ||
358 | As long as the data is never changed by the children, the configuration hash | |
359 | will be stored in shared memory, rather than there being a separate copy in each | |
360 | child process. | |
361 | ||
362 | (See L<http://search.cpan.org/~pgollucci/mod_perl-2.0.3/docs/user/performance/mpm.pod>) | |
363 | ||
364 | =head1 METHODS | |
365 | ||
366 | =over | |
367 | ||
368 | =item C<new()> | |
369 | ||
370 | $conf = Config::Merge->new($config_dir); | |
371 | ||
372 | new() instantiates a config object, loads the config from | |
373 | the directory specified, and returns the object. | |
374 | ||
375 | =cut | |
376 | ||
377 | #=================================== | |
378 | sub new { | |
379 | #=================================== | |
380 | my $proto = shift; | |
381 | my $class = ref $proto || $proto; | |
382 | ||
383 | my $self = {}; | |
384 | bless( $self, $class ); | |
385 | ||
386 | my $params | |
387 | = @_ > 1 ? {@_} | |
388 | : ref $_[0] eq 'HASH' ? shift() | |
389 | : { path => shift() }; | |
390 | ||
391 | # Emit debug messages | |
392 | $self->{debug} = $params->{debug} ? 1 : 0; | |
393 | ||
394 | die "Parameter 'sort' must be a coderef" | |
395 | if exists $params->{sort} && ref $params->{sort} ne 'CODE'; | |
396 | ||
397 | # Setup callbacks | |
398 | $self->_init_callback( $_, $params->{$_} ) | |
399 | foreach qw(skip is_local load_as sort); | |
400 | ||
401 | my $path = $params->{path} | |
402 | or die( "Configuration directory not specified when creating a new " | |
403 | . "'$class' object" ); | |
404 | ||
405 | if ( $path && -d $path && -r _ ) { | |
406 | ||
407 | $path =~ s|/?$|/|; | |
408 | $self->{config_dir} = $path; | |
409 | $self->load_config(); | |
410 | ||
411 | return $self; | |
412 | } | |
413 | else { | |
414 | die("Configuration directory '$path' not readable when creating a new " | |
415 | . "'$class' object" ); | |
416 | } | |
417 | return $self; | |
418 | } | |
419 | ||
420 | =item C<C()> | |
421 | ||
422 | $val = $config->C('key1.key2.keyn'); | |
423 | $val = $config->C('key1.key2.keyn',$hash_ref); | |
424 | ||
425 | C<Config::Merge> objects are overloaded so that this also works: | |
426 | ||
427 | $val = $config->('key1.key2.keyn'); | |
428 | $val = $config->('key1.key2.keyn',$hash_ref); | |
429 | ||
430 | Or, if used in the functional style (see L</"USING Config::Merge">): | |
431 | ||
432 | $val = C('key1.key2.keyn'); | |
433 | $val = C('key1.key2.keyn',$hash_ref); | |
434 | ||
435 | C<key1> etc can be keys in a hash, or indexes of an array. | |
436 | ||
437 | C<C('key1.key2.keyn')> returns everything from C<keyn> down, | |
438 | so you can use the return value just as you would any normal Perl variable. | |
439 | ||
440 | The return values are context-sensitive, so if called | |
441 | in list context, an array ref or hash ref will be returned as lists. | |
442 | Scalar values, code refs, regexes and blessed objects will always be returned | |
443 | as themselves. | |
444 | ||
445 | So for example: | |
446 | ||
447 | $password = C('database.main.password'); | |
448 | $regex = C('database.main.password_regex'); | |
449 | ||
450 | @countries = C('lists.countries'); | |
451 | $countries_array_ref = C('lists.countries'); | |
452 | ||
453 | etc | |
454 | ||
455 | If called with a hash ref as the second parameter, then that hash ref will be | |
456 | examined, rather than the C<$config> data. | |
457 | ||
458 | =cut | |
459 | ||
460 | #=================================== | |
461 | sub C { | |
462 | #=================================== | |
463 | my $self = shift; | |
464 | my $path = shift; | |
465 | $path = '' unless defined $path; | |
466 | ||
467 | my ( $config, @keys ); | |
468 | ||
469 | # If a private hash is passed in use that | |
470 | if (@_) { | |
471 | $config = $_[0]; | |
472 | @keys = split( /\./, $path ); | |
473 | $config = $self->_walk_path( $config, 'PRIVATE', \@keys ); | |
474 | } | |
475 | ||
476 | # Otherwise use the stored config data | |
477 | else { | |
478 | ||
479 | # Have we previously memoised this? | |
480 | if ( exists $self->{_memo}->{$path} ) { | |
481 | $config = ${ $self->{_memo}->{$path} }; | |
482 | } | |
483 | ||
484 | # Not memoised, so get it manually | |
485 | else { | |
486 | $config = $self->{config}; | |
487 | (@keys) = split( /\./, $path ); | |
488 | $config = $self->_walk_path( $config, '', \@keys ); | |
489 | $self->{_memo}->{$path} = \$config; | |
490 | } | |
491 | } | |
492 | ||
493 | return | |
494 | wantarray && ref($config) eq 'HASH' ? %{$config} | |
495 | : wantarray && ref($config) eq 'ARRAY' ? @{$config} | |
496 | : $config; | |
497 | } | |
498 | ||
499 | #=================================== | |
500 | sub _walk_path { | |
501 | #=================================== | |
502 | my $self = shift; | |
503 | my ( $config, $key_path, $keys ) = @_; | |
504 | ||
505 | foreach my $key (@$keys) { | |
506 | next unless defined $key && length($key); | |
507 | if ( ref $config eq 'ARRAY' | |
508 | && $key =~ /^[0-9]+/ | |
509 | && exists $config->[$key] ) | |
510 | { | |
511 | $config = $config->[$key]; | |
512 | $key_path .= '.' . $key; | |
513 | next; | |
514 | } | |
515 | elsif ( ref $config eq 'HASH' && exists $config->{$key} ) { | |
516 | $config = $config->{$key}; | |
517 | $key_path = $self->_join_key_path( $key_path, $key ); | |
518 | next; | |
519 | } | |
520 | die("Invalid key '$key' specified for '$key_path'\n"); | |
521 | } | |
522 | return $config; | |
523 | } | |
524 | ||
525 | =item C<clone()> | |
526 | ||
527 | This works exactly the same way as L</"C()"> but it performs a | |
528 | deep clone of the data before returning it. | |
529 | ||
530 | This means that the returned data can be changed without | |
531 | affecting the data stored in the $conf object; | |
532 | ||
533 | The data is deep cloned, using Storable, so the bigger the data, the more | |
534 | performance hit. That said, Storable's dclone is very fast. | |
535 | ||
536 | =cut | |
537 | ||
538 | #=================================== | |
539 | sub clone { | |
540 | #=================================== | |
541 | my $self = shift; | |
542 | my $data = $self->Config::Merge::C(@_); | |
543 | return Storable::dclone($data); | |
544 | } | |
545 | ||
546 | my @Builtin_Merges = qw( | |
547 | Config::Any::YAML | |
548 | Config::Any::General | |
549 | Config::Any::XML | |
550 | Config::Any::INI | |
551 | Config::Any::JSON | |
552 | Config::Merge::Perl | |
553 | ); | |
554 | ||
555 | my %Module_For_Ext = (); | |
556 | __PACKAGE__->register_loader($_) foreach @Builtin_Merges; | |
557 | ||
558 | =item C<register_loader()> | |
559 | ||
560 | Config::Merge->register_loader( 'Config::Merge::XYZ'); | |
561 | ||
562 | Config::Merge->register_loader( 'Config::Merge::XYZ' => 'xyz','xxx'); | |
563 | ||
564 | By default, C<Config::Merge> uses the C<Config::Any> | |
565 | plugins to support YAML, JSON, INI, XML, Perl and Config::General configuration | |
566 | files, using the standard file extensions to recognise the file type. (See | |
567 | L</"CONFIG TREE LAYOUT">). | |
568 | ||
569 | If you would like to change the handler for an extension (eg, you want C<.conf> | |
570 | and C<.cnf> files to be treated as YAML), do the following: | |
571 | ||
572 | Config::Merge->register_loader ('Config::Any::YAML' => 'conf', 'cnf'); | |
573 | ||
574 | If you would like to add a new config style, then your module should have two | |
575 | methods: C<extensions()> (which returns a list of the extensions it handles), | |
576 | and C<load()> which accepts the name of the file to load, and returns | |
577 | a hash ref containing the data in the file. See L<Config::Any> for details. | |
578 | ||
579 | Alternatively, you can specify the extensions when you load it: | |
580 | ||
581 | Config::Merge->register_loader ('My::Merge' => 'conf', 'cnf'); | |
582 | ||
583 | =cut | |
584 | ||
585 | #=================================== | |
586 | sub register_loader { | |
587 | #=================================== | |
588 | my $class = shift; | |
589 | my $loader = shift | |
590 | or die "No loader class passed to register_loader()"; | |
591 | eval "require $loader" | |
592 | or die $@; | |
593 | my @extensions = @_ ? @_ : $loader->extensions; | |
594 | foreach my $ext (@extensions) { | |
595 | $Module_For_Ext{ lc($ext) } = $loader; | |
596 | } | |
597 | return; | |
598 | } | |
599 | ||
600 | =item C<load_config()> | |
601 | ||
602 | $config->load_config(); | |
603 | ||
604 | Will reload the config files located in the directory specified at object | |
605 | creation (see L</"new()">). | |
606 | ||
607 | BEWARE : If you are using this in a mod_perl environment, you will lose the | |
608 | benefit of shared memory by calling this in a child process | |
609 | - each child will have its own copy of the data. | |
610 | See L<MINIMISING MEMORY USE>. | |
611 | ||
612 | Returns the config hash ref. | |
613 | ||
614 | =cut | |
615 | ||
616 | #=================================== | |
617 | sub load_config { | |
618 | #=================================== | |
619 | my $self = shift; | |
620 | $self->{_memo} = {}; | |
621 | $self->debug("Loading config data"); | |
622 | return $self->{config} = $self->_load_config() || {}; | |
623 | } | |
624 | ||
625 | #=================================== | |
626 | sub _load_config { | |
627 | #=================================== | |
628 | my $self = shift; | |
629 | my $dir = shift || $self->{config_dir}; | |
630 | my $key_path = shift || ''; | |
631 | my $loading_local = shift; | |
632 | ||
633 | my $config = {}; | |
634 | ||
635 | my @local; | |
636 | my $config_files = $self->{sort} | |
637 | ->( $self, [ glob( File::Spec->catfile( $dir, '*' ) ) ] ); | |
638 | ||
639 | my $is_local = $self->{is_local}; | |
640 | $self->debug( '', "Entering dir: $dir", '-' x ( length($dir) + 14 ) ); | |
641 | ||
642 | CONFIG_FILE: | |
643 | foreach my $config_file (@$config_files) { | |
644 | my ( $data, $name, $curr_key_path, $loader ); | |
645 | ||
646 | my $filename = ( File::Spec->splitpath($config_file) )[2]; | |
647 | ||
648 | # If it is a file | |
649 | if ( -f $config_file ) { | |
650 | $self->debug(" Found file : $config_file"); | |
651 | ||
652 | # Must have an extension | |
653 | ( $name, my $ext ) = ( $filename =~ /(.+)[.]([^.]+)/ ) | |
654 | or $self->debug(" ... No extension") && next CONFIG_FILE; | |
655 | ||
656 | # Must have an associated module | |
657 | $loader = $Module_For_Ext{ lc $ext } | |
658 | or $self->debug(" ... No loader") && next CONFIG_FILE; | |
659 | } | |
660 | elsif ( -d $config_file ) { | |
661 | $self->debug(" Found dir : $config_file"); | |
662 | $name = $filename; | |
663 | undef $loader; | |
664 | } | |
665 | ||
666 | # Anything else (eg symlink), skip | |
667 | else { | |
668 | next; | |
669 | } | |
670 | ||
671 | # If it is a local file/dir, process last | |
672 | if ( !$loading_local && $is_local->( $self, $name ) ) { | |
673 | $self->debug(" ... will merge later"); | |
674 | push @local, [ $loader, $config_file, $filename ]; | |
675 | next CONFIG_FILE; | |
676 | } | |
677 | ||
678 | # Find the key name from the filename | |
679 | $name = $self->_load_as( $key_path, $name, $loading_local ); | |
680 | next CONFIG_FILE if not defined $name; | |
681 | ||
682 | # loader = module name to load file, or undef for directory | |
683 | $data | |
684 | = $loader | |
685 | ? $self->_load_config_file( $loader, $config_file ) | |
686 | : $self->_load_config( $config_file, | |
687 | $self->_join_key_path( $key_path, $name ), | |
688 | $loading_local ); | |
689 | ||
690 | next CONFIG_FILE unless defined $data; | |
691 | ||
692 | # Merge keys if already exists | |
693 | if ( exists $config->{$name} | |
694 | && ref $config->{$name} eq 'HASH' | |
695 | && ref $data eq 'HASH' ) | |
696 | { | |
697 | $config->{$name}->{$_} = $data->{$_} foreach keys %$data; | |
698 | } | |
699 | else { | |
700 | $config->{$name} = $data; | |
701 | } | |
702 | } | |
703 | ||
704 | # Merge local config into main config | |
705 | LOCAL_FILE: | |
706 | foreach my $local_file (@local) { | |
707 | my ( $loader, $config_file, $name ) = @$local_file; | |
708 | $self->debug(" Merging file $config_file"); | |
709 | $name = $self->_load_as( $key_path, $name, 1 ); | |
710 | next LOCAL_FILE | |
711 | unless defined $name; | |
712 | ||
713 | my $data | |
714 | = $loader | |
715 | ? $self->_load_config_file( $loader, $config_file ) | |
716 | : $self->_load_config( $config_file, $key_path, 1 ); | |
717 | ||
718 | next LOCAL_FILE unless defined $data; | |
719 | ||
720 | $config = $self->_merge_hash( | |
721 | $config, $name | |
722 | ? { $name => $data } | |
723 | : $data | |
724 | ); | |
725 | } | |
726 | ||
727 | return keys %$config ? $config : undef; | |
728 | } | |
729 | ||
730 | #=================================== | |
731 | sub _load_as { | |
732 | #=================================== | |
733 | my ( $self, $key_path, $name, $loading_local ) = @_; | |
734 | ||
735 | # Find the key name from the filename | |
736 | $name = $self->{load_as}->( $self, $name, $loading_local ); | |
737 | unless ( defined $name ) { | |
738 | $self->debug(" ... Skipped by load_as()"); | |
739 | return; | |
740 | } | |
741 | ||
742 | die "load_as() cannot return '' when loading main config" | |
743 | if !$loading_local && $name eq ''; | |
744 | ||
745 | my $curr_key_path = $self->_join_key_path( $key_path, $name ); | |
746 | $self->debug( " ... loading at : " | |
747 | . ( length($curr_key_path) ? $curr_key_path : '.' ) ); | |
748 | ||
749 | if ( $self->{skip}->( $self, $curr_key_path ) ) { | |
750 | $self->debug(" ... skipped by skip()"); | |
751 | return; | |
752 | } | |
753 | return $name; | |
754 | } | |
755 | ||
756 | #=================================== | |
757 | sub _join_key_path { | |
758 | #=================================== | |
759 | my ( $self, $key_path, $name ) = @_; | |
760 | return $key_path . '.' . $name if length($key_path); | |
761 | return $name; | |
762 | } | |
763 | ||
764 | #=================================== | |
765 | sub _load_config_file { | |
766 | #=================================== | |
767 | my $self = shift; | |
768 | my ( $loader, $config_file ) = @_; | |
769 | $self->debug(" ... with : $loader"); | |
770 | my $data; | |
771 | eval { | |
772 | my @data = $loader->load($config_file); | |
773 | $data | |
774 | = @data > 1 | |
775 | ? \@data | |
776 | : $data[0]; | |
777 | }; | |
778 | if ($@) { | |
779 | die( "Error loading config file $config_file:\n\n" . $@ ); | |
780 | } | |
781 | ||
782 | return $data; | |
783 | } | |
784 | ||
785 | =item C<clear_cache()> | |
786 | ||
787 | $config->clear_cache(); | |
788 | ||
789 | Config data is generally not supposed to be changed at runtime. However, if | |
790 | you do make changes, you may get inconsistent results, because lookups are | |
791 | cached. | |
792 | ||
793 | For instance: | |
794 | ||
795 | print $config->C('db.hosts.session'); # Caches this lookup | |
796 | > "host1 host2 host3" | |
797 | ||
798 | $data = $config->C('db.hosts'); | |
799 | $data->{session} = 123; | |
800 | ||
801 | print $config->C('db.hosts.session'); # uses cached value | |
802 | > "host1 host2 host3" | |
803 | ||
804 | $config->clear_cache(); | |
805 | print $config->C('db.hosts.session'); # uses actual value | |
806 | > "123" | |
807 | ||
808 | =cut | |
809 | ||
810 | #=================================== | |
811 | sub clear_cache { | |
812 | #=================================== | |
813 | my $self = shift; | |
814 | $self->{_memo} = {}; | |
815 | return; | |
816 | } | |
817 | ||
818 | =item C<import()> | |
819 | ||
820 | C<import()> will normally be called automatically when you | |
821 | C<use Config::Merge>. However, you may want to do this: | |
822 | ||
823 | use Config::Merge(); | |
824 | Config::Merge->register_loader('My::Plugin' => 'ext'); | |
825 | Config::Merge->import('My::Config' => '/path/to/config/dir'); | |
826 | ||
827 | If called with two params: C<$config_class> and C<$config_dir>, it | |
828 | generates the new class (which inherits from Config::Merge) | |
829 | specified in C<$config_class>, creates a new | |
830 | object of that class and creates 4 subs: | |
831 | ||
832 | =over | |
833 | ||
834 | =item C<C()> | |
835 | ||
836 | As a function: | |
837 | C('keys...') | |
838 | ||
839 | is the equivalent of: | |
840 | $config->C('keys...'); | |
841 | ||
842 | =item C<clone()> | |
843 | ||
844 | As a function: | |
845 | clone('keys...') | |
846 | ||
847 | is the equivalent of: | |
848 | $config->clone('keys...'); | |
849 | ||
850 | =item C<object()> | |
851 | ||
852 | $config = My::Config->object(); | |
853 | ||
854 | Returns the C<$config> object, | |
855 | ||
856 | =item C<import()> | |
857 | ||
858 | When you use your generated config class, it exports the C<C()> sub into your | |
859 | package: | |
860 | ||
861 | use My::Config; | |
862 | $hosts = C('db.hosts.session'); | |
863 | ||
864 | =back | |
865 | ||
866 | =back | |
867 | ||
868 | =cut | |
869 | ||
870 | #=================================== | |
871 | sub import { | |
872 | #=================================== | |
873 | my $caller_class = shift; | |
874 | my ( $class, $dir ) = @_; | |
875 | return | |
876 | unless defined $class; | |
877 | ||
878 | unless ( defined $dir ) { | |
879 | $dir = $class; | |
880 | $class = $caller_class; | |
881 | } | |
882 | if ( $class eq __PACKAGE__ ) { | |
883 | die <<USAGE; | |
884 | ||
885 | USAGE : use $class ('Your::Config' => '/path/to/config/dir' ); | |
886 | ||
887 | USAGE | |
888 | ||
889 | } | |
890 | ||
891 | my $inc_path = $class; | |
892 | $inc_path =~ s{::}{/}g; | |
893 | $inc_path .= '.pm'; | |
894 | ||
895 | no strict 'refs'; | |
896 | unless ( exists $INC{$inc_path} ) { | |
897 | @{ $class . '::ISA' } = ($caller_class); | |
898 | $INC{$inc_path} = 'Auto-inflated by ' . $caller_class; | |
899 | } | |
900 | ||
901 | my $params = @_ % 2 ? shift() : {@_}; | |
902 | $params->{path} = $dir; | |
903 | my $config = $class->new(%$params); | |
904 | ||
905 | # Export C, clone to the subclass | |
906 | *{ $class . "::C" } | |
907 | = sub { my $c = ref $_[0] ? shift : $config; return C( $c, @_ ) }; | |
908 | *{ $class . "::clone" } = sub { | |
909 | my $c = ref $_[0] ? shift : $config; | |
910 | return clone( $c, @_ ); | |
911 | }; | |
912 | *{ $class . "::object" } = sub { return $config }; | |
913 | ||
914 | # Create a new import sub in the subclass | |
915 | *{ $class . "::import" } = eval ' | |
916 | sub { | |
917 | my $callpkg = caller(0); | |
918 | no strict \'refs\'; | |
919 | *{$callpkg."::C"} = \&' . $class . '::C; | |
920 | }'; | |
921 | ||
922 | return; | |
923 | } | |
924 | ||
925 | #=================================== | |
926 | sub _merge_hash { | |
927 | #=================================== | |
928 | my $self = shift; | |
929 | my $config = shift; | |
930 | my $local = shift; | |
931 | KEY: | |
932 | foreach my $key ( keys %$local ) { | |
933 | if ( ref $local->{$key} eq 'HASH' | |
934 | && exists $config->{$key} ) | |
935 | { | |
936 | if ( ref $config->{$key} eq 'HASH' ) { | |
937 | $self->debug(" ... entering hash : $key"); | |
938 | $config->{$key} | |
939 | = $self->_merge_hash( $config->{$key}, $local->{$key} ); | |
940 | next KEY; | |
941 | } | |
942 | if ( ref $config->{$key} eq 'ARRAY' | |
943 | && exists $local->{$key}{'!'} | |
944 | && ref $local->{$key}{'!'} eq 'HASH' ) | |
945 | { | |
946 | $self->_merge_array( $key, $config, $local ); | |
947 | next KEY; | |
948 | } | |
949 | } | |
950 | $self->debug(" ... setting key : $key"); | |
951 | $config->{$key} = $local->{$key}; | |
952 | } | |
953 | $self->debug(" ... leaving hash"); | |
954 | return $config; | |
955 | } | |
956 | ||
957 | =head1 ADVANCED USAGE | |
958 | ||
959 | The items in the section allow you to customise how Config::Merge | |
960 | loads your data. You may never need them. | |
961 | ||
962 | You can: | |
963 | ||
964 | =over | |
965 | ||
966 | =item * | |
967 | ||
968 | Override array values | |
969 | ||
970 | =item * | |
971 | ||
972 | Skip the loading of parts of your config tree | |
973 | ||
974 | =item * | |
975 | ||
976 | Specify which files / dirs are local | |
977 | ||
978 | =item * | |
979 | ||
980 | Specify how to translate a file / dir name into a key | |
981 | ||
982 | =item * | |
983 | ||
984 | Change order in which files are loaded | |
985 | ||
986 | =item * | |
987 | ||
988 | See debug output | |
989 | ||
990 | =back | |
991 | ||
992 | =over | |
993 | ||
994 | =item Overriding array values | |
995 | ||
996 | Overriding hash values is easy, however arrays are more complex. | |
997 | it may be simpler to copy and paste and edit the array you want to | |
998 | change locally. | |
999 | ||
1000 | However, if your array is too long, and you want to make small changes, | |
1001 | then you can use the following: | |
1002 | ||
1003 | In the main config: | |
1004 | ||
1005 | { | |
1006 | cron => [qw( job1 job2 job3 job4)] | |
1007 | } | |
1008 | ||
1009 | In the local file | |
1010 | ||
1011 | { | |
1012 | cron => { | |
1013 | '3' => 'newjob4', # changes 'job4' -> 'newjob4' | |
1014 | ||
1015 | '!' => { # signals an array override | |
1016 | ||
1017 | '-' => [1], # deletes 'job2' | |
1018 | ||
1019 | '+' => ['job5'], # appends 'job5' | |
1020 | ||
1021 | OR '+' => { # inserts 'job3a' after 'job3' | |
1022 | 2 => 'job3a' | |
1023 | } | |
1024 | } | |
1025 | } | |
1026 | ||
1027 | =over | |
1028 | ||
1029 | =item * | |
1030 | ||
1031 | The override has to be a hash, with at least this structure | |
1032 | C<< { '!' => {} } >> to signal an array override | |
1033 | ||
1034 | =item * | |
1035 | ||
1036 | Any other keys with integers are treated as indexes and | |
1037 | are used to change the value at that index in the original array | |
1038 | ||
1039 | =item * | |
1040 | ||
1041 | The C<'-'> key should contain an array ref, with the indexes of the | |
1042 | elements to remove from the array. | |
1043 | ||
1044 | =item * | |
1045 | ||
1046 | If the C<'+'> key contains an array ref, then its contents are appended | |
1047 | to the original array. | |
1048 | ||
1049 | =item * | |
1050 | ||
1051 | If the C<'+'> key contains a hash ref, then each value is inserted | |
1052 | into the original array at the index given in the key | |
1053 | ||
1054 | =item * | |
1055 | ||
1056 | Indexes are zero based, just as in Perl. | |
1057 | ||
1058 | =back | |
1059 | ||
1060 | =cut | |
1061 | ||
1062 | #=================================== | |
1063 | sub _merge_array { | |
1064 | #=================================== | |
1065 | my ( $self, $key, $config, $local ) = @_; | |
1066 | $self->debug(" ... merging array : $key"); | |
1067 | my $dest = $config->{$key}; | |
1068 | my $merge = $local->{$key}; | |
1069 | my $changes = delete $merge->{'!'}; | |
1070 | ||
1071 | # Changed elements | |
1072 | foreach my $index ( keys %$merge ) { | |
1073 | $index = '' if !defined $index; | |
1074 | die "Array override for key '$key' : '$index' is not an integer" | |
1075 | unless $index =~ /^\d+$/; | |
1076 | $dest->[$index] = $merge->{$index}; | |
1077 | $self->debug(" ... changing index : $index"); | |
1078 | } | |
1079 | ||
1080 | my %actions; | |
1081 | ||
1082 | # Deleted elements | |
1083 | my $remove = $changes->{'-'} || []; | |
1084 | die "Index delete for key '$key' : '-' is not an array ref" | |
1085 | unless ref $remove eq 'ARRAY'; | |
1086 | ||
1087 | foreach my $delete_index (@$remove) { | |
1088 | next unless $delete_index =~ /^\d+/; | |
1089 | $actions{$delete_index} = ['-'] | |
1090 | if $delete_index < @$dest; | |
1091 | } | |
1092 | ||
1093 | # Added elements | |
1094 | my $add = $changes->{'+'} || []; | |
1095 | ||
1096 | # Append | |
1097 | if ( ref $add eq 'ARRAY' ) { | |
1098 | if (@$add) { | |
1099 | push @$dest, @$add; | |
1100 | $self->debug( | |
1101 | ' ... appending ' . ( scalar @$add ) . ' element(s)' ); | |
1102 | } | |
1103 | } | |
1104 | ||
1105 | # Insert | |
1106 | elsif ( ref $add eq 'HASH' ) { | |
1107 | foreach my $add_index ( keys %$add ) { | |
1108 | next unless $add_index =~ /^\d+/; | |
1109 | $actions{$add_index} = [ | |
1110 | ( exists $actions{$add_index} || $add_index >= @$dest ) | |
1111 | ? '~' | |
1112 | : '+', | |
1113 | $add->{$add_index} | |
1114 | ]; | |
1115 | } | |
1116 | ||
1117 | } | |
1118 | else { | |
1119 | die "Array add for key '$key' : '+' is not an array or hash ref"; | |
1120 | } | |
1121 | ||
1122 | foreach my $index ( sort { $b <=> $a } keys %actions ) { | |
1123 | my ( $action, $value ) = @{ $actions{$index} }; | |
1124 | if ( $action eq '-' ) { | |
1125 | splice( @$dest, $index, 1 ); | |
1126 | $self->debug(" ... deleting index : $index"); | |
1127 | next; | |
1128 | } | |
1129 | if ( $action eq '~' ) { | |
1130 | $dest->[$index] = $value; | |
1131 | $self->debug(" ... changing index : $index"); | |
1132 | next; | |
1133 | } | |
1134 | splice( @$dest, $index, 0, $value ); | |
1135 | $self->debug(" ... inserting index : $index"); | |
1136 | } | |
1137 | return; | |
1138 | } | |
1139 | ||
1140 | =item C<skip()> | |
1141 | ||
1142 | $c = Config::Merge->new( | |
1143 | path => '/path/to/config', | |
1144 | skip => qr/regex/, | |
1145 | | [ qr/regex1/, qr/regex2/...] | |
1146 | | { name1 => 1, name2 => 2} | |
1147 | | sub {} | |
1148 | ); | |
1149 | ||
1150 | C<skip()> allows you to skip the loading of parts of your config | |
1151 | tree. For instance, if you don't need a list of cron jobs when running | |
1152 | your web server, you can skip it. | |
1153 | ||
1154 | The decision is made based on the path to that value, eg 'app.db.hosts' | |
1155 | rather than on filenames. Also, the check is only performed for each | |
1156 | new directory or filename - it doesn't check the data within each file. | |
1157 | ||
1158 | To use C<skip()>, you can either subclass it, or pass in a parameter | |
1159 | to new: | |
1160 | ||
1161 | =over | |
1162 | ||
1163 | =item C<qr/regex/> or C<[qr/regex1/, qr/regex2]> | |
1164 | ||
1165 | Each regex will be checked against the key path, and if it matches | |
1166 | then the loading of that tree will be skipped | |
1167 | ||
1168 | =item C<< {key_path => 1} >> | |
1169 | ||
1170 | If the key path exists in the hash, then loading will be skipped | |
1171 | ||
1172 | =item C<sub {}> or subclassed C<skip> | |
1173 | ||
1174 | sub { | |
1175 | my ($self,$key_path) = @_; | |
1176 | ...make decision... | |
1177 | return 1 | 0; | |
1178 | } | |
1179 | ||
1180 | =back | |
1181 | ||
1182 | =cut | |
1183 | ||
1184 | #=================================== | |
1185 | sub skip { | |
1186 | #=================================== | |
1187 | return; | |
1188 | } | |
1189 | ||
1190 | =item C<is_local()> | |
1191 | ||
1192 | $c = Config::Merge->new( | |
1193 | path => '/path/to/config', | |
1194 | is_local => qr/regex/, | |
1195 | | [ qr/regex1/, qr/regex2/...] | |
1196 | | { name1 => 1, name2 => 2} | |
1197 | | sub {} | |
1198 | ); | |
1199 | ||
1200 | C<is_local()> indicates whether a file or dir should be considered | |
1201 | part of the main config (and thus loaded normally) or part of the | |
1202 | local config (and thus merged into the main config). | |
1203 | ||
1204 | The decision is made based on the name of the file / dir, without | |
1205 | any extension. | |
1206 | ||
1207 | To use C<is_local()>, you can either subclass it, or pass in a parameter | |
1208 | to new: | |
1209 | ||
1210 | =over | |
1211 | ||
1212 | =item C<qr/regex/> or C<[qr/regex1/, qr/regex2]> | |
1213 | ||
1214 | Each regex will be checked against the file/dir name, and if it matches | |
1215 | then that tree will be merged | |
1216 | ||
1217 | =item C<< {filename => 1, dirname => 1} >> | |
1218 | ||
1219 | If the file/dir name exists in the hash, then that tree will be merged | |
1220 | ||
1221 | =item C<sub {}> or subclassed C<is_local> | |
1222 | ||
1223 | sub { | |
1224 | my ($self,$name) = @_; | |
1225 | ...make decision... | |
1226 | return 1 | 0; | |
1227 | } | |
1228 | ||
1229 | =back | |
1230 | ||
1231 | See L</"EXAMPLE USING is_local() AND load_as()">. | |
1232 | ||
1233 | =cut | |
1234 | ||
1235 | #=================================== | |
1236 | sub is_local { | |
1237 | #=================================== | |
1238 | my ( $self, $filename ) = @_; | |
1239 | return $filename =~ /^local\b/; | |
1240 | } | |
1241 | ||
1242 | =item C<load_as()> | |
1243 | ||
1244 | $c = Config::Merge->new( | |
1245 | path => '/path/to/config', | |
1246 | load_as => qr/(regex)/, | |
1247 | | sub {} | |
1248 | ); | |
1249 | ||
1250 | C<load_as()> returns the name of the key to use when loading | |
1251 | the file / dir. By default, it returns the C<$name> for main | |
1252 | config files, or C<''> for local files. | |
1253 | ||
1254 | The decision is made based on the name of the file / dir, without | |
1255 | any extension. | |
1256 | ||
1257 | If C<load_as()> returns an empty string, then each key in the file/tree | |
1258 | is merged separately. This is how the C<local.*> files work by default. | |
1259 | See L</"OVERRIDING CONFIG LOCALLY">. | |
1260 | ||
1261 | For instance: | |
1262 | ||
1263 | main.yaml: | |
1264 | key1: value | |
1265 | key2: value | |
1266 | ||
1267 | db.yaml: | |
1268 | key3: value | |
1269 | key4: value | |
1270 | ||
1271 | local.yaml: | |
1272 | main: | |
1273 | key1: new_value | |
1274 | db: | |
1275 | key4: new_value | |
1276 | ||
1277 | To use C<load_as()>, you can either subclass it, or pass in a parameter | |
1278 | to new: | |
1279 | ||
1280 | =over | |
1281 | ||
1282 | =item C<qr/(regex)/> | |
1283 | ||
1284 | The regex will be checked against the file/dir name, and if it matches | |
1285 | then it returns the string captured in the regex, otherwise it returns | |
1286 | the original name. | |
1287 | ||
1288 | =item C<sub {}> or subclassed C<is_local> | |
1289 | ||
1290 | sub { | |
1291 | my ($self,$name,$is_local) = @_; | |
1292 | ...make decision... | |
1293 | return 'string'; # string is used as the keyname | |
1294 | return ''; # acts like local.* (see above) | |
1295 | return undef; # don't load this file/dir | |
1296 | } | |
1297 | ||
1298 | =back | |
1299 | ||
1300 | Also, see L</"EXAMPLE USING is_local() AND load_as()">. | |
1301 | ||
1302 | =cut | |
1303 | ||
1304 | #=================================== | |
1305 | sub load_as { | |
1306 | #=================================== | |
1307 | my ( $self, $filename, $local ) = @_; | |
1308 | return $local ? '' : $filename; | |
1309 | } | |
1310 | ||
1311 | my %callbacks = ( | |
1312 | CODE => \&_init_code_callback, | |
1313 | HASH => \&_init_hash_callback, | |
1314 | ARRAY => \&_init_array_callback, | |
1315 | ); | |
1316 | ||
1317 | =item EXAMPLE USING C<is_local()> AND C<load_as()> | |
1318 | ||
1319 | For instance, instead of using C<local.*> files, you may want to | |
1320 | keep versioned copies of local configs for different machines, and so use: | |
1321 | ||
1322 | app.yaml | |
1323 | app-(dev1.domain.com).yaml | |
1324 | app-(dev2.domain.com).yaml | |
1325 | ||
1326 | You would implement this as follows: | |
1327 | ||
1328 | my $config = Config::Merge->new( | |
1329 | path => '/path/to/config', | |
1330 | ||
1331 | # If matches 'xxx-(yyy)' | |
1332 | is_local => sub { | |
1333 | my ( $self, $name ) = @_; | |
1334 | return $name=~/- [(] .+ [)]/x ? 1 : 0; | |
1335 | }, | |
1336 | ||
1337 | # If local and matches 'xxx-(hostname)', return xxx | |
1338 | load_as => sub { | |
1339 | my ( $self, $name, $is_local ) = @_; | |
1340 | if ($is_local) { | |
1341 | if ( $name=~/(.*) - [(] ($hostname) [)] /x ) { | |
1342 | return $1; | |
1343 | } | |
1344 | return undef; | |
1345 | } | |
1346 | return $name; | |
1347 | } | |
1348 | ); | |
1349 | ||
1350 | See C<examples/advanced.pl> for a working illustration. | |
1351 | ||
1352 | =item C<sort()> | |
1353 | ||
1354 | $c = Config::Merge->new( | |
1355 | path => '/path/to/config', | |
1356 | sort => sub {} | |
1357 | ); | |
1358 | ||
1359 | By default, directory entries are sorted alphabetically, with | |
1360 | directories before filenames. | |
1361 | ||
1362 | This would be the order for these directory entries: | |
1363 | ||
1364 | api/ | |
1365 | api-(dev1)/ | |
1366 | api.yaml | |
1367 | api-(dev1).yaml | |
1368 | ||
1369 | To override this, you can subclass C<sort()> or pass it in as a | |
1370 | parameter to new: | |
1371 | ||
1372 | sub { | |
1373 | my ($self,$names_array_ref) = @_ | |
1374 | ...sort... | |
1375 | return $names_array_ref; | |
1376 | } | |
1377 | ||
1378 | =cut | |
1379 | ||
1380 | #=================================== | |
1381 | sub sort { | |
1382 | #=================================== | |
1383 | my ( $self, $names ) = @_; | |
1384 | s/[.]([^.]+$)/ .$1/ foreach @$names; | |
1385 | $names = [ sort { $a cmp $b } @$names ]; | |
1386 | s/ [.]([^.]+$)/.$1/ foreach @$names; | |
1387 | return $names; | |
1388 | } | |
1389 | ||
1390 | =item C<debug()> | |
1391 | ||
1392 | my $config = Config::Merge->new( | |
1393 | path => '/path/to/config', | |
1394 | debug => 1 | 0 | |
1395 | ); | |
1396 | ||
1397 | If C<debug> is true, then Config::Merge prints out an explanation | |
1398 | of what it is doing on STDERR. | |
1399 | ||
1400 | =back | |
1401 | ||
1402 | =cut | |
1403 | ||
1404 | #=================================== | |
1405 | sub debug { | |
1406 | #=================================== | |
1407 | my $self = shift; | |
1408 | print STDERR ( join( "\n", @_, '' ) ) | |
1409 | if $self->{debug}; | |
1410 | return 1; | |
1411 | } | |
1412 | ||
1413 | #=================================== | |
1414 | sub _init_callback { | |
1415 | #=================================== | |
1416 | my ( $self, $callback, $check ) = @_; | |
1417 | ||
1418 | # If nothing set, use default or subclassed version | |
1419 | unless ($check) { | |
1420 | $self->{$callback} = $self->can($callback); | |
1421 | $self->debug("Using default or subclassed $callback()"); | |
1422 | return; | |
1423 | } | |
1424 | ||
1425 | $check = [$check] | |
1426 | unless exists $callbacks{ ref $check }; | |
1427 | ||
1428 | $self->debug( 'Using ' . ( ref $check ) . " handler for $callback()" ); | |
1429 | ||
1430 | $self->{$callback} = $callbacks{ ref $check }->( $check, $callback ); | |
1431 | return; | |
1432 | } | |
1433 | ||
1434 | #=================================== | |
1435 | sub _init_code_callback { | |
1436 | #=================================== | |
1437 | return $_[0]; | |
1438 | } | |
1439 | ||
1440 | #=================================== | |
1441 | sub _init_hash_callback { | |
1442 | #=================================== | |
1443 | my ( $check, $callback ) = @_; | |
1444 | die "load_as() cannot be a hashref" | |
1445 | if $callback eq 'load_as'; | |
1446 | return sub { | |
1447 | my $self = shift; | |
1448 | my $param = shift; | |
1449 | return exists $check->{$param}; | |
1450 | }; | |
1451 | } | |
1452 | ||
1453 | #=================================== | |
1454 | sub _init_array_callback { | |
1455 | #=================================== | |
1456 | my ( $check, $callback ) = @_; | |
1457 | if ( $callback eq 'load_as' ) { | |
1458 | die "load_as() must contain a single regex" | |
1459 | unless @$check == 1; | |
1460 | my $regex = $check->[0]; | |
1461 | return sub { | |
1462 | my $self = shift; | |
1463 | my $filename = shift; | |
1464 | return $filename =~ m/$regex/ | |
1465 | ? $1 | |
1466 | : $filename; | |
1467 | }; | |
1468 | } | |
1469 | ||
1470 | foreach my $value (@$check) { | |
1471 | $value ||= ''; | |
1472 | die "'$value' is not a regular expression" | |
1473 | unless ref $value eq 'Regexp'; | |
1474 | } | |
1475 | return sub { | |
1476 | my $self = shift; | |
1477 | my $value = shift; | |
1478 | foreach my $regex (@$check) { | |
1479 | return 1 if $value =~ m/$regex/; | |
1480 | } | |
1481 | return 0; | |
1482 | }; | |
1483 | } | |
1484 | ||
1485 | =head1 SEE ALSO | |
1486 | ||
1487 | L<Storable>, L<Config::Any>, L<Config::Any::YAML>, | |
1488 | L<Config::Any::JSON>, L<Config::Any::INI>, L<Config::Any::XML>, | |
1489 | L<Config::Any::General> | |
1490 | ||
1491 | =head1 THANKS | |
1492 | ||
1493 | Thanks to Hasanuddin Tamir [HASANT] for vacating the Config::Merge namespace, | |
1494 | which allowed me to rename Config::Loader to the more meaningful Config::Merge. | |
1495 | ||
1496 | His version of Config::Merge can be found in | |
1497 | L<http://backpan.cpan.org/modules/by-authors/id/H/HA/HASANT/>. | |
1498 | ||
1499 | Thanks to Joel Bernstein and Brian Cassidy for the interface to the various | |
1500 | configuration modules. Also to Ewan Edwards for his suggestions about how | |
1501 | to make Config::Merge more flexible. | |
1502 | ||
1503 | =head1 BUGS | |
1504 | ||
1505 | No bugs have been reported. | |
1506 | ||
1507 | Please report any bugs or feature requests to | |
1508 | L<http://github.com/clintongormley/ConfigMerge/issues>. | |
1509 | ||
1510 | =head1 AUTHOR | |
1511 | ||
1512 | Clinton Gormley, E<lt>clinton@traveljury.comE<gt> | |
1513 | ||
1514 | =head1 COPYRIGHT | |
1515 | ||
1516 | Copyright (C) 2007-2010 by Clinton Gormley | |
1517 | ||
1518 | =cut | |
1519 | ||
1520 | =head1 LICENSE | |
1521 | ||
1522 | This library is free software; you can redistribute it and/or modify | |
1523 | it under the same terms as Perl itself, either Perl version 5.8.7 or, | |
1524 | at your option, any later version of Perl 5 you may have available. | |
1525 | ||
1526 | ||
1527 | =cut | |
1528 | ||
1529 | 1 | |
1530 |
0 | use strict; | |
1 | use warnings FATAL => 'all', NONFATAL => 'redefine'; | |
2 | ||
3 | use File::Spec; | |
4 | use Test::More 'tests' => 30; | |
5 | ||
6 | BEGIN { use_ok('Config::Merge'); } | |
7 | ||
8 | my $config; | |
9 | ok( $config = Config::Merge->new( get_path('empty') ), | |
10 | 'OO - Load empty dir' ); | |
11 | ||
12 | ok( $config = Config::Merge->new( get_path('perl') ), | |
13 | 'OO - Load perl dir' ); | |
14 | ||
15 | is( $config->C('global.domain'), | |
16 | 'www.test.com', | |
17 | 'OO - Simple lookup' ); | |
18 | ||
19 | is( $config->('global.domain'), | |
20 | 'www.test.com', | |
21 | 'OO - Overload lookup' ); | |
22 | ||
23 | is_deeply( scalar $config->C('global.db.hosts.session'), | |
24 | [qw(host1 host2 host3)], | |
25 | 'OO - Array ref lookup' ); | |
26 | ||
27 | is_deeply( [ $config->C('global.db.hosts.session') ], | |
28 | [qw(host1 host2 host3)], | |
29 | 'OO - Array lookup' ); | |
30 | ||
31 | is( $config->C('global.db.hosts.image.1'), | |
32 | 'host5', | |
33 | 'OO - Array element lookup' ); | |
34 | ||
35 | ok( $config = Config::Merge->new( get_path('perlmulti') ), | |
36 | 'OO - Load perl dir' ); | |
37 | ||
38 | my @list; | |
39 | ok( @list = $config->C('global.testsub'), | |
40 | 'OO - Retrieve code'); | |
41 | is( scalar @list, | |
42 | 1, | |
43 | 'OO - CODE ref list context'); | |
44 | ||
45 | is( ref $list[0], | |
46 | 'CODE', | |
47 | 'OO - CODE ref'); | |
48 | ||
49 | is( ref scalar $config->C('global.testsub'), | |
50 | 'CODE', | |
51 | 'OO - CODE ref scalar context'); | |
52 | ||
53 | ok( @list = $config->C('global.testregex'), | |
54 | 'OO - Retrieve regepx'); | |
55 | is( scalar @list, | |
56 | 1, | |
57 | 'OO - Regexp ref list context'); | |
58 | ||
59 | is( ref $list[0], | |
60 | 'Regexp', | |
61 | 'OO - Regexp ref'); | |
62 | ||
63 | is( ref scalar $config->C('global.testregex'), | |
64 | 'Regexp', | |
65 | 'OO - Regexp ref scalar context'); | |
66 | ||
67 | ok( @list = $config->C('global.testobj'), | |
68 | 'OO - Retrieve object'); | |
69 | is( scalar @list, | |
70 | 1, | |
71 | 'OO - Object list context'); | |
72 | ||
73 | is( ref $list[0], | |
74 | 'ABC', | |
75 | 'OO - Object ref'); | |
76 | ||
77 | is( ref scalar $config->C('global.testobj'), | |
78 | 'ABC', | |
79 | 'OO - Object scalar context'); | |
80 | ||
81 | is( $config->C('global.db.hosts.image.1'), | |
82 | 'host5', | |
83 | 'OO - Directory lookup' ); | |
84 | ||
85 | is( $config->C('global.db.hosts.image.1'), | |
86 | 'host5', | |
87 | 'OO - Directory lookup' ); | |
88 | ||
89 | is( defined eval{$config->C('global.db3.hosts.image.1')} ? 1 : 0, | |
90 | 0, | |
91 | 'OO - Directory lookup - fail overwritten' ); | |
92 | ||
93 | is( $config->C('global.db3.different'), | |
94 | 'data', | |
95 | 'OO - Directory lookup - succeed overwritten' ); | |
96 | ||
97 | is( $config->C('global.engine'), | |
98 | 'Oracle', | |
99 | 'OO - Local override' ); | |
100 | ||
101 | is( $config->C('global.db2.hosts.session.0'), | |
102 | 'local1', | |
103 | 'OO - Local override deep' ); | |
104 | ||
105 | $config->clear_cache(); | |
106 | my $data = $config->C('global.db.hosts'); | |
107 | $data->{session} = '123'; | |
108 | is( $config->C('global.db.hosts.session'), | |
109 | '123', | |
110 | 'OO - Overwrite original' ); | |
111 | ||
112 | $data = $config->clone('global.db.hosts'); | |
113 | $data->{image} = '123'; | |
114 | isnt( $config->C('global.db.hosts.image'), | |
115 | '123', | |
116 | 'OO - Overwrite clone' ); | |
117 | ||
118 | $config->load_config(); | |
119 | isnt( $config->C('global.db.hosts.session'), | |
120 | '123', | |
121 | 'OO - Reload data' ); | |
122 | ||
123 | ||
124 | #=================================== | |
125 | sub get_path { | |
126 | #=================================== | |
127 | my ($vol,$path) = File::Spec->splitpath( | |
128 | File::Spec->rel2abs($0) | |
129 | ); | |
130 | $path = File::Spec->catdir( | |
131 | File::Spec->splitdir($path), | |
132 | 'data',@_ | |
133 | ); | |
134 | return File::Spec->catpath($vol,$path,''); | |
135 | } |
0 | use strict; | |
1 | use warnings; | |
2 | ||
3 | use File::Spec; | |
4 | use Test::More 'tests' => 31; | |
5 | ||
6 | my $path; | |
7 | BEGIN { | |
8 | my $vol; | |
9 | ($vol,$path) = File::Spec->splitpath( | |
10 | File::Spec->rel2abs($0) | |
11 | ); | |
12 | $path = File::Spec->catdir( | |
13 | File::Spec->splitdir($path), | |
14 | 'data','perlmulti' | |
15 | ); | |
16 | $path = File::Spec->catpath($vol,$path,''); | |
17 | } | |
18 | ||
19 | BEGIN { use_ok('Config::Merge', 'My' => $path); } | |
20 | BEGIN { use_ok('My'); } | |
21 | ||
22 | is( C('global.domain'), | |
23 | 'www.test.com', | |
24 | 'Func - Simple lookup' ); | |
25 | ||
26 | is_deeply( scalar C('global.db.hosts.session'), | |
27 | [qw(host1 host2 host3)], | |
28 | 'Func - Array ref lookup' ); | |
29 | ||
30 | is_deeply( [ C('global.db.hosts.session') ], | |
31 | [qw(host1 host2 host3)], | |
32 | 'Func - Array lookup' ); | |
33 | ||
34 | is( C('global.db.hosts.image.1'), | |
35 | 'host5', | |
36 | 'Func - Array element lookup' ); | |
37 | ||
38 | my @list; | |
39 | ok( @list = C('global.testsub'), | |
40 | 'Func - Retrieve coderef'); | |
41 | is( scalar @list, | |
42 | 1, | |
43 | 'Func - CODE ref list context'); | |
44 | ||
45 | is( ref $list[0], | |
46 | 'CODE', | |
47 | 'Func - CODE ref'); | |
48 | ||
49 | is( ref scalar C('global.testsub'), | |
50 | 'CODE', | |
51 | 'Func - CODE ref scalar context'); | |
52 | ||
53 | ok( @list = C('global.testregex'), | |
54 | 'Func - Retrieve regexp'); | |
55 | is( scalar @list, | |
56 | 1, | |
57 | 'Func - Regexp ref list context'); | |
58 | ||
59 | is( ref $list[0], | |
60 | 'Regexp', | |
61 | 'Func - Regexp ref'); | |
62 | ||
63 | is( ref scalar C('global.testregex'), | |
64 | 'Regexp', | |
65 | 'Func - Regexp ref scalar context'); | |
66 | ||
67 | ok( @list = C('global.testobj'), | |
68 | 'Func - Retrieve object'); | |
69 | is( scalar @list, | |
70 | 1, | |
71 | 'Func - Object list context'); | |
72 | ||
73 | is( ref $list[0], | |
74 | 'ABC', | |
75 | 'Func - Object ref'); | |
76 | ||
77 | is( ref scalar C('global.testobj'), | |
78 | 'ABC', | |
79 | 'Func - Object scalar context'); | |
80 | ||
81 | is( C('global.db.hosts.image.1'), | |
82 | 'host5', | |
83 | 'Func - Directory lookup' ); | |
84 | ||
85 | is( defined eval{C('global.db3.hosts.image.1')} ? 1 : 0, | |
86 | 0, | |
87 | 'Func - Directory lookup - fail overwritten' ); | |
88 | ||
89 | is( C('global.db3.different'), | |
90 | 'data', | |
91 | 'Func - Directory lookup - succeed overwritten' ); | |
92 | ||
93 | is( C('global.engine'), | |
94 | 'Oracle', | |
95 | 'Func - Local override' ); | |
96 | ||
97 | is( C('global.db2.hosts.session.0'), | |
98 | 'local1', | |
99 | 'Func - Local override deep' ); | |
100 | ||
101 | my $config = My->object(); | |
102 | is( $config->C('global.domain'), | |
103 | 'www.test.com', | |
104 | 'Func - object->C lookup' ); | |
105 | ||
106 | is( $config->('global.domain'), | |
107 | 'www.test.com', | |
108 | 'Func - overload lookup' ); | |
109 | ||
110 | $config->clear_cache(); | |
111 | ||
112 | my $data = C('global.db.hosts'); | |
113 | $data->{session} = '123'; | |
114 | is( C('global.db.hosts.session'), | |
115 | '123', | |
116 | 'Func - Overwrite original' ); | |
117 | ||
118 | $data = My::clone('global.db.hosts'); | |
119 | $data->{image} = '123'; | |
120 | isnt( C('global.db.hosts.image'), | |
121 | '123', | |
122 | 'Func - Overwrite clone' ); | |
123 | ||
124 | My::object->load_config(); | |
125 | isnt( C('global.db.hosts.session'), | |
126 | '123', | |
127 | 'Func - Reload data' ); | |
128 | ||
129 | is ($config, | |
130 | My::object(), | |
131 | 'Func - reload object same'); | |
132 | ||
133 | $config->clear_cache(); | |
134 | $data = $config->C('global.db.hosts'); | |
135 | $data->{session} = '123'; | |
136 | is( $config->C('global.db.hosts.session'), | |
137 | '123', | |
138 | 'Func - Object overwrite original' ); | |
139 | ||
140 | $data = $config->clone('global.db.hosts'); | |
141 | $data->{image} = '123'; | |
142 | isnt( $config->C('global.db.hosts.image'), | |
143 | '123', | |
144 | 'Func - Object overwrite clone' ); |
0 | use strict; | |
1 | use warnings; | |
2 | ||
3 | use File::Spec; | |
4 | use Test::More 'tests' => 17; | |
5 | ||
6 | BEGIN { use_ok('Config::Merge'); } | |
7 | ||
8 | my $config; | |
9 | ||
10 | eval { $config = Config::Merge->new()}; | |
11 | like( $@, | |
12 | qr/Configuration directory not specified/, | |
13 | 'New - no directory' ); | |
14 | ||
15 | eval { $config = Config::Merge->new(get_path('none'))}; | |
16 | like( $@, | |
17 | qr/not readable/, | |
18 | 'New - directory not readable' ); | |
19 | ||
20 | eval { $config = Config::Merge->new(get_path('bad'))}; | |
21 | like( $@, | |
22 | qr/Error loading config/, | |
23 | 'New - Error loading config' ); | |
24 | ||
25 | $config = Config::Merge->new(get_path('perl')); | |
26 | eval { $config->('global.nonexistent')}; | |
27 | like( $@, | |
28 | qr/Invalid key/, | |
29 | 'Invalid key' ); | |
30 | ||
31 | eval {Config::Merge->register_loader()}; | |
32 | like( $@, | |
33 | qr/No loader class/, | |
34 | 'No loader class' ); | |
35 | ||
36 | eval {Config::Merge->register_loader('Config::Merge::None')}; | |
37 | like( $@, | |
38 | qr{Can't locate Config/Merge/None.pm}, | |
39 | 'Bad loader class' ); | |
40 | ||
41 | eval {Config::Merge->import(get_path('perl'))}; | |
42 | like( $@, | |
43 | qr{USAGE}, | |
44 | 'Bad import' ); | |
45 | ||
46 | eval { $config = Config::Merge->new(path => get_path('perl'), load_as => sub {return ''})}; | |
47 | like( $@, | |
48 | qr/load_as\(\) cannot return ''/, | |
49 | "New - main load_as '' " ); | |
50 | ||
51 | eval { $config = Config::Merge->new(path => get_path('errors','array_merge'))}; | |
52 | like( $@, | |
53 | qr/Array override for key/, | |
54 | "Array override" ); | |
55 | ||
56 | eval { $config = Config::Merge->new(path => get_path('errors','array_delete_ref'))}; | |
57 | like( $@, | |
58 | qr/Index delete.*array ref/, | |
59 | "Array delete ref" ); | |
60 | ||
61 | ok ($config = Config::Merge->new(path => get_path('errors','array_delete_int')), | |
62 | 'Array delete int' | |
63 | ); | |
64 | ||
65 | eval { $config = Config::Merge->new(path => get_path('errors','array_insert_ref'))}; | |
66 | like( $@, | |
67 | qr/Array add .*ref/, | |
68 | "Array insert ref" ); | |
69 | ||
70 | ok ($config = Config::Merge->new(path => get_path('errors','array_insert_int')), | |
71 | 'Array insert int' | |
72 | ); | |
73 | ||
74 | eval { $config = Config::Merge->new(path => get_path('empty'), load_as =>{})}; | |
75 | like( $@, | |
76 | qr/load_as\(\) cannot be a hashref/, | |
77 | "Load_as hash ref" ); | |
78 | ||
79 | eval { $config = Config::Merge->new(path => get_path('empty'), load_as => [])}; | |
80 | like( $@, | |
81 | qr/single regex/, | |
82 | "Load_as array ref" ); | |
83 | ||
84 | eval { $config = Config::Merge->new(path => get_path('empty'), is_local => 'abc')}; | |
85 | like( $@, | |
86 | qr/not a regular expression/, | |
87 | "Not regex" ); | |
88 | ||
89 | #=================================== | |
90 | sub get_path { | |
91 | #=================================== | |
92 | my ($vol,$path) = File::Spec->splitpath( | |
93 | File::Spec->rel2abs($0) | |
94 | ); | |
95 | $path = File::Spec->catdir( | |
96 | File::Spec->splitdir($path), | |
97 | 'data',@_ | |
98 | ); | |
99 | return File::Spec->catpath($vol,$path,''); | |
100 | } |
0 | use strict; | |
1 | use warnings; | |
2 | ||
3 | use File::Spec; | |
4 | use Test::More 'tests' => 80; | |
5 | ||
6 | BEGIN { use_ok('Config::Merge'); } | |
7 | ||
8 | my ($c,$C); | |
9 | ||
10 | my $path = get_path('skip'); | |
11 | ||
12 | ## SKIP | |
13 | ||
14 | ok($c = Config::Merge->new(path => $path, skip => qr{skip_\d}), | |
15 | 'new - skip - regex' | |
16 | ); | |
17 | $C=$c->(); | |
18 | ||
19 | ok( exists $C->{skip_not}, | |
20 | 'skip - dir - regex - 1' | |
21 | ); | |
22 | ||
23 | ok( ! exists $C->{skip_1}, | |
24 | 'skip - dir - regex - 2' | |
25 | ); | |
26 | ||
27 | ok( exists $C->{main}{skip_not}, | |
28 | 'skip - file - regex - 1' | |
29 | ); | |
30 | ||
31 | ok(! exists $C->{main}{skip_1}, | |
32 | 'skip - file - regex - 2' | |
33 | ); | |
34 | ||
35 | ok($c = Config::Merge->new(path => $path, skip => [qr{skip_1}, qr{skip_2}]), | |
36 | 'new - skip - regexen' | |
37 | ); | |
38 | $C=$c->(); | |
39 | ||
40 | ok( exists $C->{skip_not}, | |
41 | 'skip - dir - regexen - 1' | |
42 | ); | |
43 | ||
44 | ok( ! exists $C->{skip_1}, | |
45 | 'skip - dir - regexen - 2' | |
46 | ); | |
47 | ||
48 | ok( exists $C->{main}{skip_not}, | |
49 | 'skip - file - regexen - 1' | |
50 | ); | |
51 | ||
52 | ok(! exists $C->{main}{skip_1}, | |
53 | 'skip - file - regexen - 2' | |
54 | ); | |
55 | ||
56 | ok($c = Config::Merge->new(path => $path, skip => {'skip_1' => 1,'main.skip_1' => 1}), | |
57 | 'new - skip - hash' | |
58 | ); | |
59 | $C=$c->(); | |
60 | ||
61 | ok( exists $C->{skip_not}, | |
62 | 'skip - dir - hash - 1' | |
63 | ); | |
64 | ||
65 | ok( ! exists $C->{skip_1}, | |
66 | 'skip - dir - hash - 2' | |
67 | ); | |
68 | ||
69 | ok( exists $C->{main}{skip_not}, | |
70 | 'skip - file - hash - 1' | |
71 | ); | |
72 | ||
73 | ok(! exists $C->{main}{skip_1}, | |
74 | 'skip - file - hash - 2' | |
75 | ); | |
76 | ||
77 | ok($c = Config::Merge->new(path => $path, skip => sub { | |
78 | my ($self,$filename) = @_; | |
79 | return 1 if $filename=~/skip_1/; | |
80 | return 0; | |
81 | }), | |
82 | 'new - skip - sub' | |
83 | ); | |
84 | $C=$c->(); | |
85 | ||
86 | ok( exists $C->{skip_not}, | |
87 | 'skip - dir - sub - 1' | |
88 | ); | |
89 | ||
90 | ok( ! exists $C->{skip_1}, | |
91 | 'skip - dir - sub - 2' | |
92 | ); | |
93 | ||
94 | ok( exists $C->{main}{skip_not}, | |
95 | 'skip - file - sub - 1' | |
96 | ); | |
97 | ||
98 | ok(! exists $C->{main}{skip_1}, | |
99 | 'skip - file - sub - 2' | |
100 | ); | |
101 | ||
102 | ## LOAD_AS for main config | |
103 | $path = get_path('load_as'); | |
104 | ||
105 | ok($c = Config::Merge->new(path => $path), | |
106 | 'new - main load_as - none' | |
107 | ); | |
108 | ||
109 | is($c->C('file.a'), | |
110 | 1, | |
111 | 'main load_as - none - 1' | |
112 | ); | |
113 | ||
114 | is($c->C('file-(local).a'), | |
115 | 4, | |
116 | 'main load_as - none - 2' | |
117 | ); | |
118 | ||
119 | is($c->C('dir.file.a'), | |
120 | 1, | |
121 | 'main load_as - none - 3' | |
122 | ); | |
123 | ||
124 | is($c->C('dir-(local).file.a'), | |
125 | 4, | |
126 | 'main load_as - none - 4' | |
127 | ); | |
128 | ||
129 | is($c->C('sub.test.foo'), | |
130 | 'test', | |
131 | 'main load_as - none - 5' | |
132 | ); | |
133 | ||
134 | is($c->C('sub.test.bar'), | |
135 | 'test', | |
136 | 'main load_as - none - 6' | |
137 | ); | |
138 | ||
139 | is($c->C('sub.test-(aaa).foo'), | |
140 | 'test-(aaa)', | |
141 | 'main load_as - none - 7' | |
142 | ); | |
143 | ||
144 | is($c->C('sub.test-(bbb).foo'), | |
145 | 'test-(bbb)', | |
146 | 'main load_as - none - 8' | |
147 | ); | |
148 | ||
149 | is($c->C('sub.dir.test.foo'), | |
150 | 'test', | |
151 | 'main load_as - none - 9' | |
152 | ); | |
153 | ||
154 | is($c->C('sub.dir.test.bar'), | |
155 | 'test', | |
156 | 'main load_as - none - 10' | |
157 | ); | |
158 | ||
159 | is($c->C('sub.dir-(aaa).test.foo'), | |
160 | 'test-(aaa)', | |
161 | 'main load_as - none - 11' | |
162 | ); | |
163 | ||
164 | is($c->C('sub.dir-(bbb).test.foo'), | |
165 | 'test-(bbb)', | |
166 | 'main load_as - none - 12' | |
167 | ); | |
168 | ||
169 | ok($c = Config::Merge->new(path => $path, load_as => qr/(.*)-\(local\)/), | |
170 | 'new - load_as - regex' | |
171 | ); | |
172 | $C=$c->(); | |
173 | ||
174 | is($c->C('file.a'), | |
175 | '4', | |
176 | 'load_as - regex - 1' | |
177 | ); | |
178 | ||
179 | is($c->C('file.d'), | |
180 | '5', | |
181 | 'load_as - regex - 2' | |
182 | ); | |
183 | ||
184 | is($c->C('file.b'), | |
185 | '2', | |
186 | 'load_as - regex - 3' | |
187 | ); | |
188 | ||
189 | is($c->C('dir.file.a'), | |
190 | '4', | |
191 | 'load_as - regex - 4' | |
192 | ); | |
193 | ||
194 | is($c->C('dir.file.d'), | |
195 | '5', | |
196 | 'load_as - regex - 5' | |
197 | ); | |
198 | ||
199 | ok (!exists $C->{dir}{file}{b}, | |
200 | 'load_as - regex - 6' | |
201 | ); | |
202 | ||
203 | ok($c = Config::Merge->new(path => $path, | |
204 | load_as => sub { | |
205 | my ($self,$name) = @_; | |
206 | if ($name=~/(.*)-[(](\w+)[)]/) { | |
207 | return $2 eq 'aaa' ? $1 : undef; | |
208 | } | |
209 | return $name; | |
210 | }), | |
211 | 'new - load_as - sub' | |
212 | ); | |
213 | $C=$c->(); | |
214 | ||
215 | is($c->C('sub.test.foo'), | |
216 | 'test-(aaa)', | |
217 | 'load_as - sub - 1' | |
218 | ); | |
219 | ||
220 | is($c->C('sub.test.bar'), | |
221 | 'test', | |
222 | 'load_as - sub - 2' | |
223 | ); | |
224 | ||
225 | is($c->C('sub.dir.test.foo'), | |
226 | 'test-(aaa)', | |
227 | 'load_as - sub - 3' | |
228 | ); | |
229 | ||
230 | ok (!exists $C->{sub}{dir}{test}{bar}, | |
231 | 'load_as - sub - 4' | |
232 | ); | |
233 | ||
234 | ok (!exists $C->{sub}{'test-(aaa)'}, | |
235 | 'load_as - sub - 5' | |
236 | ); | |
237 | ||
238 | ok (!exists $C->{sub}{'test-(bbb)'}, | |
239 | 'load_as - sub - 6' | |
240 | ); | |
241 | ||
242 | ok (!exists $C->{sub}{'dir-(aaa)'}, | |
243 | 'load_as - sub - 7' | |
244 | ); | |
245 | ||
246 | ok (!exists $C->{sub}{'dir-(bbb)'}, | |
247 | 'load_as - sub - 7' | |
248 | ); | |
249 | ||
250 | ## SORT | |
251 | ok($c = Config::Merge->new(path => $path, | |
252 | load_as => qr/(.*)-\(local\)/, | |
253 | sort => sub {return [sort @{$_[1]}]}), | |
254 | 'new - load_as - regex' | |
255 | ); | |
256 | ||
257 | is($c->C('file.a'), | |
258 | '1', | |
259 | 'sort' | |
260 | ); | |
261 | ||
262 | ## IS_LOCAL | |
263 | $path=get_path('local'); | |
264 | ok($c = Config::Merge->new(path => $path), | |
265 | 'new - is_local - none' | |
266 | ); | |
267 | ||
268 | is ($c->C('main.db.servers.server1.host'), | |
269 | 'host1', | |
270 | 'is_local - none - 1' | |
271 | ); | |
272 | ||
273 | is ($c->C('main.db.servers.list.0'), | |
274 | 'server1', | |
275 | 'is_local - none - 2' | |
276 | ); | |
277 | ||
278 | ok($c = Config::Merge->new(path => $path, is_local => qr{override}), | |
279 | 'new - is_local - regex - ..' | |
280 | ); | |
281 | ||
282 | is ($c->C('main.db.servers.server1.host'), | |
283 | 'host4', | |
284 | 'is_local - regex - .. - 1' | |
285 | ); | |
286 | ||
287 | is ($c->C('main.db.servers.list.0'), | |
288 | 'server3', | |
289 | 'is_local - regex - .. - 2' | |
290 | ); | |
291 | ||
292 | ok($c = Config::Merge->new(path => $path, is_local => {override => 1}), | |
293 | 'new - is_local - hash - ..' | |
294 | ); | |
295 | ||
296 | is ($c->C('main.db.servers.server1.host'), | |
297 | 'host4', | |
298 | 'is_local - hash - .. - 1' | |
299 | ); | |
300 | ||
301 | is ($c->C('main.db.servers.list.0'), | |
302 | 'server3', | |
303 | 'is_local - hash - .. - 2' | |
304 | ); | |
305 | ||
306 | ok($c = Config::Merge->new(path => $path, is_local => sub { return $_[1] eq 'override'}), | |
307 | 'new - is_local - sub - ..' | |
308 | ); | |
309 | ||
310 | is ($c->C('main.db.servers.server1.host'), | |
311 | 'host4', | |
312 | 'is_local - sub - .. - 1' | |
313 | ); | |
314 | ||
315 | is ($c->C('main.db.servers.list.0'), | |
316 | 'server3', | |
317 | 'is_local - sub - .. - 2' | |
318 | ); | |
319 | ||
320 | ok($c = Config::Merge->new(path => $path, | |
321 | is_local => qr{-[(].+[)]}, | |
322 | load_as => qr{(.*)-[(].+[)]}), | |
323 | 'new - is_local - regex - key' | |
324 | ); | |
325 | ||
326 | is ($c->C('email.address.sig'), | |
327 | 'Us', | |
328 | 'is_local - regex - key - 1' | |
329 | ); | |
330 | ||
331 | is ($c->C('email.address.from'), | |
332 | 'dev@', | |
333 | 'is_local - regex - key - 2' | |
334 | ); | |
335 | ||
336 | is ($c->C('email.address.headers.0'), | |
337 | 'ddd', | |
338 | 'is_local - regex - key - 3' | |
339 | ); | |
340 | ||
341 | is ($c->C('email.address.subject'), | |
342 | 'DEV', | |
343 | 'is_local - regex - key - 4' | |
344 | ); | |
345 | ||
346 | ||
347 | ok($c = Config::Merge->new(path => $path, | |
348 | is_local => qr{-[(].+[)]}, | |
349 | load_as => sub { | |
350 | my $name = $_[1]; | |
351 | if ($name=~/(.*)-[(](.*)[)]/) { | |
352 | return $2 eq 'aaa' ? $1 : undef; | |
353 | } | |
354 | return $name;} | |
355 | ), | |
356 | 'new - is_local - sub - key' | |
357 | ); | |
358 | ||
359 | is ($c->C('email.address.sig'), | |
360 | 'Us', | |
361 | 'is_local - sub - key - 1' | |
362 | ); | |
363 | ||
364 | is ($c->C('email.address.from'), | |
365 | 'dev@', | |
366 | 'is_local - sub - key - 2' | |
367 | ); | |
368 | ||
369 | is ($c->C('email.address.headers.0'), | |
370 | 'ddd', | |
371 | 'is_local - sub - key - 3' | |
372 | ); | |
373 | ||
374 | is ($c->C('email.address.subject'), | |
375 | 'HELP', | |
376 | 'is_local - sub - key - 4' | |
377 | ); | |
378 | ||
379 | $path = get_path('array'); | |
380 | ||
381 | ok($c = Config::Merge->new(path => $path), | |
382 | 'new - array merge' | |
383 | ); | |
384 | ||
385 | is_deeply( | |
386 | scalar $c->C('main.foo'), | |
387 | [qw(a b d f h i Z k l m)], | |
388 | 'array merge - 1' | |
389 | ); | |
390 | ||
391 | is_deeply( | |
392 | scalar $c->C('main.bar'), | |
393 | [qw(a b Z d Y f h i j), undef, undef, 'X',undef, 'W'], | |
394 | 'array merge - 2' | |
395 | ); | |
396 | ||
397 | is_deeply( | |
398 | scalar $c->C('main.baz'), | |
399 | [qw(x y z)], | |
400 | 'array merge - 3' | |
401 | ); | |
402 | ||
403 | ||
404 | ## EXPLAIN | |
405 | my ($debug, $olderr); | |
406 | open $olderr, '>&', \*STDERR or die "Can't dup STDERR: $!"; | |
407 | close STDERR or die "Can't close STDERR : $!"; | |
408 | open STDERR, '>>', \$debug or die "Can't open STDERR to debug : $!"; | |
409 | ||
410 | ok($c = Config::Merge->new(path => $path, debug => 1), | |
411 | 'new - debug' | |
412 | ); | |
413 | ||
414 | open STDERR, '>&', $olderr or die "Can't dup OLDERR: $!"; | |
415 | like($debug, | |
416 | qr/Entering dir/, | |
417 | 'debug' | |
418 | ); | |
419 | ||
420 | #=================================== | |
421 | sub get_path { | |
422 | #=================================== | |
423 | my ($vol,$path) = File::Spec->splitpath( | |
424 | File::Spec->rel2abs($0) | |
425 | ); | |
426 | $path = File::Spec->catdir( | |
427 | File::Spec->splitdir($path), | |
428 | 'data',@_ | |
429 | ); | |
430 | return File::Spec->catpath($vol,$path,''); | |
431 | } | |
432 | ||
433 | 1;⏎ |
0 | use Test::More; | |
1 | eval "use Test::Pod 1.00"; | |
2 | plan skip_all => "Test::Pod 1.00 required for testing POD" if $@; | |
3 | all_pod_files_ok(); |
0 | use Test::More; | |
1 | eval "use Test::Pod::Coverage tests=>1"; | |
2 | plan skip_all => "Test::Pod::Coverage required for testing POD coverage" | |
3 | if $@; | |
4 | ||
5 | pod_coverage_ok( "Config::Merge", "Config::Merge is covered" ); |
0 | { | |
1 | main => { | |
2 | foo => { | |
3 | '!' => { | |
4 | '-' => [2,4,6], | |
5 | '+' => [qw(k l m)], | |
6 | }, | |
7 | '9' => 'Z', | |
8 | }, | |
9 | bar => { | |
10 | '!' => { | |
11 | '-' => [2,4,6,20], | |
12 | '+' => { | |
13 | 2 => 'Z', | |
14 | 5 => 'Y', | |
15 | 12 => 'X', | |
16 | 14 => 'W', | |
17 | }, | |
18 | }, | |
19 | }, | |
20 | baz => [qw( x y z )], | |
21 | } | |
22 | } |
0 | { | |
1 | foo => [qw( a b c d e f g h i j )], | |
2 | bar => [qw( a b c d e f g h i j )], | |
3 | baz => [qw( a b c d e f g h i j )], | |
4 | } |
0 | { foo => [ 1, 2, 3, 4 ] } |
0 | { foo => [ 1, 2, 3, 4 ] } |
0 | { foo => [ 1, 2, 3, 4 ] } |
0 | { foo => [ 1, 2, 3, 4 ] } |
0 | { foo => [ 1, 2, 3, 4 ] } |
0 | { foo => 'test-(aaa)' } |
0 | { foo => 'test-(bbb)' } |
0 | { foo => 'test-(aaa)' } |
0 | { foo => 'test-(bbb)' } |
0 | { | |
1 | address => { | |
2 | sig => 'Us', | |
3 | from => 'help@', | |
4 | subject => 'HELP', | |
5 | headers => [qw(aaa bbb ccc )], | |
6 | }, | |
7 | } |
0 | { | |
1 | db => { | |
2 | servers => { | |
3 | server1 => { | |
4 | host => 'host1', | |
5 | user => 'user1', | |
6 | }, | |
7 | server2 => { | |
8 | host => 'host2', | |
9 | user => 'user2', | |
10 | }, | |
11 | list => [qw(server1 server2)], | |
12 | } | |
13 | } | |
14 | } |
0 | { | |
1 | main => { | |
2 | db => { | |
3 | servers => { | |
4 | server1 => { | |
5 | host => 'host4', | |
6 | user => 'user4', | |
7 | }, | |
8 | server3 => { | |
9 | host => 'host3', | |
10 | user => 'user3', | |
11 | }, | |
12 | list => [qw(server3)], | |
13 | } | |
14 | } | |
15 | } | |
16 | } |
0 | { | |
1 | db => { | |
2 | hosts => { | |
3 | session => [qw(host1 host2 host3)], | |
4 | image => [qw(host4 host5 host6)], | |
5 | }, | |
6 | engine => 'MySQL', | |
7 | }, | |
8 | domain => 'www.test.com', | |
9 | } | |
10 | ||
11 |
0 | { hosts => { | |
1 | session => [qw(host1 host2 host3)], | |
2 | image => [qw(host4 host5 host6)], | |
3 | }, | |
4 | }, | |
5 | ||
6 |
0 | { hosts => { | |
1 | session => [qw(host1 host2 host3)], | |
2 | image => [qw(host4 host5 host6)], | |
3 | }, | |
4 | } | |
5 | ||
6 |
0 | { hosts => { | |
1 | session => [qw(host1 host2 host3)], | |
2 | image => [qw(host4 host5 host6)], | |
3 | }, | |
4 | } | |
5 | ||
6 |
0 | { | |
1 | domain => 'www.test.com', | |
2 | db3 => { | |
3 | different => 'data', | |
4 | }, | |
5 | engine => 'MySQL', | |
6 | testsub => sub {'test'}, | |
7 | testregex => qr/test/, | |
8 | testobj => bless({},'ABC'), | |
9 | list => { a => 'b'}, | |
10 | array => [1,2,3,4], | |
11 | } | |
12 | ||
13 |
0 | 'foo' |
0 | 'foo' |
0 | 'foo' |
0 | 'foo' |
0 | 'foo' |
0 | 'foo' |