File Coverage

blib/lib/Crane/Config.pm
Criterion Covered Total %
statement 56 65 86.1
branch 20 34 58.8
condition 3 8 37.5
subroutine 10 10 100.0
pod 4 4 100.0
total 93 121 76.8


line stmt bran cond sub pod time code
1             # -*- coding: utf-8 -*-
2              
3              
4             package Crane::Config;
5              
6              
7 3     3   666 use Crane::Base qw( Exporter );
  3         7  
  3         20  
8              
9 3     3   2878 use File::Spec::Functions qw( catdir );
  3         2830  
  3         362  
10 3     3   2927 use YAML;
  3         49933  
  3         221  
11 3     3   2969 use YAML::Dumper;
  3         47991  
  3         4072  
12              
13              
14             our @EXPORT = qw(
15             &config
16             );
17              
18             our @EXPORT_OK = qw(
19             &merge_config
20             &read_config
21             &write_config
22             &load_config
23             );
24              
25              
26             my $DEFAULT_FILENAME = catdir('etc', 'default.conf');
27              
28             my $DEFAULT_CONFIG = {
29             'log' => {
30             'level' => undef, # Default log level
31            
32             'filename' => catdir('log', 'messages.log'), # Path to log file (undef -> stdout)
33             'error_filename' => catdir('log', 'errors.log'), # Path to error log file (undef -> stderr)
34             },
35             };
36              
37              
38             sub config {
39            
40 9     9   1233 return state $config = do {
41 2         5 my ( $config, @filenames ) = @_;
42            
43 2 100       16 Readonly::Hash(my %config => %{
    50          
44 2         5 load_config(
45             merge_config(
46             $DEFAULT_CONFIG,
47             ref $config eq 'HASH' ? $config : {},
48             ),
49            
50             scalar @filenames ? @filenames : $DEFAULT_FILENAME,
51             )
52             });
53            
54 2         285 \%config;
55             };
56            
57             }
58              
59              
60             sub merge_config {
61            
62 4     4 1 2484 my ( $original, $config ) = @_;
63            
64 4         12 my $type_original = ref $original;
65 4         8 my $type_config = ref $config;
66            
67 4 100       28 if ( $type_original eq $type_config ) {
68 3 50       32 if ( $type_config eq 'HASH' ) {
69 3         5 foreach my $key ( keys %{ $config } ) {
  3         15  
70 3 100       20 if ( exists $original->{ $key } ) {
71 1         6 $original->{ $key } = merge_config($original->{ $key }, $config->{ $key });
72             } else {
73 2         1263 $original->{ $key } = $config->{ $key };
74             }
75             }
76             }
77             } else {
78 1         4 $original = $config;
79             }
80            
81 4         27 return $original;
82            
83             }
84              
85              
86             sub read_config {
87            
88 1     1 1 950 my ( $filename ) = @_;
89            
90 1 50       6 if ( not defined $filename ) {
91 0         0 confess('No file name given');
92             }
93            
94 1         2 my $config = {};
95            
96 1 50       49 if ( open my $fh, '<:encoding(UTF-8)', $filename ) {
97             $config = eval {
98 1         5 local $INPUT_RECORD_SEPARATOR = undef;
99 1   50     29 return ( YAML::Load(<$fh>) )[0] || {};
100 1 50       59 } or do {
101 0         0 confess("Incorrect syntax in '$filename': $EVAL_ERROR");
102             };
103            
104 1 50       22959 close $fh or confess($OS_ERROR);
105             } else {
106 0         0 confess("Unable to read config '$filename': $OS_ERROR");
107             }
108            
109 1         11 return $config;
110            
111             }
112              
113              
114             sub write_config {
115            
116 1     1 1 4083 my ( $config, $filename ) = @_;
117            
118 1 50       8 if ( ref $config ne 'HASH' ) {
119 0         0 confess('Configuration should be a hash reference');
120             }
121            
122 1 50       5 if ( not defined $filename ) {
123 0         0 confess('No file name given');
124             }
125            
126             # Init YAML
127 1         25 state $yaml = YAML::Dumper->new(
128             'indent_width' => 4,
129             'sort_keys' => 1,
130             'use_header' => 0,
131             'use_version' => 0,
132             'use_block' => 1,
133             'use_fold' => 1,
134             'use_aliases' => 0,
135             );
136            
137             # Dump configuration
138 1 50   1   107 if ( open my $fh, '>:encoding(UTF-8)', $filename ) {
  1         10  
  1         2  
  1         8  
139 1 50 33     34437 if ( not eval { print { $fh } $yaml->dump($config) or confess($OS_ERROR) } or $EVAL_ERROR ) {
  1 50       4  
  1         11  
140 0         0 confess("YAML error while writing '$filename': $EVAL_ERROR");
141             }
142            
143 1 50       2212 close $fh or confess($OS_ERROR);
144             } else {
145 0         0 confess("Unable to write config '$filename': $OS_ERROR");
146             }
147            
148 1         10 return;
149            
150             }
151              
152              
153             sub load_config {
154            
155 2     2 1 8 my ( $config, @filenames ) = @_;
156            
157 2 50       10 if ( ref $config ne 'HASH' ) {
158 0         0 confess('Configuration should be a hash reference');
159             }
160            
161 2         4 foreach my $filename ( @filenames ) {
162 2 50 33     55 if ( defined $filename and -e $filename ) {
163 0         0 $config = merge_config($config, read_config($filename));
164             }
165             }
166            
167 2         16 return $config;
168            
169             }
170              
171              
172             1;
173              
174              
175             =head1 NAME
176              
177             Crane::Config - Configuration manager
178              
179              
180             =head1 SYNOPSIS
181              
182             use Crane::Config;
183            
184             my $filename = config->{'log'}->{'filename'};
185              
186              
187             =head1 DESCRIPTION
188              
189             Configuration manager which operates with YAML configurations. Settings are
190             available as a hash reference returned by L
191             @filenames)"> function.
192              
193             You can specify default configuration and filename by passing it to
194             L function when first call (see
195             description below).
196              
197              
198             =head1 EXPORTED FUNCTIONS
199              
200             =over
201              
202             =item B (I<$config>, I<@filenames>)
203              
204             Returns link to current configuration.
205              
206             At first call you can specify default configuration I<$config> and/or list of
207             config file names I<@filenames>.
208              
209              
210             =head1 FUNCTIONS
211              
212             =over
213              
214             =item B (I<$original>, I<$config>)
215              
216             Merge two configs (I<$config> to I<$original>).
217              
218             =item B (I<$filename>)
219              
220             Reads confugration from file named I<$filename>.
221              
222             =item B (I<$config>, I<$filename>)
223              
224             Saves configuration I<$config> to file named I<$filename>.
225              
226             =item B (I<$config>, I<@filenames>)
227              
228             Load configurations from files named I<@filenames> and merges them to
229             configuration I<$config> and I configuration.
230              
231             =back
232              
233              
234             =head1 ERRORS
235              
236             =over
237              
238             =item Incorrect syntax in 'I<%s>': I<%s>
239              
240             Where I<%s> is file name and I<%s> is error message.
241              
242             Invalid YAML configuration file.
243              
244             =item Unable to read config 'I<%s>': I<%s>
245              
246             Where I<%s> is file name and I<%s> is error message.
247              
248             Fires when unable to open configuration for read.
249              
250             =item Unable to write config 'I<%s>': I<%s>
251              
252             Where I<%s> is file name and I<%s> is error message.
253              
254             Fires when unable to open configuration for write.
255              
256             =item YAML error while writing 'I<%s>': I<%s>
257              
258             Where I<%s> is file name and I<%s> is error message.
259              
260             =item Configuration should be a hash reference
261              
262             Fires when function required hash reference as a configuration.
263              
264             =item No filename given
265              
266             Fires when function required name of file but it is undefined.
267              
268             =back
269              
270              
271             =head1 EXAMPLES
272              
273             Configuration file
274              
275             domain: "production"
276            
277             log:
278             level: 0
279             filename: "/var/log/example/messages.log"
280             error_filename: "/var/log/example/errors.log"
281            
282             servers:
283             - "127.0.0.1:3001"
284             - "127.0.0.1:3002"
285              
286             Which results to hash reference:
287              
288             {
289             'domain' => 'production',
290            
291             'log' => {
292             'level' => '0',
293            
294             'filename' => '/var/log/example/messages.log',
295             'error_filename' => '/var/log/example/errors.log',
296             },
297            
298             'servers' => [
299             '127.0.0.1:3001',
300             '127.0.0.1:3002',
301             ],
302             }
303              
304              
305             =head1 ENVIRONMENT
306              
307             See L.
308              
309              
310             =head1 FILES
311              
312             =over
313              
314             =item F
315              
316             Default configuration file (may not exist).
317              
318             =back
319              
320              
321             =head1 BUGS
322              
323             Please report any bugs or feature requests to
324             L or to
325             L.
326              
327              
328             =head1 AUTHOR
329              
330             Tema Novikov,
331              
332              
333             =head1 COPYRIGHT AND LICENSE
334              
335             Copyright (C) 2013-2014 Tema Novikov.
336              
337             This library is free software; you can redistribute it and/or modify it under
338             the terms of the Artistic License 2.0. For details, see the full text of the
339             license in the file LICENSE.
340              
341              
342             =head1 SEE ALSO
343              
344             =over
345              
346             =item * B
347              
348             L
349              
350             =item * B
351              
352             L
353              
354             =back