File Coverage

blib/lib/Config/Model/Warper.pm
Criterion Covered Total %
statement 200 245 81.6
branch 67 112 59.8
condition 30 35 85.7
subroutine 24 25 96.0
pod 2 13 15.3
total 323 430 75.1


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 Mouse;
12 59     59   414  
  59         114  
  59         321  
13             use Log::Log4perl qw(get_logger :levels);
14 59     59   19604 use Data::Dumper;
  59         139  
  59         425  
15 59     59   6981 use Storable qw/dclone/;
  59         109  
  59         3098  
16 59     59   352 use Config::Model::Exception;
  59         119  
  59         2325  
17 59     59   357 use List::MoreUtils qw/any/;
  59         136  
  59         1508  
18 59     59   28027 use Carp;
  59         665515  
  59         351  
19 59     59   52554  
  59         152  
  59         100994  
20             has 'follow' => ( is => 'ro', isa => 'HashRef[Str]', required => 1 );
21             has 'rules' => ( is => 'ro', isa => 'ArrayRef', required => 1 );
22              
23             has 'warped_object' => (
24             is => 'ro',
25             isa => 'Config::Model::AnyThing',
26             handles => ['needs_check'],
27             weak_ref => 1,
28             required => 1
29             );
30              
31             has '_values' => (
32             traits => ['Hash'],
33             is => 'ro',
34             isa => 'HashRef[HashRef | Str | Undef ]',
35             default => sub { {} },
36             handles => {
37             _set_value => 'set',
38             _get_value => 'get',
39             _value_keys => 'keys',
40             },
41             );
42              
43             my $self = shift;
44             my $warper_name = shift;
45 559     559   670 my $item = $self->_get_value($warper_name);
46 559         607  
47 559         1048 return ref($item) eq 'HASH' ? join(',', each %$item) : $item;
48             }
49 559 100       5752  
50             has [qw/ _computed_masters _warped_nodes _registered_values/] => (
51             is => 'rw',
52             isa => 'HashRef',
53             init_arg => undef, # can't use this param in constructor
54             default => sub { {} },
55             );
56              
57             has allowed => ( is => 'rw', isa => 'ArrayRef' );
58             has morph => ( is => 'ro', isa => 'Bool' );
59              
60             my $logger = get_logger("Warper");
61              
62             # create the object, check args, but don't do anything else
63             my $self = shift;
64              
65             $logger->trace( "Warper new: created for " . $self->name );
66 438     438 1 796 $self->check_warp_args;
67              
68 438         1179 $self->register_to_all_warp_masters;
69 438         4098 $self->refresh_values_from_master;
70             $self->do_warp;
71 437         1412 }
72 437         1324  
73 437         16940 # should be called only at startup
74             my $self = shift;
75              
76             my $follow = $self->follow;
77              
78 444     444 0 766 # now, follow is only { w1 => 'warp1', w2 => 'warp2'}
79             foreach my $warper_name ( keys %$follow ) {
80 444         1029 $self->register_to_one_warp_master($warper_name);
81             }
82              
83 444         980 }
84 576         1567  
85             my $self = shift;
86             my $warper_name = shift || die "register_to_one_warp_master: missing warper_name";
87              
88             my $follow = $self->follow;
89             my $warper_path = $follow->{$warper_name};
90 576     576 0 761 $logger->debug( "Warper register_to_one_warp_master: '", $self->name, "' follows '$warper_name'" );
91 576   50     1212  
92             # need to register also to all warped_nodes found on the path
93 576         1119 my @command = ($warper_path);
94 576         999 my $warper;
95 576         1189 my $warped_node;
96             my $obj = $self->warped_object;
97             my $reg_values = $self->_registered_values;
98 576         3993  
99 576         981 return if defined $reg_values->{$warper_name};
100              
101 576         1234 while (@command) {
102 576         1319  
103             # may return undef object
104 576 100       1248 ( $obj, @command ) = $obj->grab(
105             step => \@command,
106 569         1313 mode => 'step_by_step',
107             grab_non_available => 1,
108             );
109 1275         4070  
110             if ( not defined $obj ) {
111             $logger->debug("Warper register_to_one_warp_master: aborted steps. Left '@command'");
112             last;
113             }
114              
115 1275 100       3001 my $obj_loc = $obj->location;
116 87         501  
117 87         613 $logger->debug("Warper register_to_one_warp_master: step to master $obj_loc");
118              
119             if ( $obj->isa('Config::Model::Value') or $obj->isa('Config::Model::CheckList')) {
120 1188         2760 $warper = $obj;
121             if ( defined $warped_node ) {
122 1188         3675  
123             # keep obj ref to be able to unregister later on
124 1188 100 100     13656 $self->_warped_nodes->{$warped_node}{$warper_name} = $obj;
125 482         806 }
126 482 100       1099 last;
127             }
128              
129 37         184 if ( $obj->isa('Config::Model::WarpedNode') ) {
130             $logger->debug("Warper register_to_one_warp_master: register to warped_node $obj_loc");
131 482         1220 if ( defined $warped_node ) {
132              
133             # keep obj ref to be able to unregister later on
134 706 100       3021 $self->_warped_nodes->{$warped_node}{$warper_name} = $obj;
135 124         505 }
136 124 50       878 $warped_node = $obj_loc;
137             $obj->register( $self, $warper_name );
138             }
139 0         0 }
140              
141 124         205 if ( defined $warper and scalar @command ) {
142 124         460 Config::Model::Exception::Model->throw(
143             object => $self->warped_object,
144             error => "Some steps are left (@command) from warper path $warper_path",
145             );
146 569 50 100     2199 }
147 0         0  
148             $logger->debug(
149             "Warper register_to_one_warp_master:",
150             $self->name,
151             " is warped by $warper_name => '$warper_path' location in tree is: '",
152             defined $warper ? $warper->name : 'unknown', "'"
153             );
154 569 100       3387  
155             return unless defined $warper;
156              
157             Config::Model::Exception::Model->throw(
158             object => $self->warped_object,
159             error => "warper $warper_name => '$warper_path' is not a leaf"
160 569 100       4351 ) unless $warper->isa('Config::Model::Value') or $obj->isa('Config::Model::CheckList');
161              
162 482 50 66     1887 # warp will register this value object in another value object
163             # (the warper). When the warper gets a new value, it will
164             # modify the warped object according to the data passed by the
165             # user.
166              
167             my $type = $warper->register( $self, $warper_name );
168              
169             $reg_values->{$warper_name} = $warper;
170              
171             # store current warp master value
172 482         1861 if ( $type eq 'computed' ) {
173             $self->_computed_masters->{$warper_name} = $warper;
174 482         1019 }
175             }
176              
177 482 100       1823 my ( $self, $warped_node_location ) = @_;
178 1         17  
179             my $wnref = $self->_warped_nodes;
180              
181             $logger->debug( "Warper refresh_affected_registrations: called on",
182             $self->name, " from $warped_node_location'" );
183 7     7 0 20  
184             #return unless defined $wnref ;
185 7         20  
186             # remove and unregister obj affected by this warped node
187 7         17 my $ref = delete $wnref->{$warped_node_location};
188              
189             foreach my $warper_name ( keys %$ref ) {
190             $logger->debug( "Warper refresh_affected_registrations: ",
191             $self->name, " unregisters from $warper_name'" );
192             delete $self->_registered_values->{$warper_name};
193 7         52 $ref->{$warper_name}->unregister( $self->name );
194             }
195 7         21  
196 4         10 $self->register_to_all_warp_masters;
197              
198 4         29 #map { $self->register_to_one_warp_master($_) } keys %$ref;
199 4         11 }
200              
201             # should be called only at startup
202 7         21 my $self = shift;
203              
204             # should get new value from warp master
205              
206             my $follow = $self->follow;
207              
208             # now, follow is only { w1 => 'warp1', w2 => 'warp2'}
209 437     437 0 712  
210             # should try to get values only for unregister or computed warp masters
211             foreach my $warper_name ( keys %$follow ) {
212             my $warper_path = $follow->{$warper_name};
213 437         925 $logger->debug( "Warper trigger: ", $self->name, " following $warper_name" );
214              
215             # warper can itself be warped out (part of a warped out node).
216             # not just 'not available'.
217              
218 437         1316 my $warper = $self->warped_object->grab(
219 562         4667 step => $warper_path,
220 562         1337 mode => 'loose',
221             );
222              
223             if ( defined $warper and $warper->get_type eq 'leaf' ) {
224             # read the warp master values, so I can warp myself just after.
225 562         4741 my $warper_value = $warper->fetch('allow_undef');
226             my $str = $warper_value // '<undef>';
227             $logger->debug( "Warper: '$warper_name' value is: '$str'" );
228             $self->_set_value( $warper_name => $warper_value );
229             }
230 562 100 100     2687 elsif ( defined $warper and $warper->get_type eq 'check_list' ) {
    100 66        
    50          
231             if ($logger->is_debug) {
232 473         1783 my $warper_value = $warper->fetch();
233 473   100     1429 $logger->debug( "Warper: '$warper_name' checked values are: '$warper_value'" );
234 473         2173 }
235 473         4388 # store checked values are data structure, not as string
236             $self->_set_value( $warper_name => scalar $warper->get_checked_list_as_hash() );
237             }
238 2 50       4 elsif ( defined $warper ) {
239 0         0 Config::Model::Exception::Model->throw(
240 0         0 error => "warp error: warp 'follow' parameter "
241             . "does not point to a leaf element",
242             object => $self->warped_object
243 2         19 );
244             }
245             else {
246 0         0 # consider that the warp master value is undef
247             $self->_set_value( $warper_name, '' );
248             $logger->debug("Warper: '$warper_name' is not available");
249             }
250             }
251              
252             }
253              
254 87         402 my $self = shift;
255 87         3731 return "Warper of " . $self->warped_object->name;
256             }
257              
258             # And I'm going to warp them ...
259             my $self = shift;
260              
261             # retrieve current value if not provided
262 3891     3891 0 5199 my $value =
263 3891         11584 @_
264             ? $_[0]
265             : $self->fetch_no_check;
266              
267             foreach my $ref ( @{ $self->{warp_these_objects} } ) {
268 0     0 0 0 my ( $warped, $warp_index ) = @$ref;
269             next unless defined $warped; # $warped is a weak ref and may vanish
270              
271 0 0       0 # pure warp of object
272             $logger->debug(
273             "Warper ", $self->name,
274             " warp_them: (value ",
275             ( defined $value ? $value : 'undefined' ),
276 0         0 ") warping '", $warped->name, "'"
  0         0  
277 0         0 );
278 0 0       0 $warped->warp( $value, $warp_index );
279             }
280             }
281 0 0       0  
282             my $self = shift;
283              
284             # check that rules element are array ref and store them for
285             # error checking
286             my $rules_ref = $self->rules;
287 0         0  
288             my @rules =
289             ref $rules_ref eq 'HASH' ? %$rules_ref
290             : ref $rules_ref eq 'ARRAY' ? @$rules_ref
291             : Config::Model::Exception::Model->throw(
292 438     438 0 692 error => "warp error: warp 'rules' parameter " . "is not a ref ($rules_ref)",
293             object => $self->warped_object
294             );
295              
296 438         1085 my $allowed = $self->allowed;
297              
298 438 50       2087 for ( my $r_idx = 0 ; $r_idx < $#rules ; $r_idx += 2 ) {
    50          
299             my $key_set = $rules[$r_idx];
300             my @keys = ref($key_set) ? @$key_set : ($key_set);
301              
302             my $v = $rules[ $r_idx + 1 ];
303             Config::Model::Exception::Model->throw(
304             object => $self->warped_object,
305             error => "rules value for @keys is not a hash ref ($v)"
306 438         1197 ) unless ref($v) eq 'HASH';
307              
308 438         1418 foreach my $pkey ( keys %$v ) {
309 1385         1938 Config::Model::Exception::Model->throw(
310 1385 50       2536 object => $self->warped_object,
311             error => "Warp rules error for '@keys': '$pkey' "
312 1385         1959 . "parameter is not allowed, "
313 1385 100       2676 . "expected '"
314             . join( "' or '", @$allowed ) . "'"
315             ) unless any {$pkey eq $_} @$allowed ;
316             }
317             }
318 1384         2911 }
319              
320             return map { ref $_ ? [@$_] : $_ } @_;
321             }
322              
323             # Internal. This method will change element properties (like level) according to the warp effect.
324             # For instance, if a warp rule make a node no longer available in a model, its level must change to
325 1428 50   3928   4842 # 'hidden'
  3928         8203  
