File Coverage

blib/lib/Config/Model/Backend/PlainFile.pm
Criterion Covered Total %
statement 90 98 91.8
branch 26 32 81.2
condition 4 7 57.1
subroutine 15 16 93.7
pod 6 9 66.6
total 141 162 87.0


line stmt bran cond sub pod time code
1             #
2             # This file is part of Config-Model
3             #
4             # This software is Copyright (c) 2005-2022 by Dominique Dumont.
5             #
6             # This is free software, licensed under:
7             #
8             # The GNU Lesser General Public License, Version 2.1, February 1999
9             #
10             package Config::Model::Backend::PlainFile 2.153; # TRIAL
11              
12 3     3   53 use 5.10.1;
  3         14  
13 3     3   30 use Carp;
  3         6  
  3         199  
14 3     3   35 use Mouse;
  3         7  
  3         20  
15 3     3   1637 use Config::Model::Exception;
  3         6  
  3         86  
16 3     3   20 use Path::Tiny;
  3         6  
  3         174  
17 3     3   20 use Log::Log4perl qw(get_logger :levels);
  3         6  
  3         25  
18              
19             extends 'Config::Model::Backend::Any';
20              
21             with "Config::Model::Role::ComputeFunction";
22             with "Config::Model::Role::FileHandler";
23              
24             my $logger = get_logger("Backend::PlainFile");
25              
26 21     21 1 75 sub annotation { return 0; }
27              
28             # remember that a read backend (and its config file(s)) is attached to a node
29             # OTOH, PlainFile backend deal with files that are attached to elements of a node.
30             # Hence the files must not be managed by backend manager.
31              
32             # file not opened by BackendMgr
33             # file_path is undef
34 61     61 0 214 sub skip_open { 1; }
35              
36             sub get_file_name {
37 64     64 0 394 my ($self, %args) = @_;
38              
39 64         246 my $obj = $args{object}->fetch_element( name => $args{elt} );
40 64 100       301 return $args{file} ? $obj->compute_string($args{file}) : $args{elt};
41             }
42              
43             sub read {
44 21     21 1 50 my $self = shift;
45 21         149 my %args = @_;
46              
47             # args are:
48             # object => $obj, # Config::Model::Node object
49             # root => './my_test', # fake root directory, userd for tests
50             # config_dir => /etc/foo', # absolute path
51             # file => 'foo.conf', # file name
52             # file_path => './my_test/etc/foo/foo.conf'
53             # check => yes|no|skip
54              
55 21   50     69 my $check = $args{check} || 'yes';
56 21         38 my $node = $args{object};
57 21         73 $logger->trace( "called on node ", $node->name );
58              
59             # read data from leaf element from the node
60             # do not trigger warp when getting element names
61 21         231 foreach my $elt ( $node->get_element_names(all => 1) ) {
62 42         289 my $obj = $args{object}->fetch_element( name => $elt );
63              
64 42         279 my $file_name = $self->get_file_name(%args, elt => $elt);
65 42         239 my $dir = $self->get_tuned_config_dir(%args);
66 42         2351 my $file = $dir->child($file_name);
67              
68 42         1612 $logger->trace("looking for plainfile $file for ", $obj->location);
69              
70 42         422 my $type = $obj->get_type;
71              
72 42 100       157 if ( $type eq 'leaf' ) {
    50          
    0          
73 23         100 $self->read_leaf( $obj, $elt, $check, $file, \%args );
74             }
75             elsif ( $type eq 'list' ) {
76 19         67 $self->read_list( $obj, $elt, $check, $file, \%args );
77             }
78             elsif ( $type eq 'hash' ) {
79 0         0 $self->read_hash( $obj, $elt, $check, $file, \%args );
80             }
81             else {
82 0         0 $logger->debug("PlainFile read skipped $type $elt");
83             }
84              
85             }
86              
87 21         709 return 1;
88             }
89              
90             sub read_leaf {
91 23     23 1 82 my ( $self, $obj, $elt, $check, $file, $args ) = @_;
92              
93 23 100       76 return unless $file->exists;
94              
95 4         101 my $v = $file->slurp_utf8;
96 4 50       1994 chomp($v) unless $obj->value_type eq 'string';
97 4 50       18 if ($logger->is_trace) {
98 0         0 (my $str = $v) =~ s/\n.*/[...]/s;
99 0         0 $logger->trace("storing leaf value '$str' from $file ");
100             }
101 4         58 $obj->store( value => $v, check => $check );
102             }
103              
104             sub read_list {
105 19     19 1 118 my ( $self, $obj, $elt, $check, $file, $args ) = @_;
106              
107 19 100       79 return unless $file->exists;
108              
109 18         564 my @v = $file->lines_utf8({ chomp => 1});
110 18         4967 $logger->trace("storing list value @v from $file ");
111              
112 18         368 $obj->store_set(@v);
113             }
114              
115             sub read_hash {
116 0     0 1 0 my ( $self, $obj, $elt, $check, $file, $args ) = @_;
117 0         0 $logger->debug("PlainFile read skipped hash $elt");
118             }
119              
120             sub write {
121 10     10 1 33 my $self = shift;
122 10         60 my %args = @_;
123              
124             # args are:
125             # object => $obj, # Config::Model::Node object
126             # root => './my_test', # fake root directory, userd for tests
127             # config_dir => /etc/foo', # absolute path read
128             # file => 'foo.conf', # file name
129             # file_path => './my_test/etc/foo/foo.conf'
130             # check => yes|no|skip
131              
132 10   50     67 my $check = $args{check} || 'yes';
133 10         26 my $cfg_dir = $args{config_dir};
134 10         54 my $dir = $self->get_tuned_config_dir(%args);
135 10 100       572 $dir->mkpath({ mode => oct(755) } ) unless $dir->is_dir;
136              
137 10         447 my $node = $args{object};
138 10         52 $logger->debug( "PlainFile write called on node ", $node->name );
139              
140             # write data from leaf element from the node
141 10         118 foreach my $elt ( $node->get_element_name() ) {
142 20         490 my $obj = $args{object}->fetch_element( name => $elt );
143              
144 20         135 my $file_name = $self->get_file_name(%args, elt => $elt);
145 20         87 my $file = $dir->child($file_name);
146              
147 20         978 my $type = $obj->get_type;
148 20         40 my @v;
149              
150 20 100       83 if ( $type eq 'leaf' ) {
    50          
151 11         61 my $v = $obj->fetch( check => $args{check} );
152 11 100 66     72 $v .= "\n" if defined $v and $obj->value_type ne 'string';
153 11 100       38 push @v, $v if defined $v;
154             }
155             elsif ( $type eq 'list' ) {
156 9         38 @v = map { "$_\n" } $obj->fetch_all_values;
  21         69  
157             }
158             else {
159 0         0 $logger->debug("PlainFile write skipped $type $elt");
160 0         0 next;
161             }
162              
163 20 100       88 if (@v) {
    100          
164 11         53 $logger->trace("PlainFile write opening $file to write $elt");
165 11         207 $file->spew_utf8(@v);
166 11 100       7855 $file->chmod($args{file_mode}) if $args{file_mode};
167             }
168             elsif ($file->exists) {
169 1         41 $logger->trace("PlainFile delete $file");
170 1         28 $file->remove;
171             }
172             }
173              
174 10         212 return 1;
175             }
176              
177             sub delete {
178 1     1 0 5 my $self = shift;
179 1         6 my %args = @_;
180              
181             # args are:
182             # object => $obj, # Config::Model::Node object
183             # root => './my_test', # fake root directory, userd for tests
184             # config_dir => /etc/foo', # absolute path read
185             # file => 'foo.conf', # file name
186             # file_path => './my_test/etc/foo/foo.conf'
187             # check => yes|no|skip
188              
189 1         9 my $dir = $self->get_tuned_config_dir(%args);
190 1         57 my $node = $args{object};
191 1         5 $logger->debug( "PlainFile delete called on deleted node");
192              
193             # write data from leaf element from the node
194 1         17 foreach my $elt ( $node->get_element_name() ) {
195 2         104 my $obj = $node->fetch_element( name => $elt );
196              
197 2         19 my $file_name = $self->get_file_name(%args, elt => $elt);
198 2         11 my $file = $dir->child( $file_name );
199 2         91 $logger->info( "Removing $file (deleted node)" );
200 2         30 $file->remove;
201             }
202             }
203              
204 3     3   4261 no Mouse;
  3         31  
  3         58  
