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