File Coverage

lib/Class/Usul/Config.pm
Criterion Covered Total %
statement 118 125 94.4
branch 25 54 46.3
condition 9 29 31.0
subroutine 44 45 97.7
pod 3 3 100.0
total 199 256 77.7


line stmt bran cond sub pod time code
1             package Class::Usul::Config;
2              
3 20     20   14583 use namespace::autoclean;
  20         42  
  20         107  
4              
5 20         131 use Class::Usul::Constants qw( CONFIG_EXTN DEFAULT_CONFHOME
6             DEFAULT_ENCODING DEFAULT_L10N_DOMAIN
7 20     20   1375 FALSE LANG NUL PERL_EXTNS PHASE TRUE );
  20         56  
8 20     20   21551 use Class::Usul::File;
  20         42  
  20         820  
9 20         140 use Class::Usul::Functions qw( app_prefix canonicalise class2appdir
10             home2appldir is_arrayref split_on__
11             split_on_dash untaint_cmdline
12 20     20   99 untaint_identifier untaint_path );
  20         40  
13 20         139 use Class::Usul::Types qw( ArrayRef DataEncoding HashRef NonEmptySimpleStr
14 20     20   30878 NonZeroPositiveInt PositiveInt Str );
  20         42  
15 20     20   30616 use Config;
  20         196  
  20         694  
16 20     20   100 use English qw( -no_match_vars );
  20         56  
  20         154  
17 20     20   6651 use File::Basename qw( basename dirname );
  20         57  
  20         851  
18 20     20   118 use File::DataClass::Types qw( Directory File Path );
  20         43  
  20         128  
19 20     20   28794 use File::Gettext::Constants qw( LOCALE_DIRS );
  20         4879  
  20         1013  
20 20         1047 use File::Spec::Functions qw( canonpath catdir catfile
21 20     20   122 rel2abs rootdir tmpdir );
  20         55  
22 20     20   118 use File::Which qw( which );
  20         26  
  20         1235  
23 20     20   114 use Scalar::Util qw( blessed );
  20         98  
  20         816  
24 20     20   117 use Moo;
  20         55  
  20         120  
