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   353  
  59         102  
  59         308  
13             use Log::Log4perl qw(get_logger :levels);
14 59     59   19374 use Data::Dumper;
  59         120  
  59         373  
15 59     59   6905 use Storable qw/dclone/;
  59         267  
  59         3083  
16 59     59   347 use Config::Model::Exception;
  59         111  
  59         2746  
17 59     59   336 use List::MoreUtils qw/any/;
  59         118  
  59         1524  
18 59     59   28197 use Carp;
  59         669947  
  59         347  
19 59     59   53380  
  59         125  
  59         101847  
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   751 my $item = $self->_get_value($warper_name);
46 559         699  
47 559         1434 return ref($item) eq 'HASH' ? join(',', each %$item) : $item;
48             }
49 559 100       6446  
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 884 $self->check_warp_args;
67              
68 438         1340 $self->register_to_all_warp_masters;
69 438         4288 $self->refresh_values_from_master;
70             $self->do_warp;
71 437         1553 }
72 437         1449  
73 437         18321 # should be called only at startup
74             my $self = shift;
75              
76             my $follow = $self->follow;
77              
78 444     444 0 1001 # now, follow is only { w1 => 'warp1', w2 => 'warp2'}
79             foreach my $warper_name ( keys %$follow ) {
80 444         1269 $self->register_to_one_warp_master($warper_name);
81             }
82              
83 444         1112 }
84 576         1592  
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 1205 $logger->debug( "Warper register_to_one_warp_master: '", $self->name, "' follows '$warper_name'" );
91 576   50     1340  
92             # need to register also to all warped_nodes found on the path
93 576         1315 my @command = ($warper_path);
94 576         1167 my $warper;
95 576         1330 my $warped_node;
96             my $obj = $self->warped_object;
97             my $reg_values = $self->_registered_values;
98 576         4139  
99 576         1041 return if defined $reg_values->{$warper_name};
100              
101 576         1232 while (@command) {
102 576         1602  
103             # may return undef object
104 576 100       1576 ( $obj, @command ) = $obj->grab(
105             step => \@command,
106 569         1424 mode => 'step_by_step',
107             grab_non_available => 1,
108             );
109 1275         4419  
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       3083 my $obj_loc = $obj->location;
116 87         421  
117 87         612 $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         3135 $warper = $obj;
121             if ( defined $warped_node ) {
122 1188         3770  
123             # keep obj ref to be able to unregister later on
124 1188 100 100     13974 $self->_warped_nodes->{$warped_node}{$warper_name} = $obj;
125 482         871 }
126 482 100       1533 last;
127             }
128              
129 37         220 if ( $obj->isa('Config::Model::WarpedNode') ) {
130             $logger->debug("Warper register_to_one_warp_master: register to warped_node $obj_loc");
131 482         939 if ( defined $warped_node ) {
132              
133             # keep obj ref to be able to unregister later on
134 706 100       3029 $self->_warped_nodes->{$warped_node}{$warper_name} = $obj;
135 124         575 }
136 124 50       939 $warped_node = $obj_loc;
137             $obj->register( $self, $warper_name );
138             }
139 0         0 }
140              
141 124         245 if ( defined $warper and scalar @command ) {
142 124         434 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     2189 }
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       3088  
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       4312 ) unless $warper->isa('Config::Model::Value') or $obj->isa('Config::Model::CheckList');
161              
162 482 50 66     2123 # 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         1951 if ( $type eq 'computed' ) {
173             $self->_computed_masters->{$warper_name} = $warper;
174 482         1296 }
175             }
176              
177 482 100       2075 my ( $self, $warped_node_location ) = @_;
178 1         7  
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 12  
184             #return unless defined $wnref ;
185 7         16  
186             # remove and unregister obj affected by this warped node
187 7         9 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         44 $ref->{$warper_name}->unregister( $self->name );
194             }
195 7         19  
196 4         9 $self->register_to_all_warp_masters;
197              
198 4         27 #map { $self->register_to_one_warp_master($_) } keys %$ref;
199 4         67 }
200              
201             # should be called only at startup
202 7         14 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 725  
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         976 $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         1334 my $warper = $self->warped_object->grab(
219 562         5170 step => $warper_path,
220 562         1242 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         4730 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     3098 elsif ( defined $warper and $warper->get_type eq 'check_list' ) {
    100 66        
    50          
231             if ($logger->is_debug) {
232 473         1809 my $warper_value = $warper->fetch();
233 473   100     1726 $logger->debug( "Warper: '$warper_name' checked values are: '$warper_value'" );
234 473         2338 }
235 473         4634 # 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       5 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         15 );
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         450 my $self = shift;
255 87         3721 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 5177 my $value =
263 3891         11783 @_
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 783 error => "warp error: warp 'rules' parameter " . "is not a ref ($rules_ref)",
293             object => $self->warped_object
294             );
295              
296 438         1396 my $allowed = $self->allowed;
297              
298 438 50       2760 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         1334 ) unless ref($v) eq 'HASH';
307              
308 438         1519 foreach my $pkey ( keys %$v ) {
309 1385         1992 Config::Model::Exception::Model->throw(
310 1385 50       2733 object => $self->warped_object,
311             error => "Warp rules error for '@keys': '$pkey' "
312 1385         2029 . "parameter is not allowed, "
313 1385 100       2816 . "expected '"
314             . join( "' or '", @$allowed ) . "'"
315             ) unless any {$pkey eq $_} @$allowed ;
316             }
317             }
318 1384         3072 }
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   5168 # 'hidden'
  3928         8565  