326             my ( $self, $arg_ref ) = @_;
327              
328             my $warped_object = $self->warped_object;
329              
330             my @properties = qw/level/;
331 1 50   1   8  
  1         12  
332             if ( defined $warped_object->index_value ) {
333             $logger->debug("Warper set_parent_element_property: called on hash or list, aborted");
334             return;
335             }
336              
337             my $parent = $warped_object->parent;
338 754     754 0 1552 my $elt_name = $warped_object->element_name;
339             foreach my $property_name (@properties) {
340 754         1801 my $v = $arg_ref->{$property_name};
341             if ( defined $v ) {
342 754         3133 $logger->debug( "Warper set_parent_element_property: set '",
343             $parent->name, " $elt_name' $property_name with $v" );
344 754 100       3035 $parent->set_element_property(
345 11         31 property => $property_name,
346 11         65 element => $elt_name,
347             value => $v,
348             );
349 743         1697 }
350 743         1703 else {
351 743         1458  
352 743         1258 # reset ensures that property is reset to known state by default
353 743 100       1523 $logger->debug("Warper set_parent_element_property: reset $property_name");
354 76         194 $parent->reset_element_property(
355             property => $property_name,
356 76         543 element => $elt_name,
357             );
358             }
359             }
360             }
361              
362             # try to actually warp (change properties) of a warped object.
363             my $self = shift;
364              
365 667         2303 my %old_value_set = %{ $self->_values };
366 667         5197  
367             if (@_) {
368             my ( $value, $warp_name ) = @_;
369             $logger->debug(
370             "Warper: trigger called on ",
371             $self->name,
372             " with value '",
373             defined $value ? $value : '<undef>',
374             "' name $warp_name"
375             );
376 340     340 0 465 $self->_set_value( $warp_name => $value || '' );
377             }
378 340         436  
  340         1330  
