File Coverage

blib/lib/Config/Model/CheckList.pm
Criterion Covered Total %
statement 329 380 86.5
branch 160 214 74.7
condition 60 104 57.6
subroutine 53 60 88.3
pod 29 47 61.7
total 631 805 78.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::CheckList 2.153; # TRIAL
11              
12 19     19   161 use Mouse;
  19         49  
  19         170  
13 19     19   10139 use 5.010;
  19         81  
14              
15 19     19   113 use Config::Model::Exception;
  19         49  
  19         540  
16 19     19   164 use Config::Model::IdElementReference;
  19         64  
  19         594  
17 19     19   137 use Config::Model::Warper;
  19         49  
  19         676  
18 19     19   147 use List::MoreUtils qw/any none/;
  19         62  
  19         391  
19 19     19   17111 use Carp;
  19         52  
  19         1738  
20 19     19   142 use Log::Log4perl qw(get_logger :levels);
  19         62  
  19         205  
21 19     19   2902 use Storable qw/dclone/;
  19         50  
  19         42191  
22              
23             extends qw/Config::Model::AnyThing/;
24              
25             with "Config::Model::Role::WarpMaster";
26             with "Config::Model::Role::Grab";
27             with "Config::Model::Role::HelpAsText";
28             with "Config::Model::Role::ComputeFunction";
29              
30             my $logger = get_logger("Tree.Element.CheckList");
31             my $user_logger = get_logger("User");
32              
33             my @introspect_params = qw/refer_to computed_refer_to/;
34              
35             my @accessible_params = qw/default_list upstream_default_list choice ordered/;
36             my @allowed_warp_params = ( @accessible_params, qw/level/ );
37              
38             has [qw/backup data preset layered/] => ( is => 'rw', isa => 'HashRef', default => sub { {}; } );
39             has computed_refer_to => ( is => 'rw', isa => 'Maybe[HashRef]' );
40             has [qw/refer_to/] => ( is => 'rw', isa => 'Str' );
41             has [qw/ordered_data choice/] => ( is => 'rw', isa => 'ArrayRef', default => sub { []; } );
42             has [qw/ordered/] => ( is => 'ro', isa => 'Bool' );
43              
44             has [qw/warp help/] => ( is => 'rw', isa => 'Maybe[HashRef]' );
45              
46             around BUILDARGS => sub {
47             my $orig = shift;
48             my $class = shift;
49             my %args = @_;
50             my %h = map { ( $_ => $args{$_} ); } grep { defined $args{$_} } @allowed_warp_params;
51             return $class->$orig( backup => dclone( \%h ), @_ );
52             };
53              
54             sub BUILD {
55 57     57 1 160 my $self = shift;
56              
57 57 100 100     421 if ( defined $self->refer_to or defined $self->computed_refer_to ) {
58 29         123 $self->submit_to_refer_to();
59             }
60              
61 57         297 $self->set_properties(); # set will use backup data
62              
63 57 100       850 if ( defined $self->warp ) {
64 3         12 my $warp_info = $self->warp;
65 3         77 $self->{warper} = Config::Model::Warper->new(
66             warped_object => $self,
67             %$warp_info,
68             allowed => \@allowed_warp_params
69             );
70             }
71              
72 57         224 $self->cl_init;
73              
74 57         550 $logger->info( "Created check_list element " . $self->element_name );
75 57         1009 return $self;
76             }
77              
78             sub cl_init {
79 57     57 0 117 my $self = shift;
80              
81 57 100       229 $self->warp if ( $self->{warp} );
82              
83 57 100       193 if ( defined $self->{ref_object} ) {
84             my $level = $self->parent->get_element_property(
85             element => $self->{element_name},
86 29         308 property => 'level',
87             );
88 29 100       274 $self->{ref_object}->get_choice_from_referred_to if $level ne 'hidden';
89             }
90             }
91              
92             sub name {
93 524     524 1 859 my $self = shift;
94 524         1547 my $name = $self->{parent}->name . ' ' . $self->{element_name};
95 524         2485 return $name;
96             }
97              
98 159     159 0 1261 sub value_type { return 'check_list'; }
99              
100             # warning : call to 'set' are not cumulative. Default value are always
101             # restored. Lest keeping track of what was modified with 'set' is
102             # too hard for the user.
103             sub set_properties {
104 62     62 0 140 my $self = shift;
105              
106             # cleanup all parameters that are handled by warp
107 62         174 for (@allowed_warp_params) {
108 310         637 delete $self->{$_},
109             }
110              
111 62 50       273 if ( $logger->is_trace() ) {
112 0         0 my %h = @_;
113 0         0 my $keys = join( ',', keys %h );
114 0         0 $logger->trace("set_properties called on $self->{element_name} with $keys");
115             }
116              
117             # merge data passed to the constructor with data passed to set
118 62         521 my %args = ( %{ $self->{backup} }, @_ );
  62         291  
119              
120             # these are handled by Node or Warper
121 62         166 for (qw/level/) {
122 62         167 delete $args{$_}
123             }
124              
125 62   100     333 $self->{ordered} = delete $args{ordered} || 0;
126              
127 62 100       219 if ( defined $args{choice} ) {
128 30         55 my @choice = @{ delete $args{choice} };
  30         162  
129 30         80 $self->{default_choice} = \@choice;
130 30         95 $self->setup_choice(@choice);
131             }
132              
133 62 50       209 if ( defined $args{default} ) {
134 0         0 $logger->warn($self->name, ": default param is deprecated, use default_list");
135 0         0 $args{default_list} = delete $args{default};
136             }
137              
138 62 100       188 if ( defined $args{default_list} ) {
139 3         8 $self->{default_list} = delete $args{default_list};
140             }
141              
142             # store default data in a hash (more convenient)
143 62         126 $self->{default_data} = { map { $_ => 1 } @{ $self->{default_list} } };
  6         21  
  62         329  
144              
145 62 100       265 if ( defined $args{upstream_default_list} ) {
146 3         14 $self->{upstream_default_list} = delete $args{upstream_default_list};
147             }
148              
149             # store upstream default data in a hash (more convenient)
150             $self->{upstream_default_data} =
151 62         119 { map { $_ => 1 } @{ $self->{upstream_default_list} } };
  16         46  
  62         211  
152              
153 62 50       232 Config::Model::Exception::Model->throw(
154             object => $self,
155             error => "Unexpected parameters :" . join( ' ', keys %args ) ) if scalar keys %args;
156              
157 62 50       264 if ( $self->has_warped_slaves ) {
158 0         0 my $hash = $self->get_checked_list_as_hash; # force scalar context
159 0         0 $self->trigger_warp($hash, $self->fetch);
160             }
161             }
162              
163             sub setup_choice {
164 134     134 0 241 my $self = shift;
165 134 50       568 my @choice = ref $_[0] ? @{ $_[0] } : @_;
  0         0  
166              
167 134         807 $logger->trace("CheckList $self->{element_name}: setup_choice with @choice");
168              
169             # store all enum values in a hash. This way, checking
170             # whether a value is present in the enum set is easier
171 134 100       1382 delete $self->{choice_hash} if defined $self->{choice_hash};
172 134         319 for (@choice) {
173 832         1668 $self->{choice_hash}{$_} = 1;
174             }
175              
176 134         412 $self->{choice} = \@choice;
177              
178             # cleanup current preset and data if it does not fit current choices
179 134         336 foreach my $field (qw/preset data layered/) {
180 402 50       959 next unless defined $self->{$field}; # do not create if not present
181 402         552 foreach my $item ( keys %{ $self->{$field} } ) {
  402         1302  
182 109 50       300 delete $self->{$field}{$item} unless defined $self->{choice_hash}{$item};
183             }
184             }
185             }
186              
187             # Need to extract Config::Model::Reference (used by Value, and maybe AnyId).
188              
189             sub submit_to_refer_to {
190 29     29 0 68 my $self = shift;
191              
192 29 100       149 if ( defined $self->refer_to ) {
    50          
193 28         488 $self->{ref_object} = Config::Model::IdElementReference->new(
194             refer_to => $self->refer_to,
195             config_elt => $self,
196             );
197             }
198             elsif ( defined $self->computed_refer_to ) {
199 1         16 $self->{ref_object} = Config::Model::IdElementReference->new(
200             computed_refer_to => $self->computed_refer_to,
201             config_elt => $self,
202             );
203 1         5 my $var = $self->{computed_refer_to}{variables};
204              
205             # refer_to registration is done for all element that are used as
206             # variable for complex reference (ie '- $foo' , {foo => '- bar'} )
207 1         4 foreach my $path ( values %$var ) {
208              
209             # is ref during test case
210             #print "path is '$path'\n";
211 1 50       7 next if $path =~ /\$/; # next if path also contain a variable
212 1         6 my $master = $self->grab($path);
213 1 50       20 next unless $master->can('register_dependency');
214 1         17 $master->register_dependency($self);
215             }
216             }
217             else {
218 0         0 croak "checklist submit_to_refer_to: undefined refer_to or computed_refer_to";
219             }
220             }
221              
222             sub setup_reference_choice {
223 104     104 0 216 my $self = shift;
224 104         379 $self->setup_choice(@_);
225             }
226              
227             sub get_type {
228 138     138 1 274 my $self = shift;
229 138         366 return 'check_list';
230             }
231              
232 121     121 0 403 sub get_cargo_type { goto &cargo_type }
233              
234             sub cargo_type {
235 121     121 1 221 my $self = shift;
236 121         271 return 'leaf';
237             }
238              
239       12 0   sub apply_fixes {
240              
241             # no operation. THere's no check_value method because a check list
242             # supposed to be always correct. Hence apply_fixes is empty.
243             }
244              
245             sub notify_change {
246 59     59 1 144 my $self = shift;
247 59         231 my %args = @_;
248              
249 59 100 66     356 return if $self->instance->initial_load and not $args{really};
250              
251 55         222 $self->SUPER::notify_change( %args, value_type => $self->value_type );
252              
253             # shake all warped or computed objects that depends on me
254 55         300 foreach my $s ( $self->get_warped_slaves ) {
255 8 50       84 $logger->debug( "calling notify_change on slave ", $s->name )
256             if $logger->is_debug;
257 8         56 $s->needs_check(1);
258             }
259             }
260              
261              
262             # does not check the validity, but check the item of the check_list
263             sub check {
264 11     11 1 1921 my $self = shift;
265 11 50       58 my @list = ref $_[0] eq 'ARRAY' ? @{ $_[0] } : @_;
  0         0  
266 11 50       50 my %args = ref $_[0] eq 'ARRAY' ? @_[ 1, $#_ ] : ( check => 'yes' );
267 11         50 my $check = $self->_check_check( $args{check} );
268              
269 11 100       52 if ( defined $self->{ref_object} ) {
270 3         12 $self->{ref_object}->get_choice_from_referred_to;
271             }
272              
273 11         41 my @changed;
274 11         32 for (@list) {
275 14 50       43 push @changed, $_ if $self->_store( $_, 1, $check )
276             }
277              
278 9 50       78 $self->notify_change( note => "check @changed" )
279             unless $self->instance->initial_load;
280             }
281              
282             sub clear_item {
283 244     244 1 327 my $self = shift;
284 244         359 my $choice = shift;
285              
286 244         421 my $inst = $self->instance;
287 244 100       647 my $data_name =
    50          
288             $inst->preset ? 'preset'
289             : $inst->layered ? 'layered'
290             : 'data';
291 244         397 my $old_v = $self->{$data_name}{$choice};
292 244         307 my $changed = 0;
293 244 100       429 if ($old_v) {
294 34         48 $changed = 1;
295             }
296 244         376 delete $self->{$data_name}{$choice};
297              
298 244 50 33     440 if ( $self->{ordered} and $changed ) {
299 0         0 my $ord = $self->{ordered_data};
300 0         0 my @new = grep { $_ ne $choice } @$ord;
  0         0  
301 0         0 $self->{ordered_data} = \@new;
302             }
303 244         461 return $changed;
304             }
305              
306             # internal
307             sub _store {
308 706     706   16467 my ( $self, $choice, $value, $check ) = @_;
309              
310 706         1407 my $inst = $self->instance;
311              
312 706 50 66     1676 if ( $value != 0 and $value != 1 ) {
313 0         0 Config::Model::Exception::WrongValue->throw(
314             error => "store: check item value must be boolean, " . "not '$value'.",
315             object => $self
316             );
317 0         0 return;
318             }
319              
320 706   100     1477 my $ok = $self->{choice_hash}{$choice} || 0;
321 706         993 my $changed = 0;
322              
323 706 100       1228 if ($ok) {
324 697 100       1949 my $data_name =
    100          
325             $inst->preset ? 'preset'
326             : $inst->layered ? 'layered'
327             : 'data';
328 697         1124 my $old_v = $self->{$data_name}{$choice} ;
329 697 100 100     1686 if ( not defined $old_v or $old_v ne $value ) {
330             # no change notif when going from undef to 0 as the
331             # logical value does not change
332             {
333 19     19   202 no warnings qw/uninitialized/;
  19         61  
  19         68834  
  489         657  
334 489   75     1478 $changed = (!$old_v xor !$value);
335             }
336 489         896 $self->{$data_name}{$choice} = $value;
337             }
338              
339 697 100 100     1612 if ( $self->{ordered} and $value ) {
340 17         26 my $ord = $self->{ordered_data};
341 17 100       37 push @$ord, $choice unless scalar grep { $choice eq $_ } @$ord;
  41         98  
342             }
343             }
344             else {
345             my $err_str =
346             "Unknown check_list item '$choice'. Expected '"
347 9         36 . join( "', '", @{ $self->{choice} } ) . "'";
  9         42  
348             $err_str .= "\n\t" . $self->{ref_object}->reference_info
349 9 100       36 if defined $self->{ref_object};
350 9 100       28 if ($check eq 'yes') {
    50          
351 4         67 Config::Model::Exception::WrongValue->throw( error => $err_str, object => $self );
352             }
353             elsif ($check eq 'skip') {
354 5         51 $user_logger->warn($err_str);
355             }
356             }
357              
358 702 100 100     2366 if ( $ok
      100        
      33        
      66        
359             and $changed
360             and $self->has_warped_slaves
361             and not( $self->instance->layered or $self->instance->preset ) ) {
362 4         56 my $h = $self->get_checked_list_as_hash;
363 4         14 my $str = $self->fetch;
364 4         21 $self->trigger_warp($h , $str);
365             }
366              
367 702         3074 return $changed;
368             }
369              
370             sub get_arguments {
371 54     54 0 4910 my $self = shift;
372 54         96 my $arg = shift;
373 54 100       291 my @list = ref $arg eq 'ARRAY' ? @$arg : ($arg, @_);
374 54 100       278 my %args = ref $arg eq 'ARRAY' ? ( check => 'yes', @_ ) : (check => 'yes');
375 54         208 my $check = $self->_check_check( $args{check} );
376 54         245 return \@list, $check, \%args;
377             }
378              
379             sub uncheck {
380 4     4 1 17 my $self = shift;
381 4         12 my ($list, $check) = $self->get_arguments(@_);
382              
383 4 50       18 if ( defined $self->{ref_object} ) {
384 0         0 $self->{ref_object}->get_choice_from_referred_to;
385             }
386              
387 4         8 my @changed;
388 4         12 for ( @$list ) {
389 4 100       11 push @changed, $_ if $self->_store( $_, 0, $check )
390             }
391              
392 4 50       35 $self->notify_change( note => "uncheck @changed" )
393             unless $self->instance->initial_load;
394             }
395              
396             sub has_data {
397 3     3 1 1755 my $self = shift;
398 3         15 my @set = $self->get_checked_list(qw/mode custom/) ;
399 3         28 return scalar @set;
400             }
401              
402             {
403             my %accept_mode = map { ( $_ => 1 ) }
404             qw/custom standard preset default layered upstream_default non_upstream_default user backend/;
405              
406             sub is_bad_mode {
407 170     170 1 350 my ($self, $mode) = @_;
408 170 50 66     1144 if ( $mode and not defined $accept_mode{$mode} ) {
409 0         0 my $good_ones = join( ' or ', sort keys %accept_mode );
410 0         0 return "expected $good_ones as mode parameter, not $mode";
411             }
412             }
413             }
414              
415             sub is_checked {
416 12     12 1 24 my $self = shift;
417 12         20 my $choice = shift;
418 12         26 my %args = @_;
419 12   50     45 my $mode = $args{mode} || '';
420 12         50 my $check = $self->_check_check( $args{check} );
421              
422 12   50     46 my $ok = $self->{choice_hash}{$choice} || 0;
423              
424 12 50       31 if ($ok) {
    0          
425              
426 12 50       24 if ( my $err = $self->is_bad_mode($mode) ) {
427 0         0 croak "is_checked: $err";
428             }
429              
430 12         27 my $dat = $self->{data}{$choice};
431 12         22 my $pre = $self->{preset}{$choice};
432 12         20 my $def = $self->{default_data}{$choice};
433 12         24 my $ud = $self->{upstream_default_data}{$choice};
434 12         19 my $lay = $self->{layered}{$choice};
435 12   33     56 my $std_v = $pre // $def // 0;
      50        
436 12   33     34 my $non_up_def = $dat // $pre // $lay // $def // 0;
      33        
      0        
      0        
437 12   33     29 my $user_v = $dat // $pre // $lay // $def // $ud // 0;
      33        
      0        
      0        
      0        
438              
439 12 0 0     79 my $result =
    50 0        
    50 33        
    50          
    50          
    50          
    50          
    50          
    50          
    50          
440             $mode eq 'custom' ? ( $dat && !$std_v ? 1 : 0 )
441             : $mode eq 'preset' ? $pre
442             : $mode eq 'layered' ? $lay
443             : $mode eq 'upstream_default' ? $ud
444             : $mode eq 'default' ? $def
445             : $mode eq 'standard' ? $std_v
446             : $mode eq 'non_upstream_default' ? $ud
447             : $mode eq 'user' ? $user_v
448             : $mode eq 'backend' ? $dat // $std_v
449             : $dat // $std_v;
450              
451 12         59 return $result;
452             }
453             elsif ( $check eq 'yes' ) {
454             my $err_str =
455             "Unknown check_list item '$choice'. Expected '"
456 0         0 . join( "', '", @{ $self->{choice} } ) . "'";
  0         0  
457             $err_str .= "\n\t" . $self->{ref_object}->reference_info
458 0 0       0 if defined $self->{ref_object};
459 0         0 Config::Model::Exception::WrongValue->throw(
460             error => $err_str,
461             object => $self
462             );
463             }
464             }
465              
466             # get_choice is always called when using check_list, so having a
467             # warp safety check here makes sense
468              
469             sub get_choice {
470 234     234 1 1894 my $self = shift;
471              
472 234 100       595 if ( defined $self->{ref_object} ) {
473 74         378 $self->{ref_object}->get_choice_from_referred_to;
474             }
475              
476 234 100       684 if ( not defined $self->{choice} ) {
477 1         10 my $msg = "check_list element has no defined choice. " . $self->warp_error;
478 1         7 Config::Model::Exception::UnavailableElement->throw(
479             info => $msg,
480             object => $self->parent,
481             element => $self->element_name,
482             );
483             }
484              
485 233         421 return @{ $self->{choice} };
  233         1414  
486             }
487              
488             sub get_default_choice {
489 104     104 0 206 my $self = shift;
490 104 100       206 return @{ $self->{default_choice} || [] };
  104         617  
491             }
492              
493             sub get_builtin_choice {
494 0     0 0 0 carp "get_builtin_choice is deprecated, use get_upstream_default_choice";
495 0         0 goto &get_upstream_default_choice;
496             }
497              
498             sub get_upstream_default_choice {
499 0     0 0 0 my $self = shift;
500 0 0       0 return @{ $self->{upstream_default_data} || [] };
  0         0  
501             }
502              
503             sub get_help {
504 3     3 1 596 my $self = shift;
505 3         10 my $help = $self->{help};
506              
507 3 50       13 return $help unless @_;
508              
509 3         9 my $on_value = shift;
510 3 100 66     17 return $help->{$on_value} if defined $help and defined $on_value;
511              
512 2         6 return;
513             }
514              
515             sub get_info {
516 1     1 1 2 my $self = shift;
517              
518 1         3 my @items = ('type: check_list');
519 1 50       6 if ( defined $self->refer_to ) {
520 1         5 push @items, "refer_to: " . $self->refer_to;
521             }
522 1 50       6 push @items, "ordered: " . ( $self->ordered ? 'yes' : 'no' );
523 1         4 return @items;
524             }
525              
526             sub clear {
527 8     8 1 12377 my $self = shift;
528             # also triggers notify changes
529 8         20 for ($self->get_choice) {
530 208         383 $self->clear_item($_)
531             }
532             }
533              
534 0     0 0 0 sub clear_values { goto &clear; }
535              
536             sub clear_layered {
537 3     3 0 20 my $self = shift;
538 3         20 $self->{layered} = {};
539             }
540              
541             my %old_mode = ( built_in_list => 'upstream_default_list', );
542              
543             sub get_checked_list_as_hash {
544 158     158 1 1593 my $self = shift;
545 158 100       637 my %args = @_ > 1 ? @_ : ( mode => $_[0] );
546 158   100     556 my $mode = $args{mode} || '';
547              
548 158         458 foreach my $k ( keys %old_mode ) {
549 158 50       429 next unless $mode eq $k;
550 0         0 $mode = $old_mode{$k};
551 0         0 carp $self->location, " warning: deprecated mode parameter: $k, ", "expected $mode\n";
552             }
553              
554 158 50       453 if ( my $err = $self->is_bad_mode($mode)) {
555 0         0 croak "get_checked_list_as_hash: $err";
556             }
557              
558 158         351 my $dat = $self->{data};
559 158         271 my $pre = $self->{preset};
560 158         265 my $def = $self->{default_data};
561 158         249 my $lay = $self->{layered};
562 158         263 my $ud = $self->{upstream_default_data};
563              
564             # fill empty hash result
565 158         462 my %h = map { $_ => 0 } $self->get_choice;
  2406         4851  
566              
567 158         697 my %predef = ( %$def, %$pre );
568 158         459 my %std = ( %$ud, %$lay, %$def, %$pre );
569              
570             # use _std_backup if all data values are null (no checked items by user)
571 158 100   251   1157 my %old_dat = ( none { $_; } values %$dat ) ? %{ $self->{_std_backup} || {} } : %$dat;
  251 100       873  
  49         249  
572              
573 158 100 100 5   1160 if ( not $mode and any { $_; } values %predef and none { $_; } values %old_dat ) {
  27   100     95  
  5         21  
574              
575             # changed from nothing to default checked list that must be written
576 4         22 $self->{_std_backup} = { %$def, %$pre };
577 4         17 $self->notify_change( note => "use default checklist" );
578             }
579              
580             # custom test must compare the whole list at once, not just one item at a time.
581             my %result =
582 158 100 100     1492 $mode eq 'custom' ? ( ( grep { $dat->{$_} xor $std{$_} } keys %h ) ? ( %$pre, %$dat ) : () )
  384 100       1154  
    100          
    100          
    100          
    100          
    100          
    100          
583             : $mode eq 'preset' ? (%$pre)
584             : $mode eq 'layered' ? (%$lay)
585             : $mode eq 'upstream_default' ? (%$ud)
586             : $mode eq 'default' ? (%$def)
587             : $mode eq 'standard' ? %std
588             : $mode eq 'user' ? ( %h, %std, %$dat )
589             : ( %predef, %$dat );
590              
591 158 100       1454 return wantarray ? %result : \%result;
592             }
593              
594             sub get_checked_list {
595 150     150 1 12359 my $self = shift;
596              
597 150         416 my %h = $self->get_checked_list_as_hash(@_);
598 150 100       999 my @good_order = $self->{ordered} ? @{ $self->{ordered_data} } : sort keys %h;
  15         44  
599 150         416 my @res = grep { $h{$_} } @good_order;
  1127         1847  
600 150 100       963 return wantarray ? @res : \@res;
601             }
602              
603             sub fetch {
604 68     68 1 1504 my $self = shift;
605 68         235 return join( ',', $self->get_checked_list(@_) );
606             }
607              
608             sub fetch_custom {
609 1     1 0 3 my $self = shift;
610 1         5 return join( ',', $self->get_checked_list('custom') );
611             }
612              
613             sub fetch_preset {
614 0     0 0 0 my $self = shift;
615 0         0 return join( ',', $self->get_checked_list('preset') );
616             }
617              
618             sub fetch_layered {
619 0     0 0 0 my $self = shift;
620 0         0 return join( ',', $self->get_checked_list('layered') );
621             }
622              
623             sub get {
624 0     0 1 0 my $self = shift;
625 0         0 my $path = shift;
626 0 0       0 if ($path) {
627 0         0 Config::Model::Exception::User->throw(
628             object => $self,
629             message => "get() called with a value with non-empty path: '$path'"
630             );
631             }
632 0         0 return $self->fetch(@_);
633             }
634              
635             sub set {
636 3     3 1 1078 my ($self, $path, $list, %args) = @_;
637              
638 3         14 my $check_validity = $self->_check_check( $args{check} );
639 3 50       10 if ($path) {
640 0         0 Config::Model::Exception::User->throw(
641             object => $self,
642             message => "set() called with a value with non-empty path: '$path'"
643             );
644             }
645              
646 3         11 my @list = split /,/, $list;
647 3         10 return $self->set_checked_list( \@list, check => $check_validity );
648             }
649              
650             sub load {
651 17     17 1 101 goto &store;
652             }
653              
654             sub store {
655 22     22 1 7435 my $self = shift;
656 22 100       191 my %args =
    100          
657             @_ == 1 ? ( value => $_[0] )
658             : @_ == 3 ? ( 'value', @_ )
659             : @_;
660 22         126 my $check_validity = $self->_check_check( $args{check} );
661              
662 22         252 my @set = split /\s*,\s*/, $args{value};
663 22         88 foreach (@set) { s/^"|"$//g; s/\\"/"/g; }
  59         175  
  59         129  
664 22         100 $self->set_checked_list(\@set, check => $check_validity);
665             }
666              
667 0     0 1 0 sub store_set { goto &set_checked_list }
668              
669             sub set_checked_list {
670 47     47 1 12524 my $self = shift;
671 47         164 my ($list, $check) = $self->get_arguments(@_);
672              
673 47         457 $logger->trace("called with @$list");
674 47         464 my %set = map { $_ => 1 } @$list;
  139         495  
675 47         120 my @changed;
676              
677 47         169 foreach my $c ( $self->get_choice ) {
678 641   100     1887 my $v = delete $set{$c} // 0;
679 641 100       1226 push @changed, "$c:$v" if $self->_store( $c, $v, $check );
680             }
681              
682             # Items left in %set are unknown. _store will handle the error
683 47         211 foreach my $item (keys %set) {
684 4         21 $self->_store( $item, 1, $check );
685             }
686              
687 46         137 $self->{ordered_data} = $list;
688              
689 46 100 100     575 $self->notify_change( note => "set_checked_list @changed" )
690             if @changed and not $self->instance->initial_load;
691             }
692              
693             sub set_checked_list_as_hash {
694 5     5 1 4926 my $self = shift;
695 5 100       28 my %check_list = ref $_[0] eq 'HASH' ? %{ $_[0] } : @_;
  3         18  
696 5 100       33 my %args = ref $_[0] eq 'HASH' ? @_[ 1, $#_ ] : ( check => 'yes' );
697 5         18 my $check_validity = $self->_check_check( $args{check} );
698              
699 5         22 foreach my $c ( $self->get_choice ) {
700 73 100       149 if ( defined $check_list{$c} ) {
701 37         89 $self->_store( $c, $check_list{$c}, $check_validity );
702             }
703             else {
704 36         66 $self->clear_item($c);
705             }
706             }
707             }
708              
709             sub load_data {
710 12     12 1 37 my $self = shift;
711              
712 12 50       47 my %args = @_ > 1 ? @_ : ( data => shift );
713 12         32 my $data = $args{data};
714 12         58 my $check_validity = $self->_check_check( $args{check} );
715              
716 12 100       59 if ( ref($data) eq 'ARRAY' ) {
    100          
    50          
717 7         39 $self->set_checked_list($data, check => $check_validity);
718             }
719             elsif ( ref($data) eq 'HASH' ) {
720 3         11 $self->set_checked_list_as_hash($data, check => $check_validity);
721             }
722             elsif ( not ref($data) ) {
723 2         7 $self->set_checked_list([$data], check => $check_validity );
724             }
725             else {
726 0         0 Config::Model::Exception::LoadData->throw(
727             object => $self,
728             message => "check_list load_data called with unexpected type. ".
729             "Expected plain scalar, array or hash ref",
730             wrong_data => $data,
731             );
732             }
733             }
734              
735             sub swap {
736 1     1 1 628 my ( $self, $a, $b ) = @_;
737              
738 1         4 foreach my $param ( $a, $b ) {
739 2 50       7 unless ( $self->is_checked($param) ) {
740 0         0 my $err_str = "swap: choice $param must be set";
741 0         0 Config::Model::Exception::WrongValue->throw(
742             error => $err_str,
743             object => $self
744             );
745             }
746             }
747              
748             # perform swap in ordered list
749 1         3 foreach ( @{ $self->{ordered_data} } ) {
  1         4  
750 5 100       16 if ( $_ eq $a ) {
    100          
751 1         3 $_ = $b;
752             }
753             elsif ( $_ eq $b ) {
754 1         4 $_ = $a;
755             }
756             }
757             }
758              
759             sub move_up {
760 3     3 1 1851 my ( $self, $c ) = @_;
761              
762 3 50       12 unless ( $self->is_checked($c) ) {
763 0         0 my $err_str = "swap: choice $c must be set";
764 0         0 Config::Model::Exception::WrongValue->throw(
765             error => $err_str,
766             object => $self
767             );
768             }
769              
770             # perform move in ordered list
771 3         11 my $list = $self->{ordered_data};
772 3         11 for ( my $i = 1 ; $i < @$list ; $i++ ) {
773 12 100       32 if ( $list->[$i] eq $c ) {
774 2         11 $list->[$i] = $list->[ $i - 1 ];
775 2         5 $list->[ $i - 1 ] = $c;
776 2         10 last;
777             }
778             }
779             }
780              
781             sub move_down {
782 2     2 1 1234 my ( $self, $c ) = @_;
783              
784 2 50       6 unless ( $self->is_checked($c) ) {
785 0         0 my $err_str = "swap: choice $c must be set";
786 0         0 Config::Model::Exception::WrongValue->throw(
787             error => $err_str,
788             object => $self
789             );
790             }
791              
792             # perform move in ordered list
793 2         6 my $list = $self->{ordered_data};
794 2         8 for ( my $i = 0 ; $i + 1 < @$list ; $i++ ) {
795 6 100       18 if ( $list->[$i] eq $c ) {
796 1         4 $list->[$i] = $list->[ $i + 1 ];
797 1         3 $list->[ $i + 1 ] = $c;
798 1         3 last;
799             }
800             }
801             }
802              
803             # dummy to match Value call
804 2     2 0 14 sub warning_msg { '' }
805              
806             1;
807              
808             # ABSTRACT: Handle check list element
809              
810             __END__
811              
812             =pod
813              
814             =encoding UTF-8
815              
816             =head1 NAME
817              
818             Config::Model::CheckList - Handle check list element
819              
820             =head1 VERSION
821              
822             version 2.153
823              
824             =head1 SYNOPSIS
825              
826             use Config::Model;
827              
828             # define configuration tree object
829             my $model = Config::Model->new;
830             $model->create_config_class(
831             name => "MyClass",
832              
833             element => [
834              
835             # type check_list uses Config::Model::CheckList
836             my_check_list => {
837             type => 'check_list',
838             choice => [ 'A', 'B', 'C', 'D' ],
839             help => {
840             A => 'A effect is this',
841             D => 'D does that',
842             }
843             },
844             ],
845             );
846              
847             my $inst = $model->instance( root_class_name => 'MyClass' );
848              
849             my $root = $inst->config_root;
850              
851             # put data
852             $root->load( steps => 'my_check_list=A' );
853              
854             my $obj = $root->grab('my_check_list');
855              
856             my $v = $root->grab_value('my_check_list');
857             print "check_list value '$v' with help '", $obj->get_help($v), "'\n";
858              
859             # more data
860             $obj->check('D');
861             $v = $root->grab_value('my_check_list');
862             print "check_list new value is '$v'\n"; # prints check_list new value is 'A,D'
863              
864             =head1 DESCRIPTION
865              
866             This class provides a check list element for a L<Config::Model::Node>.
867             In other words, this class provides a list of booleans items. Each item
868             can be set to 1 or 0.
869              
870             The available items in the check list can be :
871              
872             =over
873              
874             =item *
875              
876             A fixed list (with the C<choice> parameter)
877              
878             =item *
879              
880             A dynamic list where the available choice are the keys of another hash
881             of the configuration tree. See L</"Choice reference"> for details.
882              
883             =back
884              
885             =head1 CONSTRUCTOR
886              
887             CheckList object should not be created directly.
888              
889             =head1 CheckList model declaration
890              
891             A check list element must be declared with the following parameters:
892              
893             =over
894              
895             =item type
896              
897             Always C<checklist>.
898              
899             =item choice
900              
901             A list ref containing the check list items (optional)
902              
903             =item refer_to
904              
905             This parameter is used when the keys of a hash are used to specify the
906             possible choices of the check list. C<refer_to> point to a hash or list
907             element in the configuration tree. See L<Choice reference> for
908             details. (optional)
909              
910             =item computed_refer_to
911              
912             Like C<refer_to>, but use a computed value to find the hash or list
913             element in the configuration tree. See L<Choice reference> for
914             details. (optional)
915              
916             =item default_list
917              
918             List ref to specify the check list items which are "on" by default.
919             (optional)
920              
921             =item ordered
922              
923             Specify whether the order of checked items must be preserved.
924              
925             =item help
926              
927             Hash ref to provide information on the check list items.
928              
929             =item warp
930              
931             Used to provide dynamic modifications of the check list properties
932             See L<Config::Model::Warper> for details
933              
934             =back
935              
936             For example:
937              
938             =over
939              
940             =item *
941              
942             A check list with help:
943              
944             choice_list => {
945             type => 'check_list',
946             choice => ['A' .. 'Z'],
947             help => { A => 'A help', E => 'E help' } ,
948             },
949              
950             =item *
951              
952             A check list with default values:
953              
954             choice_list_with_default => {
955             type => 'check_list',
956             choice => ['A' .. 'Z'],
957             default_list => [ 'A', 'D' ],
958             },
959              
960             =item *
961              
962             A check list whose available choice and default change depending on
963             the value of the C<macro> parameter:
964              
965             warped_choice_list => {
966             type => 'check_list',
967             warp => {
968             follow => '- macro',
969             rules => {
970             AD => {
971             choice => [ 'A' .. 'D' ],
972             default_list => ['A', 'B' ]
973             },
974             AH => { choice => [ 'A' .. 'H' ] },
975             }
976             }
977             },
978              
979             =back
980              
981             =head1 Introspection methods
982              
983             The following methods returns the checklist parameter :
984              
985             =over
986              
987             =item refer_to
988              
989             =item computed_refer_to
990              
991             =back
992              
993             =head1 Choice reference
994              
995             The choice items of a check_list can be given by another configuration
996             element. This other element can be:
997              
998             =over
999              
1000             =item *
1001              
1002             The keys of a hash
1003              
1004             =item *
1005              
1006             Another checklist. In this case only the checked items of the other
1007             checklist are available.
1008              
1009             =back
1010              
1011             This other hash or other checklist is indicated by the C<refer_to> or
1012             C<computed_refer_to> parameter. C<refer_to> uses the syntax of the
1013             C<steps> parameter of L<grab(...)|Config::Role::Grab/grab">
1014              
1015             See L<refer_to parameter|Config::Model::IdElementReference/"refer_to parameter">.
1016              
1017             =head2 Reference examples
1018              
1019             =over
1020              
1021             =item *
1022              
1023             A check list where the available choices are the keys of C<my_hash>
1024             configuration parameter:
1025              
1026             refer_to_list => {
1027             type => 'check_list',
1028             refer_to => '- my_hash'
1029             },
1030              
1031             =item *
1032              
1033             A check list where the available choices are the checked items of
1034             C<other_check_list> configuration parameter:
1035              
1036             other_check_list => {
1037             type => 'check_list',
1038             choice => [qw/A B C/]
1039             },
1040             refer_to_list => {
1041             type => 'check_list',
1042             refer_to => '- other_check_list'
1043             },
1044              
1045             =item *
1046              
1047             A check list where the available choices are the keys of C<my_hash>
1048             and C<my_hash2> and C<my_hash3> configuration parameter:
1049              
1050             refer_to_3_lists => {
1051             type => 'check_list',
1052             refer_to => '- my_hash + - my_hash2 + - my_hash3'
1053             },
1054              
1055             =item *
1056              
1057             A check list where the available choices are the specified choice and
1058             the choice of C<refer_to_3_lists> and a hash whose name is specified
1059             by the value of the C<indirection> configuration parameter (this
1060             example is admittedly convoluted):
1061              
1062             refer_to_check_list_and_choice => {
1063             type => 'check_list',
1064             computed_refer_to => {
1065             formula => '- refer_to_2_list + - $var',
1066             variables => {
1067             var => '- indirection '
1068             }
1069             },
1070             choice => [qw/A1 A2 A3/],
1071             },
1072              
1073             =back
1074              
1075             =head1 Methods
1076              
1077             =head2 get_type
1078              
1079             Returns C<check_list>.
1080              
1081             =head2 cargo_type
1082              
1083             Returns 'leaf'.
1084              
1085             =head2 check
1086              
1087             Set choice. Parameter is either a list of choices to set or
1088             a list ref and some optional parameter. I.e:
1089              
1090             check (\@list, check => 'skip') ;
1091              
1092             C<check> parameter decide on behavior in case of invalid
1093             choice value: either die (if yes) or discard bad value (if skip)
1094              
1095             =head2 uncheck
1096              
1097             Unset choice. Parameter is either a list of choices to unset or
1098             a list ref and some optional parameter. I.e:
1099              
1100             uncheck (\@list, check => 'skip') ;
1101              
1102             C<check> parameter decide on behavior in case of invalid
1103             choice value: either die (if yes) or discard bad value (if skip)
1104              
1105             =head2 is_checked
1106              
1107             Parameters: C<< ( choice, [ check => yes|skip ] , [ mode => ... ]) >>
1108              
1109             Return 1 if the given C<choice> was set. Returns 0 otherwise.
1110              
1111             C<check> parameter decide on behavior in case of invalid
1112             choice value: either die (if yes) or discard bad value (if skip)
1113              
1114             C<mode> is either: custom standard preset default layered upstream_default
1115              
1116             =head2 has_data
1117              
1118             Return true if the check_list contains a set of checks different from default
1119             or upstream default set of check.
1120              
1121             =head2 get_choice
1122              
1123             Returns an array of all items names that can be checked (i.e.
1124             that can have value 0 or 1).
1125              
1126             =head2 get_help
1127              
1128             Parameters: C<(choice_value)>
1129              
1130             Return the help string on this choice value
1131              
1132             =head2 get_info
1133              
1134             Returns a list of information related to the check list. See
1135             L<Config::Model::Value/get_info> for more details.
1136              
1137             =head2 clear
1138              
1139             Reset the check list (can also be called as C<clear_values>)
1140              
1141             =head2 clear_item
1142              
1143             Parameters: C<(choice_value)>
1144              
1145             Reset an element of the checklist.
1146              
1147             =head2 get_checked_list_as_hash
1148              
1149             Accept a parameter (referred below as C<mode> parameter) similar to
1150             C<mode> in L<Config::Model::Value/fetch>.
1151              
1152             Returns a hash (or a hash ref) of all items. The boolean value is the
1153             value of the hash.
1154              
1155             Example:
1156              
1157             { A => 0, B => 1, C => 0 , D => 1}
1158              
1159             By default, this method returns all items set by the user, or
1160             items set in preset mode or checked by default.
1161              
1162             With a C<mode> parameter set to a value from the list below, this method
1163             returns:
1164              
1165             =over
1166              
1167             =item backend
1168              
1169             The value written in config file, (ie. set by user or by layered data
1170             or preset or default)
1171              
1172             =item custom
1173              
1174             The list entered by the user. An empty list is returned if the list of
1175             checked items is identical to the list of items checked by default. The
1176             whole list of checked items is returned as soon as B<one> item is different
1177             from standard value.
1178              
1179             =item preset
1180              
1181             The list entered in preset mode
1182              
1183             =item standard
1184              
1185             The list set in preset mode or checked by default.
1186              
1187             =item default
1188              
1189             The default list (defined by the configuration model)
1190              
1191             =item layered
1192              
1193             The list specified in layered mode.
1194              
1195             =item upstream_default
1196              
1197             The list implemented by upstream project (defined in the configuration
1198             model)
1199              
1200             =item user
1201              
1202             The set that is active in the application. (ie. set by user or
1203             by layered data or preset or default or upstream_default)
1204              
1205             =item non_upstream_default
1206              
1207             The choice set by user or by layered data or preset or default.
1208              
1209             =back
1210              
1211             =head2 get_checked_list
1212              
1213             Parameters: C<< ( < mode > ) >>
1214              
1215             Returns a list (or a list ref) of all checked items (i.e. all items
1216             set to 1).
1217              
1218             =head2 fetch
1219              
1220             Parameters: C<< ( < mode > ) >>
1221              
1222             Returns a string listing the checked items (i.e. "A,B,C")
1223              
1224             =head2 get
1225              
1226             Parameters: C<< ( path [, < mode> ] ) >>
1227              
1228             Get a value from a directory like path.
1229              
1230             =head1 Method to check or clear items in the check list
1231              
1232             All these methods accept an optional C<check> parameter that can be:
1233              
1234             =over
1235              
1236             =item yes
1237              
1238             A wrong item to check trigger an exception (default)
1239              
1240             =item skip
1241              
1242             A wrong item trigger a warning
1243              
1244             =item no
1245              
1246             A wrong item is ignored
1247              
1248             =back
1249              
1250             =head2 set
1251              
1252             Parameters: C<< ( path, items_to_set, [ check => [ yes | no | skip ] ] ) >>
1253              
1254             Set a checklist with a directory like path. Since a checklist is a leaf, the path
1255             should be empty.
1256              
1257             The values are a comma separated list of items to set in the check list.
1258              
1259             Example :
1260              
1261             $leaf->set('','A,C,Z');
1262             $leaf->set('','A,C,Z', check => 'skip');
1263              
1264             =head2 set_checked_list
1265              
1266             Set all passed items to checked (1). All other available items
1267             in the check list are set to 0.
1268              
1269             Example, for a check list that contains A B C and D check items:
1270              
1271             # set cl to A=0 B=1 C=0 D=1
1272             $cl->set_checked_list('B','D')
1273             $cl->set_checked_list( [ 'B','D' ])
1274             $cl->set_checked_list( [ 'B','D' ], check => 'yes')
1275              
1276             =head2 store_set
1277              
1278             Alias to L</set_checked_list>, so a list and a check_list can use the same store method
1279              
1280             =head2 store
1281              
1282             Set all items listed in a string to checked. The items must be
1283             separated by commas. All other available items in the check list are
1284             set to 0.
1285              
1286             Example:
1287              
1288             $cl->store('B, D')
1289             $cl->store( value => 'B,C' )
1290             $cl->store( value => 'B,C', check => 'yes' )
1291              
1292             =head2 load
1293              
1294             Alias to L</store>.
1295              
1296             =head2 set_checked_list_as_hash
1297              
1298             Set check_list items. Missing items in the given hash of parameters
1299             are cleared (i.e. set to undef).
1300              
1301             Example for a check list containing A B C D
1302              
1303             $cl->set_checked_list_as_hash( { A => 1, B => 0} , check => 'yes' )
1304             # result A => 1 B => 0 , C and D are undef
1305              
1306             =head2 load_data
1307              
1308             Load items as an array or hash ref. Array is forwarded to
1309             L<set_checked_list> , and hash is forwarded to L<set_checked_list_as_hash>.
1310              
1311             Example:
1312              
1313             $cl->load_data(['A','B']) # cannot use check param here
1314             $cl->load_data( data => ['A','B'])
1315             $cl->load_data( data => ['A','B'], check => 'yes')
1316             $cl->load_data( { A => 1, B => 1 } )
1317             $cl->load_data( data => { A => 1, B => 1 }, check => 'yes')
1318              
1319             =head2 is_bad_mode
1320              
1321             Accept a mode parameter. This function checks if the mode is accepted
1322             by L</fetch> method. Returns an error message if not. For instance:
1323              
1324             if (my $err = $val->is_bad_mode('foo')) {
1325             croak "my_function: $err";
1326             }
1327              
1328             This method is intented as a helper ti avoid duplicating the list of
1329             accepted modes for functions that want to wrap fetch methods (like
1330             L<Config::Model::Dumper> or L<Config::Model::DumpAsData>)
1331              
1332             =head1 Ordered checklist methods
1333              
1334             All the methods below are valid only for ordered checklists.
1335              
1336             =head2 swap
1337              
1338             Parameters: C<< ( choice_a, choice_b) >>
1339              
1340             Swap the 2 given choice in the list. Both choice must be already set.
1341              
1342             =head2 move_up
1343              
1344             Parameters: C<< ( choice ) >>
1345              
1346             Move the choice up in the checklist.
1347              
1348             =head2 move_down
1349              
1350             Parameters: C<< ( choice ) >>
1351              
1352             Move the choice down in the checklist.
1353              
1354             =head1 AUTHOR
1355              
1356             Dominique Dumont, (ddumont at cpan dot org)
1357              
1358             =head1 SEE ALSO
1359              
1360             L<Config::Model>,
1361             L<Config::Model::Instance>,
1362             L<Config::Model::Node>,
1363             L<Config::Model::AnyId>,
1364             L<Config::Model::ListId>,
1365             L<Config::Model::HashId>,
1366             L<Config::Model::Value>
1367              
1368             =head1 AUTHOR
1369              
1370             Dominique Dumont
1371              
1372             =head1 COPYRIGHT AND LICENSE
1373              
1374             This software is Copyright (c) 2005-2022 by Dominique Dumont.
1375              
1376             This is free software, licensed under:
1377              
1378             The GNU Lesser General Public License, Version 2.1, February 1999
1379              
1380             =cut