File Coverage

blib/lib/Config/Model/Backend/IniFile.pm
Criterion Covered Total %
statement 230 236 97.4
branch 67 76 88.1
condition 55 76 72.3
subroutine 21 21 100.0
pod 3 4 75.0
total 376 413 91.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::IniFile 2.153; # TRIAL
11              
12 5     5   38 use Carp;
  5         24  
  5         449  
13 5     5   39 use Mouse;
  5         13  
  5         46  
14 5     5   3013 use 5.10.0;
  5         23  
15 5     5   32 use Config::Model::Exception;
  5         13  
  5         147  
16 5     5   26 use File::Path;
  5         21  
  5         369  
17 5     5   47 use Log::Log4perl qw(get_logger :levels);
  5         13  
  5         63  
18              
19 5     5   873 use base qw/Config::Model::Backend::Any/;
  5         12  
  5         2435  
20              
21 5     5   50 use feature qw/postderef signatures/;
  5         13  
  5         441  
22 5     5   34 no warnings qw/experimental::postderef experimental::signatures/;
  5         12  
  5         16163  
23              
24             # change inherited attribute. See Moose::Manual::Attributes
25             has '+node' => (
26             handles => ['load_data'],
27             );
28              
29             my $logger = get_logger("Backend::IniFile");
30              
31 44     44 1 143 sub annotation { return 1; }
32              
33             ## no critic (Subroutines::ProhibitBuiltinHomonyms)
34 44     44 1 109 sub read ($self, %args) {
  44         93  
  44         364  
  44         103  
35             # args is:
36             # object => $obj, # Config::Model::Node object
37             # root => './my_test', # fake root directory, userd for tests
38             # config_dir => /etc/foo', # absolute path
39             # file => 'foo.conf', # file name
40             # file_path => './my_test/etc/foo/foo.conf'
41             # check => yes|no|skip
42              
43 44 100       215 return 0 unless $args{file_path}->exists; # no file to read
44              
45 41         967 my $section = '<top>'; # dumb value used for logging
46              
47 41   100     261 my $delimiter = $args{comment_delimiter} || '#';
48 41   100     224 my $hash_class = $args{store_class_in_hash} || '';
49 41   100     193 my $section_map = $args{section_map} || {};
50 41         96 my $split_reg = $args{split_list_value};
51 41   100     162 my $check = $args{check} || 'yes';
52 41   100     213 my $assign_char = $args{assign_char} || '=';
53 41   100     192 my $quote_value = $args{quote_value} || '';
54 41         163 my $obj = $self->node;
55              
56 41         93 my %force_lc;
57 41 100       106 map { $force_lc{$_} = $args{"force_lc_$_"} ? 1 : 0; } qw/section key value/;
  123         495  
58              
59             #FIXME: Is it possible to store the comments with their location
60             #in the file? It would be nice if comments that are after values
61             #in input file, would be written in the same way in the output
62             #file. Also, comments at the end of file are being ignored now.
63              
64 41         176 my @lines = $args{file_path}->lines_utf8;
65              
66             # try to get global comments (comments before a blank line)
67 41         9850 $self->read_global_comments( \@lines, $delimiter );
68              
69 41         245 my @assoc = $self->associates_comments_with_data( \@lines, $delimiter );
70              
71             # store INI data in a structure:
72             # {
73             # name => value leaf
74             # name => [ value ] list
75             # name => { key => value , ... } hash
76             # name => { ... } node
77             # name => [ { ... }, ... ] list of nodes
78             # name => { key => { ... } , ... } hash of nodes
79             # }
80              
81 41         131 my $ini_data = {};
82 41         111 my %ini_comment;
83 41         106 my $section_ref = $ini_data;
84 41         95 my $section_path = '';
85              
86 41         104 foreach my $item (@assoc) {
87 318         616 my ( $vdata, $comment ) = @$item;
88 318         1125 $logger->debug("ini read: reading '$vdata'");
89 318         2259 my $comment_path;
90              
91             # Update section name
92 318 100       970 if ( $vdata =~ /^\s*\[(.*)\]/ ) {
93 57 100       283 $section = $force_lc{section} ? lc($1) : $1;
94 57   100     322 my $remap = $section_map->{$section} || '';
95 57 100       244 if ( $remap eq '!' ) {
    100          
    100          
96             # section_map maps section to root node
97 13         31 $section_ref = $ini_data;
98 13         38 $comment_path = $section_path = '';
99 13         64 $logger->debug("step 1: found node <top> [$section]");
100             }
101             elsif ($remap) {
102             # section_map maps section to some node
103 2         7 $section_ref = {};
104 2         14 $logger->debug("step 1: found node $remap [$section]");
105 2         16 $section_path = $comment_path =
106             $self->set_or_push( $ini_data, $remap, $section_ref );
107             }
108             elsif ($hash_class) {
109 28         114 $ini_data->{$hash_class}{$section} = $section_ref = {};
110 28         113 $comment_path = $section_path = "$hash_class:$section";
111 28         137 $logger->debug("step 1: found node $hash_class and path $comment_path [$section]");
112             }
113             else {
114 14         47 $section_ref = {};
115 14         85 $logger->debug("step 1: found node $section [$section]");
116 14         161 $section_path = $comment_path =
117             $self->set_or_push( $ini_data, $section, $section_ref );
118             }
119              
120             # for write later, need to store the obj if section map was used
121 57 100       473 if ( defined $section_map->{$section} ) {
122 15         87 $logger->debug("store section_map loc '$section_path' section '$section'");
123 15         150 $self->{reverse_section_map}{$section_path} = $section;
124             }
125             }
126             else {
127 261         1732 my ( $name, $val ) = split( /\s*$assign_char\s*/, $vdata, 2 );
128 261 100       824 $name = lc($name) if $force_lc{key};
129 261 50       561 $val = lc($val) if $force_lc{value};
130 261 100       581 $val =~ s/"([^"]*)"/$1/g if $quote_value eq "shell_style";
131 261         631 $comment_path = $section_path . ' ' . $self->set_or_push( $section_ref, $name, $val );
132 261         1118 $logger->debug("step 1: found node $comment_path name $name in [$section]");
133             }
134              
135 318 100       2599 $ini_comment{$comment_path} = $comment if $comment;
136             }
137              
138 41         214 my @load_args = ( data => $ini_data, check => $check );
139 41 100       232 push @load_args, split_reg => qr/$split_reg/ if $split_reg;
140 41         246 $self->load_data(@load_args);
141              
142 41         277 while ( my ( $k, $v ) = each %ini_comment ) {
143 113 100       435 my $item = $obj->grab( step => $k, mode => 'loose' ) or next;
144 106 100       434 $item = $item->fetch_with_id(0) if $item->get_type eq 'list';
145 106         583 $logger->debug("annotate '$v' on ", $item->location);
146 106         988 $item->annotation($v);
147             }
148              
149 41         820 return 1;
150             }
151              
152             sub set_or_push {
153 277     277 0 593 my ( $self, $ref, $name, $val ) = @_;
154 277         547 my $cell = $ref->{$name};
155 277         394 my $path;
156 277 100 100     812 if ( defined $cell and ref($cell) eq 'ARRAY' ) {
    100          
157 13         38 push @$cell, $val;
158 13         60 $path = $name . ':' . $#$cell;
159             }
160             elsif ( defined $cell ) {
161 27         91 $ref->{$name} = [ $cell, $val ];
162 27         85 $path = $name . ':1';
163             }
164             else {
165 237         619 $ref->{$name} = $val;
166 237         399 $path = $name; # no way to distinguish between leaf and first value of list
167             }
168 277         634 return $path;
169             }
170              
171             ## no critic (Subroutines::ProhibitBuiltinHomonyms)
172 21     21 1 52 sub write ($self, %args) {
  21         47  
  21         105  
  21         46  
173             # args is:
174             # object => $obj, # Config::Model::Node object
175             # root => './my_test', # fake root directory, userd for tests
176             # config_dir => /etc/foo', # absolute path
177             # file => 'foo.conf', # file name
178             # file_path => './my_test/etc/foo/foo.conf'
179             # check => yes|no|skip
180              
181 21         68 my $node = $args{object};
182 21   100     131 my $delimiter = $args{comment_delimiter} || '#';
183              
184 21 50       95 croak "Undefined file handle to write" unless defined $args{file_path};
185              
186             # use the first char of the list as a comment delimeter
187 21         81 my $cc = substr($delimiter,0,1);
188 21         55 $args{comment_delimiter} = $cc;
189              
190 21         71 my $res = '';
191              
192             # some INI file have a 'General' section mapped in root node
193 21         98 my $top_class_name = $self->{reverse_section_map}{''};
194 21 100       63 if ( defined $top_class_name ) {
195 4         33 $logger->debug("writing class $top_class_name from reverse_section_map");
196 4         62 $res .= $self->write_data_and_comments( $cc, "[$top_class_name]" );
197             }
198              
199 21         167 $res .= $self->_write(%args);
200 21 100       141 if ($res) {
    50          
201 19         153 $args{file_path}->spew_utf8($self->write_global_comment( $cc ) . $res);
202             }
203             elsif ($self->auto_delete) {
204 2         11 $args{file_path}->remove;
205             }
206 21         14639 return;
207             }
208              
209             sub _write_list{
210 72     72   188 my ($self, $args, $node, $elt) = @_ ;
211 72         124 my $res = '';
212              
213 72         141 my $join_list = $args->{join_list_value};
214 72   50     197 my $delimiter = $args->{comment_delimiter} || '#';
215 72   33     336 my $assign_with = $args->{assign_with} // $args->{assign_char} // ' = ';
      50        
216 72         207 my $list_obj = $node->fetch_element($elt);
217              
218 72         223 my $list_obj_note = $list_obj->annotation;
219              
220 72 100       193 if ( $join_list ) {
221 28         85 my @v = grep { length } $list_obj->fetch_all_values();
  17         46  
222 28         92 my $v = join( $join_list, @v );
223 28 100       82 if ( length($v) ) {
224 6         34 $logger->debug("writing joined list elt $elt -> $v");
225 6         76 $res .= $self->write_data_and_comments( $delimiter, "$elt$assign_with$v", $list_obj_note );
226             }
227             }
228             else {
229 44         151 foreach my $obj ( $list_obj->fetch_all('custom') ) {
230 36         128 my $note = $obj->annotation;
231 36         118 my $v = $self->_fetch_obj_value($args, $obj);
232 36 50       105 if ( length $v ) {
233 36         167 $logger->debug("writing list elt $elt -> $v");
234 36         382 $res .=
235             $self->write_data_and_comments( $delimiter, "$elt$assign_with$v",
236             $list_obj_note . $note );
237             }
238             else {
239 0         0 $logger->trace("NOT writing undef or empty list elt");
240             }
241             }
242             }
243 72         257 return $res;
244             }
245              
246             sub _write_check_list{
247 4     4   11 my ($self, $args, $node, $elt) = @_ ;
248 4         8 my $res = '';
249              
250 4         9 my $join_check_list = $args->{join_check_list_value};
251 4   50     11 my $delimiter = $args->{comment_delimiter} || '#';
252 4   33     22 my $assign_with = $args->{assign_with} // $args->{assign_char} // ' = ';
      50        
253 4         14 my $obj = $node->fetch_element($elt);
254              
255 4         14 my $obj_note = $obj->annotation;
256              
257 4 50       14 if ($join_check_list ) {
258 0         0 my $v = join( $join_check_list, $obj->get_checked_list() );
259 0 0       0 if ( length($v) ) {
260 0         0 $logger->debug("writing check_list elt $elt -> $v");
261 0         0 $res .= $self->write_data_and_comments( $delimiter, "$elt$assign_with$v", $obj_note );
262             }
263             }
264             else {
265 4         13 foreach my $v ( $obj->get_checked_list() ) {
266 7         33 $logger->debug("writing joined check_list elt $elt -> $v");
267 7         68 $res .= $self->write_data_and_comments( $delimiter, "$elt$assign_with$v", $obj_note );
268             }
269             }
270 4         15 return $res;
271             }
272              
273             sub _fetch_obj_value {
274 126     126   254 my ($self, $args, $obj) = @_ ;
275 126         348 my $v = $obj->fetch;
276 126 50 66     476 if ( defined $args->{quote_value}
      66        
      33        
277             and $args->{quote_value} eq 'shell_style'
278             and defined $v
279             and $v =~ /\s/
280             ) {
281 3         11 $v = qq!"$v"!;
282             }
283 126         293 return $v;
284             }
285              
286             sub _write_leaf {
287 90     90   207 my ($self, $args, $node, $elt) = @_ ;
288 90         148 my $res = '';
289              
290 90         197 my $write_bool_as = $args->{write_boolean_as};
291 90   50     251 my $delimiter = $args->{comment_delimiter} || '#';
292 90   66     448 my $assign_with = $args->{assign_with} // $args->{assign_char} // ' = ';
      50        
293 90         268 my $obj = $node->fetch_element($elt);
294              
295 90         281 my $obj_note = $obj->annotation;
296              
297 90         311 my $v = $self->_fetch_obj_value($args, $obj);
298 90 100 100     475 if ( $write_bool_as and defined($v) and length($v) and $obj->value_type eq 'boolean' ) {
      66        
      100        
299 1         4 $v = $write_bool_as->[$v];
300             }
301 90 100 66     308 if ( defined $v and length $v ) {
302 35         190 $logger->debug("writing leaf elt $elt -> $v");
303 35         412 $res .= $self->write_data_and_comments( $delimiter, "$elt$assign_with$v", $obj_note );
304             }
305             else {
306 55         146 $logger->trace("NOT writing undef or empty leaf elt");
307             }
308 90         668 return $res;
309             }
310              
311             sub _write_hash {
312 6     6   22 my ($self, $args, $node, $elt) = @_ ;
313 6         15 my $res = '';
314              
315 6   50     26 my $delimiter = $args->{comment_delimiter} || '#';
316 6         24 my $obj = $node->fetch_element($elt);
317 6         41 my $obj_note = $obj->annotation;
318              
319 6         36 foreach my $key ( $obj->fetch_all_indexes ) {
320 15         64 my $hash_obj = $obj->fetch_with_id($key);
321 15         66 my $note = $hash_obj->annotation;
322 15         85 $logger->debug("writing hash elt $elt key $key");
323 15         202 my $subres = $self->_write( %$args, object => $hash_obj );
324 15 100       66 if ($subres) {
325 8         52 $res .= "\n"
326             . $self->write_data_and_comments( $delimiter, "[$key]",
327             $obj_note . $note )
328             . $subres;
329             }
330             }
331 6         43 return $res;
332             }
333              
334             sub _write_node {
335 16     16   46 my ($self, $args, $node, $elt) = @_ ;
336 16         32 my $res = '';
337              
338 16   50     53 my $delimiter = $args->{comment_delimiter} || '#';
339 16         46 my $obj = $node->fetch_element($elt);
340 16         62 my $obj_note = $obj->annotation;
341              
342 16         88 $logger->debug("writing class $elt");
343 16         199 my $subres = $self->_write( %$args, object => $obj );
344 16 100       60 if ($subres) {
345              
346             # some INI file may have a section mapped to a node as exception to mapped in a hash
347 7         31 my $exception_name = $self->{reverse_section_map}{ $obj->location };
348 7 100       21 if ( defined $exception_name ) {
349 1         5 $logger->debug("writing class $exception_name from reverse_section_map");
350             }
351 7   66     54 my $c_name = $exception_name || $elt;
352 7         43 $res .= "\n"
353             . $self->write_data_and_comments( $delimiter, "[$c_name]", $obj_note )
354             . $subres;
355             }
356 16         64 return $res;
357             }
358              
359 52     52   89 sub _write ($self, %args) {
  52         99  
  52         275  
  52         89  
360 52         99 my $node = $args{object};
361 52   50     153 my $delimiter = $args{comment_delimiter} || '#';
362              
363 52         168 $logger->trace( "called on ", $node->name );
364 52         482 my $res = '';
365              
366             # Using Config::Model::ObjTreeScanner would be overkill
367             # first write list and element, then classes
368 52         164 foreach my $elt ( $node->get_element_name ) {
369 188         622 my $type = $node->element_type($elt);
370 188         738 $logger->trace("first loop on elt $elt type $type");
371 188 100 100     2043 next if $type =~ /node/ or $type eq 'hash';
372              
373 166 100       589 if ( $type eq 'list' ) {
    100          
    50          
374 72         231 $res .= $self->_write_list (\%args, $node, $elt) ;
375             }
376             elsif ( $type eq 'check_list') {
377 4         14 $res .= $self->_write_check_list (\%args, $node, $elt) ;
378             }
379             elsif ( $type eq 'leaf' ) {
380 90         276 $res .= $self->_write_leaf (\%args, $node, $elt) ;
381             }
382             else {
383 0         0 Config::Model::Exception::Model->throw(
384             error => "unexpected type $type for leaf elt $elt",
385             object => $node
386             );
387             }
388             }
389              
390 52         224 foreach my $elt ( $node->get_element_name ) {
391 188         456 my $type = $node->element_type($elt);
392 188         654 $logger->trace("second loop on elt $elt type $type");
393 188 100 100     1742 next unless $type =~ /node/ or $type eq 'hash';
394 22         89 my $obj = $node->fetch_element($elt);
395              
396 22         89 my $obj_note = $obj->annotation;
397              
398 22 100       93 if ( $type eq 'hash' ) {
399 6         73 $res .= $self->_write_hash (\%args, $node, $elt) ;
400             }
401             else {
402 16         71 $res .= $self->_write_node (\%args, $node, $elt) ;
403             }
404             }
405              
406 52         203 $logger->trace( "done on ", $node->name );
407              
408 52         517 return $res;
409             }
410              
411 5     5   50 no Mouse;
  5         22  
  5         55  
