File Coverage

blib/lib/App/Basis/Config.pm
Criterion Covered Total %
statement 92 106 86.7
branch 21 34 61.7
condition 3 9 33.3
subroutine 17 21 80.9
pod 4 5 80.0
total 137 175 78.2


line stmt bran cond sub pod time code
1             # ABSTRACT: Manage config YAML files in a simple manner
2              
3              
4              
5             package App::Basis::Config;
6             $App::Basis::Config::VERSION = '1.2';
7 1     1   23614 use 5.010;
  1         2  
8 1     1   4 use warnings;
  1         1  
  1         26  
9 1     1   3 use strict;
  1         2  
  1         14  
10 1     1   468 use Moo;
  1         9657  
  1         4  
11 1     1   1323 use YAML::XS qw( Load Dump);
  1         1861  
  1         44  
12 1     1   5 use Path::Tiny;
  1         1  
  1         31  
13 1     1   420 use Try::Tiny;
  1         893  
  1         43  
14 1     1   404 use App::Basis;
  1         2  
  1         766  
15              
16              
17              
18             # ----------------------------------------------------------------------------
19             # the raw config data hash
20             has raw => (
21             is => 'ro',
22             lazy => 1,
23              
24             # builder => '_load',
25             init_arg => undef, # dont allow setting in constructor
26             default => sub { {} },
27             writer => '_set_raw'
28             );
29              
30             has filename => (
31             is => 'ro',
32             required => 0,
33             writer => '_set_filename'
34             );
35              
36             has nostore => (
37             is => 'ro',
38             default => sub {0}
39             );
40              
41             has die_on_error => (
42             is => 'ro',
43             default => sub {0}
44             );
45              
46              
47             has has_data => (
48             is => 'ro',
49             default => sub {0},
50             init_arg => undef, # dont allow setting in constructor
51             writer => '_set_has_data'
52             );
53              
54              
55             has changed => (
56             is => 'rw',
57             default => sub {0},
58             init_arg => undef, # dont allow setting in constructor
59             # writer => '_set_changed'
60             );
61              
62              
63             has error => (
64             is => 'ro',
65             default => sub {undef},
66             init_arg => undef, # dont allow setting in constructor
67             writer => '_set_error'
68             );
69              
70             # ----------------------------------------------------------------------------
71              
72              
73             sub BUILD {
74 3     3 0 16 my $self = shift;
75              
76 3         23 $self->_set_error(undef);
77              
78             # make sure that the we expand home
79 3         16 my $fname = fix_filename( $self->filename );
80              
81 3 50       84 if ( !$fname ) {
82 0   0     0 $fname = $ENV{APP_BASIS_CFG} || fix_filename( "~/." . get_program() . ".cfg" );
83             }
84 3 100 66     71 if ( $fname && -f $fname ) {
85 2         5 $self->_set_filename($fname);
86              
87 2         4 my $config;
88             try {
89 2     2   60 $config = Load( path($fname)->slurp_utf8 );
90             }
91             catch {
92 0     0   0 $self->_set_error(
93             "Could not read/processs config file $fname. $_");
94 2         17 };
95              
96             # if there was a file to read from and we had an issue then we should
97             # report it back to the caller somehow and make sure its seen.
98 2 50       1222 if ( $self->error ) {
99 0 0       0 die $self->error if ( $self->die_on_error );
100 0         0 warn $self->error;
101             }
102              
103             # if we loaded some config
104 2 50       8 if ( keys %$config ) {
105 2         5 $self->_set_has_data(1);
106 2         43 $self->_set_raw($config);
107             }
108             }
109             else {
110 1         4 $self->_set_error("could not establish a config filename");
111 1 50       23 die $self->error if ( $self->die_on_error );
112             }
113             }
114              
115             # ----------------------------------------------------------------------------
116              
117              
118             sub store {
119 3     3 1 5 my $self = shift;
120 3         3 my $filename = shift;
121 3         3 my $need_save = 0;
122 3         2 my $status = 0;
123              
124 3         4 local $YAML::Indent = 4;
125              
126 3         6 $self->_set_error(undef);
127 3 100       11 if ( !$filename ) {
128 2         4 $filename = $self->filename;
129 2 50       7 $need_save = 1 if ( $self->changed );
130             }
131             else {
132 1         2 $need_save = 1;
133             }
134              
135             # only save if we need to
136 3 50       7 if ($need_save) {
137 3 100       9 if ( $self->nostore ) {
138 1         81 warn "Attempt to save config file "
139             . $self->filename
140             . " when nostore has been used";
141 1         5 return 0;
142             }
143              
144             # do the save
145 2         37 my $cfg = $self->raw;
146             try {
147             # do we need to create the directory to hold the file
148 2 50   2   39 if ( !-d path($filename)->dirname ) {
149 0         0 path($filename)->dirname->mkpath;
150             }
151 2         117 path($filename)->spew_utf8( Dump($cfg) );
152             }
153             catch {
154 0     0   0 $self->_set_error(
155             "Could not save config file " . $self->filename() );
156 0         0 $status = 0;
157 2         21 };
158 2 50 33     815 die $self->error if ( $self->error && $self->die_on_error );
159 2         5 $self->changed(0);
160 2         3 $status = 1;
161             }
162              
163 2         3 return $status;
164             }
165              
166             # ----------------------------------------------------------------------------
167             # return a ref to a item in the config or undef
168             # if $value is true then a path will be established and the value stored as the
169             # final node
170              
171             sub _split_path {
172 15     15   17 my $self = shift;
173 15         15 my ( $path, $value ) = @_;
174 15         14 my $done = 0;
175 15         9 my $path_separators = '/:\.';
176              
177             # remove any leading/trailing path separators
178 15         138 $path =~ s|^[$path_separators]?(.*)[$path_separators]?$|$1|;
179              
180 15         253 my $ref = $self->raw;
181 15         103 my @items = split( /[$path_separators]/, $path );
182             try {
183 15     15   284 for ( my $i = 0; $i < scalar(@items); $i++ ) {
184 30         31 my $item = $items[$i];
185 30 100       38 if ( $ref->{$item} ) {
186 20         18 $ref = $ref->{$item};
187 20         39 $done = 1;
188             }
189             else {
190 10 50       16 if ($value) {
191              
192             # is this the last thing?
193 10 100       11 if ( ( $i + 1 ) == scalar(@items) ) {
194              
195             # save the value in the last node
196 5         8 $ref->{$item} = $value;
197             }
198             else {
199 5         15 $ref->{$item} = {};
200             }
201 10         11 $ref = $ref->{$item};
202 10         21 $done = 1;
203             }
204             else {
205              
206             # missed item
207 0         0 $done = 0;
208             }
209             }
210             }
211             }
212 15     0   78 catch {};
213              
214 15 50       155 return $done ? $ref : undef;
215             }
216              
217             # ----------------------------------------------------------------------------
218              
219              
220             sub get {
221 10     10 1 2173 my $self = shift;
222 10         10 my $path = shift;
223              
224 10         18 $self->_set_error(undef);
225              
226 10         12 my $ref = $self->_split_path($path);
227              
228 10         27 return $ref;
229             }
230              
231             # ----------------------------------------------------------------------------
232              
233              
234             sub set {
235 5     5 1 918 my $self = shift;
236 5         7 my ( $path, $value ) = @_;
237              
238 5         13 $self->_set_error(undef);
239              
240             # create the path to the item
241 5         8 my $ref = $self->_split_path( $path, $value );
242              
243             # if we loaded some config
244 5 50       4 $self->_set_has_data(1) if ( keys %{ $self->raw() } );
  5         80  
245              
246             # something has changed so we may need to save it later
247 5         43 $self->changed(1);
248             }
249              
250             # ----------------------------------------------------------------------------
251              
252              
253             sub clear {
254 0     0 1   my $self = shift;
255 0           my ( $path, $value ) = @_;
256              
257 0           $self->_set_error(undef);
258              
259 0           $self->raw = {};
260 0           $self->_set_has_data(0);
261              
262             # something has changed so we may need to save it later
263 0           $self->changed(1);
264             }
265              
266             # ----------------------------------------------------------------------------
267             # make sure we do any cleanup required
268              
269       1     END {
270             }
271              
272              
273             # ----------------------------------------------------------------------------
274              
275             1;
276              
277             __END__