Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/new config system (improved) #1637

Open
wants to merge 23 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
bca9296
Move Role::ConfigReader to Role::Config
mikkoi Feb 2, 2022
743485c
Create Role::Config to be the "config" part of obj
mikkoi Feb 2, 2022
d1c4b0a
Create ConfigReader role and an implementation
mikkoi Feb 2, 2022
13640fa
Add tests for new ConfigReader
mikkoi Feb 2, 2022
25970ab
Change regex: remove POSIX form
mikkoi Mar 27, 2022
7c39543
Reformat
mikkoi Mar 27, 2022
9465fdc
Reformat: no warnings 'once'
mikkoi Mar 27, 2022
dafd2ce
Rework POD and add an example how to extend
mikkoi Mar 27, 2022
b319d6c
Rewrite documentation of Dancer2::Core::Role::Config
mikkoi Mar 27, 2022
adb4cd2
Add ABSTRACT to ConfigReader::File::Simple
mikkoi Mar 27, 2022
622336c
Remove attr config_files from Dancer2::Core::Role::Config
mikkoi Mar 27, 2022
8827022
Create Role::HasEnvironment and tests
mikkoi May 8, 2022
15bff9a
Add more logging to ConfigReader::File::Simple
mikkoi May 8, 2022
f9366ff
Add isa type to HasLocation::location
mikkoi May 8, 2022
50d009f
Separate normalizers from Config to ConfigUtils
mikkoi May 8, 2022
5dcf788
Split Role::Config to ConfigReader and Role::HasConfig
mikkoi May 8, 2022
94de9b0
Add Mikko Koivunalho to contributors list
mikkoi May 8, 2022
4447646
Cleanup code
mikkoi May 12, 2022
e598fbf
Fix documentation in File::Simple
mikkoi May 12, 2022
6959f97
Fix test to use File::Simple->config_location
mikkoi May 12, 2022
99d934a
Fix documentation in Role::ConfigReader
mikkoi May 12, 2022
8bf2e6d
Remove whitespace
mikkoi Aug 21, 2022
35ad849
Add Mikko Koivunalho to contributors
mikkoi Sep 10, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.mkdn
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 2 additions & 1 deletion lib/Dancer2.pm
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand Down
224 changes: 224 additions & 0 deletions lib/Dancer2/ConfigReader.pm
Original file line number Diff line number Diff line change
@@ -0,0 +1,224 @@
# ABSTRACT: Config reader for Dancer2 App
package Dancer2::ConfigReader;

use Moo;

use File::Spec;
use Config::Any;
use Hash::Merge::Simple;
use Carp 'croak';
use Module::Runtime qw{ use_module };

use Dancer2::Core::Factory;
use Dancer2::Core;
use Dancer2::Core::Types;
use Dancer2::ConfigUtils 'normalize_config_entry';

has location => (
is => 'ro',
isa => Str,
required => 1,
);

has default_config => (
is => 'ro',
isa => HashRef,
required => 1,
);

has config_location => (
is => 'ro',
isa => ReadableFilePath,
lazy => 1,
default => sub { $_[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' );
},
);

has config => (
is => 'ro',
isa => HashRef,
lazy => 1,
builder => '_build_config',
);

has environment => (
is => 'ro',
isa => Str,
required => 1,
);

has config_readers => (
is => 'ro',
lazy => 1,
isa => ArrayRef,
builder => '_build_config_readers',
);

# The config builder
sub _build_config {
my ($self) = @_;

my $default = $self->default_config;
my $config = Hash::Merge::Simple->merge(
$default,
map {
warn "Merging config from @{[ $_->name() ]}\n" if $ENV{DANCER_CONFIG_VERBOSE};
$_->read_config()
} @{ $self->config_readers }
);

$config = $self->_normalize_config($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) = @_;

my @config_reader_names = $ENV{'DANCER_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,
environment => $self->environment,
), @config_reader_names
];
}

1;

__END__

=head1 DESCRIPTION

This class provides a C<config> attribute that - when accessing
the first time - feeds itself by executing one or more
B<ConfigReader> packages.

Also provides a C<setting()> method which is supposed to be used by externals to
read/write config entries.

You can control which B<ConfigReader>
class or classes to use to create the config.

Use C<DANCER_CONFIG_READERS> 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 added in left-to-write order where the previous
config items get overwritten by subsequent ones.

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<ConfigReader> is C<Dancer2::ConfigReader::File::Simple>.

You can also create your own custom B<ConfigReader> classes.

If you want, you can also extend class C<Dancer2::ConfigReader::File::Simple>.
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;
};

Another (more complex) example is in the file C<Dancer2::ConfigReader::File::Simple>.

=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.
This must not be used directly.
Instead, use this via C<Dancer2::Core::Role::HasConfig> role
which manages configuration after it is created.

=attr environment

Returns the name of the environment.
Loading