Skip to content

Commit b565f92

Browse files
committed
Merge remote-tracking branch 'origin/main' into feature/new-config-system-2-recursive
2 parents 0738b39 + d372434 commit b565f92

File tree

16 files changed

+678
-202
lines changed

16 files changed

+678
-202
lines changed

Changes

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,21 @@
11
{{$NEXT}}
22

33
[ BUG FIXES ]
4-
* None
4+
* GH #1701: Split cookie values on & only (Yanick Champoux)
55

66
[ ENHANCEMENTS ]
7+
* GH #530: Make data censoring configurable (Yanick Champoux, David
8+
Precious)
79
* GH #1723: Enable use of a different Template Toolkit base class
810
(Andy Beverley)
911
* PR #1727: Don't create CPAN package files when generating new apps
1012
(Jason A. Crome)
13+
* GH #1737: Add on_hook_exception for errors during hook processing
14+
(Andy Beverley)
1115

1216
[ DOCUMENTATION ]
13-
* None
17+
* GH #1342: Document skipping private methods in pod coverage tests
18+
(Jason A. Crome)
1419

1520
[ DEPRECATED ]
1621
* None

cpanfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ requires 'Attribute::Handlers';
33
requires 'Carp';
44
requires 'Clone';
55
requires 'Config::Any';
6+
requires 'Data::Censor' => '0.04';
67
requires 'Digest::SHA';
78
requires 'Encode';
89
requires 'Exporter', '5.57';