205             __PACKAGE__->meta->make_immutable;
206              
207             1;
208              
209             # ABSTRACT: Read and write config as plain file
210              
211             __END__
212              
213             =pod
214              
215             =encoding UTF-8
216              
217             =head1 NAME
218              
219             Config::Model::Backend::PlainFile - Read and write config as plain file
220              
221             =head1 VERSION
222              
223             version 2.153
224              
225             =head1 SYNOPSIS
226              
227             use Config::Model;
228              
229             my $model = Config::Model->new;
230              
231             my $inst = $model->create_config_class(
232             name => "WithPlainFile",
233             element => [
234             [qw/source new/] => { qw/type leaf value_type uniline/ },
235             ],
236             rw_config => {
237             backend => 'plain_file',
238             config_dir => '/tmp',
239             },
240             );
241            
242             my $inst = $model->instance(root_class_name => 'WithPlainFile' );
243             my $root = $inst->config_root ;
244              
245             $root->load('source=foo new=yes' );
246              
247             $inst->write_back ;
248              
249             Now C</tmp> directory contains 2 files: C<source> and C<new>
250             with C<foo> and C<yes> inside.
251              
252             =head1 DESCRIPTION
253              
254             This module is used directly by L<Config::Model> to read or write the
255             content of a configuration tree written in several files.
256             Each element of the node is written in a plain file.
257              
258             =head1 Element type and file mapping
259              
260             Element values are written in one or several files depending on their type.
261              
262             =over
263              
264             =item leaf
265              
266             The leaf value is written in one file. This file can have several lines if the leaf
267             type is C<string>
268              
269             =item list
270              
271             The list content is written in one file. Each line of the file is a
272             value of the list.
273              
274             =item hash
275              
276             Not supported
277              
278             =back
279              
280             =head1 File mapping
281              
282             By default, the configuration file is named after the element name
283             (like in synopsis above).
284              
285             The C<file> parameter can also be used to specify a file name that
286             take into account the path in the tree using C<&index()> and
287             C<&element()> functions from L<Config::Model::Role::ComputeFunction>.
288              
289             For instance, with the following model:
290              
291             class_name => "Foo",
292             element => [
293             string_a => { type => 'leaf', value_type => 'string'}
294             string_b => { type => 'leaf', value_type => 'string'}
295             ],
296             rw_config => {
297             backend => 'PlainFile',
298             config_dir => 'foo',
299             file => '&element(-).&element',
300             file_mode => 0644, # optional
301             }
302              
303             If the configuration is loaded with C<example string_a=something
304             string_b=else>, this backend writes "C<something>" in file
305             C<example.string_a> and C<else> in file C<example.string_b>.
306              
307             C<file_mode> parameter can be used to set the mode of the written
308             file. C<file_mode> value can be in any form supported by
309             L<Path::Tiny/chmod>.
310              
311             =head1 Methods
312              
313             =head2 read_leaf
314              
315             Parameters: C<(obj, elt, check, file, args)>
316              
317             Called by L<read> method to read the file of a leaf element. C<args>
318             contains the arguments passed to L<read> method.
319              
320             =head2 read_hash (obj,elt,check,file,args);
321              
322             Like L<read_leaf> for hash elements.
323              
324             =head2 read_list
325              
326             Parameters: C<(obj, elt, check, file, args)>
327              
328             Like L<read_leaf> for list elements.
329              
330             =head2 write
331              
332             C<write> writes a file for each element of the calling class. Works only for
333             leaf and list elements. Other element type are skipped. Always return 1 (unless it died before).
334              
335             =head1 AUTHOR
336              
337             Dominique Dumont, (ddumont at cpan dot org)
338              
339             =head1 SEE ALSO
340              
341             L<Config::Model>,
342             L<Config::Model::BackendMgr>,
343             L<Config::Model::Backend::Any>,
344              
345             =head1 AUTHOR
346              
347             Dominique Dumont
348              
349             =head1 COPYRIGHT AND LICENSE
350              
351             This software is Copyright (c) 2005-2022 by Dominique Dumont.
352              
353             This is free software, licensed under:
354              
355             The GNU Lesser General Public License, Version 2.1, February 1999
356              
357             =cut