File Coverage

blib/lib/Dancer2/Core/Role/ConfigReader.pm
Criterion Covered Total %
statement 93 105 88.5
branch 18 26 69.2
condition 2 3 66.6
subroutine 22 24 91.6
pod 4 4 100.0
total 139 162 85.8


line stmt bran cond sub pod time code
1             # ABSTRACT: Config role for Dancer2 core objects
2             package Dancer2::Core::Role::ConfigReader;
3             $Dancer2::Core::Role::ConfigReader::VERSION = '0.400001';
4 144     144   91681 use Moo::Role;
  144         399  
  144         1070  
5              
6 144     144   65760 use File::Spec;
  144         402  
  144         3618  
7 144     144   68848 use Config::Any;
  144         1264386  
  144         4959  
8 144     144   63047 use Hash::Merge::Simple;
  144         70030  
  144         7235  
9 144     144   1086 use Carp 'croak';
  144         361  
  144         6352  
10 144     144   921 use Module::Runtime 'require_module';
  144         355  
  144         939  
11              
12 144     144   6066 use Dancer2::Core::Factory;
  144         336  
  144         3135  
13 144     144   802 use Dancer2::Core;
  144         368  
  144         2893  
14 144     144   829 use Dancer2::Core::Types;
  144         373  
  144         1313  
15 144     144   1873007 use Dancer2::FileUtils 'path';
  144         386  
  144         235674  
