File Coverage

blib/lib/Config/Model/HashId.pm
Criterion Covered Total %
statement 231 261 88.5
branch 88 132 66.6
condition 19 33 57.5
subroutine 32 34 94.1
pod 14 18 77.7
total 384 478 80.3


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::HashId 2.153; # TRIAL
11              
12 33     33   290 use Mouse;
  33         84  
  33         373  
13 33     33   19580 use 5.10.1;
  33         363  
14              
15 33     33   269 use Config::Model::Exception;
  33         80  
  33         1184  
16 33     33   204 use Carp;
  33         253  
  33         2866  
17              
18 33     33   292 use Mouse::Util::TypeConstraints;
  33         76  
  33         420  
19              
20             subtype 'HaskKeyArray' => as 'ArrayRef' ;
21             coerce 'HaskKeyArray' => from 'Str' => via { [$_] } ;
22              
23 33     33   6746 use Log::Log4perl qw(get_logger :levels);
  33         93  
  33         342  
24              
25             my $logger = get_logger("Tree::Element::Id::Hash");
26              
27             extends qw/Config::Model::AnyId/;
28              
29             with "Config::Model::Role::Grab";
30             with "Config::Model::Role::ComputeFunction";
31              
32             has data => ( is => 'rw', isa => 'HashRef', default => sub { {}; } );
33             has list => (
34             is => 'rw',
35             isa => 'ArrayRef[Str]',
36             traits => ['Array'],
37             default => sub { []; },
38             handles => {
39             _sort => 'sort_in_place',
40             }
41             );
42              
43             has [qw/default_keys auto_create_keys/] => (
44             is => 'rw',
45             isa => 'HaskKeyArray',
46             coerce => 1,
47             default => sub { []; }
48             );
49             has [qw/ordered write_empty_value/] => ( is => 'ro', isa => 'Bool', default => 0 );
50              
51             sub BUILD {
52 231     231 1 491 my $self = shift;
53              
54             # foreach my $wrong (qw/migrate_values_from/) {
55             # Config::Model::Exception::Model->throw (
56             # object => $self,
57             # error => "Cannot use $wrong with ".$self->get_type." element"
58             # ) if defined $self->{$wrong};
59             # }
60              
61             # could use "required", but we'd get a Moose error instead of a Config::Model
62             # error
63 231 50       2762 Config::Model::Exception::Model->throw(
64             object => $self,
65             error => "Undefined index_type"
66             ) unless defined $self->index_type;
67              
68 231         2213 return $self;
69             }
70              
71             sub set_properties {
72 264     264 0 470 my $self = shift;
73              
74 264         1101 $self->SUPER::set_properties(@_);
75              
76 263         608 my $idx_type = $self->{index_type};
77              
78             # remove unwanted items
79 263         496 my $data = $self->{data};
80              
81 263         480 my $idx = 1;
82             my $wrong = sub {
83 52     52   92 my $k = shift;
84 52 100       102 if ( $idx_type eq 'integer' ) {
85 42 50 66     163 return 1 if defined $self->{max_index} and $k > $self->{max_index};
86 42 50 66     115 return 1 if defined $self->{min_index} and $k < $self->{min_index};
87             }
88 52 50 66     117 return 1 if defined $self->{max_nb} and $idx++ > $self->{max_nb};
89 52         189 return 0;
90 263         1303 };
91              
92             # delete entries that no longer fit the constraints imposed by the
93             # warp mechanism
94 263         2288 foreach my $k ( sort keys %$data ) {
95 52 50       102 next unless $wrong->($k);
96 0         0 $logger->trace( "set_properties: ", $self->name, " deleting id $k" );
97 0         0 delete $data->{$k};
98             }
99             }
100              
101             sub _migrate {
102 1164     1164   1846 my $self = shift;
103              
104 1164 100       3135 return if $self->{migration_done};
105              
106             # migration must be done *after* initial load to make sure that all data
107             # were retrieved from the file before migration.
108 422 100       2619 return if $self->instance->initial_load;
109 228         610 $self->{migration_done} = 1;
110              
111 228 100       1048 if ( $self->{migrate_keys_from} ) {
    100          
112 1         5 my $followed = $self->safe_typed_grab( param => 'migrate_keys_from', check => 'no' );
113 1 50       7 if ( $logger->is_debug ) {
114 0         0 $logger->debug( $self->name, " migrate keys from ", $followed->name );
115             }
116              
117 1         12 for my $idx ($followed->fetch_all_indexes) {
118 4 50       15 $self->_store( $idx, undef ) unless $self->_defined($idx);
119             }
120             }
121             elsif ( $self->{migrate_values_from} ) {
122 2         10 my $followed = $self->safe_typed_grab( param => 'migrate_values_from', check => 'no' );
123 2 50       7 $logger->debug( $self->name, " migrate values from ", $followed->name )
124             if $logger->is_debug;
125 2         20 foreach my $item ( $followed->fetch_all_indexes ) {
126 5 50       20 next if $self->exists($item); # don't clobber existing entries
127 5         16 my $data = $followed->fetch_with_id($item)->dump_as_data( check => 'no' );
128 5         21 $self->fetch_with_id($item)->load_data($data);
129             }
130             }
131              
132             }
133              
134             sub get_type {
135 528     528 1 878 my $self = shift;
136 528         1306 return 'hash';
137             }
138              
139             sub get_info {
140 1     1 1 4 my $self = shift;
141              
142 1 50       7 my @items = (
143             'type: ' . $self->get_type . ( $self->ordered ? '(ordered)' : '' ),
144             'index: ' . $self->index_type,
145             'cargo: ' . $self->cargo_type,
146             );
147              
148 1 50       3 if ( $self->cargo_type eq 'node' ) {
149 1         10 push @items, "cargo class: " . $self->config_class_name;
150             }
151              
152 1         5 foreach my $what (qw/min_index max_index max_nb warn_if_key_match warn_unless_key_match/) {
153 5         44 my $v = $self->$what();
154 5         7 my $str = $what;
155 5         18 $str =~ s/_/ /g;
156 5 50       12 push @items, "$str: $v" if defined $v;
157             }
158              
159 1         15 return @items;
160             }
161              
162             # important: return the actual size (not taking into account auto-created stuff)
163             sub fetch_size {
164 456     456 1 838 my $self = shift;
165 456         716 return scalar keys %{ $self->{data} };
  456         1431  
166             }
167              
168             sub _fetch_all_indexes {
169 1201     1201   1958 my $self = shift;
170             return $self->{ordered}
171 133         832 ? @{ $self->{list} }
172 1201 100       2774 : sort keys %{ $self->{data} };
  1068         5676  
173             }
174              
175             # fetch without any check
176             sub _fetch_with_id {
177 1262     1262   2612 my ( $self, $key ) = @_;
178 1262         5187 return $self->{data}{$key};
179             }
180              
181             # store without any check
182             sub _store {
183 425     425   1051 my ( $self, $key, $value ) = @_;
184 419         1108 push @{ $self->{list} }, $key
185 425 100       1353 unless exists $self->{data}{$key};
186 425 100       1549 $self->notify_change(note => "added entry $key") if $self->write_empty_value;
187 425         1168 return $self->{data}{$key} = $value;
188             }
189              
190             sub _exists {
191 483     483   1074 my ( $self, $key ) = @_;
192 483         1742 return exists $self->{data}{$key};
193             }
194              
195             sub _defined {
196 2541     2541   4612 my ( $self, $key ) = @_;
197 2541 100       11215 return defined $self->{data}{$key} ? 1 : 0;
198             }
199              
200             #internal
201             sub auto_create_elements {
202 263     263 0 494 my $self = shift;
203              
204 263         776 my $auto_p = $self->auto_create_keys;
205 263 100       800 return unless defined $auto_p;
206              
207             # create empty slots
208 7 100       26 map { $self->_store( $_, undef ) unless exists $self->{data}{$_}; }
  32 50       91  
209             ( ref $auto_p ? @$auto_p : ($auto_p) );
210             }
211              
212             # internal
213             sub create_default {
214 925     925 0 1443 my $self = shift;
215 925         1390 my @temp = keys %{ $self->{data} };
  925         3242  
216              
217 925 100       2702 return if @temp;
218              
219             # hash is empty so create empty element for default keys
220 317         1071 my $def = $self->get_default_keys;
221 317         629 map { $self->_store( $_, undef ) } @$def;
  20         38  
222 317         921 $self->create_default_with_init;
223             }
224              
225             sub _delete {
226 11     11   42 my ( $self, $key ) = @_;
227              
228             # remove key in ordered list
229 11         38 @{ $self->{list} } = grep { $_ ne $key } @{ $self->{list} };
  11         35  
  27         77  
  11         43  
230              
231 11         47 return delete $self->{data}{$key};
232             }
233              
234             sub remove {
235 6     6 0 20 my $self = shift;
236 6         63 $self->delete(@_);
237             }
238              
239             sub _clear {
240 5     5   12 my ($self) = @_;
241 5         23 $self->{list} = [];
242 5         118 $self->{data} = {};
243             }
244              
245             sub sort {
246 2     2 1 6 my $self = shift;
247 2 50       23 if ($self->ordered) {
248 2         12 $self->_sort;
249             }
250             else {
251 0         0 Config::Model::Exception::User->throw(
252             object => $self,
253             message => "cannot call sort on non ordered hash"
254             );
255             }
256             }
257              
258             sub insort {
259 4     4 1 13 my ($self, $id) = @_;
260              
261 4 50       23 if ($self->ordered) {
262 4         16 my $elt = $self->fetch_with_id($id);
263 4         25 $self->_sort;
264 4         127 return $elt;
265             }
266             else {
267 0         0 Config::Model::Exception::User->throw(
268             object => $self,
269             message => "cannot call insort on non ordered hash"
270             );
271             }
272             }
273              
274             # hash only method
275             sub firstkey {
276 0     0 1 0 my $self = shift;
277              
278             $self->warp
279 0 0 0     0 if ( $self->{warp} and @{ $self->{warp_info}{computed_master} } );
  0         0  
280              
281 0 0       0 $self->create_default if defined $self->{default};
282              
283             # reset "each" iterator (to be sure, map is also an iterator)
284 0         0 my @list = $self->_fetch_all_indexes;
285 0         0 $self->{each_list} = \@list;
286 0         0 return shift @list;
287             }
288              
289             # hash only method
290             sub nextkey {
291 0     0 1 0 my $self = shift;
292              
293             $self->warp
294 0 0 0     0 if ( $self->{warp} and @{ $self->{warp_info}{computed_master} } );
  0         0  
295              
296 0         0 my $res = shift @{ $self->{each_list} };
  0         0  
297              
298 0 0       0 return $res if defined $res;
299              
300             # reset list for next call to next_keys
301 0         0 $self->{each_list} = [ $self->_fetch_all_indexes ];
302              
303 0         0 return;
304             }
305              
306             sub swap {
307 2     2 1 5 my $self = shift;
308 2         4 my ( $key1, $key2 ) = @_;
309              
310 2         6 foreach my $k (@_) {
311             Config::Model::Exception::User->throw(
312             object => $self,
313             message => "swap: unknow key $k"
314 4 50       13 ) unless exists $self->{data}{$k};
315             }
316              
317 2         4 my @copy = @{ $self->{list} };
  2         10  
318 2         7 for ( my $idx = 0 ; $idx <= $#copy ; $idx++ ) {
319 6 100       14 if ( $copy[$idx] eq $key1 ) {
320 2         4 $self->{list}[$idx] = $key2;
321             }
322 6 100       15 if ( $copy[$idx] eq $key2 ) {
323 2         5 $self->{list}[$idx] = $key1;
324             }
325             }
326              
327 2         12 $self->notify_change( note => "swap ordered hash keys '$key1' and '$key2'" );
328             }
329              
330             sub move {
331 6     6 1 2008 my $self = shift;
332 6         34 my ( $from, $to, %args ) = @_;
333              
334             Config::Model::Exception::User->throw(
335             object => $self,
336             message => "move: unknow key $from"
337 6 50       46 ) unless exists $self->{data}{$from};
338              
339 6         23 my $ok = $self->check_idx($to);
340              
341 6         18 my $check = $args{check};
342 6 50 33     19 if ($ok or $check eq 'no') {
    0          
343              
344             # this may clobber the old content of $self->{data}{$to}
345 6         31 $self->{data}{$to} = delete $self->{data}{$from};
346 6         14 delete $self->{warning_hash}{$from};
347              
348             # update index_value attribute in moved objects
349 6         56 $self->{data}{$to}->index_value($to);
350              
351 6         40 $self->notify_change( note => "rename key from '$from' to '$to'" );
352              
353             # data_mode is preset or layered or user. Actually only user
354             # mode makes sense here
355 6         40 my $imode = $self->instance->get_data_mode;
356 6         24 $self->set_data_mode( $to, $imode );
357              
358 6         373 my ( $to_idx, $from_idx );
359 6         17 my $list = $self->{list};
360 6         22 for (my $idx = 0; $idx <= $#$list; $idx++) {
361 22 100       47 $to_idx = $idx if $list->[$idx] eq $to;
362 22 100       57 $from_idx = $idx if $list->[$idx] eq $from;
363             }
364              
365 6 100       14 if ( defined $to_idx ) {
366             # Since $to is clobbered, $from takes its place in the list
367 2         5 $list->[$from_idx] = $to;
368              
369             # and the $from entry is removed from the list
370 2         5 splice @$list, $to_idx, 1;
371             }
372             else {
373             # $to is moved in the place of from in the list
374 4         16 $list->[$from_idx] = $to;
375             }
376             }
377             elsif ($check eq 'yes') {
378             Config::Model::Exception::WrongValue->throw(
379 0         0 error => join( "\n\t", @{ $self->{error} } ),
  0         0  
380             object => $self
381             );
382             }
383 6         33 $logger->debug("Skipped move $from -> $to");
384 6         61 return $ok;
385             }
386              
387             sub move_after {
388 3     3 1 1348 my $self = shift;
389 3         10 my ( $key_to_move, $ref_key ) = @_;
390              
391 3 50       12 if ( not $self->ordered ) {
392 0         0 $logger->warn("called move_after on unordered hash");
393 0         0 return;
394             }
395              
396 3         7 foreach my $k (@_) {
397             Config::Model::Exception::User->throw(
398             object => $self,
399             message => "swap: unknow key $k"
400 5 50       15 ) unless exists $self->{data}{$k};
401             }
402              
403             # remove the key to move in ordered list
404 3         5 @{ $self->{list} } = grep { $_ ne $key_to_move } @{ $self->{list} };
  3         10  
  15         27  
  3         8  
405              
406 3         6 my $list = $self->{list};
407              
408 3         4 my $msg;
409 3 100       8 if ( defined $ref_key ) {
410 2         9 for ( my $idx = 0 ; $idx <= $#$list ; $idx++ ) {
411 6 100       17 if ( $list->[$idx] eq $ref_key ) {
412 2         5 splice @$list, $idx + 1, 0, $key_to_move;
413 2         4 last;
414             }
415             }
416              
417 2         7 $msg = "moved key '$key_to_move' after '$ref_key'";
418             }
419             else {
420 1         4 unshift @$list, $key_to_move;
421 1         4 $msg = "moved key '$key_to_move' at beginning";
422             }
423              
424 3         11 $self->notify_change( note => $msg );
425              
426             }
427              
428             sub move_up {
429 1     1 1 3 my $self = shift;
430 1         5 my ($key) = @_;
431              
432 1 50       5 if ( not $self->ordered ) {
433 0         0 $logger->warn("called move_up on unordered hash");
434 0         0 return;
435             }
436              
437             Config::Model::Exception::User->throw(
438             object => $self,
439             message => "move_up: unknow key $key"
440 1 50       4 ) unless exists $self->{data}{$key};
441              
442 1         2 my $list = $self->{list};
443              
444             # we start from 1 as we can't move up idx 0
445 1         4 for ( my $idx = 1 ; $idx < scalar @$list ; $idx++ ) {
446 1 50       4 if ( $list->[$idx] eq $key ) {
447 1         3 $list->[$idx] = $list->[ $idx - 1 ];
448 1         3 $list->[ $idx - 1 ] = $key;
449 1         7 $self->notify_change( note => "moved up key '$key'" );
450 1         3 last;
451             }
452             }
453              
454             # notify_change is placed in the loop so the notification
455             # is not sent if the user tries to move up idx 0
456             }
457              
458             sub move_down {
459 1     1 1 3 my $self = shift;
460 1         2 my ($key) = @_;
461              
462 1 50       7 if ( not $self->ordered ) {
463 0         0 $logger->warn("called move_down on unordered hash");
464 0         0 return;
465             }
466              
467             Config::Model::Exception::User->throw(
468             object => $self,
469             message => "move_down: unknown key $key"
470 1 50       4 ) unless exists $self->{data}{$key};
471              
472 1         2 my $list = $self->{list};
473              
474             # we end at $#$list -1 as we can't move down last idx
475 1         6 for ( my $idx = 0 ; $idx < scalar @$list - 1 ; $idx++ ) {
476 2 100       7 if ( $list->[$idx] eq $key ) {
477 1         4 $list->[$idx] = $list->[ $idx + 1 ];
478 1         2 $list->[ $idx + 1 ] = $key;
479 1         6 $self->notify_change( note => "moved down key $key" );
480 1         3 last;
481             }
482             }
483              
484             # notify_change is placed in the loop so the notification
485             # is not sent if the user tries to move past last idx
486             }
487              
488             sub _load_data_from_hash {
489 30     30   62 my $self = shift;
490 30         90 my %args = @_;
491 30         61 my $data = $args{data};
492 30         141 my %backup = %$data ;
493              
494 30         70 my @ordered_keys;
495 30         77 my $from = '';
496              
497 30         141 my $order_key = '__'.$self->element_name.'_order';
498 30 100 66     221 if ( $self->{ordered} and (defined $data->{$order_key} or defined $data->{__order} )) {
    100 100        
      100        
      66        
499 3 50       20 @ordered_keys = @{ delete $data->{$order_key} or delete $data->{__order} };
  3         22  
500 3         7 $from = ' with '.$order_key;
501             }
502             elsif ( $self->{ordered} and (not $data->{__skip_order} and keys %$data > 1)) {
503 2         20 $logger->warn(
504             "HashId " . $self->location . ": loading ordered "
505             . "hash from hash ref without special key '__order'. Element "
506             . "order is not defined. If needed, this warning can be suppressed by passing "
507             . " key '__skip_order' set to 1."
508             );
509 2         159 $from = ' without '.$order_key;
510             }
511 30         61 delete $data->{__skip_order};
512              
513 30 100       86 if (@ordered_keys) {
514 3         12 my %data_keys = map { $_ => 1 ; } keys %$data;
  11         26  
515 3         11 my @left_keys;
516 3         11 foreach my $k (@ordered_keys) {
517 13 100       34 push @left_keys, $k unless delete $data_keys{$k};
518             }
519 3 100 66     19 if ( %data_keys or @left_keys) {
520 1         2 my @msg ;
521 1 50       4 push @msg, "Unlisted keys in __order:", keys %data_keys if %data_keys;
522 1 50       5 push @msg, "Extra keys in __order:", @left_keys if @left_keys;
523 1         19 Config::Model::Exception::LoadData->throw(
524             object => $self,
525             message => "load_data: ordered keys mistmatch: @msg",
526             wrong_data => \%backup,
527             );
528             }
529             }
530 29 100       162 my @load_keys = @ordered_keys ? @ordered_keys : sort keys %$data;
531              
532 29         240 $logger->info(
533             "HashId load_data (" . $self->location .
534             ") will load idx @load_keys from hash ref $from"
535             );
536 29         345 foreach my $elt (@load_keys) {
537 59         188 my $obj = $self->fetch_with_id($elt);
538 59 100       409 $obj->load_data( %args, data => $data->{$elt} ) if defined $data->{$elt};
539             }
540             }
541              
542             sub load_data {
543 32     32 1 1432 my $self = shift;
544 32 100       155 my %args = @_ > 1 ? @_ : ( data => shift );
545 32         81 my $data = delete $args{data};
546 32         149 my $check = $self->_check_check( $args{check} );
547              
548 32 100       198 if ( ref($data) eq 'HASH' ) {
    50          
    0          
549 30         157 $self->_load_data_from_hash(data => $data, %args);
550             }
551             elsif ( ref($data) eq 'ARRAY' ) {
552 2         28 $logger->info(
553             "HashId load_data (" . $self->location . ") will load idx 0..$#$data from array ref" );
554 2 50       49 $self->notify_change( note => "Converted ordered data to non ordered", really => 1) unless $self->ordered;
555 2         5 my $idx = 0;
556 2         9 while ( $idx < @$data ) {
557 8         20 my $elt = $data->[ $idx++ ];
558 8         25 my $obj = $self->fetch_with_id($elt);
559 8         46 $obj->load_data( %args, data => $data->[ $idx++ ] );
560             }
561             }
562             elsif ( defined $data ) {
563              
564             # we can skip undefined data
565 0 0         my $expected = $self->{ordered} ? 'array' : 'hash';
566 0           Config::Model::Exception::LoadData->throw(
567             object => $self,
568             message => "load_data called with non $expected ref arg",
569             wrong_data => $data,
570             );
571             }
572             }
573              
574             __PACKAGE__->meta->make_immutable;
575              
576             1;
577              
578             # ABSTRACT: Handle hash element for configuration model
579              
580             __END__
581              
582             =pod
583              
584             =encoding UTF-8
585              
586             =head1 NAME
587              
588             Config::Model::HashId - Handle hash element for configuration model
589              
590             =head1 VERSION
591              
592             version 2.153
593              
594             =head1 SYNOPSIS
595              
596             See L<Config::Model::AnyId/SYNOPSIS>
597              
598             =head1 DESCRIPTION
599              
600             This class provides hash elements for a L<Config::Model::Node>.
601              
602             The hash index can either be en enumerated type, a boolean, an integer
603             or a string.
604              
605             =head1 CONSTRUCTOR
606              
607             HashId object should not be created directly.
608              
609             =head1 Hash model declaration
610              
611             See
612             L<model declaration section|Config::Model::AnyId/"Hash or list model declaration">
613             from L<Config::Model::AnyId>.
614              
615             =head1 Methods
616              
617             =head2 get_type
618              
619             Returns C<hash>.
620              
621             =head2 fetch_size
622              
623             Returns the number of elements of the hash.
624              
625             =head2 sort
626              
627             Sort an ordered hash. Throws an error if called on a non ordered hash.
628              
629             =head2 insort
630              
631             Parameters: key
632              
633             Create a new element in the ordered hash while keeping alphabetical order of the keys
634              
635             Returns the newly created element.
636              
637             Throws an error if called on a non ordered hash.
638              
639             =head2 firstkey
640              
641             Returns the first key of the hash. Behaves like C<each> core perl
642             function.
643              
644             =head2 nextkey
645              
646             Returns the next key of the hash. Behaves like C<each> core perl
647             function.
648              
649             =head2 swap
650              
651             Parameters: C<< ( key1 , key2 ) >>
652              
653             Swap the order of the 2 keys. Ignored for non ordered hash.
654              
655             =head2 move
656              
657             Parameters: C<< ( key1 , key2 ) >>
658              
659             Rename key1 in key2.
660              
661             Also also optional check parameter to disable warning:
662              
663             move ('foo','bar', check => 'no')
664              
665             =head2 move_after
666              
667             Parameters: C<< ( key_to_move [ , after_this_key ] ) >>
668              
669             Move the first key after the second one. If the second parameter is
670             omitted, the first key is placed in first position. Ignored for non
671             ordered hash.
672              
673             =head2 move_up
674              
675             Parameters: C<< ( key ) >>
676              
677             Move the key up in a ordered hash. Attempt to move up the first key of
678             an ordered hash is ignored. Ignored for non ordered hash.
679              
680             =head2 move_down
681              
682             Parameters: C<< ( key ) >>
683              
684             Move the key down in a ordered hash. Attempt to move up the last key of
685             an ordered hash is ignored. Ignored for non ordered hash.
686              
687             =head2 load_data
688              
689             Parameters: C<< ( data => ( hash_ref | array_ref ) [ , check => ... , ... ]) >>
690              
691             Load data as a hash ref for standard hash.
692              
693             Ordered hash should be loaded with an array ref or with a hash
694             containing a special C<__order> element. E.g. loaded with either:
695              
696             [ a => 'foo', b => 'bar' ]
697              
698             or
699              
700             { __order => ['a','b'], b => 'bar', a => 'foo' }
701              
702             C<__skip_order> parameter can be used if loading order is not
703             important:
704              
705             { __skip_order => 1, b => 'bar', a => 'foo'}
706              
707             load_data can also be called with a single ref parameter.
708              
709             =head2 get_info
710              
711             Returns a list of information related to the hash. See
712             L<Config::Model::Value/get_info> for more details.
713              
714             =head1 AUTHOR
715              
716             Dominique Dumont, (ddumont at cpan dot org)
717              
718             =head1 SEE ALSO
719              
720             L<Config::Model>,
721             L<Config::Model::Instance>,
722             L<Config::Model::AnyId>,
723             L<Config::Model::ListId>,
724             L<Config::Model::Value>
725              
726             =head1 AUTHOR
727              
728             Dominique Dumont
729              
730             =head1 COPYRIGHT AND LICENSE
731              
732             This software is Copyright (c) 2005-2022 by Dominique Dumont.
733              
734             This is free software, licensed under:
735              
736             The GNU Lesser General Public License, Version 2.1, February 1999
737              
738             =cut