File Coverage

blib/lib/Config/Model/Dumper.pm
Criterion Covered Total %
statement 127 130 97.6
branch 54 62 87.1
condition 31 37 83.7
subroutine 18 18 100.0
pod 2 5 40.0
total 232 252 92.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::Dumper 2.153; # TRIAL
11              
12 59     59   527 use Carp;
  59         154  
  59         3768  
13 59     59   410 use strict;
  59         153  
  59         1230  
14 59     59   322 use warnings;
  59         132  
  59         1747  
15              
16 59     59   386 use Config::Model::Exception;
  59         150  
  59         1708  
17 59     59   30540 use Config::Model::ObjTreeScanner;
  59         188  
  59         2119  
18 59     59   46484 use Config::Model::Value;
  59         319  
  59         109950  
19              
20             sub new {
21 154     154 1 518 bless {}, shift;
22             }
23              
24             sub quote {
25 3772     3772 0 15279 _quote( qr/[\s~"#*]/, @_ );
26             }
27              
28             sub id_quote {
29 4375     4375 0 12733 _quote( qr/[\s~"@*<>.=#]/, @_ );
30             }
31              
32             sub _quote {
33 8147     8147   15424 my ( $re, @res ) = @_;
34 8147         14751 foreach (@res) {
35 8165 100 100     27768 if ( defined $_ and ( /$re/ or $_ eq '' ) ) {
      100        
36 232         663 s/"/\\"/g; # escape present quotes
37 232         783 $_ = '"' . $_ . '"'; # add my quotes
38             }
39             }
40 8147 100       19861 return wantarray ? @res : $res[0];
41             }
42              
43             sub note_quote {
44 5103     5103 0 11132 my @res = @_;
45 5103         9103 foreach (@res) {
46 5103 100 66     19683 if ( defined $_ and $_ and (/(\s|"|\*)/) ) {
      100        
47 165         509 s/"/\\"/g; # escape present quotes
48 165         578 $_ = '"' . $_ . '"'; # add my quotes
49             }
50             }
51 5103 50       12560 return wantarray ? @res : $res[0];
52             }
53              
54             sub dump_tree {
55 154     154 1 307 my $self = shift;
56              
57 154         483 my %args = @_;
58 154   50     628 my $full = delete $args{full_dump} || 0;
59 154   100     2233 my $skip_aw = delete $args{skip_auto_write} || '';
60 154   100     530 my $auto_v = delete $args{auto_vivify} || 0;
61 154   100     566 my $mode = delete $args{mode} || '';
62              
63 154 50       392 if ($full) {
64 0         0 carp "dump_tree: full_dump parameter is deprecated, please use mode => 'user'";
65             }
66              
67 154   100     562 my $check = delete $args{check} || 'yes';
68 154 50       1227 if ( $check !~ /yes|no|skip/ ) {
69 0         0 croak "dump_tree: unexpected 'check' value: $check";
70             }
71              
72             # mode parameter is slightly different from fetch's mode
73 154 100       753 my $fetch_mode =
    100          
    50          
74             $full ? 'user'
75             : $mode eq 'full' ? 'user'
76             : $mode ? $mode
77             : 'custom';
78              
79 154 50       651 if ( my $err = Config::Model::Value->is_bad_mode($fetch_mode) ) {
80 0         0 croak "dump_tree: $err";
81             }
82              
83             my $node = delete $args{node}
84 154   33     593 || croak "dump_tree: missing 'node' parameter";
85              
86             my $compute_pad = sub {
87 4885     4885   7394 my $depth = 0;
88 4885         7816 my $obj = shift;
89 4885         15596 while ( defined $obj->parent ) {
90 5979         8365 $depth++;
91 5979         14766 $obj = $obj->parent;
92             }
93 4885         12619 return ' ' x $depth;
94 154         846 };
95              
96             my $leaf_cb = sub {
97 3737     3737   7919 my ( $scanner, $data_r, $node, $element, $index, $value_obj ) = @_;
98              
99             # get value or only customized value
100 3737         10892 my $value = quote( $value_obj->fetch( mode => $fetch_mode, check => $check ) );
101 3734         10348 $index = id_quote($index);
102              
103 3734         9796 my $pad = $compute_pad->($node);
104              
105 3734 100       8065 my $name =
106             defined $index
107             ? "$element:$index"
108             : $element;
109              
110             # add annotation for obj contained in hash or list
111 3734         11429 my $note = note_quote( $value_obj->annotation );
112 3734 100 100     13126 $$data_r .= "\n" . $pad . $name if defined $value or $note;
113 3734 100       7571 if (defined $value) {
114 1242         2771 $value =~ s/\\n/\\\\n/g;
115 1242         2564 $$data_r .= '=' . $value;
116             }
117 3734 100       17299 $$data_r .= '#' . $note if $note;
118 154         741 };
119              
120             my $check_list_cb = sub {
121 38     38   125 my ( $scanner, $data_r, $node, $element, $index, $value_obj ) = @_;
122              
123             # get value or only customized value
124 38         181 my $value = $value_obj->fetch( mode => $fetch_mode, check => $check );
125 38         123 my $qvalue = quote($value);
126 38         138 $index = id_quote($index);
127 38         142 my $pad = $compute_pad->($node);
128              
129 38 50       118 my $name =
130             defined $index
131             ? "$element:$index"
132             : $element;
133              
134             # add annotation for obj contained in hash or list
135 38         160 my $note = note_quote( $value_obj->annotation );
136 38 100 66     190 $$data_r .= "\n" . $pad . $name if $value or $note;
137 38 100       113 $$data_r .= '=' . $qvalue if $value;
138 38 100       195 $$data_r .= '#' . $note if $note;
139 154         747 };
140              
141             my $list_element_cb = sub {
142 312     312   814 my ( $scanner, $data_r, $node, $element, @keys ) = @_;
143              
144 312         666 my $pad = $compute_pad->($node);
145 312         957 my $list_obj = $node->fetch_element($element);
146              
147             # add annotation for list element
148 312         956 my $list_note = note_quote( $list_obj->annotation );
149 312 100       771 $$data_r .= "\n$pad$element#$list_note" if $list_note;
150              
151 312 100       965 if ( $list_obj->cargo_type eq 'node' ) {
152 17         48 foreach my $k (@keys) {
153 34         115 $scanner->scan_list( $data_r, $node, $element, $k );
154             }
155             }
156             else {
157             # write value comments
158 295         882 foreach my $idx ( $list_obj->fetch_all_indexes ) {
159 383         970 my $note = $list_obj->fetch_with_id($idx)->annotation;
160 383 100       1228 $$data_r .= "\n$pad$element:$idx#" . note_quote($note) if $note;
161             }
162              
163             # skip undef values
164             my @val = id_quote(
165 295         1036 grep { defined $_ }
  313         724  
166             $list_obj->fetch_all_values(mode => $fetch_mode, check => $check)
167             );
168 295 100       2606 $$data_r .= "\n$pad$element:=" . join( ',', @val ) if @val;
169             }
170 154         786 };
171              
172             my $hash_element_cb = sub {
173 168     168   469 my ( $scanner, $data_r, $node, $element, @keys ) = @_;
174              
175 168         447 my $pad = $compute_pad->($node);
176 168         509 my $hash_obj = $node->fetch_element($element);
177              
178             # add annotation for list or hash element
179 168         626 my $note = note_quote( $hash_obj->annotation );
180 168 100       514 $$data_r .= "\n$pad$element#$note" if $note;
181              
182             # resume exploration
183 168         458 map { $scanner->scan_hash( $data_r, $node, $element, $_ ); } @keys;
  336         1219  
184 154         672 };
185              
186             # called for nodes contained in nodes (not root).
187             # This node can be held by a plain element or a hash element or a list element
188             my $node_element_cb = sub {
189 633     633   1524 my ( $scanner, $data_r, $node, $element, $key, $contained_node ) = @_;
190              
191 633         1658 my $type = $node->element_type($element);
192              
193 633 50 66     1800 return if $skip_aw and $contained_node->is_auto_write_for_type($skip_aw);
194              
195 633         1439 my $pad = $compute_pad->($node);
196 633         1897 my $elt = $node->fetch_element($element);
197              
198             # load string can feature only one comment per element_type
199             # ie foo#comment foo:bar#comment foo:bar=val#comment are fine
200             # but foo#comment:bar if not valid -> foo#commaent foo:bar
201              
202 633         1780 my $head = "\n$pad$element";
203 633         1708 my $node_note = note_quote( $contained_node->annotation );
204              
205 633 100 100     2489 if ( $type eq 'list' or $type eq 'hash' ) {
206 308         804 $head .= ':' . id_quote($key);
207 308 100       982 $head .= '#' . $node_note if $node_note;
208 308         521 my $sub_data = '';
209 308         1365 $scanner->scan_node( \$sub_data, $contained_node );
210 307         2871 $$data_r .= $head . $sub_data . ' -';
211             }
212             else {
213 325 100       774 $head .= '#' . $node_note if $node_note;
214 325         559 my $sub_data = '';
215 325         1247 $scanner->scan_node( \$sub_data, $contained_node );
216              
217             # skip simple nodes that do not bring data
218 325 100       2203 $$data_r .= $head . $sub_data . ' -' if $sub_data;
219             }
220 154         756 };
221              
222 154         765 my @scan_args = (
223             fallback => 'all',
224             auto_vivify => $auto_v,
225             list_element_cb => $list_element_cb,
226             hash_element_cb => $hash_element_cb,
227             leaf_cb => $leaf_cb,
228             node_element_cb => $node_element_cb,
229             check_list_element_cb => $check_list_cb,
230             check => $check,
231             );
232              
233 154         364 my @left = keys %args;
234 154 50       491 croak "Dumper: unknown parameter:@left" if @left;
235              
236             # perform the scan
237 154         1080 my $view_scanner = Config::Model::ObjTreeScanner->new(@scan_args);
238              
239 154         401 my $ret = '';
240 154         770 my $root_note = note_quote( $node->annotation );
241 154 100       481 $ret .= "\n#$root_note" if $root_note;
242 154         738 $view_scanner->scan_node( \$ret, $node );
243              
244 151         898 substr( $ret, 0, 1, '' ); # remove leading \n
245 151 100       535 $ret .= ' -' if $ret;
246 151         7255 return $ret . "\n";
247             }
248              
249             1;
250              
251             # ABSTRACT: Serialize data of config tree
252              
253             __END__
254              
255             =pod
256              
257             =encoding UTF-8
258              
259             =head1 NAME
260              
261             Config::Model::Dumper - Serialize data of config tree
262              
263             =head1 VERSION
264              
265             version 2.153
266              
267             =head1 SYNOPSIS
268              
269             use Config::Model ;
270              
271             # define configuration tree object
272             my $model = Config::Model->new ;
273             $model ->create_config_class (
274             name => "MyClass",
275             element => [
276             [qw/foo bar/] => {
277             type => 'leaf',
278             value_type => 'string'
279             },
280             baz => {
281             type => 'hash',
282             index_type => 'string' ,
283             cargo => {
284             type => 'leaf',
285             value_type => 'string',
286             },
287             },
288              
289             ],
290             ) ;
291              
292             my $inst = $model->instance(root_class_name => 'MyClass' );
293              
294             my $root = $inst->config_root ;
295              
296             # put some data in config tree the hard way
297             $root->fetch_element('foo')->store('yada') ;
298             $root->fetch_element('bar')->store('bla bla') ;
299             $root->fetch_element('baz')->fetch_with_id('en')->store('hello') ;
300              
301             # put more data the easy way
302             my $steps = 'baz:fr=bonjour baz:hr="dobar dan"';
303             $root->load( steps => $steps ) ;
304              
305             # dump only customized data
306             print $root->dump_tree;
307              
308             =head1 DESCRIPTION
309              
310             This module is used directly by L<Config::Model::Node> to serialize
311             configuration data in a compact (but readable) string.
312              
313             The serialization can be done in standard mode where only customized
314             values are dumped in the string. I.e. only data modified by the user
315             are dumped.
316              
317             All other mode supported by L<Config::Model::Value/fetch> can be used,
318             for instance, to get default values.
319              
320             The serialized string can be used by L<Config::Model::Loader> to store
321             the data back into a configuration tree.
322              
323             Note that undefined values are skipped for list element. I.e. if a list
324             element contains C<('a',undef,'b')>, the dump then contains C<'a','b'>.
325              
326             =head1 CONSTRUCTOR
327              
328             =head2 new
329              
330             No parameter. The constructor should be used only by
331             L<Config::Model::Node>.
332              
333             =head1 Methods
334              
335             =head2 dump_tree
336              
337             Return a string that contains a dump of the object tree with all the
338             values. This string follows the convention defined by
339             L<Config::Model::Loader>.
340              
341             The serialized string can be used by L<Config::Model::Loader> to store
342             the data back into a configuration tree.
343              
344             Parameters are:
345              
346             =over
347              
348             =item mode
349              
350             C<full> dumps all configuration data including default
351             values.
352              
353             All mode values from L<Config::Model::Value/fetch> can be used.
354              
355             By default, the dump contains only data modified by the user
356             (i.e. C<custom> data that differ from default or preset values).
357              
358             =item node
359              
360             Reference to the L<Config::Model::Node> object that is dumped. All
361             nodes and leaves attached to this node are also dumped.
362              
363             =item skip_auto_write ( <backend_name> )
364              
365             Skip node that have a write capability matching C<backend_name> in
366             their model. See L<Config::Model::BackendMgr>.
367              
368             This option must be used when using Dumper to write back configuration
369             data. When a configuration model contains several backends (one at the
370             tree root and others in tree nodes), setting this option ensure that
371             the "root" configuration file does not contain data duplicated in
372             configuration file of others tree nodes.
373              
374             =item auto_vivify
375              
376             Scan and create data for nodes elements even if no actual data was
377             stored in them. This may be useful to trap missing mandatory values.
378             (default: 0)
379              
380             =item check
381              
382             Check value before dumping. Valid check are 'yes', 'no' and 'skip'.
383              
384             =back
385              
386             =head1 AUTHOR
387              
388             Dominique Dumont, (ddumont at cpan dot org)
389              
390             =head1 SEE ALSO
391              
392             L<Config::Model>,L<Config::Model::Node>,L<Config::Model::Loader>
393              
394             =head1 AUTHOR
395              
396             Dominique Dumont
397              
398             =head1 COPYRIGHT AND LICENSE
399              
400             This software is Copyright (c) 2005-2022 by Dominique Dumont.
401              
402             This is free software, licensed under:
403              
404             The GNU Lesser General Public License, Version 2.1, February 1999
405              
406             =cut