412             __PACKAGE__->meta->make_immutable;
413              
414             1;
415              
416             # ABSTRACT: Read and write config as a INI file
417              
418             __END__
419              
420             =pod
421              
422             =encoding UTF-8
423              
424             =head1 NAME
425              
426             Config::Model::Backend::IniFile - Read and write config as a INI file
427              
428             =head1 VERSION
429              
430             version 2.153
431              
432             =head1 SYNOPSIS
433              
434             use Config::Model;
435              
436             my $model = Config::Model->new;
437             $model->create_config_class (
438             name => "IniClass",
439             element => [
440             [qw/foo bar/] => {
441             type => 'list',
442             cargo => {qw/type leaf value_type string/}
443             }
444             ]
445             );
446              
447             # model for free INI class name and constrained class parameters
448             $model->create_config_class(
449             name => "MyClass",
450              
451             element => [
452             ini_class => {
453             type => 'hash',
454             index_type => 'string',
455             cargo => {
456             type => 'node',
457             config_class_name => 'IniClass'
458             },
459             },
460             ],
461              
462             rw_config => {
463             backend => 'IniFile',
464             config_dir => '/tmp',
465             file => 'foo.conf',
466             store_class_in_hash => 'ini_class',
467             auto_create => 1,
468             }
469             );
470              
471             my $inst = $model->instance(root_class_name => 'MyClass' );
472             my $root = $inst->config_root ;
473              
474             $root->load('ini_class:ONE foo=FOO1 bar=BAR1 -
475             ini_class:TWO foo=FOO2' );
476              
477             $inst->write_back ;
478              
479             Now C</tmp/foo.conf> contains:
480              
481             ## file written by Config::Model
482             [ONE]
483             foo=FOO1
484              
485             bar=BAR1
486              
487             [TWO]
488             foo=FOO2
489              
490             =head1 DESCRIPTION
491              
492             This module is used directly by L<Config::Model> to read or write the
493             content of a configuration tree written with INI syntax in
494             C<Config::Model> configuration tree.
495              
496             This INI file can have arbitrary comment delimiter. See the example
497             in the SYNOPSIS that sets a semi-column as comment delimiter.
498             By default the comment delimiter is '#' like in Shell or Perl.
499              
500             Note that undefined values are skipped for list element. I.e. when a
501             list element contains C<('a',undef,'b')>, the data structure
502             contains C<'a','b'>.
503              
504             =head1 Limitations
505              
506             =head2 Structure
507              
508             Structure of the Config::Model must be very simple. Either:
509              
510             =over
511              
512             =item *
513              
514             A single class with hash of leaves elements.
515              
516             =item *
517              
518             2 levels of classes. The top level has nodes elements. All other
519             classes have only leaf elements.
520              
521             =back
522              
523             =head1 Comments in Ini file
524              
525             This backend tries to read and write comments from configuration
526             file. The comments are stored as annotation within the configuration
527             tree. Comments extraction is based on best estimation as to which
528             parameter the comment may apply. Wrong estimations are possible.
529              
530             =head1 CONSTRUCTOR
531              
532             =head2 new
533              
534             Parameters: C<< ( node => $node_obj, name => 'inifile' ) >>
535              
536             Inherited from L<Config::Model::Backend::Any>. The constructor is
537             called by L<Config::Model::BackendMgr>.
538              
539             =head1 Parameters
540              
541             Optional parameters declared in the model:
542              
543             =head2 comment_delimiter
544              
545             Change the character that starts comments in the INI file. Default is 'C<#>'.
546              
547             Some Ini files allows comments to begin with several characters
548             (e.g. C<#> or C<;>). In this case, set C<comment_delimiter> to the
549             possible characters (e.g "C<#;>"). The first character is used to
550             write back comments. (In the example above, comment C<; blah> is
551             written back as C<# blah>.
552              
553             =head2 store_class_in_hash
554              
555             See L</"Arbitrary class name">
556              
557             =head2 section_map
558              
559             Is a kind of exception of the above rule. See also L</"Arbitrary class name">
560              
561             =head2 force_lc_section
562              
563             Boolean. When set, sections names are converted to lowercase.
564              
565             =head2 force_lc_key
566              
567             Idem for key name
568              
569             =head2 force_lc_value
570              
571             Idem for all values.
572              
573             =head2 split_list_value
574              
575             Some INI values are in fact a list of items separated by a space or a comma.
576             This parameter specifies the regex to use to split the value into a list. This
577             applies only to C<list> elements.
578              
579             =head2 join_list_value
580              
581             Conversely, the list element split with C<split_list_value> needs to be written
582             back with a string to join them. Specify this string (usually ' ' or ', ')
583             with C<join_list_value>.
584              
585             =head2 split_check_list_value
586              
587             Some INI values are in fact a check list of items separated by a space or a comma.
588             This parameter specifies the regex to use to split the value read from the file
589             into a list of items to check. This applies only to C<check_list> elements.
590              
591             =head2 join_check_list_value
592              
593             Conversely, the check_list element split with C<split_list_value> needs to be written
594             back with a string to join them. Specify this string (usually ' ' or ', ')
595             with C<join_check_list_value>.
596              
597             =head2 write_boolean_as
598              
599             Array ref. Reserved for boolean value. Specify how to write a boolean value.
600             Default is C<[0,1]> which may not be the most readable. C<write_boolean_as> can be
601             specified as C<['false','true']> or C<['no','yes']>.
602              
603             =head2 assign_char
604              
605             Character used to assign value in INI file. Default is C<=>.
606              
607             =head2 assign_with
608              
609             String used write assignment in INI file. Default is "C< = >".
610              
611             =head2 quote_value
612              
613             How to quote value in INI file. Currrently only C<shell_style> is
614             supported for C<quote_value>.
615              
616             E.g. INI backend declaration can contain this parameter:
617              
618             quote_value => 'shell_style'
619              
620             Here are some example of quoted values. The 3 columns shows the
621             original value in file, how it's stored internally and how it's
622             written back:
623              
624             # read => shown => write
625             "foo" => foo => "foo"
626             "foo bar" => foo bar => "foo bar"
627             "20"x"4" => 20x4 => "20x4"
628              
629             =head1 Mapping between INI structure and model
630              
631             INI file typically have the same structure with 2 different conventions.
632             The class names can be imposed by the application or may be chosen by user.
633              
634             =head2 Imposed class name
635              
636             In this case, the class names must match what is expected by the application.
637             The elements of each class can be different. For instance:
638              
639             foo = foo_v
640             [ A ]
641             bar = bar_v
642             [ B ]
643             baz = baz_v
644              
645             In this case, class C<A> and class C<B> do not use the same configuration class.
646              
647             The model has this structure:
648              
649             Root class
650             |- leaf element foo
651             |- node element A of class_A
652             | \- leaf element bar
653             \- node element B of class_B
654             \- leaf element baz
655              
656             =head2 Arbitrary class name
657              
658             In this case, the class names can be chosen by the end user. Each class has the same
659             elements. For instance:
660              
661             foo = foo_v
662             [ A ]
663             bar = bar_v1
664             [ B ]
665             bar = bar_v2
666              
667             In this case, class C<A> and class C<B> do not use the same configuration class.
668             The model has this structure:
669              
670             Root class
671             |- leaf foo
672             \- hash element my_class_holder
673             |- key A (value is node of class_A)
674             | \- element-bar
675             \- key B (value is node of class_A)
676             \- element-bar
677              
678             In this case, the C<my_class_holder> name is specified in C<rw_config> with C<store_class_in_hash>
679             parameter:
680              
681             rw_config => {
682             backend => 'IniFile',
683             config_dir => '/tmp',
684             file => 'foo.ini',
685             store_class_in_hash => 'my_class_holder',
686             }
687              
688             Of course they are exceptions. For instance, in C<Multistrap>, the C<[General]>
689             INI class must be mapped to a specific node object. This can be specified
690             with the C<section_map> parameter:
691              
692             rw_config => }
693             backend => 'IniFile',
694             config_dir => '/tmp',
695             file => 'foo.ini',
696             store_class_in_hash => 'my_class_holder',
697             section_map => {
698             General => 'general_node',
699             }
700             }
701              
702             C<section_map> can also map an INI class to the root node:
703              
704             rw_config => {
705             backend => 'ini_file',
706             store_class_in_hash => 'sections',
707             section_map => {
708             General => '!'
709             },
710             }
711              
712             =head1 Handle key value files
713              
714             This backend is able to handle simple configuration files where the
715             values are written as key value pairs like:
716              
717             foo = bar
718              
719             or
720              
721             foo: bar
722              
723             The option C<assign_char> is used to specify which character is used
724             to assign a value in the file (white spaces are ignored).
725             C<assign_char> is "C<=>" (the default) in the first example, and
726             "C<:>" in the second.
727              
728             The C<assign_with> is used to control how the file is written back. E.g:
729              
730             foo=bar # the default
731             foo= bar # assign_with is "= "
732             foo = bar # assign_with is " = "
733             foo:bar # assign_char is ':', assign_with is the default
734             foo: bar # assign_char is ':', assign_with is ": "
735             foo : bar # assign_char is ':', assign_with is " : "
736              
737             =head1 Methods
738              
739             =head2 read
740              
741             Of all parameters passed to this read call-back, only C<file_path> is
742             used. This parameter must be L<Path::Tiny> object.
743              
744             It can also be undef. In this case, C<read> returns 0.
745              
746             When a file is read, C<read> returns 1.
747              
748             =head2 write
749              
750             Of all parameters passed to this write call-back, only C<file_path> is
751             used. This parameter must be a L<Path::Tiny> object.
752              
753             C<write> returns 1.
754              
755             =head1 AUTHOR
756              
757             Dominique Dumont, (ddumont at cpan dot org);
758             Krzysztof Tyszecki, (krzysztof.tyszecki at gmail dot com)
759              
760             =head1 SEE ALSO
761              
762             L<Config::Model>,
763             L<Config::Model::BackendMgr>,
764             L<Config::Model::Backend::Any>,
765              
766             =head1 AUTHOR
767              
768             Dominique Dumont
769              
770             =head1 COPYRIGHT AND LICENSE
771              
772             This software is Copyright (c) 2005-2022 by Dominique Dumont.
773              
774             This is free software, licensed under:
775              
776             The GNU Lesser General Public License, Version 2.1, February 1999
777              
778             =cut