25              
26             # Attribute constructors
27             my $_build_l10n_attributes = sub {
28 5     5   74 return { %{ $_[ 0 ]->_l10n_attributes }, domains => $_[ 0 ]->l10n_domains, };
  5         86  
29             };
30              
31             # Public attributes
32             has 'appclass' => is => 'ro', isa => NonEmptySimpleStr, required => TRUE;
33              
34             has 'appldir' => is => 'lazy', isa => Directory, coerce => TRUE;
35              
36             has 'binsdir' => is => 'lazy', isa => Path, coerce => TRUE;
37              
38             has 'cfgfiles' => is => 'lazy', isa => ArrayRef[NonEmptySimpleStr],
39 1     1   15375 builder => sub { [] };
40              
41             has 'ctlfile' => is => 'lazy', isa => Path, coerce => TRUE;
42              
43             has 'ctrldir' => is => 'lazy', isa => Path, coerce => TRUE;
44              
45             has 'datadir' => is => 'lazy', isa => Path, coerce => TRUE;
46              
47             has 'encoding' => is => 'ro', isa => DataEncoding, coerce => TRUE,
48             default => DEFAULT_ENCODING;
49              
50             has 'extension' => is => 'lazy', isa => NonEmptySimpleStr,
51             default => CONFIG_EXTN;
52              
53             has 'home' => is => 'lazy', isa => Directory, coerce => TRUE,
54             default => DEFAULT_CONFHOME;
55              
56             has 'locale' => is => 'ro', isa => NonEmptySimpleStr, default => LANG;
57              
58             has 'localedir' => is => 'lazy', isa => Directory, coerce => TRUE;
59              
60             has 'locales' => is => 'ro', isa => ArrayRef[NonEmptySimpleStr],
61 24     24   59338 builder => sub { [ LANG ] };
62              
63             has 'logfile' => is => 'lazy', isa => Path, coerce => TRUE;
64              
65             has 'logsdir' => is => 'lazy', isa => Directory, coerce => TRUE;
66              
67             has 'name' => is => 'lazy', isa => NonEmptySimpleStr;
68              
69             has 'no_thrash' => is => 'ro', isa => NonZeroPositiveInt, default => 3;
70              
71             has 'phase' => is => 'lazy', isa => PositiveInt;
72              
73             has 'prefix' => is => 'lazy', isa => NonEmptySimpleStr,
74             coerce => sub { untaint_cmdline $_[ 0 ] };
75              
76             has 'pathname' => is => 'lazy', isa => File, coerce => TRUE;
77              
78             has 'root' => is => 'lazy', isa => Path, coerce => TRUE;
79              
80             has 'rundir' => is => 'lazy', isa => Path, coerce => TRUE;
81              
82             has 'salt' => is => 'lazy', isa => NonEmptySimpleStr;
83              
84             has 'sessdir' => is => 'lazy', isa => Path, coerce => TRUE;
85              
86             has 'sharedir' => is => 'lazy', isa => Path, coerce => TRUE;
87              
88             has 'shell' => is => 'lazy', isa => File, coerce => TRUE;
89              
90             has 'suid' => is => 'lazy', isa => Path, coerce => TRUE;
91              
92             has 'tempdir' => is => 'lazy', isa => Directory, coerce => TRUE;
93              
94             has 'vardir' => is => 'lazy', isa => Path, coerce => TRUE;
95              
96             has 'l10n_attributes' => is => 'lazy', isa => HashRef,
97             builder => $_build_l10n_attributes, init_arg => undef;
98              
99             has 'l10n_domains' => is => 'lazy', isa => ArrayRef[NonEmptySimpleStr],
100 5     5   313 builder => sub { [ DEFAULT_L10N_DOMAIN, $_[ 0 ]->name ] };
101              
102             has '_l10n_attributes' => is => 'lazy', isa => HashRef,
103 5     5   138 builder => sub { {} }, init_arg => 'l10n_attributes';
104              
105 24     24   1268 has 'lock_attributes' => is => 'ro', isa => HashRef, builder => sub { {} };
106              
107 24     24   868 has 'log_attributes' => is => 'ro', isa => HashRef, builder => sub { {} };
108              
109             # Private functions
110             my $_is_inflated = sub {
111             my ($attr, $attr_name) = @_;
112              
113             return exists $attr->{ $attr_name } && defined $attr->{ $attr_name }
114             && $attr->{ $attr_name } !~ m{ \A __ }mx ? TRUE : FALSE;
115             };
116              
117             my $_unpack = sub {
118             my ($self, $attr) = @_; $attr //= {};
119              
120             blessed $self and return ($self, $self->{appclass}, $self->{home});
121              
122             return ($self, $attr->{appclass}, $attr->{home});
123             };
124              
125             # Construction
126             around 'BUILDARGS' => sub {
127             my ($orig, $self, @args) = @_; my $attr = $orig->( $self, @args );
128              
129             my $paths; if ($paths = $attr->{cfgfiles} and $paths->[ 0 ]) {
130             my $loaded = Class::Usul::File->data_load( paths => $paths ) || {};
131              
132             $attr = { %{ $loaded }, %{ $attr } }; # Yes this way round. Leave it alone
133             }
134              
135             for my $name (keys %{ $attr }) {
136             defined $attr->{ $name }
137             and $attr->{ $name } =~ m{ \A __([^\(]+?)__ \z }mx
138             and $attr->{ $name } = $self->inflate_symbol( $attr, $1 );
139             }
140              
141             $self->inflate_paths( $attr );
142              
143             return $attr;
144             };
145              
146             sub _build_appldir {
147 6     6   144 my ($self, $appclass, $home) = $_unpack->( @_ ); my $dir;
  6         18  
148              
149 6 0 33     33 $dir = home2appldir $home
      33        
150             and $dir = rel2abs( untaint_path $dir )
151             and -d catdir( $dir, 'lib' ) and return $dir;
152              
153 6 50 33     4267 $dir = catdir( NUL, 'var', class2appdir $appclass )
      33        
154             and $dir = rel2abs( untaint_path $dir )
155             and -d $dir and return $dir;
156              
157 6 50 33     255 $dir = rel2abs( untaint_path $home ) and -d $dir and return $dir;
158              
159 0         0 return rel2abs( untaint_path rootdir );
160             }
161              
162             sub _build_binsdir {
163 1     1   74 my $dir = $_[ 0 ]->inflate_path( $_[ 1 ], 'appldir', 'bin' );
164              
165 1 50       16 return -d $dir ? $dir : untaint_path $Config{installsitescript};
166             }
167              
168             sub _build_ctlfile {
169 2     2   56 my $name = $_[ 0 ]->inflate_symbol( $_[ 1 ], 'name' );
170 2         111 my $extension = $_[ 0 ]->inflate_symbol( $_[ 1 ], 'extension' );
171              
172 2         125 return $_[ 0 ]->inflate_path( $_[ 1 ], 'ctrldir', $name.$extension );
173             }
174              
175             sub _build_ctrldir {
176 2     2   58 my $dir = $_[ 0 ]->inflate_path( $_[ 1 ], 'vardir', 'etc' );
177              
178 2 50       25 -d $dir and return $dir;
179              
180 2         10 $dir = $_[ 0 ]->inflate_path( $_[ 1 ], 'appldir', 'etc' );
181              
182 2 50       33 return -d $dir ? $dir : [ NUL, qw( usr local etc ) ];
183             }
184              
185             sub _build_datadir {
186 2     2   1482 my $dir = $_[ 0 ]->inflate_path( $_[ 1 ], 'vardir', 'data' );
187              
188 2 50       27 return -d $dir ? $dir : $_[ 0 ]->inflate_path( $_[ 1 ], 'tempdir' );
189             }
190              
191             sub _build_localedir {
192 5     5   116 my $dir = $_[ 0 ]->inflate_path( $_[ 1 ], 'vardir', 'locale' );
193              
194 5 50       50 -d $dir and return $dir;
195              
196 5 50       14 for (map { catdir( @{ $_ } ) } @{ LOCALE_DIRS() } ) { -d $_ and return $_ }
  15         62  
  15         56  
  5         25  
  5         155  
197              
198 0         0 return $_[ 0 ]->inflate_path( $_[ 1 ], 'tempdir' );
199             }
200              
201             sub _build_logfile {
202 7     7   185 my $name = $_[ 0 ]->inflate_symbol( $_[ 1 ], 'name' );
203              
204 7         286 return $_[ 0 ]->inflate_path( $_[ 1 ], 'logsdir', "${name}.log" );
205             }
206              
207             sub _build_logsdir {
208 1     1   1747 my $dir = $_[ 0 ]->inflate_path( $_[ 1 ], 'vardir', 'logs' );
209              
210 1 50       15 return -d $dir ? $dir : $_[ 0 ]->inflate_path( $_[ 1 ], 'tempdir' );
211             }
212              
213             sub _build_name {
214 8     8   806 my $name = basename
215             ( $_[ 0 ]->inflate_path( $_[ 1 ], 'pathname' ), PERL_EXTNS );
216              
217 8   33     97 return (split_on__ $name, 1) || (split_on_dash $name, 1) || $name;
218             }
219              
220             sub _build_pathname {
221 8 50   8   197 my $name = ('-' eq substr $PROGRAM_NAME, 0, 1) ? $EXECUTABLE_NAME
222             : $PROGRAM_NAME;
223              
224 8         65 return rel2abs( (split m{ [ ][\-][ ] }mx, $name)[ 0 ] );
225             }
226              
227             sub _build_phase {
228 1     1   928 my $verdir = basename( $_[ 0 ]->inflate_path( $_[ 1 ], 'appldir' ) );
229 1         6 my ($phase) = $verdir =~ m{ \A v \d+ \. \d+ p (\d+) \z }msx;
230              
231 1 50       18 return defined $phase ? $phase : PHASE;
232             }
233              
234             sub _build_prefix {
235 2     2   49 my $appclass = $_[ 0 ]->inflate_symbol( $_[ 1 ], 'appclass' );
236              
237 2         18 return (split m{ :: }mx, lc $appclass)[ -1 ];
238             }
239              
240             sub _build_root {
241 1     1   894 my $dir = $_[ 0 ]->inflate_path( $_[ 1 ], 'vardir', 'root' );
242              
243 1 50       18 return -d $dir ? $dir : $_[ 0 ]->inflate_path( $_[ 1 ], 'tempdir' );
244             }
245              
246             sub _build_rundir {
247 1     1   1326 my $dir = $_[ 0 ]->inflate_path( $_[ 1 ], 'vardir', 'run' );
248              
249 1 50       14 return -d $dir ? $dir : $_[ 0 ]->inflate_path( $_[ 1 ], 'tempdir' );
250             }
251              
252             sub _build_sessdir {
253 1     1   1297 my $dir = $_[ 0 ]->inflate_path( $_[ 1 ], 'vardir', 'session' );
254              
255 1 50       15 return -d $dir ? $dir : $_[ 0 ]->inflate_path( $_[ 1 ], 'tempdir' );
256             }
257              
258             sub _build_sharedir {
259 1     1   1354 my $dir = $_[ 0 ]->inflate_path( $_[ 1 ], 'vardir', 'share' );
260              
261 1 50       15 return -d $dir ? $dir : $_[ 0 ]->inflate_path( $_[ 1 ], 'tempdir' );
262             }
263              
264             sub _build_shell {
265 1 50 33 1   756 my $file = $ENV{SHELL}; $file and -e $file and return $file;
  1         5  
266 1 50       7 $file = catfile( NUL, 'bin', 'ksh' ); -e $file and return $file;
  1         27  
267 1 50       6 $file = catfile( NUL, 'bin', 'bash' ); -e $file and return $file;
  1         15  
268 0 0 0     0 $file = which ( 'sh' ); $file and -e $file and return $file;
  0         0  
269 0         0 return catfile( NUL, 'bin', 'sh' );
270             }
271              
272             sub _build_salt {
273 2     2   33 return untaint_cmdline $_[ 0 ]->inflate_symbol( $_[ 1 ], 'prefix' );
274             }
275              
276             sub _build_suid {
277 1     1   2559 my $prefix = $_[ 0 ]->inflate_symbol( $_[ 1 ], 'prefix' );
278              
279 1         14 return $_[ 0 ]->inflate_path( $_[ 1 ], 'binsdir', "${prefix}-admin" );
280             }
281              
282             sub _build_tempdir {
283 0     0   0 my $dir = $_[ 0 ]->inflate_path( $_[ 1 ], 'vardir', 'tmp' );
284              
285 0 0       0 return -d $dir ? $dir : untaint_path tmpdir;
286             }
287              
288             sub _build_vardir {
289 6     6   158 my $dir = $_[ 0 ]->inflate_path( $_[ 1 ], 'appldir', 'var' );
290              
291 6 50       75 return -e $dir ? $dir : $_[ 0 ]->inflate_path( $_[ 1 ], 'appldir' );
292             }
293              
294             # Public methods
295             sub inflate_path {
296 58     58 1 181 return canonicalise $_[ 0 ]->inflate_symbol( $_[ 1 ], $_[ 2 ] ), $_[ 3 ];
297             }
298              
299             sub inflate_paths {
300 26 100   26 1 92 my ($self, $attr) = @_; defined $attr or return;
  26         113  
301              
302 25         67 for my $name (keys %{ $attr }) {
  25         90  
303             defined $attr->{ $name }
304             and $attr->{ $name } =~ m{ \A __(.+?)\((.+?)\)__ \z }mx
305 144 50 33     626 and $attr->{ $name } = $self->inflate_path( $attr, $1, $2 );
306             }
307              
308 25         73 return;
309             }
310              
311             sub inflate_symbol {
312 76 100 50 76 1 233 my ($self, $attr, $symbol) = @_; $attr //= {}; defined $symbol or return;
  76         383  
  76         196  
313              
314 74         156 my $attr_name = lc $symbol; my $method = "_build_${attr_name}";
  74         144  
315              
316             return blessed $self ? $self->$attr_name()
317 74 0       1437 : $_is_inflated->( $attr, $attr_name ) ? $attr->{ $attr_name }
    50          
318             : $self->$method( $attr );
319             }
320              
321             1;
322              
323             __END__
324              
325             =pod
326              
327             =head1 Name
328              
329             Class::Usul::Config - Configuration class with sensible attribute defaults
330              
331             =head1 Synopsis
332              
333             use Class::Usul::Constants qw( TRUE );
334             use Class::Usul::Types qw( ConfigType HashRef LoadableClass );
335             use Moo;
336              
337             has 'config' => is => 'lazy', isa => ConfigType, builder => sub {
338             $_[ 0 ]->config_class->new( $_[ 0 ]->_config_attr ) },
339             init_arg => undef;
340              
341             has '_config_attr' => is => 'ro', isa => HashRef, builder => sub { {} },
342             init_arg => 'config';
343              
344             has 'config_class' => is => 'ro', isa => LoadableClass, coerce => TRUE,
345             default => 'Class::Usul::Config';
346              
347             =head1 Description
348              
349             Defines the configuration object. Attributes have sensible defaults that
350             can be overridden by values in configuration files which are loaded on
351             request
352              
353             Pathnames passed in the L</cfgfiles> attribute are loaded and their contents
354             merged with the values passed to the configuration class constructor
355              
356             =head1 Configuration and Environment
357              
358             Defines the following list of attributes;
359              
360             =over 3
361              
362             =item C<appclass>
363              
364             Required string. The classname of the application for which this is the
365             configuration class
366              
367             =item C<appldir>
368              
369             Directory. Defaults to the application's install directory
370              
371             =item C<binsdir>
372              
373             Directory. Defaults to the application's F<bin> directory. Prefers
374             L</appldir>F</bin> but defaults to L<Config>s C<installsitebin> attribute
375              
376             =item C<cfgfiles>
377              
378             An array reference of non empty simple strings. The list of configuration
379             files to load when instantiating an instance of the configuration class.
380             Defaults to an empty list
381              
382             =item C<ctlfile>
383              
384             File in the F<ctrldir> directory that contains this programs control data
385             The default filename is comprised of L</name> and L</extension>
386              
387             =item C<ctrldir>
388              
389             Directory containing the per program configuration files. Prefers F<var/etc>,
390             then L</appldir>F</etc> defaulting to F</usr/local/etc>
391              
392             =item C<datadir>
393              
394             Directory containing data files used by the application. Prefers F<var/data>
395             but defaults to L</tempdir>
396              
397             =item C<encoding>
398              
399             String default to the constant C<DEFAULT_ENCODING>
400              
401             =item C<extension>
402              
403             String defaults to the constant C<CONFIG_EXTN>
404              
405             =item C<home>
406              
407             Directory containing the config file. Defaults to the constant
408             C<DEFAULT_CONFHOME>
409              
410             =item C<l10n_attributes>
411              
412             Hash reference of attributes used to construct a L<Class::Usul::L10N>
413             object. By default contains one key, C<domains>. The filename(s) used to
414             translate messages into different languages
415              
416             =item C<l10n_domains>
417              
418             An array reference which defaults to the constant C<DEFAULT_L10N_DOMAIN> and
419             the applications configuration name. Merged into L<l10n_attributes> as the
420             C<domains> attribute
421              
422             =item C<locale>
423              
424             The locale for language translation of text. Defaults to the constant
425             C<LANG>
426              
427             =item C<localedir>
428              
429             Directory containing the GNU Gettext portable object files used to translate
430             messages into different languages. Prefers F<var/locale> but defaults to
431             either the first existing directory in the list provided by the C<LOCALE_DIRS>
432             constant or failing that L</tempdir>
433              
434             =item C<locales>
435              
436             Array reference containing the list of supported locales. The default list
437             contains only the constant C<LANG>
438              
439             =item C<lock_attributes>
440              
441             Hash reference of attributes used to construct an L<IPC::SRLock> object
442              
443             =item C<log_attributes>
444              
445             Hash reference of attributes used to construct a L<Class::Usul::Log> object
446              
447             =item C<logfile>
448              
449             File in the C<logsdir> to which this program will log. Defaults to
450             L</name>.log
451              
452             =item C<logsdir>
453              
454             Directory containing the application log files. Prefers F<var/logs> but
455             defaults to L</tempdir>
456              
457             =item C<name>
458              
459             String. Derived from the L</pathname>. It is either; the last component of the
460             program name when split on underscores or dashes, or the program name itself
461             if it contains no underscores or dashes
462              
463             =item C<no_thrash>
464              
465             Integer default to 3. Number of seconds to sleep in a polling loop to
466             avoid processor thrash
467              
468             =item C<pathname>
469              
470             File defaults to the absolute path to the C<PROGRAM_NAME> system constant
471              
472             =item C<phase>
473              
474             Integer. Phase number indicates the type of install, e.g. 1 live, 2 test,
475             3 development
476              
477             =item C<prefix>
478              
479             String. Program prefix. Defaults to the last component of the L</appclass>
480             lower cased
481              
482             =item C<root>
483              
484             Directory. Path to the web applications document root. Prefers F<var/root>
485             but defaults to L</tempdir>
486              
487             =item C<rundir>
488              
489             Directory. Contains a running programs PID file. Prefers F<var/run> but defaults
490             to L</tempdir>
491              
492             =item C<salt>
493              
494             String. This applications salt for passwords as set by the administrators . It
495             is used to perturb the encryption methods. Defaults to the L</prefix>
496             attribute value
497              
498             =item C<sessdir>
499              
500             Directory. The session directory. Prefers F<var/session> but defaults to
501             L</tempdir>
502              
503             =item C<sharedir>
504              
505             Directory containing assets used by the application. Prefers F<var/share>
506             but defaults to L</tempdir>
507              
508             =item C<shell>
509              
510             File. The default shell used to create new OS users. Defaults to the
511             environment variable C<SHELL>. If that is not set tries (in order);
512             F</bin/ksh>, F</bin/bash>. L<which|File::Which/which> 'sh', and finally
513             defaults to F</bin/sh>. If the selected file does not exist then the
514             type constraint on the attribute will throw
515              
516             =item C<suid>
517              
518             File. Name of the setuid root program in the L</binsdir> directory. Defaults to
519             L</prefix>-admin
520              
521             =item C<tempdir>
522              
523             Directory. It is the location of any temporary files created by the
524             application. Prefers F<var/tmp> but defaults to the L<File::Spec> C<tempdir>
525              
526             =item C<vardir>
527              
528             Directory. Contains all of the non program code directories. Prefers F<var>
529             but defaults to L</appldir>
530              
531             =back
532              
533             =head1 Subroutines/Methods
534              
535             =head2 BUILDARGS
536              
537             Loads the configuration files if specified in the C<cfgfiles> attribute. Calls
538             L</inflate_symbol> and L</inflate_path> as required
539              
540             =head2 inflate_path
541              
542             Inflates the C<__symbol( relative_path )__> values to their actual runtime
543             values
544              
545             =head2 inflate_paths
546              
547             Calls L</inflate_path> for each of the matching values in the hash that
548             was passed as argument
549              
550             =head2 inflate_symbol
551              
552             Inflates the C<__SYMBOL__> values to their actual runtime values
553              
554             =head1 Diagnostics
555              
556             None
557              
558             =head1 Dependencies
559              
560             =over 3
561              
562             =item L<Class::Usul::File>
563              
564             =item L<Moo>
565              
566             =back
567              
568             =head1 Incompatibilities
569              
570             There are no known incompatibilities in this module
571              
572             =head1 Bugs and Limitations
573              
574             There are no known bugs in this module.
575             Please report problems to the address below.
576             Patches are welcome
577              
578             =head1 Author
579              
580             Peter Flanigan, C<< <pjfl@cpan.org> >>
581              
582             =head1 License and Copyright
583              
584             Copyright (c) 2017 Peter Flanigan. All rights reserved
585              
586             This program is free software; you can redistribute it and/or modify it
587             under the same terms as Perl itself. See L<perlartistic>
588              
589             This program is distributed in the hope that it will be useful,
590             but WITHOUT WARRANTY; without even the implied warranty of
591             MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE
592              
593             =cut
594              
595             # Local Variables:
596             # mode: perl
597             # tab-width: 3
598             # End: