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   30 use Mouse;
  5         8  
  5         288  
13 5     5   26 use 5.10.0;
  5         8  
  5         34  
14 5     5   2430 use Config::Model::Exception;
  5         16  
15 5     5   25 use File::Path;
  5         11  
  5         116  
16 5     5   28 use Log::Log4perl qw(get_logger :levels);
  5         22  
  5         249  
17 5     5   34  
  5         25  
  5         34  
18             use base qw/Config::Model::Backend::Any/;
19 5     5   560  
  5         11  
  5         1712  
20             use feature qw/postderef signatures/;
21 5     5   32 no warnings qw/experimental::postderef experimental::signatures/;
  5         11  
  5         305  
22 5     5   27  
  5         9  
  5         11711  
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 129 ## no critic (Subroutines::ProhibitBuiltinHomonyms)
32             # args is:
33             # object => $obj, # Config::Model::Node object
34 44     44 1 93 # root => './my_test', # fake root directory, userd for tests
  44         98  
  44         296  
  44         75  
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       202  
44             my $delimiter = $args{comment_delimiter} || '#';
45 41         701 my $hash_class = $args{store_class_in_hash} || '';
46             my $section_map = $args{section_map} || {};
47 41   100     205 my $split_reg = $args{split_list_value};
48 41   100     185 my $check = $args{check} || 'yes';
49 41   100     154 my $assign_char = $args{assign_char} || '=';
50 41         84 my $quote_value = $args{quote_value} || '';
51 41   100     117 my $obj = $self->node;
52 41   100     166  
53 41   100     156 my %force_lc;
54 41         151 map { $force_lc{$_} = $args{"force_lc_$_"} ? 1 : 0; } qw/section key value/;
55              
56 41         66 #FIXME: Is it possible to store the comments with their location
57 41 100       88 #in the file? It would be nice if comments that are after values
  123         404  
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         153 $self->read_global_comments( \@lines, $delimiter );
65              
66             my @assoc = $self->associates_comments_with_data( \@lines, $delimiter );
67 41         6458  
68             # store INI data in a structure:
69 41         170 # {
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         86 my $section_path = '';
82 41         76  
83 41         78 foreach my $item (@assoc) {
84 41         77 my ( $vdata, $comment ) = @$item;
85             $logger->debug("ini read: reading '$vdata'");
86 41         86 my $comment_path;
87 318         478  
88 318         835 # Update section name
89 318         1781 if ( $vdata =~ /^\s*\[(.*)\]/ ) {
90             $section = $force_lc{section} ? lc($1) : $1;
91             my $remap = $section_map->{$section} || '';
92 318 100       751 if ( $remap eq '!' ) {
93 57 100       228 # section_map maps section to root node
94 57   100     232 $section_ref = $ini_data;
95 57 100       202 $comment_path = $section_path = '';
    100          
    100          
96             $logger->debug("step 1: found node <top> [$section]");
97 13         31 }
98 13         36 elsif ($remap) {
99 13         56 # 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         4 $self->set_or_push( $ini_data, $remap, $section_ref );
104 2         7 }
105 2         13 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         98 }
110 28         90 else {
111 28         117 $section_ref = {};
112             $logger->debug("step 1: found node $section [$section]");
113             $section_path = $comment_path =
114 14         27 $self->set_or_push( $ini_data, $section, $section_ref );
115 14         78 }
116 14         97  
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       353 }
122 15         72 }
123 15         144 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         1223 $val =~ s/"([^"]*)"/$1/g if $quote_value eq "shell_style";
128 261 100       606 $comment_path = $section_path . ' ' . $self->set_or_push( $section_ref, $name, $val );
129 261 50       448 $logger->debug("step 1: found node $comment_path name $name in [$section]");
130 261 100       450 }
131 261         573  
132 261         837 $ini_comment{$comment_path} = $comment if $comment;
133             }
134              
135 318 100       2016 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         144  
139 41 100       210 while ( my ( $k, $v ) = each %ini_comment ) {
140 41         211 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         212 $logger->debug("annotate '$v' on ", $item->location);
143 113 100       364 $item->annotation($v);
144 106 100       293 }
145 106         488  
146 106         780 return 1;
147             }
148              
149 41         985 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 466 push @$cell, $val;
154 277         437 $path = $name . ':' . $#$cell;
155 277         313 }
156 277 100 100     631 elsif ( defined $cell ) {
    100          
157 13         25 $ref->{$name} = [ $cell, $val ];
158 13         32 $path = $name . ':1';
159             }
160             else {
161 27         58 $ref->{$name} = $val;
162 27         54 $path = $name; # no way to distinguish between leaf and first value of list
163             }
164             return $path;
165 237         499 }
166 237         295  
167             ## no critic (Subroutines::ProhibitBuiltinHomonyms)
168 277         497 # 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 47 # file => 'foo.conf', # file name
  21         40  
  21         111  
  21         41  
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         44 # use the first char of the list as a comment delimeter
182 21   100     123 my $cc = substr($delimiter,0,1);
183             $args{comment_delimiter} = $cc;
184 21 50       78  
185             my $res = '';
186              
187 21         55 # some INI file have a 'General' section mapped in root node
188 21         50 my $top_class_name = $self->{reverse_section_map}{''};
189             if ( defined $top_class_name ) {
190 21         42 $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         94  
194 21 100       60 $res .= $self->_write(%args);
195 4         27 if ($res) {
196 4         51 $args{file_path}->spew_utf8($self->write_global_comment( $cc ) . $res);
197             }
198             elsif ($self->auto_delete) {
199 21         125 $args{file_path}->remove;
200 21 100       114 }
    50          
201 19         116 return;
202             }
203              
204 2         18 my ($self, $args, $node, $elt) = @_ ;
205             my $res = '';
206 21         10204  
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   152 my $list_obj = $node->fetch_element($elt);
211 72         117  
212             my $list_obj_note = $list_obj->annotation;
213 72         110  
214 72   50     153 if ( $join_list ) {
215 72   33     328 my @v = grep { length } $list_obj->fetch_all_values();
      50        
216 72         182 my $v = join( $join_list, @v );
217             if ( length($v) ) {
218 72         242 $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       142 }
221 28         152 }
  17         43  
222 28         85 else {
223 28 100       79 foreach my $obj ( $list_obj->fetch_all('custom') ) {
224 6         40 my $note = $obj->annotation;
225 6         69 my $v = $self->_fetch_obj_value($args, $obj);
226             if ( length $v ) {
227             $logger->debug("writing list elt $elt -> $v");
228             $res .=
229 44         125 $self->write_data_and_comments( $delimiter, "$elt$assign_with$v",
230 36         83 $list_obj_note . $note );
231 36         81 }
232 36 50       75 else {
233 36         118 $logger->trace("NOT writing undef or empty list elt");
234 36         287 }
235             }
236             }
237             return $res;
238             }
239 0         0  
240             my ($self, $args, $node, $elt) = @_ ;
241             my $res = '';
242              
243 72         234 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   8  
248 4         6 my $obj_note = $obj->annotation;
249              
250 4         7 if ($join_check_list ) {
251 4   50     8 my $v = join( $join_check_list, $obj->get_checked_list() );
252 4   33     18 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         10 }
256             }
257 4 50       8 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         11  
266 7         23 my ($self, $args, $obj) = @_ ;
267 7         47 my $v = $obj->fetch;
268             if ( defined $args->{quote_value}
269             and $args->{quote_value} eq 'shell_style'
270 4         11 and defined $v
271             and $v =~ /\s/
272             ) {
273             $v = qq!"$v"!;
274 126     126   213 }
275 126         312 return $v;
276 126 50 66     396 }
      66        
      33        
277              
278             my ($self, $args, $node, $elt) = @_ ;
279             my $res = '';
280              
281 3         11 my $write_bool_as = $args->{write_boolean_as};
282             my $delimiter = $args->{comment_delimiter} || '#';
283 126         232 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   184  
288 90         129 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         136 $v = $write_bool_as->[$v];
291 90   50     213 }
292 90   66     436 if ( defined $v and length $v ) {
      50        
293 90         249 $logger->debug("writing leaf elt $elt -> $v");
294             $res .= $self->write_data_and_comments( $delimiter, "$elt$assign_with$v", $obj_note );
295 90         240 }
296             else {
297 90         251 $logger->trace("NOT writing undef or empty leaf elt");
298 90 100 100     428 }
      66        
      100        
299 1         4 return $res;
300             }
301 90 100 66     295  
302 35         141 my ($self, $args, $node, $elt) = @_ ;
303 35         324 my $res = '';
304              
305             my $delimiter = $args->{comment_delimiter} || '#';
306 55         135 my $obj = $node->fetch_element($elt);
307             my $obj_note = $obj->annotation;
308 90         538  
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   20 $logger->debug("writing hash elt $elt key $key");
313 6         16 my $subres = $self->_write( %$args, object => $hash_obj );
314             if ($subres) {
315 6   50     23 $res .= "\n"
316 6         24 . $self->write_data_and_comments( $delimiter, "[$key]",
317 6         20 $obj_note . $note )
318             . $subres;
319 6         34 }
320 15         71 }
321 15         50 return $res;
322 15         74 }
323 15         190  
324 15 100       85 my ($self, $args, $node, $elt) = @_ ;
325 8         43 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         28 $logger->debug("writing class $elt");
332             my $subres = $self->_write( %$args, object => $obj );
333             if ($subres) {
334              
335 16     16   39 # some INI file may have a section mapped to a node as exception to mapped in a hash
336 16         22 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         33 }
340 16         37 my $c_name = $exception_name || $elt;
341             $res .= "\n"
342 16         58 . $self->write_data_and_comments( $delimiter, "[$c_name]", $obj_note )
343 16         157 . $subres;
344 16 100       47 }
345             return $res;
346             }
347 7         30  
348 7 100       21 my $node = $args{object};
349 1         3 my $delimiter = $args{comment_delimiter} || '#';
350              
351 7   66     30 $logger->trace( "called on ", $node->name );
352 7         32 my $res = '';
353              
354             # Using Config::Model::ObjTreeScanner would be overkill
355             # first write list and element, then classes
356 16         42 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   87 next if $type =~ /node/ or $type eq 'hash';
  52         74  
  52         303  
  52         71  
360 52         95  
361 52   50     134 if ( $type eq 'list' ) {
362             $res .= $self->_write_list (\%args, $node, $elt) ;
363 52         167 }
364 52         368 elsif ( $type eq 'check_list') {
365             $res .= $self->_write_check_list (\%args, $node, $elt) ;
366             }
367             elsif ( $type eq 'leaf' ) {
368 52         141 $res .= $self->_write_leaf (\%args, $node, $elt) ;
369 188         484 }
370 188         601 else {
371 188 100 100     1457 Config::Model::Exception::Model->throw(
372             error => "unexpected type $type for leaf elt $elt",
373 166 100       454 object => $node
    100          
    50          
374 72         196 );
375             }
376             }
377 4         12  
378             foreach my $elt ( $node->get_element_name ) {
379             my $type = $node->element_type($elt);
380 90         253 $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         180 $res .= $self->_write_node (\%args, $node, $elt) ;
391 188         362 }
392 188         533 }
393 188 100 100     1460  
394 22         55 $logger->trace( "done on ", $node->name );
395              
396 22         55 return $res;
397             }
398 22 100       58  
399 6         33 no Mouse;
400             __PACKAGE__->meta->make_immutable;
401              
402 16         47 1;
403              
404             # ABSTRACT: Read and write config as a INI file
405              
406 52         183  
407             =pod
408 52         438  
409             =encoding UTF-8
410              
411 5     5   41 =head1 NAME
  5         9  
  5         28  
412              
413             Config::Model::Backend::IniFile - Read and write config as a INI file
414              
415             =head1 VERSION
416              
417             version 2.151
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