379             # read warp master values that are computed
380 340 50       735 my $cm = $self->_computed_masters;
381 340         573 foreach my $name ( keys %$cm ) {
382 340 100       674 $self->_set_value( $name => $cm->{$name}->fetch );
383             }
384              
385             # check if new values are different from old values
386             my $same = 1;
387             foreach my $name ( $self->_value_keys ) {
388             my $old = $old_value_set{$name};
389 340   100     2877 my $new = $self->_get_value_gist($name);
390             $same = 0
391             if ( $old ? 1 : 0 xor $new ? 1 : 0 )
392             or ( $old and $new and $new ne $old );
393 340         11802 }
394 340         726  
395 0         0 if ($same) {
396             no warnings "uninitialized";
397             if ( $logger->is_debug ) {
398             $logger->debug(
399 340         445 "Warper: warp skipped because no change in value set ",
400 340         751 "(old: '", join( "' '", %old_value_set ),
401 559         2460 "' new: '", join( "' '", %{ $self->_values() } ), "')"
402 559         979 );
403 559 100 100     3570 }
    100 66        
    100 100        
      100        
404             return;
405             }
406              
407             $self->do_warp;
408 340 100       689 }
409 59     59   517  
  59         158  
  59         19739  
410 30 50       64 # undef values are changed to '' so compute_bool no longer returns
411             # undef. It returns either 1 or 0
412             my $self = shift;
413             my $expr = shift;
414 0         0  
  0         0  
