File Coverage

lib/Mojolicious/Plugin/INIConfig/Extended.pm
Criterion Covered Total %
statement 81 82 98.7
branch 35 42 83.3
condition 19 26 73.0
subroutine 9 9 100.0
pod 2 2 100.0
total 146 161 90.6


line stmt bran cond sub pod time code
1             package Mojolicious::Plugin::INIConfig::Extended;
2 2     2   22019 use Mojo::Base 'Mojolicious::Plugin::INIConfig';
  2         4  
  2         13  
3 2     2   4746 use Config::Tiny;
  2         4  
  2         37  
4 2     2   13 use File::Spec::Functions 'file_name_is_absolute';
  2         3  
  2         100  
5 2     2   9 use Mojo::Util qw/encode decode slurp/;
  2         2  
  2         97  
6 2     2   9 use Data::Dumper;
  2         3  
  2         1915  
7              
8             our $VERSION = '0.06';
9              
10             sub register {
11 8     8 1 25115 my ($self, $app, $conf) = @_;
12            
13             # Config file
14 8   66     30 my $file = $conf->{file} || $ENV{MOJO_CONFIG};
15 8   50     27 $file ||= $app->moniker . '.' . ($conf->{ext} || 'ini');
      66        
16              
17             # Mode specific config file
18 8 100       112 my $mode = $file =~ /^(.*)\.([^.]+)$/ ? join('.', $1, $app->mode, $2) : '';
19              
20 8         51 my $home = $app->home;
21 8 100       46 $file = $home->rel_file($file) unless file_name_is_absolute $file;
22 8 100 100     161 $mode = $home->rel_file($mode) if $mode && !file_name_is_absolute $mode;
23 8 100 100     174 $mode = undef unless $mode && -e $mode;
24              
25             # Read config file
26 8         14 my $config = {};
27              
28 8 100 66     143 if (-e $file) {
    100 66        
    100          
29 1         9 $config = $self->load($file, $conf, $app);
30             # check to see if we should overload a base configuration
31             } elsif( exists $conf->{'base_config'} && exists $conf->{'config_files'} ) {
32 4         9 $config = $self->inherit( $conf );
33             # Check for default and mode specific config file
34             } elsif (!$conf->{default} && !$mode) {
35 2         18 die qq{Config file "$file" missing, maybe you need to create it?\n};
36             }
37              
38             # Merge everything
39 4 100       1058 if ($mode) {
40 1         4 my $mode_config = $self->load($mode, $conf, $app);
41 1         1136 for my $key (keys %$mode_config) {
42             $config->{$key}
43 1 50       2 = {%{$config->{$key} || {}}, %{$mode_config->{$key} || {}}};
  1 50       5  
  1         7  
44             }
45             }
46 4 100       10 if ($conf->{default}) {
47 1         3 my $default_config = $conf->{default};
48 1         4 for my $key (keys %$default_config) {
49             $config->{$key}
50 1 50       3 = {%{$default_config->{$key} || {}}, %{$config->{$key} || {}}, };
  1 50       4  
  1         11  
51             }
52             }
53 4         13 my $current = $app->defaults(config => $app->config)->config;
54 4         99 for my $key (keys %$config) {
55 2         21 %{$current->{$key}}
56 2 50       2 = (%{$current->{$key} || {}}, %{$config->{$key} || {}});
  2 50       9  
  2         6  
57             }
58              
59 4         19 return $current;
60             }
61              
62             sub inherit {
63 4     4 1 4 my $self = shift;
64 4         5 my $args = shift;
65             # print STDERR '::inherit() got these $args: ' . Dumper( $args );
66              
67             die 'config_files key expects an ARRAYREF'
68 4 100       22 unless ref $args->{'config_files'} eq 'ARRAY';
69 3         3 foreach my $ini ( @{$args->{'config_files'}} ){
  3         7  
70 4 100       63 die 'Unable to read ' . Dumper( $ini ) unless -r $ini;
71             }
72 2         3 my $cfg_overloaded;
73 2 50       6 if(ref $args->{'base_config'} ne 'HASH'){
74 0         0 return;
75             } else {
76 2         3 my $cfg_overloaded = $args->{'base_config'};
77 2         5 my $cfg_overload = Config::Tiny->read( $args->{'config_files'}->[0], 'utf8' );
78             # print STDERR '->inherit() says the $cfg_overload is: ' . Dumper $cfg_overload;
79 2         217 my $stanzas_base_config = _get_stanzas( $args->{'base_config'} );
80 2         4 my $stanzas_cfg_overload = _get_stanzas( $cfg_overload );
81             # print STDERR '->inherit() says stanza include: ' . Dumper $stanzas;
82 2         2 push @{ $cfg_overloaded->{'default'}->{'config_files'} }, $args->{'config_files'}->[0];
  2         5  
83 2         3 foreach my $stanza ( @{$stanzas_base_config}, @{$stanzas_cfg_overload} ){
  2         2  
  2         4  
84 4         5 my $keys_base_config = _get_keys( $args->{'base_config'}, $stanza );
85 4         5 my $keys_cfg_overload = _get_keys( $cfg_overload, $stanza );
86             # print STDERR "->inherit() says base configuration's $stanza stanza includes: \n";
87 4         5 foreach my $key ( @{$keys_base_config}, @{$keys_cfg_overload} ){
  4         4  
  4         6  
88             # print STDERR "\t" . $key . ' => ' . $cfg_overloaded->{$stanza}->{$key} . "\n";
89 6 100 66     18 next if( $stanza eq 'default' && $key eq 'config_files' );
90             $cfg_overloaded->{$stanza}->{$key}
91             = exists $cfg_overload->{$stanza}->{$key}
92             ? $cfg_overload->{$stanza}->{$key}
93 5 100       29 : $cfg_overloaded->{$stanza}->{$key}
94             }
95             }
96             }
97              
98 2         5 return $cfg_overloaded;
99             }
100              
101             sub _get_stanzas {
102 4     4   3 my $cfg = shift;
103 4         4 my @stanzas = keys %{$cfg};
  4         11  
104 4         5 return \@stanzas;
105             }
106              
107             sub _get_keys {
108 8     8   8 my $cfg = shift;
109 8         7 my $stanza = shift;
110             my @keys = exists $cfg->{$stanza} && (ref $cfg->{$stanza} eq 'HASH' )
111 8 100 66     28 ? keys %{$cfg->{$stanza}}
  4         11  
112             : ();
113 8         9 return \@keys;
114             }
115              
116             1;
117              
118             =head1 NAME
119              
120             Mojolicious::Plugin::INIConfig::Extended - Mojolicious Plugin to overload a Configuration
121              
122             =head1 CAUTION
123              
124             B
125              
126             =head1 SYNOPSIS
127              
128             # myapp.ini
129             [section]
130             foo=bar
131             music_dir=<%= app->home->rel_dir('music') %>
132              
133             # Mojolicious
134             my $config = $self->plugin('INIConfig::Extended');
135              
136             # Mojolicious::Lite
137             my $config = plugin 'INIConfig::Extended';
138              
139             # foo.html.ep
140             %= $config->{section}{foo}
141              
142             # The configuration is available application wide
143             my $config = app->config;
144              
145             # Everything can be customized with options
146             my $config = plugin INIConfig::Extended => {file => '/etc/myapp.conf'};
147              
148             $self->plugin('INIConfig::Extended', {
149             base_config => $self->app->config,
150             config_files => \@config_files });
151              
152             If no $self->app->config already exists, you can provide an empty hashref {} instead
153             and this ought to work, but please see the KNOWN BUGS section below.
154              
155             =head1 DESCRIPTION
156              
157             L
158             provides configuration inheritance and overloading
159              
160             L is a INI configuration plugin that
161             preprocesses its input with L.
162              
163             The application object can be accessed via C<$app> or the C function. You
164             can extend the normal config file C with C specific ones
165             like C. A default configuration filename will be generated
166             from the value of L.
167              
168             This ::INIConfig::Extended module seeks to do for Mojolicious::Plugin::INIConfig,
169             what my earlier cpan contribution, Config::Simple::Extended
170             did for Config::Simple.
171              
172             The code here barely refactors the INIConfig plugin's ->register method
173             to route to a new ->inherit method when appropriate. I copied over the
174             test suite from ::INIConfig and ::INIConfig::Extended introduces no
175             regression and may be used as a drop in replacement.
176              
177             =head1 OPTIONS
178              
179             L inherits all options from
180             L and supports the following new ones.
181              
182             =head2 base_config
183              
184             # Mojolicious::Lite
185             plugin Config => { base_config => $app->cfg, file => 'conf.d/example.com/site_config.ini' };
186              
187             Overload a base configuration with key->value pairs from an
188             additional configuration file.
189              
190             =head2 config_files
191              
192             # Mojolicious::Lite
193             plugin Config => { config_files => [ qw{ conf.d/base_config.ini conf.d/example.com/site_config.ini ] };
194              
195             Build configuration from an ordered list of configuration files,
196             subsequent ones overloading preceeding ones.
197              
198             =head1 METHODS
199              
200             L inherits all methods from
201             L and implements the following new ones.
202              
203             =head2 inherit
204              
205             ## $plugin->inherit($content, $file, $conf, $app); <-- UNTESTED
206              
207             $self->plugin('INIConfig::Extended', {
208             base_config => $self->app->config,
209             config_files => \@config_files });
210              
211              
212             Overload a Config::Tiny configuration, return it as $app->cfg
213              
214             =head1 BACKWARDS COMPATIBILITY POLICY
215              
216             At least for now, in its early stages of development,
217             this module should be considered experimental.
218             EXPERIMENTAL features may be changed without warnings.
219              
220             =head1 KNOWN BUGS
221              
222             For the moment, as currently implemented, the ->inherit method, although
223             it expects both a base_config (hash ref) and a config_files (array ref),
224             and its design anticipates in the future processing that array of config files
225             to overload the configuration; it currently only processes the first ini file
226             in that array. All other config files will be ignored.
227              
228             Patches with tests are welcome in the form of a Pull Request. Or with
229             patience I will soon enough encounter a use case which should make me
230             return to this project and to complete the implementation of its original
231             design. For the moment, though, this serves my immediate needs. For clues
232             on how to invoke the ->inherit method to overcome this limitation please
233             see `perldoc Config::Simple::Extended`.
234              
235             =head1 BUGS
236              
237             Please tell me bugs if you find bug.
238              
239             C<< >>
240              
241             L
242             L
243              
244             =head1 COPYRIGHT & LICENSE
245              
246             Copyright 2015 Hugh Esco and YMD Partners LLC, all rights reserved.
247              
248             with appreciation to the original author for their work:
249             Copyright 2013 Yuki Kimoto, all rights reserved.
250              
251             This program is free software; you can redistribute it and/or modify it
252             under the same terms as Perl itself.
253              
254             =cut
255