From 366d87bb9274c2858d08a3ce6e13e31f2f07709d Mon Sep 17 00:00:00 2001 From: Arthur Axel 'fREW' Schmidt Date: Sat, 28 Mar 2015 13:21:08 -0500 Subject: [PATCH] Add a configuration system to allow users to set their own defaults --- Changes | 2 + bin/charon | 51 +++++++++++++++++++++++-- cpanfile | 6 +++ lib/App/Charon/Config.pm | 23 +++++++++++ lib/App/Charon/ConfigLoader.pm | 70 ++++++++++++++++++++++++++++++++++ 5 files changed, 148 insertions(+), 4 deletions(-) create mode 100644 lib/App/Charon/Config.pm create mode 100644 lib/App/Charon/ConfigLoader.pm diff --git a/Changes b/Changes index d3e3e45..29904f7 100644 --- a/Changes +++ b/Changes @@ -1,6 +1,8 @@ Revision history for {{$dist->name}} {{$NEXT}} + - Add a configuration system to allow users to set their own defaults + (Fixes GH#10) 0.001002 2015-03-07 13:08:54-06:00 America/Chicago - Correctly munge the index html so links will work diff --git a/bin/charon b/bin/charon index 2a8f035..78ed4b4 100755 --- a/bin/charon +++ b/bin/charon @@ -15,15 +15,25 @@ use Future; use IO::Socket::IP; use App::Charon::Web; +use App::Charon::ConfigLoader; +my $c = App::Charon::ConfigLoader->new( + env_key => 'CHARON', + config_class => 'App::Charon::Config', + location => '.charonrc', +)->load; my ($opt, $usage) = describe_options( 'charon %o [file|dir]', - [ 'listen|l=s', "Listens on an address, whether HOST:PORT or :PORT"], - [ 'autoshutdown=s', 'Shutdown after a given time period (example: 1h30m)'], - [ 'query-param-auth=s', 'Use query param auth. Eg: pass=station will require ?pass=station in requests'], - [ 'index!', 'Show index (default on)', { default => 1 }], + [ 'listen|l=s', "Listens on an address, whether HOST:PORT or :PORT", + $c->opt_default_for('listen')], + [ 'autoshutdown=s', 'Shutdown after a given time period (example: 1h30m)', + $c->opt_default_for('autoshutdown')], + [ 'query-param-auth=s', 'Use query param auth. Eg: pass=station will require ?pass=station in requests', + $c->opt_default_for('query_param_auth')], + [ 'index!', 'Show index', $c->opt_default_for('index', { default => 1 })], [], [ 'help|h', "print usage message and exit" ], + { show_defaults => 1 }, ); print($usage->text), exit if $opt->help; @@ -129,6 +139,39 @@ various machines have the right tools and whatnot to serve files from one machine to another. C helps with this by making some of what used to be Plack oneliners into a much more robust and convenient tool. +=head1 CONFIGURATION + +C uses +L +that allows the user to use either a file, or environment variables, or both. +Currently all the configuration allows you to do is set defaults for the +commandline options. Basically it works like this: + +=over 3 + +=item * if C is set, use that to set C + +=item * if C<$foo> exists in the config file, use that to set C + +=back + +The location of the config file is similarly dynamic. The default is just +F<.charonrc> in the current directory. The location can be overridden by +setting C. Currently the configuration file is L. + +So to be clear, if you wanted to set a default for C<--query-param-auth>, you +can either: + +=over 3 + +=item 1. Set C + +=item 2. Put a line like C< query_param_auth: password=station > in F<.charonrc> + +=item 3. Set C to F<~/.charonrc> and add a line like the above to that file. + +=back + =head1 COMMANDLINE OPTIONS =head2 single positional argument diff --git a/cpanfile b/cpanfile index fde82ef..dfdebd0 100644 --- a/cpanfile +++ b/cpanfile @@ -9,6 +9,12 @@ requires 'Time::Duration::Parse'; requires 'App::Genpass'; requires 'IO::Socket::IP'; requires 'HTML::Zoom'; +requires 'Moo' => 2; +requires 'JSONY'; +requires 'IO::All'; +requires 'Try::Tiny'; +requires 'Module::Runtime'; +requires 'namespace::clean'; on test => sub { requires 'Test::More' => 1; diff --git a/lib/App/Charon/Config.pm b/lib/App/Charon/Config.pm new file mode 100644 index 0000000..4743b61 --- /dev/null +++ b/lib/App/Charon/Config.pm @@ -0,0 +1,23 @@ +package App::Charon::Config; + +use utf8; +use Moo; +use warnings NONFATAL => 'all'; + +has [qw(listen autoshutdown query_param_auth index)] => ( + is => 'ro', + predicate => 1, +); + +sub opt_default_for { + my ($self, $attr, @default) = @_; + + my $pred = "has_$attr"; + if ($self->$pred) { + return { default => $self->$attr } + } else { + return @default + } +} + +1; diff --git a/lib/App/Charon/ConfigLoader.pm b/lib/App/Charon/ConfigLoader.pm new file mode 100644 index 0000000..069ab29 --- /dev/null +++ b/lib/App/Charon/ConfigLoader.pm @@ -0,0 +1,70 @@ +package App::Charon::ConfigLoader; + +use utf8; +use Moo; +use warnings NONFATAL => 'all'; + +use JSONY; +use IO::All; +use Try::Tiny; +use Module::Runtime 'use_module'; +use namespace::clean; + +has _env_key => ( + is => 'ro', + init_arg => 'env_key', + required => 1, +); + +has _location => ( + is => 'ro', + init_arg => undef, + lazy => 1, + default => sub { + my $self = shift; + $ENV{$self->_env_key . '_CONFLOC'} || $self->__location + }, +); + +has __location => ( + is => 'ro', + init_arg => 'location', +); + +has _config_class => ( + is => 'ro', + init_arg => 'config_class', + required => 1, +); + +sub _io { io->file(shift->_location) } + +sub _read_config_from_file { + my $self = shift; + try { + JSONY->new->load($self->_io->all) + } catch { + {} + } +} + +sub _read_config_from_env { + my $k_re = '^' . quotemeta($_[0]->_env_key) . '_(.+)'; + + +{ + map {; m/$k_re/; lc $1 => $ENV{$_[0]->_env_key . "_$1"} } + grep m/$k_re/, + keys %ENV + } +} + +sub _read_config { + { + %{$_[0]->_read_config_from_file}, + %{$_[0]->_read_config_from_env}, + } +} + +sub load { use_module($_[0]->_config_class)->new($_[0]->_read_config) } + +1;