File Coverage

blib/lib/Config/Model/Describe.pm
Criterion Covered Total %
statement 119 120 99.1
branch 43 48 89.5
condition 24 32 75.0
subroutine 16 16 100.0
pod 2 2 100.0
total 204 218 93.5


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              
12             use Carp;
13 59     59   349 use strict;
  59         120  
  59         3153  
14 59     59   326 use warnings;
  59         114  
  59         1013  
15 59     59   291  
  59         144  
  59         1586  
16             use Config::Model::Exception;
17 59     59   402 use Config::Model::ObjTreeScanner;
  59         150  
  59         1387  
18 59     59   301 use List::Util qw/max/;
  59         130  
  59         1578  
19 59     59   312 use utf8;
  59         151  
  59         3526  
20 59     59   28380  
  59         720  
  59         305  
21             bless {}, shift;
22             }
23 9     9 1 22  
24             my $self = shift;
25              
26             my %args = @_;
27 9     9 1 19 my $desc_node = delete $args{node}
28             || croak "describe: missing 'node' parameter";
29 9         27 my $check = delete $args{check} || 'yes';
30              
31 9   33     27 my $element = delete $args{element} ; # optional
32 9   100     33 my $pattern = delete $args{pattern} ; # optional
33             my $hide_empty = delete $args{hide_empty} // 0 ; # optional
34 9         16 my $verbose = delete $args{verbose} // 0 ; # optional
35 9         15  
36 9   100     38 my $show_empty = ! $hide_empty ;
37 9   100     23  
38             my $tag_name = sub {
39 9         14 $_[1] .= ' ⚠' if $_[0]->has_warning;
40             };
41              
42 66 100   66   180 my $my_content_cb = sub {
43 9         34 my ( $scanner, $data_ref, $node, @element ) = @_;
44             # filter elements according to pattern
45             my @scan = $pattern ? grep { $_ =~ $pattern } @element : @element;
46 8     8   41 for (@scan) { $scanner->scan_element( $data_ref, $node, $_ ) }
47             };
48 8 100       28  
  56         124  
