From bca92962dea61126a45d11aba476a64d97ae8824 Mon Sep 17 00:00:00 2001 From: Mikko Johannes Koivunalho Date: Wed, 2 Feb 2022 20:55:40 +0100 Subject: [PATCH 01/23] Move Role::ConfigReader to Role::Config Role::Config is the configuration part of an object Signed-off-by: Mikko Johannes Koivunalho --- lib/Dancer2/Core/Role/{ConfigReader.pm => Config.pm} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename lib/Dancer2/Core/Role/{ConfigReader.pm => Config.pm} (100%) diff --git a/lib/Dancer2/Core/Role/ConfigReader.pm b/lib/Dancer2/Core/Role/Config.pm similarity index 100% rename from lib/Dancer2/Core/Role/ConfigReader.pm rename to lib/Dancer2/Core/Role/Config.pm From 743485cc4a327310f7611d063a2fa447a0d63854 Mon Sep 17 00:00:00 2001 From: Mikko Johannes Koivunalho Date: Wed, 2 Feb 2022 20:57:22 +0100 Subject: [PATCH 02/23] Create Role::Config to be the "config" part of obj Remove everything that has to do with loading config from files or elsewhere. Signed-off-by: Mikko Johannes Koivunalho --- lib/Dancer2/Core/App.pm | 2 +- lib/Dancer2/Core/Role/Config.pm | 149 +++++++++++++++++--------------- 2 files changed, 80 insertions(+), 71 deletions(-) diff --git a/lib/Dancer2/Core/App.pm b/lib/Dancer2/Core/App.pm index af1147040..3413552e7 100644 --- a/lib/Dancer2/Core/App.pm +++ b/lib/Dancer2/Core/App.pm @@ -39,7 +39,7 @@ our $EVAL_SHIM; $EVAL_SHIM ||= sub { # we have hooks here with qw< Dancer2::Core::Role::Hookable - Dancer2::Core::Role::ConfigReader + Dancer2::Core::Role::Config >; sub supported_engines { [ qw ] } diff --git a/lib/Dancer2/Core/Role/Config.pm b/lib/Dancer2/Core/Role/Config.pm index 0d0bc1abe..fec20ec01 100644 --- a/lib/Dancer2/Core/Role/Config.pm +++ b/lib/Dancer2/Core/Role/Config.pm @@ -1,5 +1,5 @@ # ABSTRACT: Config role for Dancer2 core objects -package Dancer2::Core::Role::ConfigReader; +package Dancer2::Core::Role::Config; use Moo::Role; @@ -7,7 +7,7 @@ use File::Spec; use Config::Any; use Hash::Merge::Simple; use Carp 'croak'; -use Module::Runtime 'require_module'; +use Module::Runtime qw{ require_module use_module }; use Dancer2::Core::Factory; use Dancer2::Core; @@ -66,6 +66,13 @@ has config_files => ( builder => '_build_config_files', ); +has config_readers => ( + is => 'ro', + lazy => 1, + isa => ArrayRef, + builder => '_build_config_readers', +); + has local_triggers => ( is => 'ro', isa => HashRef, @@ -84,9 +91,11 @@ has global_triggers => ( }, }; + no warnings 'once'; # Disable: Name "Dancer2::runner" used only once: possible typo my $runner_config = defined $Dancer2::runner ? Dancer2->runner->config : {}; + use warnings 'once'; for my $global ( keys %$runner_config ) { next if exists $triggers->{$global}; @@ -107,62 +116,45 @@ sub _build_environment { 'development' } sub _build_config_files { my ($self) = @_; - my $location = $self->config_location; - # an undef location means no config files for the caller - return [] unless defined $location; - - my $running_env = $self->environment; - my @available_exts = Config::Any->extensions; - my @files; - - my @exts = @available_exts; - if (my $ext = $ENV{DANCER_CONFIG_EXT}) { - if (grep { $ext eq $_ } @available_exts) { - @exts = $ext; - warn "Only looking for configs ending in '$ext'\n" - if $ENV{DANCER_CONFIG_VERBOSE}; - } else { - warn "DANCER_CONFIG_EXT environment variable set to '$ext' which\n" . - "is not recognized by Config::Any. Looking for config file\n" . - "using default list of extensions:\n" . - "\t@available_exts\n"; - } - } - - foreach my $file ( [ $location, "config" ], - [ $self->environments_location, $running_env ] ) - { - foreach my $ext (@exts) { - my $path = path( $file->[0], $file->[1] . ".$ext" ); - next if !-r $path; - - # Look for *_local.ext files - my $local = path( $file->[0], $file->[1] . "_local.$ext" ); - push @files, $path, ( -r $local ? $local : () ); - } - } - - return \@files; + return [ map { + warn "Merging config_files from @{[ $_->name() ]}\n" if $ENV{DANCER_CONFIG_VERBOSE}; + @{ $_->config_files() } + } @{ $self->config_readers } + ]; } +# The new config builder sub _build_config { my ($self) = @_; - my $location = $self->config_location; my $default = $self->default_config; - my $config = Hash::Merge::Simple->merge( $default, map { - warn "Merging config file $_\n" if $ENV{DANCER_CONFIG_VERBOSE}; - $self->load_config_file($_) - } @{ $self->config_files } + warn "Merging config from @{[ $_->name() ]}\n" if $ENV{DANCER_CONFIG_VERBOSE}; + $_->read_config() + } @{ $self->config_readers } ); $config = $self->_normalize_config($config); return $config; } +sub _build_config_readers { + my ($self) = @_; + + my @config_reader_names = $ENV{'DANCER_CONFIG_READERS'} + ? (split qr{ [[:space:]]{1,} }msx, $ENV{'DANCER_CONFIG_READERS'}) + : ( q{Dancer2::ConfigReader::File::Simple} ); + + return [ map { + use_module($_)->new( + location => $self->location, + environment => $self->environment, + ); + } @config_reader_names ]; +} + sub _set_config_entries { my ( $self, @args ) = @_; my $no = scalar @args; @@ -217,24 +209,6 @@ sub has_setting { return exists $self->config->{$name}; } -sub load_config_file { - my ( $self, $file ) = @_; - my $config; - - eval { - my @files = ($file); - my $tmpconfig = - Config::Any->load_files( { files => \@files, use_ext => 1 } )->[0]; - ( $file, $config ) = %{$tmpconfig} if defined $tmpconfig; - }; - if ( my $err = $@ || ( !$config ) ) { - croak "Unable to parse the configuration file: $file: $@"; - } - - # TODO handle mergeable entries - return $config; -} - # private my $_normalizers = { @@ -281,8 +255,46 @@ __END__ =head1 DESCRIPTION -Provides a C attribute that feeds itself by finding and parsing -configuration files. +This is the redesigned C +to manage the Dancer2 configuration. + +It is now possible for user to control which B +class to use to create the config. + +Use C environment variable to define +which class or classes you want. + + DANCER_CONFIG_READERS='Dancer2::ConfigReader::File::Simple Dancer2::ConfigReader::CustomConfig' + +If you want several, separate them with whitespace. +Configs are read in left-to-write order where the previous +config items get overwritten by subsequent ones. + +You can create your own custom B. +The default is to use C +which was the only way to read config files earlier. + +If you want, you can also extend class C. +Here is an example: + + package Dancer2::ConfigReader::FileExtended; + use Moo; + extends 'Dancer2::ConfigReader::File::Simple'; + has name => ( + is => 'ro', + default => sub {'FileExtended'}, + ); + around read_config => sub { + my ($orig, $self) = @_; + my $config = $orig->($self, @_); + $config->{'dummy'}->{'item'} = 123; + return $config; + }; + + +Provides a C attribute that - when accessing +the first time - feeds itself by executing one or more +B packages. Also provides a C method which is supposed to be used by externals to read/write config entries. @@ -299,19 +311,20 @@ Gets the location from the configuration. Same as C<< $object->location >>. =attr environments_location -Gets the directory were the environment files are stored. +Gets the directory where the environment files are stored. =attr config Returns the whole configuration. -=attr environments +=attr environment Returns the name of the environment. =attr config_files -List of all the configuration files. +List of all the configuration files. This information +is queried from the B classes. =head1 METHODS @@ -326,7 +339,3 @@ Get or set an element from the configuration. =head2 has_setting Verifies that a key exists in the configuration. - -=head2 load_config_file - -Load the configuration files. From d1c4b0af82964ebd8ae4fa6a7f1aa1216b3fe093 Mon Sep 17 00:00:00 2001 From: Mikko Johannes Koivunalho Date: Wed, 2 Feb 2022 21:43:14 +0100 Subject: [PATCH 03/23] Create ConfigReader role and an implementation Create Dancer2::Core::Role::ConfigReader and Dancer2::ConfigReader::File::Simple. Signed-off-by: Mikko Johannes Koivunalho --- lib/Dancer2/ConfigReader/File/Simple.pm | 147 ++++++++++++++++++++++++ lib/Dancer2/Core/Role/ConfigReader.pm | 125 ++++++++++++++++++++ 2 files changed, 272 insertions(+) create mode 100644 lib/Dancer2/ConfigReader/File/Simple.pm create mode 100644 lib/Dancer2/Core/Role/ConfigReader.pm diff --git a/lib/Dancer2/ConfigReader/File/Simple.pm b/lib/Dancer2/ConfigReader/File/Simple.pm new file mode 100644 index 000000000..f4353980f --- /dev/null +++ b/lib/Dancer2/ConfigReader/File/Simple.pm @@ -0,0 +1,147 @@ +package Dancer2::ConfigReader::File::Simple; + +use Moo; + +use File::Spec; +use Config::Any; +use Hash::Merge::Simple; +use Carp 'croak'; +use Module::Runtime 'require_module'; + +use Dancer2::Core::Factory; +use Dancer2::Core; +use Dancer2::Core::Types; +use Dancer2::FileUtils 'path'; + +with 'Dancer2::Core::Role::ConfigReader'; + +has name => ( + is => 'ro', + isa => Str, + lazy => 0, + default => sub {'File::Simple'}, +); + +has config_files => ( + is => 'ro', + lazy => 1, + isa => ArrayRef, + builder => '_build_config_files', +); + +sub read_config { + my ($self) = @_; + + my $config = Hash::Merge::Simple->merge( + map { + warn "Merging config file $_\n" if $ENV{DANCER_CONFIG_VERBOSE}; + $self->_load_config_file($_) + } @{ $self->config_files } + ); + + return $config; +} + +sub _build_config_files { + my ($self) = @_; + + my $location = $self->config_location; + # an undef location means no config files for the caller + return [] unless defined $location; + + my $running_env = $self->environment; + my @available_exts = Config::Any->extensions; + my @files; + + my @exts = @available_exts; + if (my $ext = $ENV{DANCER_CONFIG_EXT}) { + if (grep { $ext eq $_ } @available_exts) { + @exts = $ext; + warn "Only looking for configs ending in '$ext'\n" + if $ENV{DANCER_CONFIG_VERBOSE}; + } else { + warn "DANCER_CONFIG_EXT environment variable set to '$ext' which\n" . + "is not recognized by Config::Any. Looking for config file\n" . + "using default list of extensions:\n" . + "\t@available_exts\n"; + } + } + + foreach my $file ( [ $location, "config" ], + [ $self->environments_location, $running_env ] ) + { + foreach my $ext (@exts) { + my $path = path( $file->[0], $file->[1] . ".$ext" ); + next if !-r $path; + + # Look for *_local.ext files + my $local = path( $file->[0], $file->[1] . "_local.$ext" ); + push @files, $path, ( -r $local ? $local : () ); + } + } + + return \@files; +} + +sub _load_config_file { + my ( $self, $file ) = @_; + my $config; + + eval { + my @files = ($file); + my $tmpconfig = + Config::Any->load_files( { files => \@files, use_ext => 1 } )->[0]; + ( $file, $config ) = %{$tmpconfig} if defined $tmpconfig; + }; + if ( my $err = $@ || ( !$config ) ) { + croak "Unable to parse the configuration file: $file: $@"; + } + + # TODO handle mergeable entries + return $config; +} + +1; + +__END__ + +=head1 DESCRIPTION + +This class provides the same features to read configuration files +as were earlier done by C. + +If you need to add additional functionality to the reading +mechanism, you can extend this class. An example of this is +in: B + +=head1 ATTRIBUTES + +=attr name + +The name of the class. + +=attr location + +Absolute path to the directory where the server started. + +=attr config_location + +Gets the location from the configuration. Same as C<< $object->location >>. + +=attr environments_location + +Gets the directory where the environment files are stored. + +=attr environment + +Returns the name of the environment. + +=attr config_files + +List of all the configuration files. + +=head1 METHODS + +=head2 read_config + +Load the configuration files. diff --git a/lib/Dancer2/Core/Role/ConfigReader.pm b/lib/Dancer2/Core/Role/ConfigReader.pm new file mode 100644 index 000000000..b63fe1fa6 --- /dev/null +++ b/lib/Dancer2/Core/Role/ConfigReader.pm @@ -0,0 +1,125 @@ +# ABSTRACT: Config reader role for Dancer2 core objects +package Dancer2::Core::Role::ConfigReader; + +use Moo::Role; + +use File::Spec; +use Config::Any; +use Hash::Merge::Simple; +use Carp 'croak'; +use Module::Runtime 'require_module'; + +use Dancer2::Core::Factory; +use Dancer2::Core; +use Dancer2::Core::Types; +use Dancer2::FileUtils 'path'; + +has config_location => ( + is => 'ro', + isa => ReadableFilePath, + lazy => 1, + default => sub { $ENV{DANCER_CONFDIR} || $_[0]->location }, +); + +# The type for this attribute is Str because we don't require +# an existing directory with configuration files for the +# environments. An application without environments is still +# valid and works. +has environments_location => ( + is => 'ro', + isa => Str, + lazy => 1, + default => sub { + $ENV{DANCER_ENVDIR} + || File::Spec->catdir( $_[0]->config_location, 'environments' ) + || File::Spec->catdir( $_[0]->location, 'environments' ); + }, +); + +# It is required to get environment from the caller. +# Environment should be passed down from Dancer2::Core::App. +has environment => ( + is => 'ro', + isa => Str, + required => 1, +); + +# It is required to get location from the caller. +has location => ( + is => 'ro', + isa => ReadableFilePath, + required => 1, +); + +1; + +__END__ + +=head1 DESCRIPTION + +This role is implemented by different +config readers. A config reader creates the +configuration for Dancer2 app. +Config can be created by reading configuration +files, from environment variables, by fetching +it from a cloud service, or any other means. + +By default, the config loader +which is used, is C +but user can create his own config reader +if he wants to replace or augment +the default method of config creation. +That method should implement this role. + +The implementing module gets the following parameters +during creation: + +=attr environment + +The name of the environment used, e.g. +production, development, staging. + +==attr location + +Absolute path to the directory where the server started. + +These arguments are passed when the object is created by +C. How the config +reader applies them as it needs. + +Provides a C attribute that - when accessing +the first time - feeds itself by finding and parsing +configuration files. + +Also provides a C method which is +supposed to be used by externals to +read/write config entries. + +=head1 ATTRIBUTES + +=attr location + +Absolute path to the directory where the server started. + +=attr config_location + +Gets the location from the configuration. Same as C<< $object->location >>. + +=attr environments_location + +Gets the directory where the environment files are stored. + +=attr config + +Returns the whole configuration. + +=attr environments + +Returns the name of the environment. + +=head1 METHODS + +=head2 read_config + +Load the configuration. +Whatever source the config comes from, files, env vars, etc. From 13640fad311007b3b152e27e4f92e6b94d43ee4a Mon Sep 17 00:00:00 2001 From: Mikko Johannes Koivunalho Date: Wed, 2 Feb 2022 21:46:33 +0100 Subject: [PATCH 04/23] Add tests for new ConfigReader Signed-off-by: Mikko Johannes Koivunalho --- t/app/t_config_file_extended/bin/app.psgi | 6 ++ t/app/t_config_file_extended/config.yml | 5 ++ t/app/t_config_file_extended/lib/App1.pm | 6 ++ t/config_file_extended.t | 34 +++++++++++ t/config_many.t | 30 ++++++++++ t/{config_reader.t => config_role.t} | 16 ++--- t/lib/Dancer2/ConfigReader/File/Extended.pm | 66 +++++++++++++++++++++ t/lib/Dancer2/ConfigReader/TestDummy.pm | 36 +++++++++++ 8 files changed, 191 insertions(+), 8 deletions(-) create mode 100644 t/app/t_config_file_extended/bin/app.psgi create mode 100644 t/app/t_config_file_extended/config.yml create mode 100644 t/app/t_config_file_extended/lib/App1.pm create mode 100644 t/config_file_extended.t create mode 100644 t/config_many.t rename t/{config_reader.t => config_role.t} (91%) create mode 100644 t/lib/Dancer2/ConfigReader/File/Extended.pm create mode 100644 t/lib/Dancer2/ConfigReader/TestDummy.pm diff --git a/t/app/t_config_file_extended/bin/app.psgi b/t/app/t_config_file_extended/bin/app.psgi new file mode 100644 index 000000000..ef8335285 --- /dev/null +++ b/t/app/t_config_file_extended/bin/app.psgi @@ -0,0 +1,6 @@ +#!perl + +use Dancer2; +use App1; + +start; diff --git a/t/app/t_config_file_extended/config.yml b/t/app/t_config_file_extended/config.yml new file mode 100644 index 000000000..e839c49f2 --- /dev/null +++ b/t/app/t_config_file_extended/config.yml @@ -0,0 +1,5 @@ +app: + config: ok +extended: + one: ${ENV:DANCER_FILE_EXTENDED_ONE} + two: Begin ${ENV:DANCER_FILE_EXTENDED_TWO} End diff --git a/t/app/t_config_file_extended/lib/App1.pm b/t/app/t_config_file_extended/lib/App1.pm new file mode 100644 index 000000000..62a86f3c6 --- /dev/null +++ b/t/app/t_config_file_extended/lib/App1.pm @@ -0,0 +1,6 @@ +package App1; +use strict; +use warnings; +use Dancer2; + +1; diff --git a/t/config_file_extended.t b/t/config_file_extended.t new file mode 100644 index 000000000..b40946b97 --- /dev/null +++ b/t/config_file_extended.t @@ -0,0 +1,34 @@ +use strict; +use warnings; + +use Test::More; +use File::Spec; + +BEGIN { + # undefine ENV vars used as defaults for app environment in these tests + local $ENV{DANCER_ENVIRONMENT}; + local $ENV{PLACK_ENV}; + $ENV{DANCER_CONFIG_READERS} = 'Dancer2::ConfigReader::File::Extended'; + $ENV{DANCER_FILE_EXTENDED_ONE} = 'Extended String'; + $ENV{DANCER_FILE_EXTENDED_TWO} = 'ExtendedToo'; +} +use lib '.'; +use lib './t/lib'; + +use t::app::t_config_file_extended::lib::App1; + +my $app = Dancer2->runner->apps->[0]; + +is_deeply $app->config_files, + [ File::Spec->rel2abs(File::Spec->catfile( 't', 'app', + 't_config_file_extended', 'config.yml' )) ], + $app->name . ": config files found"; + +is $app->config->{app}->{config}, 'ok', + $app->name . ": config loaded properly"; +is $app->config->{extended}->{one}, 'Extended String', + $app->name . ": extended config (extended:one) loaded properly"; +is $app->config->{extended}->{two}, 'Begin ExtendedToo End', + $app->name . ": extended config (extended:two) loaded properly"; + +done_testing; diff --git a/t/config_many.t b/t/config_many.t new file mode 100644 index 000000000..4755560db --- /dev/null +++ b/t/config_many.t @@ -0,0 +1,30 @@ +use strict; +use warnings; + +use Test::More; +use File::Spec; + +BEGIN { + # undefine ENV vars used as defaults for app environment in these tests + local $ENV{DANCER_ENVIRONMENT}; + local $ENV{PLACK_ENV}; + $ENV{DANCER_CONFIG_READERS} + = 'Dancer2::ConfigReader::File::Simple Dancer2::ConfigReader::TestDummy'; +} +use lib '.'; +use lib './t/lib'; + +use t::app::t1::lib::App1; + +my $app = Dancer2->runner->apps->[0]; + +is_deeply $app->config_files, + [ File::Spec->rel2abs(File::Spec->catfile( 't', 'app', 't1', 'config.yml' )) ], + $app->name . ": config files found"; + +is $app->config->{app}->{config}, 'ok', + $app->name . ": config loaded properly"; +is $app->config->{dummy}->{dummy_subitem}, 2, + $app->name . ": dummy config loaded properly"; + +done_testing; diff --git a/t/config_reader.t b/t/config_role.t similarity index 91% rename from t/config_reader.t rename to t/config_role.t index 3426ba73c..bb8893382 100644 --- a/t/config_reader.t +++ b/t/config_role.t @@ -21,7 +21,7 @@ my $location2 = File::Spec->rel2abs( path( dirname(__FILE__), 'config2' ) ); package Prod; use Moo; - with 'Dancer2::Core::Role::ConfigReader'; + with 'Dancer2::Core::Role::Config'; sub name {'Prod'} @@ -31,7 +31,7 @@ my $location2 = File::Spec->rel2abs( path( dirname(__FILE__), 'config2' ) ); package Dev; use Moo; - with 'Dancer2::Core::Role::ConfigReader'; + with 'Dancer2::Core::Role::Config'; sub _build_environment {'development'} sub _build_location {$location}; @@ -39,7 +39,7 @@ my $location2 = File::Spec->rel2abs( path( dirname(__FILE__), 'config2' ) ); package Failure; use Moo; - with 'Dancer2::Core::Role::ConfigReader'; + with 'Dancer2::Core::Role::Config'; sub _build_environment {'failure'} sub _build_location {$location} @@ -47,7 +47,7 @@ my $location2 = File::Spec->rel2abs( path( dirname(__FILE__), 'config2' ) ); package Staging; use Moo; - with 'Dancer2::Core::Role::ConfigReader'; + with 'Dancer2::Core::Role::Config'; sub _build_environment {'staging'} sub _build_location {$location} @@ -55,7 +55,7 @@ my $location2 = File::Spec->rel2abs( path( dirname(__FILE__), 'config2' ) ); package Merging; use Moo; - with 'Dancer2::Core::Role::ConfigReader'; + with 'Dancer2::Core::Role::Config'; sub name {'Merging'} @@ -65,7 +65,7 @@ my $location2 = File::Spec->rel2abs( path( dirname(__FILE__), 'config2' ) ); package LocalConfig; use Moo; - with 'Dancer2::Core::Role::ConfigReader'; + with 'Dancer2::Core::Role::Config'; sub name {'LocalConfig'} @@ -81,8 +81,8 @@ is_deeply $d->config_files, "config_files() only sees existing files"; my $f = Prod->new; -is $f->does('Dancer2::Core::Role::ConfigReader'), 1, - "role Dancer2::Core::Role::ConfigReader is consumed"; +is $f->does('Dancer2::Core::Role::Config'), 1, + "role Dancer2::Core::Role::Config is consumed"; is_deeply $f->config_files, [ path( $location, 'config.yml' ), diff --git a/t/lib/Dancer2/ConfigReader/File/Extended.pm b/t/lib/Dancer2/ConfigReader/File/Extended.pm new file mode 100644 index 000000000..d6764763e --- /dev/null +++ b/t/lib/Dancer2/ConfigReader/File/Extended.pm @@ -0,0 +1,66 @@ +package Dancer2::ConfigReader::File::Extended; + +use Moo; +use Dancer2::Core::Types; + +use Carp 'croak'; + +extends 'Dancer2::ConfigReader::File::Simple'; + +has name => ( + is => 'ro', + isa => Str, + lazy => 0, + default => sub {'File::Extended'}, +); + +around read_config => sub { + my ($orig, $self) = @_; + my $config = $orig->($self, @_); + $self->_replace_env_vars($config); + return $config; +}; + +# Attn. We are traversing along the original data structure all the time, +# using references, and changing values on the spot, not returning anything. +sub _replace_env_vars { + my ( $self, $entry ) = @_; + if( ref $entry ne 'HASH' && ref $entry ne 'ARRAY' ) { + croak 'Param entry is not HASH or ARRAY'; + } + if( ref $entry eq 'HASH' ) { + foreach my $value (values %{ $entry }) { + if( (ref $value) =~ m/(HASH|ARRAY)/msx ) { + $self->_replace_env_vars( $value ); + } elsif( (ref $value) =~ m/(CODE|REF|GLOB)/msx ) { + # Pretty much anything else except SCALAR. Do nothing + 1; + } else { + if( $value ) { + while( my ($k, $v) = each %ENV) { + $value =~ s/ \$ [{] ENV:$k [}] /$v/gmsx; + } + } + } + } + } else { + # ref $entry is 'ARRAY' + foreach my $value (@{ $entry }) { + if( (ref $value) =~ m/(HASH|ARRAY)/msx ) { + $self->_replace_env_vars( $value ); + } elsif( (ref $value) =~ m/(CODE|REF|GLOB)/msx ) { + # Pretty much anything else except SCALAR. Do nothing + 1; + } else { + if( $value ) { + while( my ($k, $v) = each %ENV) { + $value =~ s/ \$ [{] ENV:$k [}] /$v/gmsx; + } + } + } + } + } + return; +} + +1; diff --git a/t/lib/Dancer2/ConfigReader/TestDummy.pm b/t/lib/Dancer2/ConfigReader/TestDummy.pm new file mode 100644 index 000000000..042a8ce6d --- /dev/null +++ b/t/lib/Dancer2/ConfigReader/TestDummy.pm @@ -0,0 +1,36 @@ +package Dancer2::ConfigReader::TestDummy; +use Moo; +use Dancer2::Core::Factory; +use Dancer2::Core; +use Dancer2::Core::Types; +use Dancer2::FileUtils 'path'; + +with 'Dancer2::Core::Role::ConfigReader'; + +has name => ( + is => 'ro', + isa => Str, + lazy => 0, + default => sub {'TestDummy'}, +); + +has config_files => ( + is => 'ro', + lazy => 1, + isa => ArrayRef, + default => sub { + my ($self) = @_; + return []; + }, +); + +sub read_config { + my %config = ( + dummy => { + dummy_subitem => 2, + } + ); + return \%config; +} + +1; From 25970ab0a521c47f2f5e84567fd9d6808ea3d5d3 Mon Sep 17 00:00:00 2001 From: Mikko Johannes Koivunalho Date: Sun, 27 Mar 2022 14:17:12 +0200 Subject: [PATCH 05/23] Change regex: remove POSIX form Signed-off-by: Mikko Johannes Koivunalho --- lib/Dancer2/Core/Role/Config.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Dancer2/Core/Role/Config.pm b/lib/Dancer2/Core/Role/Config.pm index fec20ec01..5eedf8fc9 100644 --- a/lib/Dancer2/Core/Role/Config.pm +++ b/lib/Dancer2/Core/Role/Config.pm @@ -144,7 +144,7 @@ sub _build_config_readers { my ($self) = @_; my @config_reader_names = $ENV{'DANCER_CONFIG_READERS'} - ? (split qr{ [[:space:]]{1,} }msx, $ENV{'DANCER_CONFIG_READERS'}) + ? (split qr{ \s+ }msx, $ENV{'DANCER_CONFIG_READERS'}) : ( q{Dancer2::ConfigReader::File::Simple} ); return [ map { From 7c395437ee971b551ebc157efbcc17cf3effa494 Mon Sep 17 00:00:00 2001 From: Mikko Johannes Koivunalho Date: Sun, 27 Mar 2022 14:22:27 +0200 Subject: [PATCH 06/23] Reformat Signed-off-by: Mikko Johannes Koivunalho --- lib/Dancer2/Core/Role/Config.pm | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/Dancer2/Core/Role/Config.pm b/lib/Dancer2/Core/Role/Config.pm index 5eedf8fc9..6b3e58534 100644 --- a/lib/Dancer2/Core/Role/Config.pm +++ b/lib/Dancer2/Core/Role/Config.pm @@ -147,12 +147,12 @@ sub _build_config_readers { ? (split qr{ \s+ }msx, $ENV{'DANCER_CONFIG_READERS'}) : ( q{Dancer2::ConfigReader::File::Simple} ); - return [ map { - use_module($_)->new( - location => $self->location, + return [ + map use_module($_)->new( + location => $self->location, environment => $self->environment, - ); - } @config_reader_names ]; + ), @config_reader_names + ]; } sub _set_config_entries { From 9465fdcc71feebcd257da48dd6221da53629f20d Mon Sep 17 00:00:00 2001 From: Mikko Johannes Koivunalho Date: Sun, 27 Mar 2022 14:24:39 +0200 Subject: [PATCH 07/23] Reformat: no warnings 'once' Signed-off-by: Mikko Johannes Koivunalho --- lib/Dancer2/Core/Role/Config.pm | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/lib/Dancer2/Core/Role/Config.pm b/lib/Dancer2/Core/Role/Config.pm index 6b3e58534..ecf2eec66 100644 --- a/lib/Dancer2/Core/Role/Config.pm +++ b/lib/Dancer2/Core/Role/Config.pm @@ -91,11 +91,13 @@ has global_triggers => ( }, }; - no warnings 'once'; # Disable: Name "Dancer2::runner" used only once: possible typo - my $runner_config = defined $Dancer2::runner - ? Dancer2->runner->config - : {}; - use warnings 'once'; + my $runner_config; + { + no warnings 'once'; + $runner_config = defined $Dancer2::runner + ? Dancer2->runner->config + : {}; + } for my $global ( keys %$runner_config ) { next if exists $triggers->{$global}; From dafd2ce5ba560c196079ebba31b376741a2aac0f Mon Sep 17 00:00:00 2001 From: Mikko Johannes Koivunalho Date: Sun, 27 Mar 2022 14:39:33 +0200 Subject: [PATCH 08/23] Rework POD and add an example how to extend Signed-off-by: Mikko Johannes Koivunalho --- lib/Dancer2/ConfigReader/File/Simple.pm | 76 ++++++++++++++++++++++++- 1 file changed, 74 insertions(+), 2 deletions(-) diff --git a/lib/Dancer2/ConfigReader/File/Simple.pm b/lib/Dancer2/ConfigReader/File/Simple.pm index f4353980f..e5b42a07d 100644 --- a/lib/Dancer2/ConfigReader/File/Simple.pm +++ b/lib/Dancer2/ConfigReader/File/Simple.pm @@ -107,13 +107,85 @@ __END__ =head1 DESCRIPTION -This class provides the same features to read configuration files -as were earlier done by C. +This class is an implementation of C. +It reads the configuration files of C. + +Please see C for more information. If you need to add additional functionality to the reading mechanism, you can extend this class. An example of this is in: B +An example of this is providing the possibility to replace +random parts of the file config with environmental variables: + + package Dancer2::ConfigReader::File::Extended; + + use Moo; + use Dancer2::Core::Types; + + use Carp 'croak'; + + extends 'Dancer2::ConfigReader::File::Simple'; + + has name => ( + is => 'ro', + isa => Str, + lazy => 0, + default => sub {'File::Extended'}, + ); + + around read_config => sub { + my ($orig, $self) = @_; + my $config = $orig->($self, @_); + $self->_replace_env_vars($config); + return $config; + }; + + # Attn. We are traversing along the original data structure all the time, + # using references, and changing values on the spot, not returning anything. + sub _replace_env_vars { + my ( $self, $entry ) = @_; + if( ref $entry ne 'HASH' && ref $entry ne 'ARRAY' ) { + croak 'Param entry is not HASH or ARRAY'; + } + if( ref $entry eq 'HASH' ) { + foreach my $value (values %{ $entry }) { + if( (ref $value) =~ m/(HASH|ARRAY)/msx ) { + $self->_replace_env_vars( $value ); + } elsif( (ref $value) =~ m/(CODE|REF|GLOB)/msx ) { + # Pretty much anything else except SCALAR. Do nothing + 1; + } else { + if( $value ) { + while( my ($k, $v) = each %ENV) { + $value =~ s/ \$ [{] ENV:$k [}] /$v/gmsx; + } + } + } + } + } else { + # ref $entry is 'ARRAY' + foreach my $value (@{ $entry }) { + if( (ref $value) =~ m/(HASH|ARRAY)/msx ) { + $self->_replace_env_vars( $value ); + } elsif( (ref $value) =~ m/(CODE|REF|GLOB)/msx ) { + # Pretty much anything else except SCALAR. Do nothing + 1; + } else { + if( $value ) { + while( my ($k, $v) = each %ENV) { + $value =~ s/ \$ [{] ENV:$k [}] /$v/gmsx; + } + } + } + } + } + return; + } + + 1; + =head1 ATTRIBUTES =attr name From b319d6cb84582d44a9e2b983001b688ab8c7ddf8 Mon Sep 17 00:00:00 2001 From: Mikko Johannes Koivunalho Date: Sun, 27 Mar 2022 15:16:15 +0200 Subject: [PATCH 09/23] Rewrite documentation of Dancer2::Core::Role::Config Signed-off-by: Mikko Johannes Koivunalho --- lib/Dancer2/Core/Role/Config.pm | 68 +++++++++++++++++++++++++-------- 1 file changed, 53 insertions(+), 15 deletions(-) diff --git a/lib/Dancer2/Core/Role/Config.pm b/lib/Dancer2/Core/Role/Config.pm index ecf2eec66..79cb651a1 100644 --- a/lib/Dancer2/Core/Role/Config.pm +++ b/lib/Dancer2/Core/Role/Config.pm @@ -257,11 +257,15 @@ __END__ =head1 DESCRIPTION -This is the redesigned C -to manage the Dancer2 configuration. +This class provides a C attribute that - when accessing +the first time - feeds itself by executing one or more +B packages. + +Also provides a C method which is supposed to be used by externals to +read/write config entries. -It is now possible for user to control which B -class to use to create the config. +You can control which B +class or classes to use to create the config. Use C environment variable to define which class or classes you want. @@ -269,12 +273,52 @@ which class or classes you want. DANCER_CONFIG_READERS='Dancer2::ConfigReader::File::Simple Dancer2::ConfigReader::CustomConfig' If you want several, separate them with whitespace. -Configs are read in left-to-write order where the previous +Configs are added in left-to-write order where the previous config items get overwritten by subsequent ones. -You can create your own custom B. -The default is to use C -which was the only way to read config files earlier. +For example, if config + + item1: content1 + item2: content2 + item3: + subitem1: subcontent1 + subitem2: subcontent2 + subitem3: + subsubitem1: + subsubcontent1 + item4: + subitem1: subcontent1 + subitem2: subcontent2 + +was followed by config + + item2: content9 + item3: + subitem2: subcontent8 + subitem3: + subsubitem1: + subsubcontent7 + subitem4: + subsubitem5: subsubcontent5 + item4: content4 + +then the final config would be + + item1: content1 + item2: content9 + item3: + subitem1: subcontent1 + subitem2: subcontent8 + subitem3: + subsubitem1: + subsubcontent7 + subitem4: + subsubitem5: subsubcontent5 + item4: content4 + +The default B is C. + +You can also create your own custom B classes. If you want, you can also extend class C. Here is an example: @@ -293,13 +337,7 @@ Here is an example: return $config; }; - -Provides a C attribute that - when accessing -the first time - feeds itself by executing one or more -B packages. - -Also provides a C method which is supposed to be used by externals to -read/write config entries. +Another (more complex) example is in the file C. =head1 ATTRIBUTES From adb4cd202933a29f1a68d348774c96f57b5d6bee Mon Sep 17 00:00:00 2001 From: Mikko Johannes Koivunalho Date: Sun, 27 Mar 2022 15:49:44 +0200 Subject: [PATCH 10/23] Add ABSTRACT to ConfigReader::File::Simple Signed-off-by: Mikko Johannes Koivunalho --- lib/Dancer2/ConfigReader/File/Simple.pm | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/Dancer2/ConfigReader/File/Simple.pm b/lib/Dancer2/ConfigReader/File/Simple.pm index e5b42a07d..85df8f420 100644 --- a/lib/Dancer2/ConfigReader/File/Simple.pm +++ b/lib/Dancer2/ConfigReader/File/Simple.pm @@ -1,3 +1,4 @@ +# ABSTRACT: Config reader for files package Dancer2::ConfigReader::File::Simple; use Moo; From 622336cb1f16d95c56995e4c1b726d2d112486e0 Mon Sep 17 00:00:00 2001 From: Mikko Johannes Koivunalho Date: Sun, 27 Mar 2022 16:22:14 +0200 Subject: [PATCH 11/23] Remove attr config_files from Dancer2::Core::Role::Config Signed-off-by: Mikko Johannes Koivunalho --- lib/Dancer2/Core/App.pm | 2 -- lib/Dancer2/Core/Role/Config.pm | 22 ---------------------- t/config-files-logged.t | 17 ----------------- t/config_file_extended.t | 5 ----- t/config_many.t | 4 ---- t/config_multiapp.t | 4 ---- t/config_role.t | 30 ------------------------------ 7 files changed, 84 deletions(-) delete mode 100644 t/config-files-logged.t diff --git a/lib/Dancer2/Core/App.pm b/lib/Dancer2/Core/App.pm index 3413552e7..c9d0a49fb 100644 --- a/lib/Dancer2/Core/App.pm +++ b/lib/Dancer2/Core/App.pm @@ -1109,8 +1109,6 @@ sub BUILD { my $self = shift; $self->init_route_handlers(); $self->_init_hooks(); - - $self->log(core => 'Built config from files: ' . join(' ', @{$self->config_files})); } sub finish { diff --git a/lib/Dancer2/Core/Role/Config.pm b/lib/Dancer2/Core/Role/Config.pm index 79cb651a1..ca83bdf2a 100644 --- a/lib/Dancer2/Core/Role/Config.pm +++ b/lib/Dancer2/Core/Role/Config.pm @@ -59,13 +59,6 @@ has environment => ( builder => '_build_environment', ); -has config_files => ( - is => 'ro', - lazy => 1, - isa => ArrayRef, - builder => '_build_config_files', -); - has config_readers => ( is => 'ro', lazy => 1, @@ -115,16 +108,6 @@ sub _build_default_config { +{} } sub _build_environment { 'development' } -sub _build_config_files { - my ($self) = @_; - - return [ map { - warn "Merging config_files from @{[ $_->name() ]}\n" if $ENV{DANCER_CONFIG_VERBOSE}; - @{ $_->config_files() } - } @{ $self->config_readers } - ]; -} - # The new config builder sub _build_config { my ($self) = @_; @@ -361,11 +344,6 @@ Returns the whole configuration. Returns the name of the environment. -=attr config_files - -List of all the configuration files. This information -is queried from the B classes. - =head1 METHODS =head2 settings diff --git a/t/config-files-logged.t b/t/config-files-logged.t deleted file mode 100644 index aac1d7822..000000000 --- a/t/config-files-logged.t +++ /dev/null @@ -1,17 +0,0 @@ -use strict; -use warnings; - -use Test::More; -use File::Spec; - -use lib '.'; -use t::app::t3::lib::App4; - -my ($app) = @{ Dancer2->runner->apps }; -my $trap = $app->logger_engine->trapper; -my $logs = $trap->read; - -like( $logs->[0]{message}, qr/Built config from files/, 'log message ok'); - -done_testing; - diff --git a/t/config_file_extended.t b/t/config_file_extended.t index b40946b97..fc4b3323c 100644 --- a/t/config_file_extended.t +++ b/t/config_file_extended.t @@ -19,11 +19,6 @@ use t::app::t_config_file_extended::lib::App1; my $app = Dancer2->runner->apps->[0]; -is_deeply $app->config_files, - [ File::Spec->rel2abs(File::Spec->catfile( 't', 'app', - 't_config_file_extended', 'config.yml' )) ], - $app->name . ": config files found"; - is $app->config->{app}->{config}, 'ok', $app->name . ": config loaded properly"; is $app->config->{extended}->{one}, 'Extended String', diff --git a/t/config_many.t b/t/config_many.t index 4755560db..ab28a66d0 100644 --- a/t/config_many.t +++ b/t/config_many.t @@ -18,10 +18,6 @@ use t::app::t1::lib::App1; my $app = Dancer2->runner->apps->[0]; -is_deeply $app->config_files, - [ File::Spec->rel2abs(File::Spec->catfile( 't', 'app', 't1', 'config.yml' )) ], - $app->name . ": config files found"; - is $app->config->{app}->{config}, 'ok', $app->name . ": config loaded properly"; is $app->config->{dummy}->{dummy_subitem}, 2, diff --git a/t/config_multiapp.t b/t/config_multiapp.t index a2acc6c48..e54eb4426 100644 --- a/t/config_multiapp.t +++ b/t/config_multiapp.t @@ -13,10 +13,6 @@ for my $app ( @{ Dancer2->runner->apps } ) { # Need to determine path to config; use apps' name for now.. my $path = $app->name eq 'App3' ? 't2' : 't1'; - is_deeply $app->config_files, - [ File::Spec->rel2abs(File::Spec->catfile( 't', 'app', $path, 'config.yml' )) ], - $app->name . ": config files found"; - is $app->config->{app}->{config}, 'ok', $app->name . ": config loaded properly" } diff --git a/t/config_role.t b/t/config_role.t index bb8893382..97e060c9f 100644 --- a/t/config_role.t +++ b/t/config_role.t @@ -76,37 +76,15 @@ my $location2 = File::Spec->rel2abs( path( dirname(__FILE__), 'config2' ) ); } my $d = Dev->new(); -is_deeply $d->config_files, - [ path( $location, 'config.yml' ), ], - "config_files() only sees existing files"; my $f = Prod->new; is $f->does('Dancer2::Core::Role::Config'), 1, "role Dancer2::Core::Role::Config is consumed"; -is_deeply $f->config_files, - [ path( $location, 'config.yml' ), - path( $location, 'environments', 'production.yml' ), - ], - "config_files() works"; - -my $j = Staging->new; -is_deeply $j->config_files, - [ path( $location, 'config.yml' ), - path( $location, 'environments', 'staging.json' ), - ], - "config_files() does JSON too!"; - note "bad YAML file"; my $fail = Failure->new; is $fail->environment, 'failure'; -is_deeply $fail->config_files, - [ path( $location, 'config.yml' ), - path( $location, 'environments', 'failure.yml' ), - ], - "config_files() works"; - like( exception { $fail->config }, qr{Unable to parse the configuration file}, 'Configuration file parsing failure', @@ -126,14 +104,6 @@ is_deeply $m->config->{application}, { my $l = LocalConfig->new; - is_deeply $l->config_files, - [ path( $location2, 'config.yml' ), - path( $location2, 'config_local.yml' ), - path( $location2, 'environments', 'lconfig.yml' ), - path( $location2, 'environments', 'lconfig_local.yml' ), - ], - "config_files() with local config works"; - is_deeply $l->config->{application}, { feature_1 => 'foo', feature_2 => 'alpha', From 8827022b10d464f4864cbdee6ae1f2dd63f7a221 Mon Sep 17 00:00:00 2001 From: Mikko Johannes Koivunalho Date: Sun, 8 May 2022 20:16:35 +0200 Subject: [PATCH 12/23] Create Role::HasEnvironment and tests Signed-off-by: Mikko Johannes Koivunalho --- lib/Dancer2/Core/Role/HasEnvironment.pm | 21 +++++++ .../Dancer2-Core-Role-HasEnvironment/with.t | 63 +++++++++++++++++++ 2 files changed, 84 insertions(+) create mode 100644 lib/Dancer2/Core/Role/HasEnvironment.pm create mode 100644 t/classes/Dancer2-Core-Role-HasEnvironment/with.t diff --git a/lib/Dancer2/Core/Role/HasEnvironment.pm b/lib/Dancer2/Core/Role/HasEnvironment.pm new file mode 100644 index 000000000..631dd7264 --- /dev/null +++ b/lib/Dancer2/Core/Role/HasEnvironment.pm @@ -0,0 +1,21 @@ +# ABSTRACT: Role for application environment name +package Dancer2::Core::Role::HasEnvironment; + +use Moo::Role; +use Dancer2::Core::Types; + +my $DEFAULT_ENVIRONMENT = q{development}; + +has environment => ( + is => 'ro', + isa => Str, + lazy => 1, + builder => '_build_environment', +); + +sub _build_environment { + my ($self) = @_; + return $ENV{DANCER_ENVIRONMENT} || $ENV{PLACK_ENV} || $DEFAULT_ENVIRONMENT; +} + +1; diff --git a/t/classes/Dancer2-Core-Role-HasEnvironment/with.t b/t/classes/Dancer2-Core-Role-HasEnvironment/with.t new file mode 100644 index 000000000..a5dff0c0e --- /dev/null +++ b/t/classes/Dancer2-Core-Role-HasEnvironment/with.t @@ -0,0 +1,63 @@ +use strict; +use warnings; +use Test::More; +use Test::Fatal; +use Carp 'croak'; + +use Dancer2::Core::Runner; +use Dancer2::FileUtils qw/dirname path/; +use File::Spec; +use File::Temp; + +# undefine ENV vars used as defaults for app environment in these tests +local $ENV{DANCER_ENVIRONMENT}; +local $ENV{PLACK_ENV}; + +my $runner = Dancer2::Core::Runner->new(); +my $location = File::Spec->rel2abs( path( dirname(__FILE__), 'config' ) ); +my $location2 = File::Spec->rel2abs( path( dirname(__FILE__), 'config2' ) ); + +{ + package Dancer2::Test::TestRoleOne; + use Moo; + with 'Dancer2::Core::Role::HasEnvironment'; +} + +{ + undef $ENV{DANCER_ENVIRONMENT}; + undef $ENV{PLACK_ENV}; + my $test_one = Dancer2::Test::TestRoleOne->new(); + is( $test_one->environment, 'development', 'Default env is OK' ); +} +{ + $ENV{DANCER_ENVIRONMENT} = 'staging'; + undef $ENV{PLACK_ENV}; + my $test_one = Dancer2::Test::TestRoleOne->new(); + is( $test_one->environment, q{staging}, 'Dancer env is OK when dancer env has value and plack env is not defined' ); +} +{ + $ENV{DANCER_ENVIRONMENT} = 'staging'; + $ENV{PLACK_ENV} = 'other_env'; + my $test_one = Dancer2::Test::TestRoleOne->new(); + is( $test_one->environment, q{staging}, 'Dancer env is OK when dancer and plack env vars are both used' ); +} +{ + undef $ENV{DANCER_ENVIRONMENT}; + $ENV{PLACK_ENV} = 'plack_env'; + my $test_one = Dancer2::Test::TestRoleOne->new(); + is( $test_one->environment, q{plack_env}, 'Dancer env is OK when dancer env var is not defined' ); +} +{ + $ENV{DANCER_ENVIRONMENT} = ''; + $ENV{PLACK_ENV} = 'plack_env'; + my $test_one = Dancer2::Test::TestRoleOne->new(); + is( $test_one->environment, q{plack_env}, 'Dancer env is OK when one env var is empty string' ); +} +{ + $ENV{DANCER_ENVIRONMENT} = ''; + $ENV{PLACK_ENV} = ''; + my $test_one = Dancer2::Test::TestRoleOne->new(); + is( $test_one->environment, q{development}, 'Dancer env is OK when both env vars are empty string' ); +} + +done_testing; From 15bff9a42a2e7115814eff0f238efd2f3ee5ddbf Mon Sep 17 00:00:00 2001 From: Mikko Johannes Koivunalho Date: Sun, 8 May 2022 20:17:29 +0200 Subject: [PATCH 13/23] Add more logging to ConfigReader::File::Simple Signed-off-by: Mikko Johannes Koivunalho --- lib/Dancer2/ConfigReader/File/Simple.pm | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/Dancer2/ConfigReader/File/Simple.pm b/lib/Dancer2/ConfigReader/File/Simple.pm index 85df8f420..c35a2d8e5 100644 --- a/lib/Dancer2/ConfigReader/File/Simple.pm +++ b/lib/Dancer2/ConfigReader/File/Simple.pm @@ -47,6 +47,7 @@ sub _build_config_files { my ($self) = @_; my $location = $self->config_location; + warn "Searching config files in location: $location\n" if $ENV{DANCER_CONFIG_VERBOSE}; # an undef location means no config files for the caller return [] unless defined $location; @@ -81,6 +82,7 @@ sub _build_config_files { } } + warn "Found following config files: @files\n" if $ENV{DANCER_CONFIG_VERBOSE}; return \@files; } From f9366ffe4e7049e1237ea368b50c311606b13977 Mon Sep 17 00:00:00 2001 From: Mikko Johannes Koivunalho Date: Sun, 8 May 2022 20:18:17 +0200 Subject: [PATCH 14/23] Add isa type to HasLocation::location Also specify lazy => 1. Signed-off-by: Mikko Johannes Koivunalho --- lib/Dancer2/Core/Role/HasLocation.pm | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/Dancer2/Core/Role/HasLocation.pm b/lib/Dancer2/Core/Role/HasLocation.pm index e37d2e531..12c716795 100644 --- a/lib/Dancer2/Core/Role/HasLocation.pm +++ b/lib/Dancer2/Core/Role/HasLocation.pm @@ -24,6 +24,8 @@ has caller => ( has location => ( is => 'ro', + isa => Str, + lazy => 1, builder => '_build_location', ); From 50d009f01309385447f42a4cbeb0402235908196 Mon Sep 17 00:00:00 2001 From: Mikko Johannes Koivunalho Date: Sun, 8 May 2022 20:20:46 +0200 Subject: [PATCH 15/23] Separate normalizers from Config to ConfigUtils Signed-off-by: Mikko Johannes Koivunalho --- lib/Dancer2/ConfigUtils.pm | 41 ++++++++++++++++++++++++++++++++++++++ t/config_utils.t | 17 ++++++++++++++++ 2 files changed, 58 insertions(+) create mode 100644 lib/Dancer2/ConfigUtils.pm create mode 100644 t/config_utils.t diff --git a/lib/Dancer2/ConfigUtils.pm b/lib/Dancer2/ConfigUtils.pm new file mode 100644 index 000000000..3854f952b --- /dev/null +++ b/lib/Dancer2/ConfigUtils.pm @@ -0,0 +1,41 @@ +package Dancer2::ConfigUtils; +# ABSTRACT: Config utility helpers + +use strict; +use warnings; + +use Carp; +use Module::Runtime qw{ require_module }; + +use Exporter 'import'; +our @EXPORT_OK = qw( + normalize_config_entry +); + +my $NORMALIZERS = { + charset => sub { + my ($charset) = @_; + return $charset if !length( $charset || '' ); + + require_module('Encode'); + my $encoding = Encode::find_encoding($charset); + croak + "Charset defined in configuration is wrong : couldn't identify '$charset'" + unless defined $encoding; + my $name = $encoding->name; + + # Perl makes a distinction between the usual perl utf8, and the strict + # utf8 charset. But we don't want to make this distinction + $name = 'utf-8' if $name eq 'utf-8-strict'; + return $name; + }, +}; + +sub normalize_config_entry { + my ( $name, $value ) = @_; + $value = $NORMALIZERS->{$name}->($value) + if exists $NORMALIZERS->{$name}; + return $value; +} + +1; diff --git a/t/config_utils.t b/t/config_utils.t new file mode 100644 index 000000000..cd49676d4 --- /dev/null +++ b/t/config_utils.t @@ -0,0 +1,17 @@ +use strict; +use warnings; + +use Test::More; +use Test::Fatal; + +use Dancer2::ConfigUtils qw/normalize_config_entry/; + +is( normalize_config_entry( 'charset', 'UTF-8' ), 'utf-8', 'normalized UTF-8 to utf-8'); + +like( + exception { normalize_config_entry( 'charset', 'BOGUS' ) }, + qr{Charset defined in configuration is wrong : couldn't identify 'BOGUS'}, + 'Configuration file charset failure', +); + +done_testing; From 5dcf7885937a25c6edc2998996ec90b277925ee9 Mon Sep 17 00:00:00 2001 From: Mikko Johannes Koivunalho Date: Sun, 8 May 2022 20:23:54 +0200 Subject: [PATCH 16/23] Split Role::Config to ConfigReader and Role::HasConfig Signed-off-by: Mikko Johannes Koivunalho --- .../{Core/Role/Config.pm => ConfigReader.pm} | 195 +++--------------- lib/Dancer2/Core/App.pm | 52 ++++- lib/Dancer2/Core/Role/HasConfig.pm | 158 ++++++++++++++ t/classes/Dancer2-Core-Role-HasConfig/with.t | 42 ++++ t/config.t | 92 +++++++++ t/config_role.t | 159 -------------- 6 files changed, 369 insertions(+), 329 deletions(-) rename lib/Dancer2/{Core/Role/Config.pm => ConfigReader.pm} (54%) create mode 100644 lib/Dancer2/Core/Role/HasConfig.pm create mode 100644 t/classes/Dancer2-Core-Role-HasConfig/with.t create mode 100644 t/config.t delete mode 100644 t/config_role.t diff --git a/lib/Dancer2/Core/Role/Config.pm b/lib/Dancer2/ConfigReader.pm similarity index 54% rename from lib/Dancer2/Core/Role/Config.pm rename to lib/Dancer2/ConfigReader.pm index ca83bdf2a..2d77b89c6 100644 --- a/lib/Dancer2/Core/Role/Config.pm +++ b/lib/Dancer2/ConfigReader.pm @@ -1,33 +1,36 @@ -# ABSTRACT: Config role for Dancer2 core objects -package Dancer2::Core::Role::Config; +# ABSTRACT: Config reader for Dancer2 App +package Dancer2::ConfigReader; -use Moo::Role; +use Moo; use File::Spec; use Config::Any; use Hash::Merge::Simple; use Carp 'croak'; -use Module::Runtime qw{ require_module use_module }; +use Module::Runtime qw{ use_module }; use Dancer2::Core::Factory; use Dancer2::Core; use Dancer2::Core::Types; -use Dancer2::FileUtils 'path'; +use Dancer2::ConfigUtils 'normalize_config_entry'; -with 'Dancer2::Core::Role::HasLocation'; +has location => ( + is => 'ro', + isa => Str, + required => 1, +); has default_config => ( is => 'ro', isa => HashRef, - lazy => 1, - builder => '_build_default_config', + required => 1, ); has config_location => ( is => 'ro', isa => ReadableFilePath, lazy => 1, - default => sub { $ENV{DANCER_CONFDIR} || $_[0]->location }, + default => sub { $_[0]->location }, ); # The type for this attribute is Str because we don't require @@ -40,8 +43,7 @@ has environments_location => ( lazy => 1, default => sub { $ENV{DANCER_ENVDIR} - || File::Spec->catdir( $_[0]->config_location, 'environments' ) - || File::Spec->catdir( $_[0]->location, 'environments' ); + || File::Spec->catdir( $_[0]->config_location, 'environments' ); }, ); @@ -55,64 +57,23 @@ has config => ( has environment => ( is => 'ro', isa => Str, - lazy => 1, - builder => '_build_environment', + required => 1, ); has config_readers => ( is => 'ro', lazy => 1, + #isa => ArrayRef[ InstanceOf['Dancer2::Core::Role::ConfigReader'] ], isa => ArrayRef, builder => '_build_config_readers', ); -has local_triggers => ( - is => 'ro', - isa => HashRef, - default => sub { +{} }, -); - -has global_triggers => ( - is => 'ro', - isa => HashRef, - default => sub { - my $triggers = { - traces => sub { - my ( $self, $traces ) = @_; - # Carp is already a dependency - $Carp::Verbose = $traces ? 1 : 0; - }, - }; - - my $runner_config; - { - no warnings 'once'; - $runner_config = defined $Dancer2::runner - ? Dancer2->runner->config - : {}; - } - - for my $global ( keys %$runner_config ) { - next if exists $triggers->{$global}; - $triggers->{$global} = sub { - my ($self, $value) = @_; - Dancer2->runner->config->{$global} = $value; - } - } - - return $triggers; - }, -); - -sub _build_default_config { +{} } - -sub _build_environment { 'development' } - -# The new config builder +# The config builder sub _build_config { my ($self) = @_; my $default = $self->default_config; + use Data::Dumper; my $config = Hash::Merge::Simple->merge( $default, map { @@ -125,6 +86,16 @@ sub _build_config { return $config; } +sub _normalize_config { + my ( $self, $config ) = @_; + + foreach my $key ( keys %{$config} ) { + my $value = $config->{$key}; + $config->{$key} = normalize_config_entry( $key, $value ); + } + return $config; +} + sub _build_config_readers { my ($self) = @_; @@ -132,6 +103,7 @@ sub _build_config_readers { ? (split qr{ \s+ }msx, $ENV{'DANCER_CONFIG_READERS'}) : ( q{Dancer2::ConfigReader::File::Simple} ); + warn "ConfigReaders to use: @config_reader_names\n" if $ENV{DANCER_CONFIG_VERBOSE}; return [ map use_module($_)->new( location => $self->location, @@ -140,100 +112,6 @@ sub _build_config_readers { ]; } -sub _set_config_entries { - my ( $self, @args ) = @_; - my $no = scalar @args; - while (@args) { - $self->_set_config_entry( shift(@args), shift(@args) ); - } - return $no; -} - -sub _set_config_entry { - my ( $self, $name, $value ) = @_; - - $value = $self->_normalize_config_entry( $name, $value ); - $value = $self->_compile_config_entry( $name, $value, $self->config ); - $self->config->{$name} = $value; -} - -sub _normalize_config { - my ( $self, $config ) = @_; - - foreach my $key ( keys %{$config} ) { - my $value = $config->{$key}; - $config->{$key} = $self->_normalize_config_entry( $key, $value ); - } - return $config; -} - -sub _compile_config { - my ( $self, $config ) = @_; - - foreach my $key ( keys %{$config} ) { - my $value = $config->{$key}; - $config->{$key} = - $self->_compile_config_entry( $key, $value, $config ); - } - return $config; -} - -sub settings { shift->config } - -sub setting { - my $self = shift; - my @args = @_; - - return ( scalar @args == 1 ) - ? $self->settings->{ $args[0] } - : $self->_set_config_entries(@args); -} - -sub has_setting { - my ( $self, $name ) = @_; - return exists $self->config->{$name}; -} - -# private - -my $_normalizers = { - charset => sub { - my ($charset) = @_; - return $charset if !length( $charset || '' ); - - require_module('Encode'); - my $encoding = Encode::find_encoding($charset); - croak - "Charset defined in configuration is wrong : couldn't identify '$charset'" - unless defined $encoding; - my $name = $encoding->name; - - # Perl makes a distinction between the usual perl utf8, and the strict - # utf8 charset. But we don't want to make this distinction - $name = 'utf-8' if $name eq 'utf-8-strict'; - return $name; - }, -}; - -sub _normalize_config_entry { - my ( $self, $name, $value ) = @_; - $value = $_normalizers->{$name}->($value) - if exists $_normalizers->{$name}; - return $value; -} - -sub _compile_config_entry { - my ( $self, $name, $value, $config ) = @_; - - my $trigger = exists $self->local_triggers->{$name} ? - $self->local_triggers->{$name} : - $self->global_triggers->{$name}; - - defined $trigger or return $value; - - return $trigger->( $self, $value, $config ); -} - 1; __END__ @@ -339,21 +217,10 @@ Gets the directory where the environment files are stored. =attr config Returns the whole configuration. +This must not be used directly. +Instead, use this via C role +which manages configuration after it is created. =attr environment Returns the name of the environment. - -=head1 METHODS - -=head2 settings - -Alias for config. Equivalent to <<$object->config>>. - -=head2 setting - -Get or set an element from the configuration. - -=head2 has_setting - -Verifies that a key exists in the configuration. diff --git a/lib/Dancer2/Core/App.pm b/lib/Dancer2/Core/App.pm index c9d0a49fb..7bab282f2 100644 --- a/lib/Dancer2/Core/App.pm +++ b/lib/Dancer2/Core/App.pm @@ -19,6 +19,7 @@ use Plack::Middleware::Conditional; use Plack::Middleware::ConditionalGET; use Dancer2::FileUtils 'path'; +use Dancer2::ConfigReader; use Dancer2::Core; use Dancer2::Core::Cookie; use Dancer2::Core::Error; @@ -39,7 +40,9 @@ our $EVAL_SHIM; $EVAL_SHIM ||= sub { # we have hooks here with qw< Dancer2::Core::Role::Hookable - Dancer2::Core::Role::Config + Dancer2::Core::Role::HasConfig + Dancer2::Core::Role::HasLocation + Dancer2::Core::Role::HasEnvironment >; sub supported_engines { [ qw ] } @@ -213,7 +216,7 @@ sub _build_logger_engine { my $logger = $self->_factory->create( logger => $value, %{$engine_options}, - location => $self->config_location, + location => $self->config_reader->config_location, environment => $self->environment, app_name => $self->name, postponed_hooks => $self->postponed_hooks @@ -405,16 +408,53 @@ has session => ( predicate => '_has_session', ); -around _build_config => sub { - my ( $orig, $self ) = @_; - my $config = $self->$orig; +has config_reader => ( + is => 'ro', + isa => InstanceOf['Dancer2::ConfigReader'], + lazy => 0, + builder => '_build_config_reader', +); + +sub _build_config_reader { + my ($self) = @_; + my $cfgr = Dancer2::ConfigReader->new( + environment => $self->environment, + location => $ENV{DANCER_CONFDIR} || $self->location, + default_config => $self->_build_default_config(), + ); + return $cfgr; +} + +has '+config' => ( + is => 'ro', + isa => HashRef, + lazy => 1, + builder => '_build_config', +); + +sub _build_config { + my ($self) = @_; + + my $config_reader = $self->config_reader; + my $config = $config_reader->config; if ( $config && $config->{'engines'} ) { $self->_validate_engine($_) for keys %{ $config->{'engines'} }; } return $config; -}; +} + +# around _build_config => sub { +# my ( $orig, $self ) = @_; +# my $config = $self->$orig; +# +# if ( $config && $config->{'engines'} ) { +# $self->_validate_engine($_) for keys %{ $config->{'engines'} }; +# } +# +# return $config; +# }; sub _build_response { my $self = shift; diff --git a/lib/Dancer2/Core/Role/HasConfig.pm b/lib/Dancer2/Core/Role/HasConfig.pm new file mode 100644 index 000000000..50eb084e0 --- /dev/null +++ b/lib/Dancer2/Core/Role/HasConfig.pm @@ -0,0 +1,158 @@ +# ABSTRACT: Role that represents the config of Dancer2 App +package Dancer2::Core::Role::HasConfig; + +use Moo::Role; + +use File::Spec; +use Config::Any; +use Hash::Merge::Simple; +use Carp 'croak'; +use Module::Runtime qw{ require_module use_module }; + +use Dancer2::Core::Factory; +use Dancer2::Core; +use Dancer2::Core::Types; +use Dancer2::FileUtils 'path'; +use Dancer2::ConfigUtils 'normalize_config_entry'; + +has config => ( + is => 'ro', + isa => HashRef, + lazy => 0, + builder => '_build_config', +); + +has local_triggers => ( + is => 'ro', + isa => HashRef, + default => sub { +{} }, +); + +has global_triggers => ( + is => 'ro', + isa => HashRef, + default => sub { + my $triggers = { + traces => sub { + my ( $self, $traces ) = @_; + # Carp is already a dependency + $Carp::Verbose = $traces ? 1 : 0; + }, + }; + + my $runner_config; + { + no warnings 'once'; + $runner_config = defined $Dancer2::runner + ? Dancer2->runner->config + : {}; + } + + for my $global ( keys %$runner_config ) { + next if exists $triggers->{$global}; + $triggers->{$global} = sub { + my ($self, $value) = @_; + Dancer2->runner->config->{$global} = $value; + } + } + + return $triggers; + }, +); + +sub _set_config_entries { + my ( $self, @args ) = @_; + my $no = scalar @args; + while (@args) { + $self->_set_config_entry( shift(@args), shift(@args) ); + } + return $no; +} + +sub _set_config_entry { + my ( $self, $name, $value ) = @_; + + $value = normalize_config_entry( $name, $value ); + $value = $self->_compile_config_entry( $name, $value, $self->config ); + $self->config->{$name} = $value; +} + +# Who calls this method? +# sub _compile_config { +# my ( $self, $config ) = @_; +# +# foreach my $key ( keys %{$config} ) { +# my $value = $config->{$key}; +# $config->{$key} = +# $self->_compile_config_entry( $key, $value, $config ); +# } +# return $config; +# } + +sub settings { shift->config } + +sub setting { + my $self = shift; + my @args = @_; + + return ( scalar @args == 1 ) + ? $self->settings->{ $args[0] } + : $self->_set_config_entries(@args); +} + +sub has_setting { + my ( $self, $name ) = @_; + return exists $self->config->{$name}; +} + +# private + +sub _compile_config_entry { + my ( $self, $name, $value, $config ) = @_; + + my $trigger = exists $self->local_triggers->{$name} ? + $self->local_triggers->{$name} : + $self->global_triggers->{$name}; + + defined $trigger or return $value; + + return $trigger->( $self, $value, $config ); +} + +1; + +__END__ + +=head1 DESCRIPTION + +This role provides a C attribute that is +used to read the configuration. +When accessing +the first time, it calls method C<_build_config()> which +must be implemented by the using class. +This method should return the whole config which has been +created by executing one or more +B packages. + +Also provides a C method which is supposed to be used by externals to +read/write config entries. + +=head1 ATTRIBUTES + +=attr config + +Returns the whole configuration. + +=head1 METHODS + +=head2 settings + +Alias for config. Equivalent to <<$object->config>>. + +=head2 setting + +Get or set an element from the configuration. + +=head2 has_setting + +Verifies that a key exists in the configuration. diff --git a/t/classes/Dancer2-Core-Role-HasConfig/with.t b/t/classes/Dancer2-Core-Role-HasConfig/with.t new file mode 100644 index 000000000..b0bd4fdbb --- /dev/null +++ b/t/classes/Dancer2-Core-Role-HasConfig/with.t @@ -0,0 +1,42 @@ +use strict; +use warnings; + +use Test::More; + +# undefine ENV vars used as defaults for app environment in these tests +local $ENV{DANCER_ENVIRONMENT}; +local $ENV{PLACK_ENV}; + +{ + + package Dev; + use Moo; + with 'Dancer2::Core::Role::HasConfig'; + + sub name {'Dev'} + sub _build_config { + return { + content_type => 'text/html', + charset => 'UTF-8', + environment => 'development', + template => 'Tiny', + } + } + +} + +my $d = Dev->new(); +is $d->does('Dancer2::Core::Role::HasConfig'), 1, + 'role Dancer2::Core::Role::Config is consumed'; + +is $d->config->{'environment'}, 'development', 'Correct config value'; +is $d->config->{'template'}, 'Tiny', 'Correct config value'; +is $d->settings->{'charset'}, 'UTF-8', 'Correct config value normalized'; + +$d->setting( 'entry_one', 'value_one', 'entry_two', 'value_two' ); +is $d->config->{'entry_one'}, 'value_one', 'Correct config value set'; +is $d->config->{'entry_two'}, 'value_two', 'Correct config value set'; +ok $d->has_setting('entry_one'), 'Has value we set previously'; +isnt $d->has_setting('entry_missing'), 1, 'Correctly missing value'; + +done_testing; diff --git a/t/config.t b/t/config.t new file mode 100644 index 000000000..4f451eab6 --- /dev/null +++ b/t/config.t @@ -0,0 +1,92 @@ +use strict; +use warnings; +use Test::More; +use Test::Fatal; +use Carp 'croak'; + +# use Dancer2::Core::Runner; +use Dancer2::FileUtils qw/dirname path/; +use File::Spec; +use File::Temp; +use Dancer2::ConfigReader; + +# undefine ENV vars used as defaults for app environment in these tests +local $ENV{DANCER_ENVIRONMENT}; +local $ENV{PLACK_ENV}; + +# my $runner = Dancer2::Core::Runner->new(); +my $location = File::Spec->rel2abs( path( dirname(__FILE__), 'config' ) ); +my $location2 = File::Spec->rel2abs( path( dirname(__FILE__), 'config2' ) ); + +{ + my $cfgr = Dancer2::ConfigReader->new( + environment => 'my_env', + location => $location, + default_config => { + content_type => 'text/html', + charset => 'UTF-8', + }, + ); + is( $cfgr->config->{'application'}->{'some_feature'}, 'foo', 'Ok config' ); + is( $cfgr->config->{'charset'}, 'utf-8', 'Ok default config' ); +} + +{ + # note "bad YAML file: environments/failure.yml"; + like( + exception { + Dancer2::ConfigReader->new( + environment => 'failure', + location => $location, + default_config => { }, + )->config; + }, + qr{Unable to parse the configuration file}, 'Configuration file parsing failure', + ); +} + +{ + my $cfgr = Dancer2::ConfigReader->new( + environment => 'any_env', + location => $location, + default_config => { }, + ); + my $cfg = $cfgr->config; + isnt( $cfg, undef, 'OK config read' ); +} +{ + my $cfgr = Dancer2::ConfigReader->new( + environment => 'merging', + location => $location, + default_config => { }, + ); + # note "config merging"; + # Check the 'application' top-level key; its the only key that + # is currently a HoH in the test configurations + is_deeply $cfgr->config->{application}, + { + some_feature => 'bar', + another_setting => 'baz', + }, + "full merging of configuration hashes"; +} +{ + my $cfgr = Dancer2::ConfigReader->new( + environment => 'lconfig', + location => $location2, + default_config => { }, + ); + is_deeply $cfgr->config->{application}, + { feature_1 => 'foo', + feature_2 => 'alpha', + feature_3 => 'replacement', + feature_4 => 'blat', + feature_5 => 'beta', + feature_6 => 'bar', + feature_7 => 'baz', + feature_8 => 'goober', + }, + "full merging of local configuration hashes"; +} + +done_testing; diff --git a/t/config_role.t b/t/config_role.t deleted file mode 100644 index 97e060c9f..000000000 --- a/t/config_role.t +++ /dev/null @@ -1,159 +0,0 @@ -use strict; -use warnings; -use Test::More; -use Test::Fatal; -use Carp 'croak'; - -use Dancer2::Core::Runner; -use Dancer2::FileUtils qw/dirname path/; -use File::Spec; -use File::Temp; - -# undefine ENV vars used as defaults for app environment in these tests -local $ENV{DANCER_ENVIRONMENT}; -local $ENV{PLACK_ENV}; - -my $runner = Dancer2::Core::Runner->new(); -my $location = File::Spec->rel2abs( path( dirname(__FILE__), 'config' ) ); -my $location2 = File::Spec->rel2abs( path( dirname(__FILE__), 'config2' ) ); - -{ - - package Prod; - use Moo; - with 'Dancer2::Core::Role::Config'; - - sub name {'Prod'} - - sub _build_environment {'production'} - sub _build_location {$location} - sub _build_default_config {$runner->config} - - package Dev; - use Moo; - with 'Dancer2::Core::Role::Config'; - - sub _build_environment {'development'} - sub _build_location {$location}; - sub _build_default_config {$runner->config} - - package Failure; - use Moo; - with 'Dancer2::Core::Role::Config'; - - sub _build_environment {'failure'} - sub _build_location {$location} - sub _build_default_config {$runner->config} - - package Staging; - use Moo; - with 'Dancer2::Core::Role::Config'; - - sub _build_environment {'staging'} - sub _build_location {$location} - sub _build_default_config {$runner->config} - - package Merging; - use Moo; - with 'Dancer2::Core::Role::Config'; - - sub name {'Merging'} - - sub _build_environment {'merging'} - sub _build_location {$location} - sub _build_default_config {$runner->config} - - package LocalConfig; - use Moo; - with 'Dancer2::Core::Role::Config'; - - sub name {'LocalConfig'} - - sub _build_environment {'lconfig'} - sub _build_location {$location2} - sub _build_default_config {$runner->config} - -} - -my $d = Dev->new(); - -my $f = Prod->new; -is $f->does('Dancer2::Core::Role::Config'), 1, - "role Dancer2::Core::Role::Config is consumed"; - -note "bad YAML file"; -my $fail = Failure->new; -is $fail->environment, 'failure'; - -like( - exception { $fail->config }, - qr{Unable to parse the configuration file}, 'Configuration file parsing failure', -); - -note "config merging"; -my $m = Merging->new; - -# Check the 'application' top-level key; its the only key that -# is currently a HoH in the test configurations -is_deeply $m->config->{application}, - { some_feature => 'bar', - another_setting => 'baz', - }, - "full merging of configuration hashes"; - -{ - my $l = LocalConfig->new; - - is_deeply $l->config->{application}, - { feature_1 => 'foo', - feature_2 => 'alpha', - feature_3 => 'replacement', - feature_4 => 'blat', - feature_5 => 'beta', - feature_6 => 'bar', - feature_7 => 'baz', - feature_8 => 'goober', - }, - "full merging of local configuration hashes"; - -} - -note "config parsing"; - -is $f->config->{show_stacktrace}, 0; -is $f->config->{main}, 1; -is $f->config->{charset}, 'utf-8', "normalized UTF-8 to utf-8"; - -ok( $f->has_setting('charset') ); -ok( !$f->has_setting('foobarbaz') ); - -note "default values"; -is $f->setting('apphandler'), 'Standalone'; - -like( - exception { $f->_normalize_config( { charset => 'BOGUS' } ) }, - qr{Charset defined in configuration is wrong : couldn't identify 'BOGUS'}, - 'Configuration file charset failure', -); - -{ - - package Foo; - use Carp 'croak'; - sub foo { croak "foo" } -} - -is $f->setting('traces'), 0; -unlike( exception { Foo->foo() }, qr{Foo::foo}, "traces are not enabled", ); - -$f->setting( traces => 1 ); -like( exception { Foo->foo() }, qr{Foo::foo}, "traces are enabled", ); - -{ - my $tmpdir = File::Temp::tempdir( CLEANUP => 1, TMPDIR => 1 ); - $ENV{DANCER_CONFDIR} = $tmpdir; - my $f = Prod->new; - is $f->config_location, $tmpdir; -} - -done_testing; From 94de9b0ebd3f4fba9219508db18d321dd97d5fe5 Mon Sep 17 00:00:00 2001 From: Mikko Johannes Koivunalho Date: Sun, 8 May 2022 20:24:17 +0200 Subject: [PATCH 17/23] Add Mikko Koivunalho to contributors list Signed-off-by: Mikko Johannes Koivunalho --- lib/Dancer2.pm | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/Dancer2.pm b/lib/Dancer2.pm index f43580917..ea9ac2d0d 100644 --- a/lib/Dancer2.pm +++ b/lib/Dancer2.pm @@ -355,6 +355,7 @@ We are also on IRC: #dancer on irc.perl.org. Michael Kröll Michał Wojciechowski Mike Katasonov + Mikko Koivunalho Mohammad S Anwar mokko Nick Patch From 444764668308c209a11bc694ac4f911cfef7a1be Mon Sep 17 00:00:00 2001 From: Mikko Johannes Koivunalho Date: Thu, 12 May 2022 18:48:03 +0200 Subject: [PATCH 18/23] Cleanup code * Remove previously commented out code * Remove 'use Data::Dumper' Signed-off-by: Mikko Johannes Koivunalho --- lib/Dancer2/ConfigReader.pm | 2 -- lib/Dancer2/Core/App.pm | 11 ----------- lib/Dancer2/Core/Role/HasConfig.pm | 12 ------------ 3 files changed, 25 deletions(-) diff --git a/lib/Dancer2/ConfigReader.pm b/lib/Dancer2/ConfigReader.pm index 2d77b89c6..273400b3e 100644 --- a/lib/Dancer2/ConfigReader.pm +++ b/lib/Dancer2/ConfigReader.pm @@ -63,7 +63,6 @@ has environment => ( has config_readers => ( is => 'ro', lazy => 1, - #isa => ArrayRef[ InstanceOf['Dancer2::Core::Role::ConfigReader'] ], isa => ArrayRef, builder => '_build_config_readers', ); @@ -73,7 +72,6 @@ sub _build_config { my ($self) = @_; my $default = $self->default_config; - use Data::Dumper; my $config = Hash::Merge::Simple->merge( $default, map { diff --git a/lib/Dancer2/Core/App.pm b/lib/Dancer2/Core/App.pm index 7bab282f2..6e242bda4 100644 --- a/lib/Dancer2/Core/App.pm +++ b/lib/Dancer2/Core/App.pm @@ -445,17 +445,6 @@ sub _build_config { return $config; } -# around _build_config => sub { -# my ( $orig, $self ) = @_; -# my $config = $self->$orig; -# -# if ( $config && $config->{'engines'} ) { -# $self->_validate_engine($_) for keys %{ $config->{'engines'} }; -# } -# -# return $config; -# }; - sub _build_response { my $self = shift; return Dancer2::Core::Response->new( diff --git a/lib/Dancer2/Core/Role/HasConfig.pm b/lib/Dancer2/Core/Role/HasConfig.pm index 50eb084e0..f506396f6 100644 --- a/lib/Dancer2/Core/Role/HasConfig.pm +++ b/lib/Dancer2/Core/Role/HasConfig.pm @@ -77,18 +77,6 @@ sub _set_config_entry { $self->config->{$name} = $value; } -# Who calls this method? -# sub _compile_config { -# my ( $self, $config ) = @_; -# -# foreach my $key ( keys %{$config} ) { -# my $value = $config->{$key}; -# $config->{$key} = -# $self->_compile_config_entry( $key, $value, $config ); -# } -# return $config; -# } - sub settings { shift->config } sub setting { From e598fbf68280442907a22f1560ffd30712f28001 Mon Sep 17 00:00:00 2001 From: Mikko Johannes Koivunalho Date: Thu, 12 May 2022 19:00:19 +0200 Subject: [PATCH 19/23] Fix documentation in File::Simple Signed-off-by: Mikko Johannes Koivunalho --- lib/Dancer2/ConfigReader/File/Simple.pm | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/lib/Dancer2/ConfigReader/File/Simple.pm b/lib/Dancer2/ConfigReader/File/Simple.pm index c35a2d8e5..0e8b6a5bc 100644 --- a/lib/Dancer2/ConfigReader/File/Simple.pm +++ b/lib/Dancer2/ConfigReader/File/Simple.pm @@ -116,9 +116,7 @@ It reads the configuration files of C. Please see C for more information. If you need to add additional functionality to the reading -mechanism, you can extend this class. An example of this is -in: B - +mechanism, you can extend this class. An example of this is providing the possibility to replace random parts of the file config with environmental variables: @@ -193,7 +191,7 @@ random parts of the file config with environmental variables: =attr name -The name of the class. +The name of the Config Reader class: C. =attr location From 6959f974afef5e19da6fda8a6ca35f770f869fa1 Mon Sep 17 00:00:00 2001 From: Mikko Johannes Koivunalho Date: Thu, 12 May 2022 19:38:03 +0200 Subject: [PATCH 20/23] Fix test to use File::Simple->config_location Signed-off-by: Mikko Johannes Koivunalho --- t/issues/gh-634.t | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/t/issues/gh-634.t b/t/issues/gh-634.t index 01ce44ea6..04bd4eaff 100644 --- a/t/issues/gh-634.t +++ b/t/issues/gh-634.t @@ -5,6 +5,11 @@ use File::Temp qw/tempdir/; use File::Spec; my $log_dir = tempdir( CLEANUP => 1 ); +sub config_location { + my ($app) = @_; + my %config_readers = map { $_->{name} => $_ } @{ $app->config_reader->config_readers }; + return $config_readers{ 'File::Simple' }->config_location; +} { package LogDirSpecified; @@ -56,7 +61,7 @@ my $check_cb = sub { is( $logger->location, - $app->config_location, + config_location( $app ), 'Logger got correct location', ); @@ -96,7 +101,7 @@ subtest 'test Logger::File with log_dir NOT specified' => sub { $check_cb->( $app, - File::Spec->catdir( $app->config_location, 'logs' ), + File::Spec->catdir( config_location( $app ), 'logs' ), $app->environment . '.log', ); }; @@ -116,4 +121,3 @@ subtest 'test Logger::File with non-existent log_dir specified' => sub { 'test_log.log', ); }; - From 99d934affd8580012b542cab437cd70635800e3e Mon Sep 17 00:00:00 2001 From: Mikko Johannes Koivunalho Date: Thu, 12 May 2022 19:47:05 +0200 Subject: [PATCH 21/23] Fix documentation in Role::ConfigReader Signed-off-by: Mikko Johannes Koivunalho --- lib/Dancer2/Core/Role/ConfigReader.pm | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/lib/Dancer2/Core/Role/ConfigReader.pm b/lib/Dancer2/Core/Role/ConfigReader.pm index b63fe1fa6..cfc3c529c 100644 --- a/lib/Dancer2/Core/Role/ConfigReader.pm +++ b/lib/Dancer2/Core/Role/ConfigReader.pm @@ -79,13 +79,19 @@ during creation: The name of the environment used, e.g. production, development, staging. -==attr location +=attr location -Absolute path to the directory where the server started. +The absolute path to the directory where the server started. + +=attr default_config + +A hash ref which contains the default values. These arguments are passed when the object is created by -C. How the config -reader applies them as it needs. +C. +ConfigReader then passes C and C forward to every +config reader class when it instantiates them. +How the config reader applies them, depend on its needs. Provides a C attribute that - when accessing the first time - feeds itself by finding and parsing From 8bf2e6d6266b9a211657e6574c90db775a08c852 Mon Sep 17 00:00:00 2001 From: Mikko Johannes Koivunalho Date: Sun, 21 Aug 2022 15:31:59 +0200 Subject: [PATCH 22/23] Remove whitespace Signed-off-by: Mikko Johannes Koivunalho --- lib/Dancer2.pm | 2 +- lib/Dancer2/Core/Role/ConfigReader.pm | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/Dancer2.pm b/lib/Dancer2.pm index ea9ac2d0d..00cf9401f 100644 --- a/lib/Dancer2.pm +++ b/lib/Dancer2.pm @@ -134,7 +134,7 @@ Dancer2 is easy and fun: use Dancer2; get '/' => sub { "Hello World" }; - dance; + dance; This is the main module for the Dancer2 distribution. It contains logic for creating a new Dancer2 application. diff --git a/lib/Dancer2/Core/Role/ConfigReader.pm b/lib/Dancer2/Core/Role/ConfigReader.pm index cfc3c529c..a0c1f6e14 100644 --- a/lib/Dancer2/Core/Role/ConfigReader.pm +++ b/lib/Dancer2/Core/Role/ConfigReader.pm @@ -88,7 +88,7 @@ The absolute path to the directory where the server started. A hash ref which contains the default values. These arguments are passed when the object is created by -C. +C. ConfigReader then passes C and C forward to every config reader class when it instantiates them. How the config reader applies them, depend on its needs. From 35ad84961334da2da03226b7ddc736f24174a804 Mon Sep 17 00:00:00 2001 From: Mikko Johannes Koivunalho Date: Sat, 10 Sep 2022 23:12:42 +0200 Subject: [PATCH 23/23] Add Mikko Koivunalho to contributors Signed-off-by: Mikko Johannes Koivunalho --- README.mkdn | 1 + 1 file changed, 1 insertion(+) diff --git a/README.mkdn b/README.mkdn index 34a2b2001..75decdcbb 100644 --- a/README.mkdn +++ b/README.mkdn @@ -232,6 +232,7 @@ We are also on IRC: #dancer on irc.perl.org. Michael Kröll Michał Wojciechowski Mike Katasonov + Mikko Koivunalho Mohammad S Anwar mokko Nick Patch