326             my ( $self, $arg_ref ) = @_;
327              
328             my $warped_object = $self->warped_object;
329              
330             my @properties = qw/level/;
331 1 50   1   9  
  1         15  
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 1783 my $elt_name = $warped_object->element_name;
339             foreach my $property_name (@properties) {
340 754         3971 my $v = $arg_ref->{$property_name};
341             if ( defined $v ) {
342 754         1781 $logger->debug( "Warper set_parent_element_property: set '",
343             $parent->name, " $elt_name' $property_name with $v" );
344 754 100       3029 $parent->set_element_property(
345 11         36 property => $property_name,
346 11         67 element => $elt_name,
347             value => $v,
348             );
349 743         1975 }
350 743         1994 else {
351 743         1727  
352 743         1514 # reset ensures that property is reset to known state by default
353 743 100       1811 $logger->debug("Warper set_parent_element_property: reset $property_name");
354 76         237 $parent->reset_element_property(
355             property => $property_name,
356 76         654 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         2431 my %old_value_set = %{ $self->_values };
366 667         5683  
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 537 $self->_set_value( $warp_name => $value || '' );
377             }
378 340         475  
  340         1718  
379             # read warp master values that are computed
380 340 50       834 my $cm = $self->_computed_masters;
381 340         694 foreach my $name ( keys %$cm ) {
382 340 100       878 $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     3333 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         14305 }
394 340         895  
395 0         0 if ($same) {
396             no warnings "uninitialized";
397             if ( $logger->is_debug ) {
398             $logger->debug(
399 340         550 "Warper: warp skipped because no change in value set ",
400 340         884 "(old: '", join( "' '", %old_value_set ),
401 559         2857 "' new: '", join( "' '", %{ $self->_values() } ), "')"
402 559         1225 );
403 559 100 100     4195 }
    100 66        
    100 100        
      100        
404             return;
405             }
406              
407             $self->do_warp;
408 340 100       791 }
409 59     59   524  
  59         147  
  59         19760  
410 30 50       82 # 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         218 # my $warp_value_set = $self->_values ;
418             $logger->debug( "Warper compute_bool: data:\n",
419             Data::Dumper->Dump( [ $self->_values ], ['data'] ) );
420 310         800  
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 1621     1621 0 2472  
427 1621         2285 my @init_code;
428             my %eval_data ;
429 1621         5823 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 1621         17823 }
433              
434             my $perl_code = join( "\n", @init_code, $expr );
435             $logger->trace("Warper compute_bool: eval code '$perl_code'");
436              
437 1621         94847 my $ret;
  10         57  
438             {
439 1621         3313 my $warped_obj = $self->warped_object ;
440             no warnings "uninitialized";
441 1621         2551 $ret = eval($perl_code); ## no critic (ProhibitStringyEval)
442             }
443 1621         4765  
444 2317         14711 if ($@) {
445 2317         29237 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 1621         4078 );
449 1621         5024 }
450              
451 1621         9672 $logger->debug( "compute_bool: eval result: ", ( $ret ? 'true' : 'false' ) );
452             return $ret;
453 1621         2104 }
  1621         3399  
454 59     59   430  
  59         2227  
  59         48250  
455 1621         111868 my $self = shift;
456              
457             my $warp_value_set = $self->_values;
458 1621 50       6659 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 1621 100       6150 my $found_bool = ''; # this variable may be used later in error message
466 1621         13529  
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 1560 next unless $res;
471             $found_bool = $bool_expr;
472 754         1902 $found_rule = $rule_hash{$bool_expr} || {};
473 754         32200 $logger->trace(
474 754         3853 "do_warp found rule for '$bool_expr':\n",
475             Data::Dumper->Dump( [$found_rule], ['found_rule'] ) );
476             last;
477             }
478              
479 754         1705 if ( $logger->is_info ) {
480 754         1502 my @warp_str = map { defined $_ ? $_ : 'undef' } keys %$warp_value_set;
481              
482 754         1807 $logger->info(
483 2909 100       5790 "do_warp: warp called from '$found_bool' on '",
484 1621         3687 $self->warped_object->name,
485 1621 100       4400 "' with elements '",
486 333         672 join( "','", @warp_str ),
487 333   50     1101 "', warp rule is ",
488 333         1786 ( scalar %$found_rule ? "" : 'not ' ),
489             "found"
490             );
491 333         16773 }
492              
493             $logger->trace( "do_warp: call set_parent_element_property on '",
494 754 100       2594 $self->name, "' with ", Data::Dumper->Dump( [$found_rule], ['found_rule'] ) );
495 7 50       36  
  7         31  
496             $self->set_parent_element_property($found_rule);
497 7 100       45  
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         6137 Config::Model::Exception::Model->throw(
509             object => $self->warped_object,
510             error => "Warp failed when following '"
511 754         36079 . join( "','", @warp_str )
512             . "' from \"$found_bool\". Check model rules:\n\t"
513 754         3214 . $msg
514             );
515             }
516             }
517 754         32724  
  754         3839  
518             # Usually a warp error occurs when the item is not actually available
519 754 100       6237 # or when a setting is wrong. Then guiding the user toward a warp
520 1 50       3 # master value that has a rule attached to it is a good idea.
  1         5  
521 1         2  
522 1 50       7 # But sometime, the user wants to remove and item. In this case it
523 1         15 # 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 14 . "the following configuration parameters:\n";
542              
543 7 50       53 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.152
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