49 8         17 my $std_cb = sub {
  68         190  
50 9         29 my ( $scanner, $data_r, $obj, $element, $index, $value_obj ) = @_;
51              
52             my $value = $value_obj->fetch( check => $check, mode => 'user' );
53 39     39   83  
54             return unless $show_empty or (defined $value and length($value));
55 39         99  
56             $value = substr($value,0,12).'[…]' if $value and length($value) > 12;
57 39 100 66     146  
      66        
58             $value = '"' . $value . '"' if defined $value and $value =~ /\s/;
59 34 100 100     94  
60             my $name = defined $index ? "$element:$index" : $element;
61 34 100 100     137 $value = defined $value ? $value : '[undef]';
62              
63 34 100       68 my $type = $value_obj->value_type;
64 34 100       53 my @comment;
65             if (my $default = $value_obj->fetch(mode => 'standard')) {
66 34         65 my $defstr = $type =~ /uniline|string/ ? qq!"$default"! : $default;
67 34         40 push @comment, "default: $defstr";
68 34 100       70 }
69 18 100       76 push @comment, "choice: " . join( ' ', @{ $value_obj->choice } )
70 18         38 if $type eq 'enum';
71             push @comment, 'mandatory' if $value_obj->mandatory;
72 34 100       77  
  6         35  
73             $tag_name->($value_obj,$element);
74 34 100       97 push @$data_r, [ $name, $type, $value, join( ', ', @comment ) ];
75             };
76 34         71  
77 34         343 my $list_element_cb = sub {
78 9         47 my ( $scanner, $data_r, $obj, $element, @keys ) = @_;
79              
80             #print "DEBUG: list_element_cb on $element, keys @keys\n";
81 12     12   28 my $list_obj = $obj->fetch_element($element);
82             my $elt_type = $list_obj->cargo_type;
83              
84 12         31 $tag_name->($list_obj,$element);
85 12         47 if ( $elt_type eq 'node' ) {
86             my $class_name = $list_obj->config_class_name;
87 12         30 my @show_keys = @keys ? @keys : ('<empty>');
88 12 100       25 push @$data_r, [ $element, "<$class_name>", 'node list', "indexes: @show_keys" ];
89 3         10 }
90 3 50       9 else {
91 3         24 my @values = grep { $show_empty or length } $list_obj->fetch_all_values( check => 'no' ) ;
92             push @$data_r,
93             [ $element, 'list', join( ',', @values ), '' ] if ($show_empty or @values);
94 9 100       23 }
  21         52  
95 9 100 100     72 };
96              
97             my $check_list_element_cb = sub {
98 9         36 my ( $scanner, $data_r, $obj, $element, @choices ) = @_;
99              
100             my $list_obj = $obj->fetch_element($element);
101 3     3   8 $tag_name->($list_obj,$element);
102             my @checked = $list_obj->get_checked_list;
103 3         8 push @$data_r, [ $element, 'check_list', join( ',', @checked ), '' ] if $show_empty or @checked;
104 3         8 };
105 3         10  
106 3 50 66     28 my $hash_element_cb = sub {
107 9         27 my ( $scanner, $data_r, $obj, $element, @keys ) = @_;
108              
109             #print "DEBUG: hash_element_cb on $element, keys @keys\n";
110 17     17   38 my $hash_obj = $obj->fetch_element($element);
111             my $elt_type = $hash_obj->cargo_type;
112              
113 17         37 $tag_name->($hash_obj,$element);
114 17         47 if ( $elt_type eq 'node' ) {
115             my $class_name = $hash_obj->config_class_name;
116 17         42 my @show_keys = @keys ? map { qq("$_") } @keys : ('<empty>');
117 17 100       44 my $show_str = "keys: @show_keys";
    100          
118 4         19 push @$data_r, [ $element, 'node hash', "<$class_name>", $show_str ];
119 4 50       13 }
  8         21  
120 4         13 elsif (@keys) {
121 4         26 for ( @keys ) { $scanner->scan_hash( $data_r, $obj, $element, $_ ) }
122             }
123             else {
124 4         10 push @$data_r, [ $element, 'value hash', "[empty hash]", "" ] if $show_empty;
  8         29  
125             }
126             };
127 9 100       62  
128             my $node_element_cb = sub {
129 9         41 my ( $scanner, $data_r, $obj, $element, $key, $next ) = @_;
130              
131             #print "DEBUG: elt_cb on $element, key $key\n";
132 6     6   12 my $type = $obj->element_type($element);
133              
134             my $class_name = $next->config_class_name;
135 6         14 push @$data_r, [ $element, 'node', "<$class_name>", $obj->gist ];
136              
137 6         16 #$ret .= ":$key" if $type eq 'list' or $type eq 'hash';
138 6         31  
139             #$view_scanner->scan_node($next);
140             };
141              
142             my @scan_args = (
143 9         33 fallback => 'all',
144             auto_vivify => 0,
145 9         34 list_element_cb => $list_element_cb,
146             check_list_element_cb => $check_list_element_cb,
147             hash_element_cb => $hash_element_cb,
148             leaf_cb => $std_cb,
149             node_element_cb => $node_element_cb,
150             node_content_cb => $my_content_cb,
151             );
152              
153             my @left = keys %args;
154             croak "Describe: unknown parameter:@left" if @left;
155              
156 9         20 # perform the scan
157 9 50       29 my $view_scanner = Config::Model::ObjTreeScanner->new(@scan_args);
158              
159             my @ret;
160 9         52 if ( defined $element and $desc_node->has_element($element) ) {
161             $view_scanner->scan_element( \@ret, $desc_node, $element );
162 9         16 }
163 9 100 66     31 elsif ( defined $element ) {
    50          
164 1         7 Config::Model::Exception::UnknownElement->throw(
165             object => $desc_node,
166             function => 'Describe',
167 0   0     0 where => $desc_node->location || 'configuration root',
168             element => $element,
169             );
170             }
171             else {
172             $view_scanner->scan_node( \@ret, $desc_node );
173             }
174              
175 8         28 my @header = qw/name type value/;
176             my $name_length = max map { length($_->[0]) } (@ret, \@header );
177             my $type_length = max map { length($_->[1]) } (@ret, \@header );
178 9         22 my $value_length = max map { length($_->[2]) } (@ret, \@header );
179 9         17 my $sep_length = $name_length + $type_length + $value_length + 4 ;
  71         115  
180 9         23 my @format = ("%-${name_length}s", "%-${type_length}s", "%-${value_length}s") ;
  71         97  
181 9         23  
  71         89  
