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