File Coverage

blib/lib/Mojolicious/Plugin/MultiConfig.pm
Criterion Covered Total %
statement 12 12 100.0
branch n/a
condition n/a
subroutine 4 4 100.0
pod n/a
total 16 16 100.0


line stmt bran cond sub pod time code
1             package Mojolicious::Plugin::MultiConfig;
2              
3 1     1   24723 use v5.10;
  1         4  
  1         53  
4 1     1   11 use strict;
  1         2  
  1         33  
5 1     1   6 use warnings;
  1         12  
  1         35  
6 1     1   927 use parent 'Mojolicious::Plugin';
  1         315  
  1         5  
7              
8             use Config::Any;
9             use File::Spec::Functions;
10              
11             =head1 NAME
12              
13             Mojolicious::Plugin::MultiConfig - Load multiple configs and merge
14              
15             =head1 VERSION
16              
17             Version 0.2
18              
19             =cut
20              
21             our $VERSION = '0.2';
22              
23             =head1 SYNOPSIS
24              
25             # In Mojo startup()...
26              
27             my $config = $self->plugin('multi_config', foo => 'bar');
28             say($config->{this});
29             say($self->config->{that});
30              
31             =head1 DESCRIPTION
32              
33             L is a wrapper around L to load multiple configuration files and merge them together.
34              
35             The default behaviour is to load 3 config files based on templates containing moniker (%a), mode (%m), username (%u) and file extenstion (%e):
36              
37             * %a.%e
38             * %a.%m.%e
39             * %a.%m.%u.%e
40              
41             The config files are loaded in order, with later files in the chain overwriting option(s) in the previous config file.
42              
43             The filenames are configurable, see OPTIONS section below.
44              
45             Missing config files are ignored without error. (TODO: config option to be added in the near future ensure an error).
46              
47             =head1 OPTIONS
48              
49             L supports some options. They can be passed as named args. e.g.:
50              
51             $self->plugin('multi_config', option1 => 'value1', foo => 'bar');
52              
53             =head2 moniker
54              
55             moniker => 'appconfig'
56              
57             Will use this value in place of %a (moniker) in filenames. Defaults to C<$app-Emoniker> if not specified.
58              
59             =head2 mode
60              
61             mode => 'test'
62              
63             Will use this value in place of %m (mode) in filenames. Defaults to C<$app-Emode> if not specified.
64              
65             =head2 dir
66              
67             dir => 'my_config_dir'
68              
69             Will load config files from this directory. Defaults to C<$app-Ehome('conf')> if not specified.
70              
71             =head2 files
72              
73             files => ['file1.conf', 'file2.%u.conf']
74              
75             Will load these config files instead of the default. You can use template codes as per DESCRIPTION section.
76              
77             Defaults to C<['%a.%e', '%a.%m.%e', '%a.%m.%u.%e']> if not specified.
78              
79             =head2 ext
80              
81             ext => 'yml'
82              
83             Will use this value in place of %e (file extension) in filenames. Defaults to C if not specified.
84              
85             =head1 METHODS
86              
87             =head2 register
88              
89             Register as a plugin and load config files. Called automatically by C<$app-Eplugin>
90              
91             =cut
92              
93             sub register
94             {
95             my $self = shift;
96             my $app = shift;
97             my $arg = shift;
98             my $config = {};
99             my $username = (getpwuid($<))[0]; # TODO: Not on Windows!
100             my @files;
101              
102             # Default args if not set
103             $arg->{moniker} //= $app->moniker;
104             $arg->{mode} //= $app->mode;
105             $arg->{dir} //= catfile($app->home, 'conf');
106             $arg->{files} //= ['%a.%e', '%a.%m.%e', '%a.%m.%u.%e'];
107             $arg->{ext} //= 'conf';
108              
109             for (my $i = 0; $i < @{$arg->{files}}; $i++) {
110             # Prefix dir
111             $arg->{files}->[$i] = catfile($arg->{dir}, $arg->{files}->[$i]);
112              
113             # Search/replace for codes used in files
114             $arg->{files}->[$i] =~ s/\%a/$arg->{moniker}/g;
115             $arg->{files}->[$i] =~ s/\%m/$arg->{mode}/g;
116             $arg->{files}->[$i] =~ s/\%u/$username/g;
117             $arg->{files}->[$i] =~ s/\%e/$arg->{ext}/g;
118              
119             if (-e $arg->{files}->[$i]) {
120             $app->log->debug(__PACKAGE__ . ': found ' . $arg->{files}->[$i]);
121             }
122             else {
123             $app->log->debug(__PACKAGE__ . ': cannot find ' . $arg->{files}->[$i]);
124             splice(@{$arg->{files}}, $i--, 1);
125             }
126             }
127              
128             # Load the config file(s)
129             my $config_tmp = Config::Any->load_files({
130             files => $arg->{files},
131             use_ext => 1,
132             flatten_to_hash => 1,
133             driver_args => {
134             General => {-UTF8 => 1},
135             },
136             });
137              
138             # Merge
139             $config = {%$config, %{$config_tmp->{$_}}} for (@{$arg->{files}});
140              
141             $app->config($config);
142             $app->log->debug($app->dumper($config));
143            
144             return $config;
145             }
146              
147             =head1 AUTHOR
148              
149             Ben Vinnerd, C<< >>
150              
151             =head1 LICENCE AND COPYRIGHT
152              
153             Copyright 2013 Ben Vinnerd.
154              
155             This program is free software; you can redistribute it and/or modify it
156             under the terms of the the Artistic License (2.0). You may obtain a
157             copy of the full license at:
158              
159             L
160              
161             Any use, modification, and distribution of the Standard or Modified
162             Versions is governed by this Artistic License. By using, modifying or
163             distributing the Package, you accept this license. Do not use, modify,
164             or distribute the Package, if you do not accept this license.
165              
166             If your Modified Version has been derived from a Modified Version made
167             by someone other than you, you are nevertheless required to ensure that
168             your Modified Version complies with the requirements of this license.
169              
170             This license does not grant you the right to use any trademark, service
171             mark, tradename, or logo of the Copyright Holder.
172              
173             This license includes the non-exclusive, worldwide, free-of-charge
174             patent license to make, have made, use, offer to sell, sell, import and
175             otherwise transfer the Package with respect to any patent claims
176             licensable by the Copyright Holder that are necessarily infringed by the
177             Package. If you institute patent litigation (including a cross-claim or
178             counterclaim) against any party alleging that the Package constitutes
179             direct or contributory patent infringement, then this Artistic License
180             to you shall terminate on the date that such litigation is filed.
181              
182             Disclaimer of Warranty: THE PACKAGE IS PROVIDED BY THE COPYRIGHT HOLDER
183             AND CONTRIBUTORS "AS IS' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES.
184             THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
185             PURPOSE, OR NON-INFRINGEMENT ARE DISCLAIMED TO THE EXTENT PERMITTED BY
186             YOUR LOCAL LAW. UNLESS REQUIRED BY LAW, NO COPYRIGHT HOLDER OR
187             CONTRIBUTOR WILL BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, OR
188             CONSEQUENTIAL DAMAGES ARISING IN ANY WAY OUT OF THE USE OF THE PACKAGE,
189             EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
190              
191             =cut
192              
193             1;