415             $logger->trace("Warper compute_bool: called for '$expr'");
416              
417 30         177 # my $warp_value_set = $self->_values ;
418             $logger->debug( "Warper compute_bool: data:\n",
419             Data::Dumper->Dump( [ $self->_values ], ['data'] ) );
420 310         610  
421             # checklist: $stuff.is_set(&index)
422             # get_value of a checklist gives { 'val1' => 1, 'val2' => 0,...}
423             $expr =~ s/(\$\w+)\.is_set\(([&$"'\w]+)\)/$1.'->{'.$2.'}'/eg;
424              
425             $expr =~ s/&(\w+)/\$warped_obj->$1/g;
426 1618     1618 0 2248  
427 1618         2127 my @init_code;
428             my %eval_data ;
429 1618         5636 foreach my $warper_name ( $self->_value_keys ) {
430             $eval_data{$warper_name} = $self->_get_value($warper_name) ;
431             push @init_code, "my \$$warper_name = \$eval_data{'$warper_name'} ;";
432 1618         16073 }
433              
434             my $perl_code = join( "\n", @init_code, $expr );
435             $logger->trace("Warper compute_bool: eval code '$perl_code'");
436              
437 1618         88240 my $ret;
  10         67  
438             {
439 1618         3184 my $warped_obj = $self->warped_object ;
440             no warnings "uninitialized";
441 1618         2292 $ret = eval($perl_code); ## no critic (ProhibitStringyEval)
442             }
443 1618         4584  
444 2314         13758 if ($@) {
445 2314         26716 Config::Model::Exception::Model->throw(
446             object => $self->warped_object,
447             error => "Warp boolean expression failed:\n$@" . "eval'ed code is: \n$perl_code"
448 1618         3842 );
449 1618         4847 }
450              
451 1618         9368 $logger->debug( "compute_bool: eval result: ", ( $ret ? 'true' : 'false' ) );
452             return $ret;
453 1618         1893 }
  1618         3029  
454 59     59   431  
  59         2296  
  59         47596  
455 1618         100556 my $self = shift;
456              
457             my $warp_value_set = $self->_values;
458 1618 50       5953 my $rules = dclone( $self->rules );
459 0         0 my %rule_hash = @$rules;
460              
461             # try all boolean expression with warp_value_set to get the
462             # correct rule
463              
464             my $found_rule = {};
465 1618 100       6269 my $found_bool = ''; # this variable may be used later in error message
466 1618         13104  
467             foreach my $bool_expr (@$rules) {
468             next if ref($bool_expr); # it's a rule not a bool expr
469             my $res = $self->compute_bool($bool_expr);
470 754     754 0 1107 next unless $res;
471             $found_bool = $bool_expr;
472 754         1657 $found_rule = $rule_hash{$bool_expr} || {};
473 754         25863 $logger->trace(
474 754         3235 "do_warp found rule for '$bool_expr':\n",
475             Data::Dumper->Dump( [$found_rule], ['found_rule'] ) );
476             last;
477             }
478              
479 754         1314 if ( $logger->is_info ) {
480 754         1203 my @warp_str = map { defined $_ ? $_ : 'undef' } keys %$warp_value_set;
481              
482 754         1674 $logger->info(
483 2903 100       5499 "do_warp: warp called from '$found_bool' on '",
484 1618         3502 $self->warped_object->name,
485 1618 100       4001 "' with elements '",
486 333         552 join( "','", @warp_str ),
487 333   50     958 "', warp rule is ",
488 333         1456 ( scalar %$found_rule ? "" : 'not ' ),
489             "found"
490             );
491 333         16123 }
492              
493             $logger->trace( "do_warp: call set_parent_element_property on '",
494 754 100       2136 $self->name, "' with ", Data::Dumper->Dump( [$found_rule], ['found_rule'] ) );
495 7 50       34  
  7         28  
496             $self->set_parent_element_property($found_rule);
497 7 100       37  
498             $logger->debug(
499             "do_warp: call set_properties on '",
500             $self->warped_object->name,
501             "' with ", Data::Dumper->Dump( [$found_rule], ['found_rule'] ) );
502             eval { $self->warped_object->set_properties(%$found_rule); };
503              
504             if ($@) {
505             my @warp_str = map { defined $_ ? $_ : 'undef' } keys %$warp_value_set;
506             my $e = $@;
507             my $msg = ref $e ? $e->as_string : $e;
508 754         5510 Config::Model::Exception::Model->throw(
509             object => $self->warped_object,
510             error => "Warp failed when following '"
511 754         33332 . join( "','", @warp_str )
512             . "' from \"$found_bool\". Check model rules:\n\t"
513 754         2897 . $msg
514             );
515             }
516             }
517 754         32457  
  754         3520  
518             # Usually a warp error occurs when the item is not actually available
519 754 100       5666 # or when a setting is wrong. Then guiding the user toward a warp
520 1 50       4 # master value that has a rule attached to it is a good idea.
  1         4  
521 1         2  
522 1 50       52 # But sometime, the user wants to remove and item. In this case it
523 1         21 # must be warped out by setting a warp master value that has not rule
524             # attached. This case is indicated when $want_remove is set to 1
525             my ($self) = @_;
526              
527             return '' unless defined $self->{warp};
528             my $follow = $self->{warp}{follow};
529             my @rules = @{ $self->{warp}{rules} };
530              
531             # follow is either ['warp1','warp2',...]
532             # or { warp1 => {....} , ...} or 'warp'
533             my @warper_paths =
534             ref($follow) eq 'ARRAY' ? @$follow
535             : ref($follow) eq 'HASH' ? values %$follow
536             : ($follow);
537              
538             my $str =
539             "You may solve the problem by modifying "
540             . ( @warper_paths > 1 ? "one or more of " : '' )
541 7     7 1 13 . "the following configuration parameters:\n";
542              
543 7 50       38 my $expected_error = 'Config::Model::Exception::UnavailableElement';
544 0            
545 0           foreach my $warper_path (@warper_paths) {
  0            
546             my $warper_value;
547             my $warper;
548              
549 0 0         # try
    0          
550             eval {
551             $warper = $self->get_warper_object($warper_path);
552             $warper_value = $warper->fetch;
553             };
554 0 0         my $e = $@;
555             # catch
556             if ( ref($e) eq $expected_error ) {
557             $str .= "\t'$warper_path' which is unavailable\n";
558             next;
559 0           }
560              
561 0           $warper_value = 'undef' unless defined $warper_value;
562 0            
563             my @choice =
564             defined $warper->choice ? @{ $warper->choice }
565             : $warper->{value_type} eq 'boolean' ? ( 0, 1 )
566 0           : ();
567 0            
568 0           my @try = sort grep { $_ ne $warper_value } @choice;
569              
570 0           $str .= "\t'" . $warper->location . "': Try ";
571              
572 0 0         my $a = $warper->{value_type} =~ /^[aeiou]/ ? 'an' : 'a';
573 0            
574 0           $str .=
575             @try
576             ? "'" . join( "' or '", @try ) . "' instead of "
577 0 0         : "$a $warper->{value_type} value different from ";
578              
579             $str .= "'$warper_value'\n";
580 0            
581 0 0         if ( defined $warper->{compute} ) {
    0          
582             $str .= "\n\tHowever, '" . $warper->name . "' " . $warper->compute_info . "\n";
583             }
584 0           }
  0            
585              
586 0           $str .= "Warp parameters:\n" . Data::Dumper->Dump( [ $self->{warp} ], ['warp'] )
587             if $logger->is_debug;
588 0 0          
589             return $str;
590 0 0         }
591              
592             __PACKAGE__->meta->make_immutable;
593              
594             # ABSTRACT: Warp tree properties
595 0            
596             1;
597 0 0          
598 0            
599             =pod
600              
601             =encoding UTF-8
602 0 0          
603             =head1 NAME
604              
605 0           Config::Model::Warper - Warp tree properties
606              
607             =head1 VERSION
608              
609             version 2.151
610              
611             =head1 SYNOPSIS
612              
613             # internal class
614              
615             =head1 DESCRIPTION
616              
617             Depending on the value of a warp master (In fact a L<Config::Model::Value> or a L<Config::Model::CheckList> object),
618             this class changes the properties of a node (L<Config::Model::WarpedNode>),
619             a hash (L<Config::Model::HashId>), a list (L<Config::Model::ListId>),
620             a checklist (L<Config::Model::CheckList>) or another value.
621              
622             =head1 Warper and warped
623              
624             Warping an object means that the properties of the object is
625             changed depending on the value of another object.
626              
627             The changed object is referred as the I<warped> object.
628              
629             The other object that holds the important value is referred as the
630             I<warp master> or the I<warper> object.
631              
632             You can also set up several warp master for one warped object. This
633             means that the properties of the warped object is changed
634             according to a combination of values of the warp masters.
635              
636             =head1 Warp arguments
637              
638             Warp arguments are passed in a hash ref whose keys are C<follow> and
639             and C<rules>:
640              
641             =head2 Warp follow argument
642              
643             L<Grab string|Config::Model::Role::Grab/grab> leading to the
644             C<Config::Model::Value> or L<Config::Model::CheckList> warp master. E.g.:
645              
646             follow => '! tree_macro'
647              
648             In case of several warp master, C<follow> is set to an array ref
649             of several L<grab string|Config::Model::Role::Grab/grab>:
650              
651             follow => [ '! macro1', '- macro2' ]
652              
653             You can also use named parameters:
654              
655             follow => { m1 => '! macro1', m2 => '- macro2' }
656              
657             Note: By design C<follow> argument of warper module is a plain path to keep
658             warp mechanism (relatively) simple. C<follow> argument
659             of L<Config::Model::ValueComputer> has more features and is documented
660             L<there|Config::Model::ValueComputer/"Compute variables">
661              
662             =head2 Warp rules argument
663              
664             String, hash ref or array ref that specify the warped object property
665             changes. These rules specifies the actual property changes for the
666             warped object depending on the value(s) of the warp master(s).
667              
668             E.g. for a simple case (rules is a hash ref) :
669              
670             follow => '! macro1' ,
671             rules => { A => { <effect when macro1 is A> },
672             B => { <effect when macro1 is B> }
673             }
674              
675             In case of similar effects, you can use named parameters and
676             a boolean expression to specify the effect. The first match
677             is applied. In this case, rules is a list ref:
678              
679             follow => { m => '! macro1' } ,
680             rules => [ '$m eq "A"' => { <effect for macro1 == A> },
681             '$m eq "B" or $m eq"C "' => { <effect for macro1 == B|C > }
682             ]
683              
684             In case of several warp masters, C<follow> must use named parameters, and
685             rules must use boolean expression:
686              
687             follow => { m1 => '! macro1', m2 => '- macro2' } ,
688             rules => [
689             '$m1 eq "A" && $m2 eq "C"' => { <effect for A C> },
690             '$m1 eq "A" && $m2 eq "D"' => { <effect for A D> },
691             '$m1 eq "B" && $m2 eq "C"' => { <effect for B C> },
692             '$m1 eq "B" && $m2 eq "D"' => { <effect for B D> },
693             ]
694              
695             Of course some combinations of warp master values can have the same
696             effect:
697              
698             follow => { m1 => '! macro1', m2 => '- macro2' } ,
699             rules => [
700             '$m1 eq "A" && $m2 eq "C"' => { <effect X> },
701             '$m1 eq "A" && $m2 eq "D"' => { <effect Y> },
702             '$m1 eq "B" && $m2 eq "C"' => { <effect Y> },
703             '$m1 eq "B" && $m2 eq "D"' => { <effect Y> },
704             ]
705              
706             In this case, you can use different boolean expression to save typing:
707              
708             follow => { m1 => '! macro1', m2 => '- macro2' } ,
709             rules => [
710             '$m1 eq "A" && $m2 eq "C"' => { <effect X> },
711             '$m1 eq "A" && $m2 eq "D"' => { <effect Y> },
712             '$m1 eq "B" && ( $m2 eq "C" or $m2 eq "D") ' => { <effect Y> },
713             ]
714              
715             Note that the boolean expression is sanitized and used in a Perl
716             eval, so you can use most Perl syntax and regular expressions.
717              
718             Functions (like C<&foo>) are called like C<< $self->foo >> before evaluation
719             of the boolean expression.
720              
721             The rules must be declared with a slightly different way when a
722             check_list is used as a warp master: a check_list has not a simple
723             value. The rule must check whether a value is checked or not amongs
724             all the possible items of a check list.
725              
726             For example, let's say that C<$cl> in the rule below point to a check list whose
727             items are C<A> and C<B>. The rule must verify if the item is set or not:
728              
729             rules => [
730             '$cl.is_set(A)' => { <effect when A is set> },
731             '$cl.is_set(B)' => { <effect when B is set> },
732             # can be combined
733             '$cl.is_set(B) and $cl.is_set(A)' => { <effect when A and B are set> },
734             ],
735              
736             With this feature, you can control with a check list whether some element must
737             be shown or not (assuming C<FooClass> and C<BarClass> classes are declared):
738              
739             element => [
740             # warp master
741             my_check_list => {
742             type => 'check_list',
743             choice => ['has_foo','has_bar']
744             },
745             # controlled element that show up only when has_foo is set
746             foo => {
747             type => 'warped_node',
748             level => 'hidden',
749             config_class_name => 'FooClass',
750             follow => {
751             selected => '- my_check_list'
752             },
753             'rules' => [
754             '$selected.is_set(has_foo)' => {
755             level => 'normal'
756             }
757             ]
758             },
759             # controlled element that show up only when has_bar is set
760             bar => {
761             type => 'warped_node',
762             level => 'hidden',
763             config_class_name => 'BarClass',
764             follow => {
765             selected => '- my_check_list'
766             },
767             'rules' => [
768             '$selected.is_set(has_bar)' => {
769             level => 'normal'
770             }
771             ]
772             }
773             ]
774              
775             =head1 Methods
776              
777             =head2 warp_error
778              
779             This method returns a string describing:
780              
781             =over
782              
783             =item *
784              
785             The location(s) of the warp master
786              
787             =item *
788              
789             The current value(s) of the warp master(s)
790              
791             =item *
792              
793             The other values accepted by the warp master that can be tried (if the
794             warp master is an enumerated type)
795              
796             =back
797              
798             =head1 How does this work ?
799              
800             =over
801              
802             =item Registration
803              
804             =over
805              
806             =item *
807              
808             When a warped object is created, the constructor registers to the
809             warp masters. The warp master are found by using the special string
810             passed to the C<follow> parameter. As explained in
811             L<grab method|Config::Model::Role::Grab/grab>,
812             the string provides the location of the warp master in the
813             configuration tree using a symbolic form.
814              
815             =item *
816              
817             Then the warped object retrieve the value(s) of the warp master(s)
818              
819             =item *
820              
821             Then the warped object warps itself using the above
822             value(s). Depending on these value(s), the properties of the warped
823             object are modified.
824              
825             =back
826              
827             =item Master update
828              
829             =over
830              
831             =item *
832              
833             When a warp master value is updated, the warp master calls I<all>
834             its warped object and pass them the new master value.
835              
836             =item *
837              
838             Then each warped object modifies properties according to the
839             new warp master value.
840              
841             =back
842              
843             =back
844              
845             =head1 AUTHOR
846              
847             Dominique Dumont, (ddumont at cpan dot org)
848              
849             =head1 SEE ALSO
850              
851             L<Config::Model::AnyThing>,
852             L<Config::Model::HashId>,
853             L<Config::Model::ListId>,
854             L<Config::Model::WarpedNode>,
855             L<Config::Model::Value>
856              
857             =head1 AUTHOR
858              
859             Dominique Dumont
860              
861             =head1 COPYRIGHT AND LICENSE
862              
863             This software is Copyright (c) 2005-2022 by Dominique Dumont.
864              
865             This is free software, licensed under:
866              
867             The GNU Lesser General Public License, Version 2.1, February 1999
868              
869             =cut