File Coverage

blib/lib/Config/Model/ObjTreeScanner.pm
Criterion Covered Total %
statement 125 129 96.9
branch 37 58 63.7
condition 24 52 46.1
subroutine 19 19 100.0
pod 6 7 85.7
total 211 265 79.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              
11             use strict;
12 59     59   345 use Config::Model::Exception;
  59         145  
  59         1397  
13 59     59   255 use Scalar::Util qw/blessed/;
  59         114  
  59         1124  
14 59     59   273 use Carp::Assert::More;
  59         109  
  59         2557  
15 59     59   25938 use Carp;
  59         187170  
  59         8333  
16 59     59   470 use warnings;
  59         133  
  59         2417  
17 59     59   315  
  59         112  
  59         1511  
18             use Carp qw/croak confess cluck/;
19 59     59   273  
  59         102  
  59         83362  
20             my $type = shift;
21             my %args = @_;
22 247     247 1 9987  
23 247         1398 my $self = { auto_vivify => 1, check => 'yes' };
24             bless $self, $type;
25 247         857  
26 247         468 $self->{leaf_cb} = delete $args{leaf_cb}
27             or croak __PACKAGE__, "->new: missing leaf_cb parameter";
28              
29 247 50       997 # we may use leaf_cb
30             $self->create_fallback( delete $args{fallback} || 'all' );
31              
32 247   100     1257 # get all call_backs
33             my @value_cb =
34             map { $_ . '_value_cb' } qw/boolean enum string uniline integer number reference/;
35              
36 247         572 foreach my $param (
  1729         3285  
37             qw/check node_element_cb hash_element_cb
38 247         774 list_element_cb check_list_element_cb node_content_cb
39             node_content_hook list_element_hook hash_element_hook
40             auto_vivify up_cb/, @value_cb
41             ) {
42             $self->{$param} = $args{$param} if defined $args{$param};
43             delete $args{$param}; # may exists but be undefined
44 4446 100       8700 croak __PACKAGE__, "->new: missing $param parameter"
45 4446         5138 unless defined $self->{$param};
46             }
47 4446 50       7231  
48             if (delete $args{experience}) {
49             carp "->new: experience parameter is deprecated";
50 247 50       829 }
51 0         0  
52             # this parameter is optional and does not need a fallback
53             $self->{node_dispatch_cb} = delete $args{node_dispatch_cb} || {};
54              
55 247   100     1106 croak __PACKAGE__, "->new: node_dispatch_cb is not a hash ref"
56             unless ref( $self->{node_dispatch_cb} ) eq 'HASH';
57              
58 247 50       861 croak __PACKAGE__, "->new: unexpected check: $self->{check}"
59             unless $self->{check} =~ /yes|no|skip/;
60              
61 247 50       1703 croak __PACKAGE__, "->new: unexpected parameter: ", join( ' ', keys %args )
62             if scalar %args;
63 247 50       704  
64             return $self;
65             }
66 247         832  
67             # internal
68             my $self = shift;
69             my $fallback = shift;
70              
71 247     247 0 451 map {
72 247         398 $self->{$_} =
73             sub { }
74             } qw/node_content_hook hash_element_hook list_element_hook/;
75 247         604  
76       2105     return if not defined $fallback or $fallback eq 'none';
77 741         2969  
78             my $done = 0;
79 247 50 33     1222  
80             if ( $fallback eq 'node' or $fallback eq 'all' ) {
81 247         444 $done++;
82             my $node_content_cb = sub {
83 247 50 33     1046 my ( $scanner, $data_r, $node, @element ) = @_;
84 247         395 map { $scanner->scan_element( $data_r, $node, $_ ) } @element;
85             };
86 984     984   2598  
87 984         1915 my $node_element_cb = sub {
  5559         13607  
88 247         889 my ( $scanner, $data_r, $node, $element_name, $key, $next_node ) = @_;
89             $scanner->scan_node( $data_r, $next_node );
90             };
91 291     291   655  
92 291         757 my $hash_element_cb = sub {
93 247         951 my ( $scanner, $data_r, $node, $element_name, @keys ) = @_;
94             map { $scanner->scan_hash( $data_r, $node, $element_name, $_ ) } @keys;
95             };
96 75     75   186  
97 75         170 $self->{list_element_cb} = $hash_element_cb;
  72         227  
98 247         967 $self->{hash_element_cb} = $hash_element_cb;
99             $self->{node_element_cb} = $node_element_cb;
100 247         517 $self->{node_content_cb} = $node_content_cb;
101 247         610 $self->{up_cb} = sub { }; # do nothing
102 247         497 }
103 247         429  
104 247     1264   879 if ( $fallback eq 'leaf' or $fallback eq 'all' ) {
105             $done++;
106             my $l = $self->{string_value_cb} ||= $self->{leaf_cb};
107 247 50 33     1107  
108 247         395 $self->{check_list_element_cb} ||= $l;
109 247   33     987 $self->{enum_value_cb} ||= $l;
110             $self->{integer_value_cb} ||= $l;
111 247   33     988 $self->{number_value_cb} ||= $l;
112 247   33     969 $self->{boolean_value_cb} ||= $l;
113 247   33     899 $self->{reference_value_cb} ||= $l;
114 247   33     1123 $self->{uniline_value_cb} ||= $l;
115 247   33     1034 }
116 247   33     1001  
117 247   33     863 croak __PACKAGE__, "->new: Unexpected fallback value '$fallback'. ",
118             "Expected 'node', 'leaf', 'all' or 'none'"
119             if not $done;
120 247 50       647 }
121              
122             my ( $self, $data_r, $node ) = @_;
123              
124             #print "scan_node ",$node->name,"\n";
125             # get all elements according to catalog
126 1281     1281 1 3076  
127             Config::Model::Exception::Internal->throw( error => "'$node' is not a Config::Model object" )
128             unless blessed($node)
129             and $node->isa("Config::Model::AnyThing");
130              
131 1281 50 33     9321 # skip exploration of warped out node
132             if ( $node->isa('Config::Model::WarpedNode') ) {
133             $node = $node->get_actual_node;
134             return unless defined $node;
135             }
136 1281 100       6029  
137 254         962 my $config_class = $node->config_class_name;
138 254 50       642 my $node_dispatch_cb = $self->{node_dispatch_cb}{$config_class};
139              
140             my $actual_cb = $node_dispatch_cb || $self->{node_content_cb};
141 1281         4147  
142 1281         2306 my @element_list = $node->get_element_name( check => $self->{check} );
143              
144 1281   66     3867 $self->{node_content_hook}->( $self, $data_r, $node, @element_list );
145              
146 1281         3996 # we could add here a "last element" call-back, but it's not
147             # very useful if the last element is a hash.
148 1281         4447 $actual_cb->( $self, $data_r, $node, @element_list );
149              
150             $self->{up_cb}->( $self, $data_r, $node );
151             }
152 1281         4178  
153             my ( $self, $data_r, $node, $element_name ) = @_;
154 1277         3717  
155             my $element_type = $node->element_type($element_name);
156              
157             my $autov = $self->{auto_vivify};
158 7208     7208 1 12812  
159             #print "scan_element $element_name ";
160 7208         17635 if ( $element_type eq 'hash' ) {
161              
162 7208         11470 #print "type hash\n";
163             my @keys = $self->get_keys( $node, $element_name );
164              
165 7208 100       26544 # if hash element grab keys and perform callback
    100          
    100          
    100          
    100          
    50          
166             $self->{hash_element_hook}->( $self, $data_r, $node, $element_name, @keys );
167             $self->{hash_element_cb}->( $self, $data_r, $node, $element_name, @keys );
168 373         965 }
169             elsif ( $element_type eq 'list' ) {
170              
171 373         1250 #print "type list\n";
172 373         1166 my @keys = $self->get_keys( $node, $element_name );
173             $self->{list_element_hook}->( $self, $data_r, $node, $element_name, @keys );
174             $self->{list_element_cb}->( $self, $data_r, $node, $element_name, @keys );
175             }
176             elsif ( $element_type eq 'check_list' ) {
177 498         1282  
178 498         1596 #print "type list\n";
179 498         1403 my $cl_elt = $node->fetch_element( name => $element_name, check => $self->{check} );
180             $self->{check_list_element_cb}->( $self, $data_r, $node, $element_name, undef, $cl_elt );
181             }
182             elsif ( $element_type eq 'node' ) {
183              
184 91         278 #print "type object\n";
185 91         339 # avoid auto-vivification
186             my $next_obj =
187             ( $autov or $node->is_element_defined($element_name) )
188             ? $node->fetch_element( name => $element_name, check => $self->{check} )
189             : undef;
190              
191             # if obj element, cb
192             $self->{node_element_cb}->( $self, $data_r, $node, $element_name, undef, $next_obj );
193             }
194 362 50 66     1848 elsif ( $element_type eq 'warped_node' ) {
195              
196             #print "type warped\n";
197 362         1368 my $next_obj =
198             ( $autov or $node->is_element_defined($element_name) )
199             ? $node->fetch_element( name => $element_name, check => $self->{check} )
200             : undef;
201             $self->{node_element_cb}->( $self, $data_r, $node, $element_name, undef, $next_obj ) if $next_obj;
202             }
203             elsif ( $element_type eq 'leaf' ) {
204             my $next_obj = $node->fetch_element( name => $element_name, check => $self->{check} );
205 257 50 66     1344 my $type = $next_obj->value_type;
206 257 50       1325 return unless $type;
207             my $cb_name = $type . '_value_cb';
208             my $cb = $self->{$cb_name};
209 5627         13485 croak "scan_element: No call_back specified for '$cb_name'"
210 5627         16757 unless defined $cb;
211 5627 50       9728 $cb->( $self, $data_r, $node, $element_name, undef, $next_obj );
212 5627         9066 }
213 5627         8545 else {
214 5627 50       9263 croak "Unexpected element_type: $element_type";
215             }
216 5627         12507 }
217              
218             my ( $self, $data_r, $node, $element_name, $key ) = @_;
219 0         0  
220             assert_like( $node->element_type($element_name), qr/(hash|list)/ );
221              
222             #print "scan_hash ",$node->name," element $element_name key $key ";
223             my $item = $node->fetch_element( name => $element_name, check => $self->{check} );
224 837     837 1 1813  
225             my $cargo_type = $item->cargo_type($element_name);
226 837         2189 my $next_obj = $item->fetch_with_id( index => $key, check => $self->{check} );
227              
228             if ( $cargo_type =~ /node$/ ) {
229 837         14155  
230             #print "type object or warped\n";
231 837         2978 $self->{node_element_cb}->( $self, $data_r, $node, $element_name, $key, $next_obj );
232 837         2808 }
233             elsif ( $cargo_type eq 'leaf' ) {
234 837 100       3151 my $cb_name = $next_obj->value_type . '_value_cb';
    50          
235             my $cb = $self->{$cb_name};
236             croak "scan_hash: No call_back specified for '$cb_name'"
237 445         1453 unless defined $cb;
238             $cb->( $self, $data_r, $node, $element_name, $key, $next_obj );
239             }
240 392         1390 else {
241 392         657 croak "Unexpected cargo_type: $cargo_type";
242 392 50       706 }
243             }
244 392         929  
245             goto &scan_hash;
246             }
247 0         0  
248             my ( $self, $node, $element_name ) = @_;
249              
250             my $element_type = $node->element_type($element_name);
251             my $item = $node->fetch_element( name => $element_name, check => $self->{check} );
252 91     91 1 236  
253             return $item->fetch_all_indexes
254             if $element_type eq 'hash'
255             || $element_type eq 'list';
256 874     874 1 1758  
257             Config::Model::Exception::Internal->throw(
258 874         1699 error => "called get_keys on non hash or non list" . " element $element_name",
259 874         2671 object => $node
260             );
261 874 50 66     4754  
262             }
263              
264             1;
265 0            
266             # ABSTRACT: Scan config tree and perform call-backs for each element or node
267              
268              
269             =pod
270              
271             =encoding UTF-8
272              
273             =head1 NAME
274              
275             Config::Model::ObjTreeScanner - Scan config tree and perform call-backs for each element or node
276              
277             =head1 VERSION
278              
279             version 2.151
280              
281             =head1 SYNOPSIS
282              
283             use Config::Model ;
284              
285             # define configuration tree object
286             my $model = Config::Model->new ;
287             $model ->create_config_class (
288             name => "MyClass",
289             element => [
290             [qw/foo bar/] => {
291             type => 'leaf',
292             value_type => 'string'
293             },
294             baz => {
295             type => 'hash',
296             index_type => 'string' ,
297             cargo => {
298             type => 'leaf',
299             value_type => 'string',
300             },
301             },
302              
303             ],
304             ) ;
305              
306             my $inst = $model->instance(root_class_name => 'MyClass' );
307              
308             my $root = $inst->config_root ;
309              
310             # put some data in config tree the hard way
311             $root->fetch_element('foo')->store('yada') ;
312             $root->fetch_element('bar')->store('bla bla') ;
313             $root->fetch_element('baz')->fetch_with_id('en')->store('hello') ;
314              
315             # put more data the easy way
316             my $steps = 'baz:fr=bonjour baz:hr="dobar dan"';
317             $root->load( steps => $steps ) ;
318              
319             # define leaf call back
320             my $disp_leaf = sub {
321             my ($scanner, $data_ref, $node,$element_name,$index, $leaf_object) = @_ ;
322             $$data_ref .= "disp_leaf called for '". $leaf_object->name.
323             "' value '".$leaf_object->fetch."'\n";
324             } ;
325              
326             # simple scanner, (print all values)
327             my $scan = Config::Model::ObjTreeScanner-> new (
328             leaf_cb => $disp_leaf, # only mandatory parameter
329             ) ;
330              
331             my $result = '';
332             $scan->scan_node(\$result, $root) ;
333             print $result ;
334              
335             =head1 DESCRIPTION
336              
337             This module creates an object that explores (depth first) a
338             configuration tree.
339              
340             For each part of the configuration tree, ObjTreeScanner object calls
341             one of the subroutine reference passed during construction. (a call-back
342             or a hook)
343              
344             Call-back and hook routines are called:
345              
346             =over
347              
348             =item *
349              
350             For each node containing elements (including root node)
351              
352             =item *
353              
354             For each element of a node. This element can be a list, hash, node or
355             leaf element.
356              
357             =item *
358              
359             For each item contained in a node, hash or list. This item can be a
360             leaf or another node.
361              
362             =back
363              
364             To continue the exploration, these call-backs must also call the
365             scanner. (i.e. perform another call-back). In other words the user's
366             subroutine and the scanner play a game of ping-pong until the tree is
367             completely explored.
368              
369             Hooks routines are not required to resume the exploration, i.e. to call
370             the scanner. This is done once the hook routine has returned.
371              
372             The scanner provides a set of default callback for the nodes. This
373             way, the user only have to provide call-backs for the leaves.
374              
375             The scan is started with a call to C<scan_node>. The first parameter
376             of scan_node is a ref that is passed untouched to all call-back. This
377             ref may be used to store whatever result you want.
378              
379             =head1 CONSTRUCTOR
380              
381             =head2 new
382              
383             One way or another, the ObjTreeScanner object must be able to find all
384             callback for all the items of the tree. All the possible call-back and
385             hooks are listed below:
386              
387             =over
388              
389             =item leaf callback:
390              
391             C<leaf_cb> is a catch-all generic callback. All other are specialized
392             call-back : C<enum_value_cb>, C<integer_value_cb>, C<number_value_cb>,
393             C<boolean_value_cb>, C<string_value_cb>, C<uniline_value_cb>,
394             C<reference_value_cb>
395              
396             =item node callback:
397              
398             C<node_content_cb> , C<node_dispatch_cb>
399              
400             =item node hooks:
401              
402             C<node_content_hook>
403              
404             =item element callback:
405              
406             All these call-backs are called on the elements of a node:
407             C<list_element_cb>, C<check_list_element_cb>, C<hash_element_cb>,
408             C<node_element_cb>, C<node_content_cb>.
409              
410             =item element hooks:
411              
412             C<list_element_hook>, C<hash_element_hook>.
413              
414             =back
415              
416             The user may specify all of them by passing a sub ref to the
417             constructor:
418              
419             $scan = Config::Model::ObjTreeScanner-> new
420             (
421             list_element_cb => sub { ... },
422             ...
423             )
424              
425             Or use some default callback using the fallback parameter. Note that
426             at least one callback must be provided: C<leaf_cb>.
427              
428             Optional parameter:
429              
430             =over
431              
432             =item fallback
433              
434             If set to C<node>, the scanner provides default call-back for node
435             items. If set to C<leaf>, the scanner sets all leaf callback (like
436             enum_value_cb ...) to string_value_cb or to the mandatory leaf_cb
437             value. "fallback" callback does not override callbacks provided by the
438             user.
439              
440             If set to C<all> , the scanner provides fallbacks for leaf and node.
441             By default, all fallback are provided.
442              
443             =item auto_vivify
444              
445             Whether to create configuration objects while scanning (default is 1).
446              
447             =item check
448              
449             C<yes>, C<no> or C<skip>.
450              
451             =back
452              
453             =head1 Callback prototypes
454              
455             =head2 Leaf callback
456              
457             C<leaf_cb> is called for each leaf of the tree. The leaf callback is
458             called with the following parameters:
459              
460             ($scanner, $data_ref,$node,$element_name,$index, $leaf_object)
461              
462             where:
463              
464             =over
465              
466             =item *
467              
468             C<$scanner> is the scanner object.
469              
470             =item *
471              
472             C<$data_ref> is a reference that is first passed to the first call of
473             the scanner. Then C<$data_ref> is relayed through the various
474             call-backs
475              
476             =item *
477              
478             C<$node> is the node that contain the leaf.
479              
480             =item *
481              
482             C<$element_name> is the element (or attribute) that contain the leaf.
483              
484             =item *
485              
486             C<$index> is the index (or hash key) used to get the leaf. This may
487             be undefined if the element type is scalar.
488              
489             =item *
490              
491             C<$leaf_object> is a L<Config::Model::Value> object.
492              
493             =back
494              
495             =head2 List element callback
496              
497             C<list_element_cb> is called on all list element of a node, i.e. call
498             on the list object itself and not in the elements contained in the
499             list.
500              
501             ($scanner, $data_ref,$node,$element_name,@indexes)
502              
503             C<@indexes> is a list containing all the indexes of the list.
504              
505             Example:
506              
507             sub my_list_element_cb {
508             my ($scanner, $data_ref,$node,$element_name,@idx) = @_ ;
509              
510             # custom code using $data_ref
511              
512             # resume exploration (if needed)
513             map {$scanner->scan_list($data_ref,$node,$element_name,$_)} @idx ;
514              
515             # note: scan_list and scan_hash are equivalent
516             }
517              
518             =head2 List element hook
519              
520             C<list_element_hook>: Works like the list element callback. Except that the calls to
521             C<scan_list> are not required. This is done once the hook returns.
522              
523             =head2 Check list element callback
524              
525             C<check_list_element_cb>: Like C<list_element_cb>, but called on a
526             check_list element.
527              
528             ($scanner, $data_ref,$node,$element_name, index, check_list_obj)
529              
530             C<index> is always undef as a check_list cannot be contained in a hash or list (yet)
531              
532             =head2 Hash element callback
533              
534             C<hash_element_cb>: Like C<list_element_cb>, but called on a
535             hash element.
536              
537             ($scanner, $data_ref,$node,$element_name,@keys)
538              
539             C<@keys> is an list containing all the keys of the hash.
540              
541             Example:
542              
543             sub my_hash_element_cb {
544             my ($scanner, $data_ref,$node,$element_name,@keys) = @_ ;
545              
546             # custom code using $data_ref
547              
548             # resume exploration
549             map {$scanner->scan_hash($data_ref,$node,$element_name,$_)} @keys ;
550             }
551              
552             =head2 Hash element hook
553              
554             C<hash_element_hook>: Works like the hash element callback. Except that the calls to
555             C<scan_hash> are not required. This is done once the hook returns.
556              
557             =head2 Node content callback
558              
559             C<node_content_cb>: This call-back is called foreach node (including
560             root node).
561              
562             ($scanner, $data_ref,$node,@element_list)
563              
564             C<@element_list> contains all the element names of the node.
565              
566             Example:
567              
568             sub my_content_cb {
569             my ($scanner, $data_ref,$node,@element) = @_ ;
570              
571             # custom code using $data_ref
572              
573             # resume exploration
574             map {$scanner->scan_element($data_ref, $node,$_)} @element ;
575             }
576              
577             =head2 Node content hook
578              
579             C<node_content_hook>: This hook is called foreach node (including
580             root node). Works like the node content call-back. Except that the calls to
581             C<scan_element> are not required. This is done once the hook returns.
582              
583             =head2 Dispatch node callback
584              
585             C<node_dispatch_cb>: Any callback specified in the hash is called for
586             each instance of the specified configuration class.
587             (this may include the root node).
588              
589             For instance, if you have:
590              
591             node_dispach_cb => {
592             ClassA => \&my_class_a_dispatch_cb,
593             ClassB => \&my_class_b_dispatch_cb,
594             }
595              
596             C<&my_class_a_dispatch_cb> is called for each instance of C<ClassA> and
597             C<&my_class_b_dispatch_cb> is called for each instance of C<ClassB>.
598              
599             They is called with the following parameters:
600              
601             ($scanner, $data_ref,$node,@element_list)
602              
603             C<@element_list> contains all the element names of the node.
604              
605             Example:
606              
607             sub my_class_a_dispatch_cb = {
608             my ($scanner, $data_ref,$node,@element) = @_ ;
609              
610             # custom code using $data_ref
611              
612             # resume exploration
613             map {$scanner->scan_element($data_ref, $node,$_)} @element ;
614             }
615              
616             =head2 Node element callback
617              
618             C<node_element_cb> is called for each node contained within a node
619             (i.e not with root node). This node can be held by a plain element or
620             a hash element or a list element:
621              
622             ($scanner, $data_ref,$node,$element_name,$key, $contained_node)
623              
624             C<$key> may be undef if C<$contained_node> is not a part of a hash or
625             a list. C<$element_name> and C<$key> specifies the element name and
626             key of the the contained node you want to scan. (passed with
627             C<$contained_node>) Note that C<$contained_node> may be undef if
628             C<auto_vivify> is 0.
629              
630             Example:
631              
632             sub my_node_element_cb {
633             my ($scanner, $data_ref,$node,$element_name,$key, $contained_node) = @_;
634              
635             # your custom code using $data_ref
636              
637             # explore next node
638             $scanner->scan_node($data_ref,$contained_node);
639             }
640              
641             =head1 METHODS
642              
643             =head2 scan_node
644              
645             Parameters: C<< ($data_r,$node) >>
646              
647             Explore the node and call either C<node_dispatch_cb> (if the node class
648             name matches the dispatch_node hash) B<or> (e.g. xor) C<node_element_cb> passing
649             all element names.
650              
651             C<up_cb> is called once the first callback returns.
652              
653             =head2 scan_element
654              
655             Parameters: C<< ($data_r,$node,$element_name) >>
656              
657             Explore the element and call either C<hash_element_cb>,
658             C<list_element_cb>, C<node_content_cb> or a leaf call-back (the leaf
659             call-back called depends on the Value object properties: enum, string,
660             integer and so on)
661              
662             =head2 scan_hash
663              
664             Parameters: C<< ($data_r,$node,$element_name,$key) >>
665              
666             Explore the hash member (or hash value) and call either C<node_content_cb> or
667             a leaf call-back.
668              
669             =head2 scan_list
670              
671             Parameters: C<< ($data_r,$node,$element_name,$index) >>
672              
673             Just like C<scan_hash>: Explore the list member and call either
674             C<node_content_cb> or a leaf call-back.
675              
676             =head2 get_keys
677              
678             Parameters: C<< ($node, $element_name) >>
679              
680             Returns an list containing the sorted keys of a hash element or returns
681             an list containing (0.. last_index) of an list element.
682              
683             Throws an exception if element is not an list or a hash element.
684              
685             =head1 AUTHOR
686              
687             Dominique Dumont, (ddumont at cpan dot org)
688              
689             =head1 SEE ALSO
690              
691             L<Config::Model>,L<Config::Model::Node>,L<Config::Model::Instance>,
692             L<Config::Model::HashId>,
693             L<Config::Model::ListId>,
694             L<Config::Model::CheckList>,
695             L<Config::Model::Value>
696              
697             =head1 AUTHOR
698              
699             Dominique Dumont
700              
701             =head1 COPYRIGHT AND LICENSE
702              
703             This software is Copyright (c) 2005-2022 by Dominique Dumont.
704              
705             This is free software, licensed under:
706              
707             The GNU Lesser General Public License, Version 2.1, February 1999
708              
709             =cut