16              
17             with 'Dancer2::Core::Role::HasLocation';
18              
19             has default_config => (
20             is => 'ro',
21             isa => HashRef,
22             lazy => 1,
23             builder => '_build_default_config',
24             );
25              
26             has config_location => (
27             is => 'ro',
28             isa => ReadableFilePath,
29             lazy => 1,
30             default => sub { $ENV{DANCER_CONFDIR} || $_[0]->location },
31             );
32              
33             # The type for this attribute is Str because we don't require
34             # an existing directory with configuration files for the
35             # environments. An application without environments is still
36             # valid and works.
37             has environments_location => (
38             is => 'ro',
39             isa => Str,
40             lazy => 1,
41             default => sub {
42             $ENV{DANCER_ENVDIR}
43             || File::Spec->catdir( $_[0]->config_location, 'environments' )
44             || File::Spec->catdir( $_[0]->location, 'environments' );
45             },
46             );
47              
48             has config => (
49             is => 'ro',
50             isa => HashRef,
51             lazy => 1,
52             builder => '_build_config',
53             );
54              
55             has environment => (
56             is => 'ro',
57             isa => Str,
58             lazy => 1,
59             builder => '_build_environment',
60             );
61              
62             has config_files => (
63             is => 'ro',
64             lazy => 1,
65             isa => ArrayRef,
66             builder => '_build_config_files',
67             );
68              
69             has local_triggers => (
70             is => 'ro',
71             isa => HashRef,
72             default => sub { +{} },
73             );
74              
75             has global_triggers => (
76             is => 'ro',
77             isa => HashRef,
78             default => sub {
79             my $triggers = {
80             traces => sub {
81             my ( $self, $traces ) = @_;
82             # Carp is already a dependency
83             $Carp::Verbose = $traces ? 1 : 0;
84             },
85             };
86              
87             my $runner_config = defined $Dancer2::runner
88             ? Dancer2->runner->config
89             : {};
90              
91             for my $global ( keys %$runner_config ) {
92             next if exists $triggers->{$global};
93             $triggers->{$global} = sub {
94             my ($self, $value) = @_;
95             Dancer2->runner->config->{$global} = $value;
96             }
97             }
98              
99             return $triggers;
100             },
101             );
102              
103 0     0   0 sub _build_default_config { +{} }
104              
105 36     36   915 sub _build_environment { 'development' }
106              
107             sub _build_config_files {
108 242     242   5205 my ($self) = @_;
109              
110 242         4081 my $location = $self->config_location;
111             # an undef location means no config files for the caller
112 242 50       2806 return [] unless defined $location;
113              
114 242         3956 my $running_env = $self->environment;
115 242         4120 my @available_exts = Config::Any->extensions;
116 242         1848687 my @files;
117              
118 242         925 my @exts = @available_exts;
119 242 50       1296 if (my $ext = $ENV{DANCER_CONFIG_EXT}) {
120 0 0       0 if (grep { $ext eq $_ } @available_exts) {
  0         0  
121 0         0 @exts = $ext;
122             warn "Only looking for configs ending in '$ext'\n"
123 0 0       0 if $ENV{DANCER_CONFIG_VERBOSE};
124             } else {
125 0         0 warn "DANCER_CONFIG_EXT environment variable set to '$ext' which\n" .
126             "is not recognized by Config::Any. Looking for config file\n" .
127             "using default list of extensions:\n" .
128             "\t@available_exts\n";
129             }
130             }
131              
132 242         7077 foreach my $file ( [ $location, "config" ],
133             [ $self->environments_location, $running_env ] )
134             {
135 484         15956 foreach my $ext (@exts) {
136 4840         21716 my $path = path( $file->[0], $file->[1] . ".$ext" );
137 4840 100       61177 next if !-r $path;
138              
139             # Look for *_local.ext files
140 140         1148 my $local = path( $file->[0], $file->[1] . "_local.$ext" );
141 140 100       2344 push @files, $path, ( -r $local ? $local : () );
142             }
143             }
144              
145 242         6780 return \@files;
146             }
147              
148             sub _build_config {
149 240     240   2298 my ($self) = @_;
150              
151 240         4282 my $location = $self->config_location;
152 240         20680 my $default = $self->default_config;
153              
154             my $config = Hash::Merge::Simple->merge(
155             $default,
156             map {
157 139 50       4496 warn "Merging config file $_\n" if $ENV{DANCER_CONFIG_VERBOSE};
158 139         768 $self->load_config_file($_)
159 240         15015 } @{ $self->config_files }
  240         4442  
160             );
161              
162 239         12920 $config = $self->_normalize_config($config);
163 239         1073 return $config;
164             }
165              
166             sub _set_config_entries {
167 144     144   507 my ( $self, @args ) = @_;
168 144         361 my $no = scalar @args;
169 144         530 while (@args) {
170 149         722 $self->_set_config_entry( shift(@args), shift(@args) );
171             }
172 144         180674 return $no;
173             }
174              
175             sub _set_config_entry {
176 149     149   453 my ( $self, $name, $value ) = @_;
177              
178 149         664 $value = $self->_normalize_config_entry( $name, $value );
179 149         3363 $value = $self->_compile_config_entry( $name, $value, $self->config );
180 149         5159 $self->config->{$name} = $value;
181             }
182              
183             sub _normalize_config {
184 240     240   1546 my ( $self, $config ) = @_;
185              
186 240         578 foreach my $key ( keys %{$config} ) {
  240         1212  
187 2351         3752 my $value = $config->{$key};
188 2351         4355 $config->{$key} = $self->_normalize_config_entry( $key, $value );
189             }
190 239         768 return $config;
191             }
192              
193             sub _compile_config {
194 0     0   0 my ( $self, $config ) = @_;
195              
196 0         0 foreach my $key ( keys %{$config} ) {
  0         0  
197 0         0 my $value = $config->{$key};
198 0         0 $config->{$key} =
199             $self->_compile_config_entry( $key, $value, $config );
200             }
201 0         0 return $config;
202             }
203              
204 2     2 1 48 sub settings { shift->config }
205              
206             sub setting {
207 490     490 1 4260 my $self = shift;
208 490         1499 my @args = @_;
209              
210             return ( scalar @args == 1 )
211 490 100       2975 ? $self->settings->{ $args[0] }
212             : $self->_set_config_entries(@args);
213             }
214              
215             sub has_setting {
216 2     2 1 2558 my ( $self, $name ) = @_;
217 2         50 return exists $self->config->{$name};
218             }
219              
220             sub load_config_file {
221 139     139 1 486 my ( $self, $file ) = @_;
222 139         302 my $config;
223              
224 139         306 eval {
225 139         378 my @files = ($file);
226 139         1281 my $tmpconfig =
227             Config::Any->load_files( { files => \@files, use_ext => 1 } )->[0];
228 138 50       905664 ( $file, $config ) = %{$tmpconfig} if defined $tmpconfig;
  138         801  
229             };
230 139 100 66     6021 if ( my $err = $@ || ( !$config ) ) {
231 1         91 croak "Unable to parse the configuration file: $file: $@";
232             }
233              
234             # TODO handle mergeable entries
235 138         1869 return $config;
236             }
237              
238             # private
239              
240             my $_normalizers = {
241             charset => sub {
242             my ($charset) = @_;
243             return $charset if !length( $charset || '' );
244              
245             require_module('Encode');
246             my $encoding = Encode::find_encoding($charset);
247             croak
248             "Charset defined in configuration is wrong : couldn't identify '$charset'"
249             unless defined $encoding;
250             my $name = $encoding->name;
251              
252             # Perl makes a distinction between the usual perl utf8, and the strict
253             # utf8 charset. But we don't want to make this distinction
254             $name = 'utf-8' if $name eq 'utf-8-strict';
255             return $name;
256             },
257             };
258              
259             sub _normalize_config_entry {
260 2500     2500   4528 my ( $self, $name, $value ) = @_;
261             $value = $_normalizers->{$name}->($value)
262 2500 100       5998 if exists $_normalizers->{$name};
263 2499         5307 return $value;
264             }
265              
266             sub _compile_config_entry {
267 149     149   2176 my ( $self, $name, $value, $config ) = @_;
268              
269             my $trigger = exists $self->local_triggers->{$name} ?
270             $self->local_triggers->{$name} :
271 149 100       1249 $self->global_triggers->{$name};
272              
273 149 100       634 defined $trigger or return $value;
274              
275 92         497 return $trigger->( $self, $value, $config );
276             }
277              
278             1;
279              
280             __END__
281              
282             =pod
283              
284             =encoding UTF-8
285              
286             =head1 NAME
287              
288             Dancer2::Core::Role::ConfigReader - Config role for Dancer2 core objects
289              
290             =head1 VERSION
291              
292             version 0.400001
293              
294             =head1 DESCRIPTION
295              
296             Provides a C<config> attribute that feeds itself by finding and parsing
297             configuration files.
298              
299             Also provides a C<setting()> method which is supposed to be used by externals to
300             read/write config entries.
301              
302             =head1 ATTRIBUTES
303              
304             =head2 location
305              
306             Absolute path to the directory where the server started.
307              
308             =head2 config_location
309              
310             Gets the location from the configuration. Same as C<< $object->location >>.
311              
312             =head2 environments_location
313              
314             Gets the directory were the environment files are stored.
315              
316             =head2 config
317              
318             Returns the whole configuration.
319              
320             =head2 environments
321              
322             Returns the name of the environment.
323              
324             =head2 config_files
325              
326             List of all the configuration files.
327              
328             =head1 METHODS
329              
330             =head2 settings
331              
332             Alias for config. Equivalent to <<$object->config>>.
333              
334             =head2 setting
335              
336             Get or set an element from the configuration.
337              
338             =head2 has_setting
339              
340             Verifies that a key exists in the configuration.
341              
342             =head2 load_config_file
343              
344             Load the configuration files.
345              
346             =head1 AUTHOR
347              
348             Dancer Core Developers
349              
350             =head1 COPYRIGHT AND LICENSE
351              
352             This software is copyright (c) 2023 by Alexis Sukrieh.
353              
354             This is free software; you can redistribute it and/or modify it under
355             the same terms as the Perl 5 programming language system itself.
356              
357             =cut