182 9         23 my @show ;
183 9         31 if ($verbose) {
184             push @format, "%-35s";
185 9         14 @show = (
186 9 100       21 sprintf( join(" │ ", @format)."\n", qw/name type value comment/) ,
187 6         8 sprintf( join("─┼─", @format)."\n", '─' x $name_length,'─' x $type_length,'─' x $value_length,'─' x 20, ) ,
188             map { sprintf( join(" │ ", @format)."\n", @$_ ) } @ret
189             );
190             }
191 6         62 else {
  25         127  
192             @show = (
193             sprintf( join(" │ ", @format)."\n", qw/name type value/) ,
194             sprintf( join("─┼─", @format)."\n", '─' x $name_length,'─' x $type_length,'─' x $value_length ) ,
195             map { sprintf( join(" │ ", @format)."\n", @$_[0,1,2] ) } @ret
196             );
197             }
198 3         52  
  37         162  
199             return join ('', @show );
200             }
201              
202 9         382 1;
203              
204             # ABSTRACT: Provide a description of a node element
205              
206              
207             =pod
208              
209             =encoding UTF-8
210              
211             =head1 NAME
212              
213             Config::Model::Describe - Provide a description of a node element
214              
215             =head1 VERSION
216              
217             version 2.151
218              
219             =head1 SYNOPSIS
220              
221             use Config::Model;
222              
223             # define configuration tree object
224             my $model = Config::Model->new;
225             $model->create_config_class(
226             name => "Foo",
227             element => [
228             [qw/foo bar/] => {
229             type => 'leaf',
230             value_type => 'string'
231             },
232             ]
233             );
234             $model ->create_config_class (
235             name => "MyClass",
236              
237             element => [
238              
239             [qw/foo bar/] => {
240             type => 'leaf',
241             value_type => 'string'
242             },
243             hash_of_nodes => {
244             type => 'hash', # hash id
245             index_type => 'string',
246             cargo => {
247             type => 'node',
248             config_class_name => 'Foo'
249             },
250             },
251             ],
252             ) ;
253              
254             my $inst = $model->instance(root_class_name => 'MyClass' );
255              
256             my $root = $inst->config_root ;
257              
258             # put data
259             my $steps = 'foo=FOO hash_of_nodes:fr foo=bonjour -
260             hash_of_nodes:en foo=hello ';
261             $root->load( steps => $steps );
262              
263             print $root->describe ;
264              
265             ### prints
266             # name type value comment
267             # foo string FOO
268             # bar string [undef]
269             # hash_of_nodes node hash <Foo> keys: "en" "fr"
270              
271             =head1 DESCRIPTION
272              
273             This module is used directly by L<Config::Model::Node> to describe
274             a node element. This module returns a human readable string that
275             shows the content of a configuration node.
276              
277             For instance (as shown by C<fstab> example:
278              
279             name type value comment
280             fs_spec string [undef] mandatory
281             fs_vfstype enum [undef] choice: auto davfs ext2 ext3 swap proc iso9660 vfat ignore, mandatory
282             fs_file string [undef] mandatory
283             fs_freq boolean 0
284             fs_passno integer 0
285              
286             This module is also used by the C<ll> command of L<Config::Model::TermUI>.
287              
288             =head1 CONSTRUCTOR
289              
290             =head2 new
291              
292             No parameter. The constructor should be used only by
293             L<Config::Model::Node>.
294              
295             =head1 Methods
296              
297             =head2 describe
298              
299             Return a description string.
300              
301             Parameters are:
302              
303             =over
304              
305             =item node
306              
307             Reference to a L<Config::Model::Node> object. Mandatory
308              
309             =item element
310              
311             Describe only this element from the node. Optional. All elements are
312             described if omitted.
313              
314             =item pattern
315              
316             Describe the element matching the regexp ref. Example:
317              
318             describe => ( pattern => qr/^foo/ )
319              
320             =item hide_empty
321              
322             Boolean. Whether to hide empty value (i.e. C<undef> or C<''>) or not. Default is false.
323              
324             =item verbose
325              
326             Boolean. Display more information with each element. Default is false.
327              
328             =back
329              
330             =head1 AUTHOR
331              
332             Dominique Dumont, (ddumont at cpan dot org)
333              
334             =head1 SEE ALSO
335              
336             L<Config::Model>,L<Config::Model::Node>,L<Config::Model::ObjTreeScanner>
337              
338             =head1 AUTHOR
339              
340             Dominique Dumont
341              
342             =head1 COPYRIGHT AND LICENSE
343              
344             This software is Copyright (c) 2005-2022 by Dominique Dumont.
345              
346             This is free software, licensed under:
347              
348             The GNU Lesser General Public License, Version 2.1, February 1999
349              
350             =cut