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   366 use strict;
  59         117  
  59         3105  
14 59     59   332 use warnings;
  59         113  
  59         1073  
15 59     59   287  
  59         113  
  59         1577  
16             use Config::Model::Exception;
17 59     59   367 use Config::Model::ObjTreeScanner;
  59         161  
  59         1214  
18 59     59   353 use List::Util qw/max/;
  59         139  
  59         1610  
19 59     59   359 use utf8;
  59         139  
  59         3543  
20 59     59   28697  
  59         763  
  59         295  
21             bless {}, shift;
22             }
23 9     9 1 26  
24             my $self = shift;
25              
26             my %args = @_;
27 9     9 1 22 my $desc_node = delete $args{node}
28             || croak "describe: missing 'node' parameter";
29 9         36 my $check = delete $args{check} || 'yes';
30              
31 9   33     39 my $element = delete $args{element} ; # optional
32 9   100     48 my $pattern = delete $args{pattern} ; # optional
33             my $hide_empty = delete $args{hide_empty} // 0 ; # optional
34 9         22 my $verbose = delete $args{verbose} // 0 ; # optional
35 9         19  
36 9   100     37 my $show_empty = ! $hide_empty ;
37 9   100     29  
38             my $tag_name = sub {
39 9         19 $_[1] .= ' ⚠' if $_[0]->has_warning;
40             };
41              
42 66 100   66   205 my $my_content_cb = sub {
43 9         43 my ( $scanner, $data_ref, $node, @element ) = @_;
44             # filter elements according to pattern
45             my @scan = $pattern ? grep { $_ =~ $pattern } @element : @element;
46 8     8   31 for (@scan) { $scanner->scan_element( $data_ref, $node, $_ ) }
47             };
48 8 100       36  
  56         126  
49 8         25 my $std_cb = sub {
  68         218  
50 9         38 my ( $scanner, $data_r, $obj, $element, $index, $value_obj ) = @_;
51              
52             my $value = $value_obj->fetch( check => $check, mode => 'user' );
53 39     39   85  
54             return unless $show_empty or (defined $value and length($value));
55 39         130  
56             $value = substr($value,0,12).'[…]' if $value and length($value) > 12;
57 39 100 66     161  
      66        
58             $value = '"' . $value . '"' if defined $value and $value =~ /\s/;
59 34 100 100     124  
60             my $name = defined $index ? "$element:$index" : $element;
61 34 100 100     152 $value = defined $value ? $value : '[undef]';
62              
63 34 100       76 my $type = $value_obj->value_type;
64 34 100       69 my @comment;
65             if (my $default = $value_obj->fetch(mode => 'standard')) {
66 34         69 my $defstr = $type =~ /uniline|string/ ? qq!"$default"! : $default;
67 34         43 push @comment, "default: $defstr";
68 34 100       171 }
69 18 100       84 push @comment, "choice: " . join( ' ', @{ $value_obj->choice } )
70 18         44 if $type eq 'enum';
71             push @comment, 'mandatory' if $value_obj->mandatory;
72 34 100       95  
  6         33  
73             $tag_name->($value_obj,$element);
74 34 100       120 push @$data_r, [ $name, $type, $value, join( ', ', @comment ) ];
75             };
76 34         82  
77 34         377 my $list_element_cb = sub {
78 9         61 my ( $scanner, $data_r, $obj, $element, @keys ) = @_;
79              
80             #print "DEBUG: list_element_cb on $element, keys @keys\n";
81 12     12   32 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         42 if ( $elt_type eq 'node' ) {
86             my $class_name = $list_obj->config_class_name;
87 12         36 my @show_keys = @keys ? @keys : ('<empty>');
88 12 100       31 push @$data_r, [ $element, "<$class_name>", 'node list', "indexes: @show_keys" ];
89 3         12 }
90 3 50       11 else {
91 3         29 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       36 }
  21         108  
95 9 100 100     98 };
96              
97             my $check_list_element_cb = sub {
98 9         49 my ( $scanner, $data_r, $obj, $element, @choices ) = @_;
99              
100             my $list_obj = $obj->fetch_element($element);
101 3     3   10 $tag_name->($list_obj,$element);
102             my @checked = $list_obj->get_checked_list;
103 3         11 push @$data_r, [ $element, 'check_list', join( ',', @checked ), '' ] if $show_empty or @checked;
104 3         10 };
105 3         16  
106 3 50 66     54 my $hash_element_cb = sub {
107 9         47 my ( $scanner, $data_r, $obj, $element, @keys ) = @_;
108              
109             #print "DEBUG: hash_element_cb on $element, keys @keys\n";
110 17     17   88 my $hash_obj = $obj->fetch_element($element);
111             my $elt_type = $hash_obj->cargo_type;
112              
113 17         50 $tag_name->($hash_obj,$element);
114 17         56 if ( $elt_type eq 'node' ) {
115             my $class_name = $hash_obj->config_class_name;
116 17         47 my @show_keys = @keys ? map { qq("$_") } @keys : ('<empty>');
117 17 100       52 my $show_str = "keys: @show_keys";
    100          
118 4         26 push @$data_r, [ $element, 'node hash', "<$class_name>", $show_str ];
119 4 50       14 }
  8         24  
120 4         16 elsif (@keys) {
121 4         26 for ( @keys ) { $scanner->scan_hash( $data_r, $obj, $element, $_ ) }
122             }
123             else {
124 4         11 push @$data_r, [ $element, 'value hash', "[empty hash]", "" ] if $show_empty;
  8         29  
125             }
126             };
127 9 100       60  
128             my $node_element_cb = sub {
129 9         60 my ( $scanner, $data_r, $obj, $element, $key, $next ) = @_;
130              
131             #print "DEBUG: elt_cb on $element, key $key\n";
132 6     6   15 my $type = $obj->element_type($element);
133              
134             my $class_name = $next->config_class_name;
135 6         15 push @$data_r, [ $element, 'node', "<$class_name>", $obj->gist ];
136              
137 6         25 #$ret .= ":$key" if $type eq 'list' or $type eq 'hash';
138 6         44  
139             #$view_scanner->scan_node($next);
140             };
141              
142             my @scan_args = (
143 9         38 fallback => 'all',
144             auto_vivify => 0,
145 9         49 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         22 # perform the scan
157 9 50       30 my $view_scanner = Config::Model::ObjTreeScanner->new(@scan_args);
158              
159             my @ret;
160 9         68 if ( defined $element and $desc_node->has_element($element) ) {
161             $view_scanner->scan_element( \@ret, $desc_node, $element );
162 9         25 }
163 9 100 66     48 elsif ( defined $element ) {
    50          
164 1         5 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         44 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         32 my $value_length = max map { length($_->[2]) } (@ret, \@header );
179 9         21 my $sep_length = $name_length + $type_length + $value_length + 4 ;
  71         141  
180 9         21 my @format = ("%-${name_length}s", "%-${type_length}s", "%-${value_length}s") ;
  71         99  
181 9         21  
  71         100  
182 9         22 my @show ;
183 9         44 if ($verbose) {
184             push @format, "%-35s";
185 9         15 @show = (
186 9 100       26 sprintf( join(" │ ", @format)."\n", qw/name type value comment/) ,
187 6         13 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         99 else {
  25         152  
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         51  
  37         171  
199             return join ('', @show );
200             }
201              
202 9         635 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.152
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