lib/Dancer2/ConfigReader.pm

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,7 @@ sub _build_config_readers {
145145

146146
my @config_reader_names = $ENV{'DANCER_CONFIG_READERS'}
147147
? (split qr{,}msx, $ENV{'DANCER_CONFIG_READERS'})
148-
: ( q{Dancer2::ConfigReader::File::Simple} );
148+
: ( q{Dancer2::ConfigReader::Config::Any} );
149149

150150
warn "ConfigReaders to use: @config_reader_names\n" if $ENV{DANCER_CONFIG_VERBOSE};
151151
return [
@@ -164,7 +164,7 @@ __END__
164164
165165
This class provides a C<config> attribute that
166166
is populated by executing one or more B<ConfigReader> packages.
167-
The default ConfigReader used by default is C<Dancer2::ConfigReader::File::Simple>.
167+
The default ConfigReader used by default is C<Dancer2::ConfigReader::Config::Any>.
168168
169169
Also provides a C<setting()> method which is supposed to be used by externals to
170170
read/write config entries.
@@ -187,7 +187,7 @@ if the first config reader returns
187187
subitem1: subcontent1
188188
subitem2: subcontent2
189189
190-
and the second returns
190+
and the second returns
191191
192192
item2: content9
193193
item3:
@@ -213,14 +213,13 @@ then the final config is
213213
subsubitem5: subsubcontent5
214214
item4: content4
215215
216-
217216
=head2 Configuring the ConfigReaders via DANCER_CONFIG_READERS
218217
219218
You can control which B<ConfigReader>
220219
class or classes to use to create the config
221220
via the C<DANCER_CONFIG_READERS> environment.
222221
223-
DANCER_CONFIG_READERS='Dancer2::ConfigReader::File::Simple,Dancer2::ConfigReader::CustomConfig'
222+
DANCER_CONFIG_READERS='Dancer2::ConfigReader::File::Config::Any,Dancer2::ConfigReader::CustomConfig'
224223
225224
If you want several, separate them with a comma (",").
226225
@@ -234,7 +233,7 @@ instantiated and added to the list of configurations to merge. This way you can,
234233
path: /path/to/sqlite.db
235234
table: config
236235
237-
The default ConfigReader L<Dancer2::ConfigReader::File::Simple> will pick that file and proceed to instantiate C<Dancer2::ConfigReader::SQLite>
236+
The default ConfigReader L<Dancer2::ConfigReader::::Config::Any> will pick that file and proceed to instantiate C<Dancer2::ConfigReader::SQLite>
238237
with the provided parameters.
239238
240239
C<additional_config_readers> can take one or a list of reader configurations, which can be either the name of the ConfigReader's class, or the
@@ -258,7 +257,7 @@ Here's an example extending class C<Dancer2::ConfigReader::File::Simple>.
258257
return $config;
259258
};
260259
261-
Another (more complex) example is in the file C<Dancer2::ConfigReader::File::Simple>.
260+
Another (more complex) example is in class C<Dancer2::ConfigReader::Config::Any>.
262261
263262
=head1 ATTRIBUTES
264263
Lines changed: 220 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,220 @@
1+
# ABSTRACT: Config reader for files
2+
package Dancer2::ConfigReader::Config::Any;
3+
4+
use Moo;
5+
6+
use File::Spec;
7+
use Config::Any;
8+
use Hash::Merge::Simple;
9+
use Carp 'croak';
10+
use Module::Runtime 'require_module';
11+
12+
use Dancer2::Core::Factory;
13+
use Dancer2::Core;
14+
use Dancer2::Core::Types;
15+
use Dancer2::FileUtils 'path';
16+
17+
with 'Dancer2::Core::Role::ConfigReader';
18+
19+
has name => (
20+
is => 'ro',
21+
isa => Str,
22+
lazy => 0,
23+
default => sub {'Config::Any'},
24+
);
25+
26+
has config_files => (
27+
is => 'ro',
28+
lazy => 1,
29+
isa => ArrayRef,
30+
builder => '_build_config_files',
31+
);
32+
33+
sub read_config {
34+
my ($self) = @_;
35+
36+
my $config = Hash::Merge::Simple->merge(
37+
map {
38+
warn "Merging config file $_\n" if $ENV{DANCER_CONFIG_VERBOSE};
39+
$self->_load_config_file($_)
40+
} @{ $self->config_files }
41+
);
42+
43+
return $config;
44+
}
45+
46+
sub _build_config_files {
47+
my ($self) = @_;
48+
49+
my $location = $self->config_location;
50+
warn "Searching config files in location: $location\n" if $ENV{DANCER_CONFIG_VERBOSE};
51+
# an undef location means no config files for the caller
52+
return [] unless defined $location;
53+
54+
my $running_env = $self->environment;
55+
my @available_exts = Config::Any->extensions;
56+
my @files;
57+
58+
my @exts = @available_exts;
59+
if (my $ext = $ENV{DANCER_CONFIG_EXT}) {
60+
if (grep { $ext eq $_ } @available_exts) {
61+
@exts = $ext;
62+
warn "Only looking for configs ending in '$ext'\n"
63+
if $ENV{DANCER_CONFIG_VERBOSE};
64+
} else {
65+
warn "DANCER_CONFIG_EXT environment variable set to '$ext' which\n" .
66+
"is not recognized by Config::Any. Looking for config file\n" .
67+
"using default list of extensions:\n" .
68+
"\t@available_exts\n";
69+
}
70+
}
71+
72+
foreach my $file ( [ $location, "config" ],
73+
[ $self->environments_location, $running_env ] )
74+
{
75+
foreach my $ext (@exts) {
76+
my $path = path( $file->[0], $file->[1] . ".$ext" );
77+
next if !-r $path;
78+
79+
# Look for *_local.ext files
80+
my $local = path( $file->[0], $file->[1] . "_local.$ext" );
81+
push @files, $path, ( -r $local ? $local : () );
82+
}
83+
}
84+
85+
warn "Found following config files: @files\n" if $ENV{DANCER_CONFIG_VERBOSE};
86+
return \@files;
87+
}
88+
89+
sub _load_config_file {
90+
my ( $self, $file ) = @_;
91+
my $config;
92+
93+
eval {
94+
my @files = ($file);
95+
my $tmpconfig =
96+
Config::Any->load_files( { files => \@files, use_ext => 1 } )->[0];
97+
( $file, $config ) = %{$tmpconfig} if defined $tmpconfig;
98+
};
99+
if ( my $err = $@ || ( !$config ) ) {
100+
croak "Unable to parse the configuration file: $file: $@";
101+
}
102+
103+
# TODO handle mergeable entries
104+
return $config;
105+
}
106+
107+
1;
108+
109+
__END__
110+
111+
=head1 DESCRIPTION
112+
113+
This class is an implementation of C<Dancer2::Core::Role::ConfigReader>.
114+
It reads the configuration files of C<Dancer2>.
115+
116+
Please see C<Dancer2::Config> for more information.
117+
118+
If you need to add additional functionality to the reading
119+
mechanism, you can extend this class.
120+
An example of this is providing the possibility to replace
121+
random parts of the file config with environmental variables:
122+
123+
package Dancer2::ConfigReader::File::Extended;
124+
125+
use Moo;
126+
use Dancer2::Core::Types;
127+
128+
use Carp 'croak';
129+
130+
extends 'Dancer2::ConfigReader::Config::Any';
131+
132+
has name => (
133+
is => 'ro',
134+
isa => Str,
135+
lazy => 0,
136+
default => sub {'File::Extended'},
137+
);
138+
139+
around read_config => sub {
140+
my ($orig, $self) = @_;
141+
my $config = $orig->($self, @_);
142+
$self->_replace_env_vars($config);
143+
return $config;
144+
};
145+
146+
# Attn. We are traversing along the original data structure all the time,
147+
# using references, and changing values on the spot, not returning anything.
148+
sub _replace_env_vars {
149+
my ( $self, $entry ) = @_;
150+
if( ref $entry ne 'HASH' && ref $entry ne 'ARRAY' ) {
151+
croak 'Param entry is not HASH or ARRAY';
152+
}
153+
if( ref $entry eq 'HASH' ) {
154+
foreach my $value (values %{ $entry }) {
155+
if( (ref $value) =~ m/(HASH|ARRAY)/msx ) {
156+
$self->_replace_env_vars( $value );
157+
} elsif( (ref $value) =~ m/(CODE|REF|GLOB)/msx ) {
158+
# Pretty much anything else except SCALAR. Do nothing
159+
1;
160+
} else {
161+
if( $value ) {
162+
while( my ($k, $v) = each %ENV) {
163+
$value =~ s/ \$ [{] ENV:$k [}] /$v/gmsx;
164+
}
165+
}
166+
}
167+
}
168+
} else {
169+
# ref $entry is 'ARRAY'
170+
foreach my $value (@{ $entry }) {
171+
if( (ref $value) =~ m/(HASH|ARRAY)/msx ) {
172+
$self->_replace_env_vars( $value );
173+
} elsif( (ref $value) =~ m/(CODE|REF|GLOB)/msx ) {
174+
# Pretty much anything else except SCALAR. Do nothing
175+
1;
176+
} else {
177+
if( $value ) {
178+
while( my ($k, $v) = each %ENV) {
179+
$value =~ s/ \$ [{] ENV:$k [}] /$v/gmsx;
180+
}
181+
}
182+
}
183+
}
184+
}
185+
return;
186+
}
187+
188+
1;
189+
190+
=head1 ATTRIBUTES
191+
192+
=attr name
193+
194+
The name of the Config Reader class: C<Config::Any>.
195+
196+
=attr location
197+
198+
Absolute path to the directory where the server started.
199+
200+
=attr config_location
201+
202+
Gets the location from the configuration. Same as C<< $object->location >>.
203+
204+
=attr environments_location
205+
206+
Gets the directory where the environment files are stored.
207+
208+
=attr environment
209+
210+
Returns the name of the environment.
211+
212+
=attr config_files
213+
214+
List of all the configuration files.
215+
216+
=head1 METHODS
217+
218+
=head2 read_config
219+
220+
Load the configuration files.

lib/Dancer2/Core/App.pm

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -800,6 +800,7 @@ sub supported_hooks {
800800
core.app.before_request
801801
core.app.after_request
802802
core.app.route_exception
803+
core.app.hook_exception
803804
core.app.before_file_render
804805
core.app.after_file_render
805806
core.error.before
@@ -819,6 +820,7 @@ sub hook_aliases {
819820
before_error => 'core.error.before',
820821
after_error => 'core.error.after',
821822
on_route_exception => 'core.app.route_exception',
823+
on_hook_exception => 'core.app.hook_exception',
822824

823825
before_file_render => 'core.app.before_file_render',
824826
after_file_render => 'core.app.after_file_render',
@@ -1235,7 +1237,26 @@ sub compile_hooks {
12351237
eval { $EVAL_SHIM->($hook,@_); 1; }
12361238
or do {
12371239
my $err = $@ || "Zombie Error";
1238-
$app->cleanup;
1240+
my $is_hook_exception = $position eq 'core.app.hook_exception';
1241+
# Don't execute the hook_exception hook if the exception
1242+
# has been generated from a hook exception handler itself,
1243+
# thus preventing potentially recursive code.
1244+
$app->execute_hook( 'core.app.hook_exception', $app, $err )
1245+
unless $is_hook_exception;
1246+
my $is_halted = $app->response->is_halted; # Capture before cleanup
1247+
# We can't cleanup if we're in the hook for a hook
1248+
# exception, as this would clear the custom response that
1249+
# may have been set by the hook. However, there is no need
1250+
# to do so, as the upper hook that called this hook
1251+
# exception will perform the cleanup instead anyway
1252+
$app->cleanup
1253+
unless $is_hook_exception;
1254+
# Allow the hook function to halt the response, thus
1255+
# retaining any response it may have set. Otherwise the
1256+
# croak from this function will overwrite any content that
1257+
# may have been set by the hook
1258+
return if $is_halted;
1259+
# Default behavior if nothing else defined
12391260
$app->log('error', "Exception caught in '$position' filter: $err");
12401261
croak "Exception caught in '$position' filter: $err";
12411262
};

0 commit comments

Comments
 (0)