File Coverage

blib/lib/Config/ROFL.pm
Criterion Covered Total %
statement 81 95 85.2
branch 14 28 50.0
condition 4 8 50.0
subroutine 24 27 88.8
pod 2 2 100.0
total 125 160 78.1


line stmt bran cond sub pod time code
1             package Config::ROFL;
2              
3 1     1   133377 use strict;
  1         3  
  1         30  
4 1     1   18 use warnings;
  1         2  
  1         23  
5              
6 1     1   11 use v5.10;
  1         3  
7              
8 1     1   11 use Carp ();
  1         3  
  1         13  
9 1     1   574 use Config::ZOMG ();
  1         81929  
  1         32  
10 1     1   630 use Data::Rmap ();
  1         1584  
  1         24  
11 1     1   455 use File::Share ();
  1         29444  
  1         32  
12 1     1   13 use Path::Tiny qw( cwd path );
  1         2  
  1         67  
13 1     1   11 use List::Util ();
  1         3  
  1         25  
14 1     1   14 use Scalar::Util qw( readonly );
  1         2  
  1         41  
15 1     1   1672 use Types::Standard qw/Str HashRef/;
  1         106471  
  1         14  
16              
17 1     1   948 use Moo;
  1         2  
  1         15  
18 1     1   1985 use namespace::clean;
  1         25687  
  1         10  
