File Coverage

blib/lib/Config/Model/Iterator.pm
Criterion Covered Total %
statement 129 135 95.5
branch 38 50 76.0
condition 26 39 66.6
subroutine 22 22 100.0
pod 6 9 66.6
total 221 255 86.6


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::Iterator 2.153; # TRIAL
11              
12 59     59   818 use v5.20;
  59         231  
13 59     59   343 use Carp;
  59         137  
  59         3239  
14 59     59   415 use strict;
  59         148  
  59         1531  
15 59     59   346 use warnings;
  59         148  
  59         1918  
16 59     59   392 use Config::Model::ObjTreeScanner;
  59         235  
  59         2130  
17 59     59   432 use Log::Log4perl qw(get_logger :levels);
  59         152  
  59         436  
18              
19 59     59   8522 use Config::Model::Exception;
  59         152  
  59         2231  
20              
21 59     59   468 use feature qw/postderef signatures/;
  59         148  
  59         5930  
22 59     59   486 no warnings qw/experimental::postderef experimental::signatures/;
  59         165  
  59         119850  
23              
24             my $logger = get_logger("Iterator");
25              
26 1     1 0 3 sub new ($type, %args){
  1         2  
  1         7  
  1         3  
27              
28 1         6 my $self = {
29             call_back_on_important => 0,
30             forward => 1,
31             status => 'standard',
32             };
33              
34 1 50       4 if (delete $args{experience}) {
35 0         0 carp "experience parameter is deprecated";
36             }
37              
38 1         3 foreach my $p (qw/root/) {
39 1 50       6 $self->{$p} = delete $args{$p}
40             or croak "Iterator->new: Missing $p parameter";
41             }
42              
43 1         3 foreach my $p (qw/call_back_on_important call_back_on_warning status/) {
44 3 100       11 $self->{$p} = delete $args{$p} if defined $args{$p};
45             }
46              
47 1         3 bless $self, $type;
48              
49 1         2 my %cb_hash;
50              
51             # mandatory call-back parameters
52 1         3 foreach my $item (qw/leaf_cb hash_element_cb/) {
53 2 50       9 $cb_hash{$item} = delete $args{$item}
54             or croak "Iterator->new: Missing $item parameter";
55             }
56              
57             # handle optional list_element_cb parameter
58             $cb_hash{list_element_cb} = delete $args{list_element_cb}
59 1   33     4 || $cb_hash{hash_element_cb};
60              
61             # optional call-back parameter
62             $cb_hash{check_list_element_cb} =
63 1   33     8 delete $args{check_list_element_cb} || $cb_hash{leaf_cb};
64              
65             # optional call-back parameters
66 1         3 foreach my $p (
67             qw/enum_value reference_value
68             integer_value number_value
69             boolean_value string_value uniline_value/
70             ) {
71 7         14 my $item = $p . '_cb';
72 7   66     24 $cb_hash{$item} = delete $args{$item} || $cb_hash{leaf_cb};
73             }
74              
75 1         41 $self->{dispatch_cb} = \%cb_hash;
76              
77 1 50       8 if (%args) {
78 0         0 die "Iterator->new: unexpected parameters: ", join( ' ', keys %args ), "\n";
79             }
80              
81             # user call-back are *not* passed to ObjTreeScanner. They will be
82             # called indirectly through wizard-helper own call-backs
83              
84             $self->{scanner} = Config::Model::ObjTreeScanner->new(
85             fallback => 'all',
86 15     15   44 hash_element_cb => sub { $self->hash_element_cb(@_) },
87 5     5   17 list_element_cb => sub { $self->hash_element_cb(@_) },
88 36     36   113 node_content_cb => sub { $self->node_content_cb(@_) },
89 175     175   458 leaf_cb => sub { $self->leaf_cb(@_) },
90 1         33 );
91              
92 1         6 return $self;
93             }
94              
95             sub start {
96 1     1 1 364 my $self = shift;
97 1         7 $self->{bail_out} = 0;
98 1         12 $self->{scanner}->scan_node( undef, $self->{root} );
99 1         5 return;
100             }
101              
102             sub bail_out {
103 1     1 1 21 my $self = shift;
104 1         4 $self->{bail_out} = 1;
105 1         2 return;
106             }
107              
108             # internal. This call-back is passed to ObjTreeScanner. It will call
109             # scan_element in an order which depends on $self->{forward}.
110             sub node_content_cb {
111 36     36 0 108 my ( $self, $scanner, $data_r, $node, @element ) = @_;
112              
113 36         93 $logger->info( "node_content_cb called on '", $node->name, "' element: @element" );
114              
115 36         268 my $element;
116              
117 36         52 while (1) {
118              
119             # @element from ObjTreeScanner is not used as user actions may
120             # change the element list due to warping
121             $element = $node->next_element(
122             name => $element,
123             status => $self->{status},
124 232         866 reverse => 1 - $self->{forward} );
125              
126 232 100       481 last unless defined $element;
127              
128 197         825 $logger->info( "node_content_cb calls scan_element ", "on element $element" );
129              
130 197         1945 $self->{scanner}->scan_element( $data_r, $node, $element );
131 197 100       440 return if $self->{bail_out};
132             }
133 35         112 return;
134             }
135              
136             # internal. Used to find which user call-back to use for a given
137             # element type.
138             sub get_cb {
139 195     195 0 356 my $self = shift;
140 195         268 my $elt_type = shift;
141 195   33     630 return $self->{dispatch_cb}{ $elt_type . '_cb' }
142             || croak "wizard get_cb: unexpected type $elt_type";
143             }
144              
145             # internal. This call-back is passed to ObjTreeScanner. It will call
146             # scan_hash in an order which depends on $self->{forward}. it will
147             # also check if the hash (or list) element is flagged as 'important'
148             # and call user's hash or list call-back if needed
149 20     20 1 26 sub hash_element_cb ( $self, $scanner, $data_r, $node, $element, @raw_keys ) {
  20         33  
  20         28  
  20         28  
  20         32  
  20         26  
  20         31  
  20         25  
150 20         54 my @keys = sort @raw_keys;
151              
152 20         59 my $level = $node->get_element_property( element => $element, property => 'level' );
153              
154 20         165 $logger->info( "hash_element_cb (element $element) called on '",
155             $node->location, "' level $level, keys: '@keys'" );
156              
157             # get the call-back to use
158 20         218 my $cb = $self->get_cb( $node->element_type($element) . '_element' );
159              
160             # use the same algorithm for check_important and
161             # scan_element pseudo elements
162 20 100       71 my $i = $self->{forward} == 1 ? 0 : 1;
163              
164 20   100     89 while ( $i >= 0 and $i < 2 ) {
165 41 100 66     165 if ( $self->{call_back_on_important} and $i == 0 and $level eq 'important' ) {
      100        
166 3         20 $cb->( $self, $data_r, $node, $element, @keys );
167 3 50       2509 return if $self->{bail_out}; # may be modified in callback
168             # recompute keys as they may have been modified during call-back
169 3         12 @keys = $self->{scanner}->get_keys( $node, $element );
170             }
171              
172 41 50 66     153 if ( $self->{call_back_on_warning} and $i == 0 and $node->fetch_element($element)->has_warning ) {
      66        
173 0         0 $logger->info("hash_element_cb found elt with warning: '", $node->name, "' element $element");
174 0         0 $cb->( $self, $data_r, $node, $element, @keys );
175             }
176              
177 41 100       436 if ( $i == 1 ) {
178 21 100       48 my $j = $self->{forward} == 1 ? 0 : $#keys;
179 21   100     86 while ( $j >= 0 and $j < @keys ) {
180 33         63 my $k = $keys[$j];
181 33         141 $logger->info( "hash_element_cb (element $element) calls ", "scan_hash on key $k" );
182 33         311 $self->{scanner}->scan_hash( $data_r, $node, $element, $k );
183 33         151 $j += $self->{forward};
184             }
185             }
186 41         144 $i += $self->{forward};
187             }
188 20         63 return;
189             }
190              
191             # internal. This call-back is passed to ObjTreeScanner. It will also
192             # check if the leaf element is flagged as 'important' or if the leaf
193             # element contains an error (mostly undefined mandatory values) and
194             # call user's call-back if needed
195              
196             sub leaf_cb {
197 175     175 1 371 my ( $self, $scanner, $data_r, $node, $element, $index, $value_obj ) = @_;
198              
199 175 100       422 $logger->info(
200             "leaf_cb called on '",
201             $node->name,
202             "' element '$element'",
203             defined $index ? ", index $index" : ''
204             );
205              
206 175         1473 my $elt_type = $node->element_type($element);
207 175 100       575 my $key =
208             $elt_type eq 'check_list'
209             ? 'check_list_element'
210             : $value_obj->value_type . '_value';
211              
212 175         350 my $user_leaf_cb = $self->get_cb($key);
213              
214 175         436 my $level = $node->get_element_property( element => $element, property => 'level' );
215              
216 175 100 66     667 if ( $self->{call_back_on_important} and $level eq 'important' ) {
217 19 100       54 $logger->info(
218             "leaf_cb found important elt: '",
219             $node->name,
220             "' element $element",
221             defined $index ? ", index $index" : ''
222             );
223 19         158 $user_leaf_cb->( $self, $data_r, $node, $element, $index, $value_obj );
224             }
225              
226 175 100 66     14786 if ( $self->{call_back_on_warning} and $value_obj->warning_msg ) {
227 1 50       31 $logger->info(
228             "leaf_cb found elt with warning: '",
229             $node->name,
230             "' element $element",
231             defined $index ? ", index $index" : ''
232             );
233 1         15 $user_leaf_cb->( $self, $data_r, $node, $element, $index, $value_obj );
234             }
235              
236             # now need to check for errors...
237 175         3780 my $result;
238 175         276 eval { $result = $value_obj->fetch(); };
  175         484  
239              
240 175         344 my $e = $@;
241 175 100 66     569 if ( ref $e and $e->isa('Config::Model::Exception::User') ) {
    50          
    50          
242              
243             # ignore errors that has just been catched and call user call-back
244 3 50       14 $logger->info(
245             "leaf_cb oopsed on '",
246             $node->name,
247             "' element $element",
248             defined $index ? ", index $index" : ''
249             );
250 3         43 $user_leaf_cb->( $self, $data_r, $node, $element, $index, $value_obj, $e->error );
251             }
252             elsif ( ref $e ) {
253 0         0 $e->rethrow;
254             # does not return ...
255             }
256             elsif ($e) {
257 0         0 die "Iterator failed on value object: $e";
258             }
259 175         3160 return;
260             }
261              
262             sub go_forward {
263 2     2 1 60 my $self = shift;
264 2 50       26 $logger->info("Going forward") if $self->{forward} == -1;
265 2         20 $self->{forward} = 1;
266 2         5 return;
267             }
268              
269             sub go_backward {
270 3     3 1 69 my $self = shift;
271 3 50       22 $logger->info("Going backward") if $self->{forward} == 1;
272 3         33 $self->{forward} = -1;
273 3         16 return;
274             }
275              
276             1;
277              
278             # ABSTRACT: Iterates forward or backward a configuration tree
279              
280             __END__
281              
282             =pod
283              
284             =encoding UTF-8
285              
286             =head1 NAME
287              
288             Config::Model::Iterator - Iterates forward or backward a configuration tree
289              
290             =head1 VERSION
291              
292             version 2.153
293              
294             =head1 SYNOPSIS
295              
296             use Config::Model;
297              
298             # define configuration tree object
299             my $model = Config::Model->new;
300             $model->create_config_class(
301             name => "Foo",
302             element => [
303             [qw/bar baz/] => {
304             type => 'leaf',
305             value_type => 'string',
306             level => 'important' ,
307             },
308             ]
309             );
310             $model->create_config_class(
311             name => "MyClass",
312             element => [
313             foo_nodes => {
314             type => 'hash', # hash id
315             index_type => 'string',
316             level => 'important' ,
317             cargo => {
318             type => 'node',
319             config_class_name => 'Foo'
320             },
321             },
322             ],
323             );
324              
325             my $inst = $model->instance( root_class_name => 'MyClass' );
326             # create some Foo objects
327             $inst->config_root->load("foo_nodes:foo1 - foo_nodes:foo2 ") ;
328              
329             my $my_leaf_cb = sub {
330             my ($iter, $data_r,$node,$element,$index, $leaf_object) = @_ ;
331             print "leaf_cb called for ",$leaf_object->location,"\n" ;
332             } ;
333             my $my_hash_cb = sub {
334             my ($iter, $data_r,$node,$element,@keys) = @_ ;
335             print "hash_element_cb called for element $element with keys @keys\n" ;
336             } ;
337              
338             my $iterator = $inst -> iterator (
339             leaf_cb => $my_leaf_cb,
340             hash_element_cb => $my_hash_cb ,
341             );
342              
343             $iterator->start ;
344             ### prints
345             # hash_element_cb called for element foo_nodes with keys foo1 foo2
346             # leaf_cb called for foo_nodes:foo1 bar
347             # leaf_cb called for foo_nodes:foo1 baz
348             # leaf_cb called for foo_nodes:foo2 bar
349             # leaf_cb called for foo_nodes:foo2 baz
350              
351             =head1 DESCRIPTION
352              
353             This module provides a class that is able to iterate forward or backward a configuration tree.
354             The iterator stops and calls back user defined subroutines on one of the following condition:
355              
356             =over
357              
358             =item *
359              
360             A configuration item contains an error (mostly undefined mandatory
361             values)
362              
363             =item *
364              
365             A configuration item contains warnings and the constructor's argument
366             C<call_back_on_warning> was set.
367              
368             =item *
369              
370             A configuration item has a C<important> level and the constructor's argument
371             C<call_back_on_important> was set.. See
372             L<level parameter|Config::Model::Node/"Configuration class declaration">
373             for details.
374              
375             =back
376              
377             The iterator supports going forward and backward
378             (to support C<back> and C<next> buttons on a wizard widget).
379              
380             =head1 CONSTRUCTOR
381              
382             The constructor should be used only by L<Config::Model::Instance> with
383             the L<iterator|Config::Model::Instance/"iterator ( ... )">
384             method.
385              
386             =head1 Creating an iterator
387              
388             A iterator requires at least two kind of call-back:
389             a call-back for leaf elements and a call-back
390             for hash elements (which is also used for list elements).
391              
392             These call-back must be passed when creating the iterator (the
393             parameters are named C<leaf_cb> and C<hash_element_cb>)
394              
395             Here are the the parameters accepted by C<iterator>:
396              
397             =head2 call_back_on_important
398              
399             Whether to call back when an important element is found (default 0).
400              
401             =head2 call_back_on_warning
402              
403             Whether to call back when an item with warnings is found (default 0).
404              
405             =head2 status
406              
407             Specifies the status of the element scanned by the wizard (default
408             'standard').
409              
410             =head2 leaf_cb
411              
412             Subroutine called backed for leaf elements. See
413             L<Config::Model::ObjTreeScanner/"Callback prototypes"> for signature
414             and details. (mandatory)
415              
416             =head2 hash_element_cb
417              
418             Subroutine called backed for hash elements. See
419             L<Config::Model::ObjTreeScanner/"Callback prototypes"> for signature
420             and details. (mandatory)
421              
422             =head1 Custom callbacks
423              
424             By default, C<leaf_cb> is called for all types of leaf elements
425             (i.e enum. integer, strings, ...). But you can provide dedicated
426             call-back for each type of leaf:
427              
428             enum_value_cb, integer_value_cb, number_value_cb, boolean_value_cb,
429             uniline_value_cb, string_value_cb
430              
431             Likewise, you can also provide a call-back dedicated to list elements with
432             C<list_element_cb>
433              
434             =head1 Methods
435              
436             =head2 start
437              
438             Start the scan and perform call-back when needed. This function returns
439             when the scan is completely done.
440              
441             =head2 bail_out
442              
443             When called, a variable is set so that all call_backs returns as soon as possible. Used to
444             abort wizard.
445              
446             =head2 go_forward
447              
448             Set wizard in forward (default) mode.
449              
450             =head2 go_backward
451              
452             Set wizard in backward mode.
453              
454             =head1 AUTHOR
455              
456             Dominique Dumont, (ddumont at cpan dot org)
457              
458             =head1 SEE ALSO
459              
460             L<Config::Model>,
461             L<Config::Model::Instance>,
462             L<Config::Model::Node>,
463             L<Config::Model::HashId>,
464             L<Config::Model::ListId>,
465             L<Config::Model::Value>,
466             L<Config::Model::CheckList>,
467             L<Config::Model::ObjTreeScanner>,
468              
469             =head1 AUTHOR
470              
471             Dominique Dumont
472              
473             =head1 COPYRIGHT AND LICENSE
474              
475             This software is Copyright (c) 2005-2022 by Dominique Dumont.
476              
477             This is free software, licensed under:
478              
479             The GNU Lesser General Public License, Version 2.1, February 1999
480              
481             =cut