19              
20             has 'global_path' => is => 'lazy', isa => Str, default => '/etc/';
21             has 'config' => is => 'rw', lazy => 1, builder => 1;
22             has 'config_path' => is => 'lazy', coerce => sub { ref $_[0] eq 'Path::Tiny' ? $_[0] : path($_[0]); }, builder => 1;
23             has 'dist' => is => 'lazy', isa => Str, default => '';
24             has 'relative_dir' => is => 'lazy', coerce => sub { ref $_[0] eq 'Path::Tiny' ? $_[0] : path($_[0]); }, builder => 1;
25             has 'mode' => is => 'lazy', isa => Str, default => sub { $ENV{CONFIG_ROFL_MODE} // ($ENV{HARNESS_ACTIVE} && 'test' || 'dev') };
26             has 'name' => is => 'lazy', isa => Str, default => sub { $ENV{CONFIG_ROFL_NAME} || 'config' };
27             has 'lookup_order' => is => 'lazy', default => sub {
28             (shift->mode eq 'test') ? ['relative', 'by_dist', 'by_self'] : ['by_dist', 'by_self', 'relative']
29             };
30              
31             sub _build_relative_dir {
32 0     0   0 my ($self) = @_;
33              
34 0 0       0 return $ENV{CONFIG_ROFL_RELATIVE_DIR} if $ENV{CONFIG_ROFL_RELATIVE_DIR};
35              
36 0         0 my $pm = _class_to_pm(ref $self);
37 0 0       0 if (my $path = $INC{$pm}) {
38 0         0 return path($path)->parent->parent->child('share');
39             }
40             }
41              
42             with 'MooX::Singleton';
43              
44             sub _build_config {
45 8     8   75 my ($self) = @_;
46              
47 8         142 my $config = Config::ZOMG->new(
48             name => $self->name,
49             path => $self->config_path,
50             local_suffix => $self->mode,
51             driver =>
52             { General => {'-LowerCaseNames' => 1, '-InterPolateEnv' => 1, '-InterPolateVars' => 1,}, }
53             );
54              
55 8         9275 $config->load;
56              
57 8 50       111475 if ($config->found) {
58 8         312 _post_process_config($config->load);
59 8         196 say {*STDERR} "Loaded configs: " . (
60             join ', ',
61             map {
62 9         288 my $realpath = path($_)->realpath;
63 9         2396 my $rel_path = cwd->relative($realpath);
64 9 50       3955 $rel_path =~ /^\.\./ ? $realpath : $rel_path
65             } $config->found
66 8 50       35 ) if $ENV{CONFIG_ROFL_DEBUG};
67             }
68             else {
69 0         0 Carp::croak 'Could not find config file: ' . $self->config_path . '/' . $self->name . '.(conf|yml|json)';
70             }
71              
72 8         576 return $config;
73             }
74              
75             around 'config' => sub {
76             my $orig = shift;
77             my $self = shift;
78              
79             return $orig->($self, @_)->load;
80             };
81              
82             sub _build_config_path {
83 7     7   356 my $self = shift;
84 7 100       27 return $ENV{CONFIG_ROFL_CONFIG_PATH} if $ENV{CONFIG_ROFL_CONFIG_PATH};
85              
86 5 100   22   102 if (List::Util::first {-e} glob path($self->global_path, $self->name) . '.{conf,yml,yaml,json,ini}') {
  22         957  
87 1         29 return $self->global_path
88             }
89              
90 4         30 my $path;
91              
92 4         9 for my $type (@{ $self->lookup_order }) {
  4         103  
93 10         220 my $method = "_lookup_$type";
94 10   66     48 $path //= $self->$method;
95             }
96              
97 4 50       21 die 'Could not find relative path (' . $self->relative_dir . ') , nor dist path (' . $self->dist . ')' unless $path;
98              
99 4         15 return path($path)->child('/etc');
100             }
101              
102              
103             sub _post_process_config {
104 8     8   59 my ($hash) = @_;
105              
106             Data::Rmap::rmap_scalar {
107 12 50 33 12   1220 defined $_ && (!readonly $_) && ($_ =~ s/__ENV\((\w+)\)__/_env_substitute($1)/eg);
  2         7  
108             }
109 8         58 $hash;
110              
111 8         279 return;
112             }
113              
114             sub _env_substitute {
115 2     2   6 my ($prefix) = @_;
116 2   50     15 return $ENV{$prefix} || '';
117             }
118              
119             sub _class_to_pm {
120 0     0   0 my ($module) = @_;
121 0         0 $module =~ s{(-|::)}{/}g;
122 0         0 return "$module.pm";
123             }
124              
125             sub _lookup_relative {
126 3     3   8 my ($self) = @_;
127              
128 3         57 my $path = $self->relative_dir;
129 3 50       44 return $path if $path->exists;
130             }
131              
132             sub _lookup_by_dist {
133 1     1   10 my ($self) = @_;
134              
135 1         3 my $path;
136 1 50       24 return $path unless $self->dist;
137              
138 1 50       17 eval { $path = File::Share::dist_dir($self->dist) } or warn $@;
  1         18  
139              
140 1         3813 return $path;
141             }
142              
143             sub _lookup_by_self {
144 0     0   0 my ($self) = @_;
145              
146 0         0 my $path;
147 0 0       0 eval { $path = File::Share::dist_dir(ref $self) } or warn $@;
  0         0  
148              
149 0         0 return $path;
150             }
151              
152             sub get {
153 13     13 1 13081 my ($self, @keys) = @_;
154              
155 13 100   23   352 return List::Util::reduce { $a->{$b} || $a->{lc $b} } $self->config, @keys;
  23         349  
156             }
157              
158 4     4 1 3719 sub share_file { shift->config_path->parent->child(@_) }
159              
160             1;
161              
162             =encoding utf8
163              
164             =head1 NAME
165              
166             Config::ROFL - Yet another config module
167              
168             =head1 SYNOPSIS
169              
170             use Config::ROFL;
171             my $config = Config::ROFL->new;
172             $config->get("frobs");
173             $config->get(qw/mail server host/);
174              
175             $config->share_file("system.yml");
176              
177             =head1 DESCRIPTION
178              
179             Subclassable and auto-magic config module utilizing L. It looks up which config path to use based on current mode, share dir and class name. Falls back to a relative share dir when run as part of tests.
180              
181             =head1 ATTRIBUTES
182              
183             =head2 config
184              
185             Returns a hashref representation of the config
186              
187             =head2 dist
188              
189             The dist name used to find a share dir where the config file is located.
190              
191             =head2 global_path
192              
193             Global path overriding any lookup by dist, relative or by class of object.
194              
195             =head2 mode
196              
197             String used as part of name to lookup up config merged on top of general config.
198             For instance if mode is set to "production", the config used will be: config.production.yml merged on top of config.yml
199             Default is 'dev', except when HARNESS_ACTIVE env-var is set for instance when running tests, then mode is 'test'.
200              
201             =head2 name
202              
203             Name of config file, default is "config"
204              
205             =head2 config_path
206              
207             Path where to look for config files.
208              
209             =head2 lookup_order
210              
211             Order of config lookup. Default is ['by_dist', 'by_self', 'relative'], except when under tests when it is ['relative', 'by_dist', 'by_self']
212              
213             =head1 METHODS
214              
215             =head2 get
216              
217             Gets a config value, supports an array of strings to traverse down to a certain child hash value.
218              
219             =head2 new
220              
221             Create a new config instance
222              
223             =head2 new
224              
225             Get an existing config instance if already created see L. Beware that altering env-vars between invocations will not affect the instance init args.
226              
227             =head2 share_file
228              
229             Gets the full path to a file residing in the share folder relative to the config.
230              
231             =head1 SEE ALSO
232              
233             L
234              
235             L
236              
237             L
238              
239             L
240              
241             =head1 COPYRIGHT
242              
243             Nicolas Mendoza 2023 - All rights reserved
244              
245             =cut