File Coverage

blib/lib/Config/Model/IdElementReference.pm
Criterion Covered Total %
statement 71 82 86.5
branch 17 22 77.2
condition 7 7 100.0
subroutine 7 9 77.7
pod 2 5 40.0
total 104 125 83.2


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::IdElementReference 2.153; # TRIAL
11              
12 59     59   486 use Mouse;
  59         177  
  59         404  
13              
14 59     59   23014 use Carp;
  59         153  
  59         3670  
15 59     59   488 use Config::Model::ValueComputer;
  59         193  
  59         2002  
16 59     59   429 use Log::Log4perl qw(get_logger :levels);
  59         186  
  59         496  
17              
18             my $logger = get_logger("Tree::Element::IdElementReference");
19              
20             # config_elt is a reference to the object that called new
21             has config_elt => ( is => 'ro', isa => 'Config::Model::AnyThing', required => 1, weak_ref => 1 );
22             has refer_to => ( is => 'ro', isa => 'Maybe[Str]' );
23             has computed_refer_to => ( is => 'ro', isa => 'Maybe[HashRef]' );
24              
25             sub BUILD {
26 84     84 1 1784 my $self = shift;
27              
28 84         218 my $found = scalar grep { defined $self->$_; } qw/refer_to computed_refer_to/;
  168         798  
29              
30 84 50       422 if ( not $found ) {
    50          
31 0         0 Config::Model::Exception::Model->throw(
32             object => $self->config_elt,
33             message => "missing " . "refer_to or computed_refer_to parameter"
34             );
35             }
36             elsif ( $found > 1 ) {
37 0         0 Config::Model::Exception::Model->throw(
38             object => $self->config_elt,
39             message => "cannot specify both " . "refer_to and computed_refer_to parameters"
40             );
41             }
42              
43 84         295 my $rft = $self->{refer_to};
44 84   100     474 my $crft = $self->{computed_refer_to} || {};
45 84         298 my %c_args = %$crft;
46              
47             my $refer_path =
48             defined $rft
49             ? $rft
50 84 100       275 : delete $c_args{formula};
51              
52             # split refer_path on + then create as many ValueComputer as
53             # required
54 84         571 my @references = split /\s+\+\s+/, $refer_path;
55              
56 84         278 foreach my $single_path (@references) {
57 130         2597 push @{ $self->{compute} }, Config::Model::ValueComputer->new(
58             formula => $single_path,
59             variables => {},
60             %c_args,
61             value_object => $self->{config_elt},
62 130         251 value_type => 'string' # a reference is always a string
63             );
64             }
65              
66 84         709 return $self;
67             }
68              
69             # internal
70              
71             # FIXME: do not call back value object -> may recurse
72             sub get_choice_from_referred_to {
73 319     319 0 622 my $self = shift;
74              
75 319         624 my $config_elt = $self->{config_elt};
76 319         1020 my @enum_choice = $config_elt->get_default_choice;
77              
78 319         655 foreach my $compute_obj ( @{ $self->{compute} } ) {
  319         905  
79 472         1676 my $user_spec = $compute_obj->compute;
80              
81 472 100       1476 next unless defined $user_spec;
82              
83 469         2600 my @path = split( /\s+/, $user_spec );
84              
85 469         2515 $logger->trace("path: @path");
86              
87 469         3824 my $referred_to = eval { $config_elt->grab("@path"); };
  469         2067  
88              
89 469 100       1426 if (ref $@) {
90 2         4 my $e = $@;
91             # don't use $e->full_description as it will recurse badly
92 2         25 Config::Model::Exception::Model->throw(
93             object => $config_elt,
94             error => "'refer_to' parameter with path '@path': " .$e->description
95             );
96             }
97              
98 467         991 my $element = pop @path;
99 467         1267 my $obj = $referred_to->parent;
100 467         1336 my $type = $obj->element_type($element);
101              
102 467         873 my @choice;
103 467 100       1485 if ( $type eq 'check_list' ) {
    100          
    50          
104 9         26 @choice = $obj->fetch_element($element)->get_checked_list();
105             }
106             elsif ( $type eq 'hash' ) {
107 454         1206 @choice = $obj->fetch_element($element)->fetch_all_indexes();
108             }
109             elsif ( $type eq 'list' ) {
110 4         17 my $list_obj = $obj->fetch_element($element);
111 4         34 my $ct = $list_obj->get_cargo_type;
112 4 50       15 if ( $ct eq 'leaf' ) {
113 4         27 @choice = $list_obj->fetch_all_values( mode => 'user' );
114             }
115             else {
116 0         0 Config::Model::Exception::Model->throw(
117             object => $obj,
118             message => "element '$element' cargo_type is $ct. " . "Expected 'leaf'"
119             );
120             }
121             }
122             else {
123 0         0 Config::Model::Exception::Model->throw(
124             object => $obj,
125             message => "element '$element' type is $type. "
126             . "Expected hash or list or check_list"
127             );
128             }
129              
130             # use a hash so choices are unique
131 467         1988 push @enum_choice, @choice;
132             }
133              
134             # prune out repeated items
135 317         644 my %h;
136             my @unique =
137 317   100     734 grep { my $found = $h{$_} || 0; $h{$_} = 1; not $found; } @enum_choice;
  707         2184  
  707         1254  
  707         2077  
138              
139 317         637 my @res;
140 317 100 100     1547 if ( $config_elt->value_type eq 'check_list' and $config_elt->ordered ) {
141 3         10 @res = @unique;
142             }
143             else {
144 314         990 @res = sort @unique;
145             }
146              
147 317         1612 $logger->debug( "Setting choice to '", join( "','", @res ), "'" );
148              
149 317         3569 $config_elt->setup_reference_choice(@res);
150             }
151              
152             sub reference_info {
153 1     1 1 3 my $self = shift;
154 1         4 my $str = "choice was retrieved with: ";
155              
156 1         2 foreach my $compute_obj ( @{ $self->{compute} } ) {
  1         3  
157 2         11 my $path = $compute_obj->formula;
158 2 50       8 $path = defined $path ? "'$path'" : 'undef';
159 2         6 $str .= "\n\tpath $path";
160 2         12 $str .= "\n\t" . $compute_obj->compute_info;
161             }
162 1         9 return $str;
163             }
164              
165             sub compute_obj {
166 0     0 0   my $self = shift;
167 0           return @{ $self->{compute} };
  0            
168             }
169              
170             sub reference_path {
171 0     0 0   my $self = shift;
172 0           return map { $_->formula } @{ $self->{compute} };
  0            
  0            
173             }
174              
175             __PACKAGE__->meta->make_immutable;
176              
177             1;
178              
179             # ABSTRACT: Refer to id element(s) and extract keys
180              
181             __END__
182              
183             =pod
184              
185             =encoding UTF-8
186              
187             =head1 NAME
188              
189             Config::Model::IdElementReference - Refer to id element(s) and extract keys
190              
191             =head1 VERSION
192              
193             version 2.153
194              
195             =head1 SYNOPSIS
196              
197             # synopsis shows an example of model of a network to use references
198              
199             use Config::Model;
200              
201             my $model = Config::Model->new;
202              
203             # model of several hosts with several NICs
204             $model->create_config_class(
205             name => 'Host',
206             'element' => [
207             ip_nic => {
208             type => 'hash',
209             index_type => 'string',
210             cargo => {
211             type => 'leaf',
212             value_type => 'uniline',
213             }
214             },
215             ]
216             );
217              
218             # model to choose a master host and a master NIC (whatever that may be)
219             # among configured hosts. Once these 2 are configured, the model computes
220             # the master IP
221              
222             $model->create_config_class(
223             name => "MyNetwork",
224              
225             element => [
226             host => {
227             type => 'hash',
228             index_type => 'string',
229             cargo => {
230             type => 'node',
231             config_class_name => 'Host'
232             },
233             },
234              
235             # master_host is one of the configured hosts
236             master_host => {
237             type => 'leaf',
238             value_type => 'reference', # provided by tConfig::Model::IdElementReference
239             refer_to => '! host'
240             },
241              
242             # master_nic is one NIC of the master host
243             master_nic => {
244             type => 'leaf',
245             value_type => 'reference', # provided by tConfig::Model::IdElementReference
246             computed_refer_to => { # provided by Config::Model::ValueComputer
247             formula => ' ! host:$h ip_nic ',
248             variables => { h => '- master_host' }
249             }
250             },
251              
252             # provided by Config::Model::ValueComputer
253             master_ip => {
254             type => 'leaf',
255             value_type => 'string',
256             compute => {
257             formula => '$ip',
258             variables => {
259             h => '- master_host',
260             nic => '- master_nic',
261             ip => '! host:$h ip_nic:$nic'
262             }
263             }
264             },
265              
266             ],
267             );
268              
269             my $inst = $model->instance(root_class_name => 'MyNetwork' );
270              
271             my $root = $inst->config_root ;
272              
273             # configure hosts on my network
274             my $steps = 'host:foo ip_nic:eth0=192.168.0.1 ip_nic:eth1=192.168.1.1 -
275             host:bar ip_nic:eth0=192.168.0.2 ip_nic:eth1=192.168.1.2 -
276             host:baz ip_nic:eth0=192.168.0.3 ip_nic:eth1=192.168.1.3 ';
277             $root->load( steps => $steps );
278              
279             print "master host can be one of ",
280             join(' ',$root->fetch_element('master_host')->get_choice),"\n" ;
281             # prints: master host can be one of bar baz foo
282              
283             # choose master host
284             $root->load('master_host=bar') ;
285              
286             print "master NIC of master host can be one of ",
287             join(' ',$root->fetch_element('master_nic')->get_choice),"\n" ;
288             # prints: master NIC of master host can be one of eth0 eth1
289              
290             # choose master nic
291             $root->load('master_nic=eth1') ;
292              
293             # check what is the master IP computed by the model
294             print "master IP is ",$root->grab_value('master_ip'),"\n";
295             # prints master IP is 192.168.1.2
296              
297             =head1 DESCRIPTION
298              
299             This class is user by L<Config::Model::Value> to set up an enumerated
300             value where the possible choice depends on the key of a
301             L<Config::Model::HashId> or the content of a L<Config::Model::ListId>
302             object.
303              
304             This class is also used by L<Config::Model::CheckList> to define the
305             checklist items from the keys of another hash (or content of a list).
306              
307             =head1 CONSTRUCTOR
308              
309             Construction is handled by the calling object (L<Config::Model::Node>).
310              
311             =head1 Config class parameters
312              
313             =over
314              
315             =item refer_to
316              
317             C<refer_to> is used to specify a hash element that is used as a
318             reference. C<refer_to> points to an array or hash element in the
319             configuration tree using the path syntax (See
320             L<Config::Model::Role::Grab/grab> for details).
321              
322             =item computed_refer_to
323              
324             When C<computed_refer_to> is used, the path is computed using values
325             from several elements in the configuration tree. C<computed_refer_to>
326             is a hash with 2 mandatory elements: C<formula> and C<variables>.
327              
328             =back
329              
330             The available choice of this (computed or not) reference value is made
331             from the available keys of the C<referred_to> hash element or the values
332             of the C<referred_to> array element.
333              
334             The example means the the value must correspond to an existing host:
335              
336             value_type => 'reference',
337             refer_to => '! host'
338              
339             This example means the the value must correspond to an existing lan
340             within the host whose Id is specified by hostname:
341              
342             value_type => 'reference',
343             computed_refer_to => {
344             formula => '! host:$a lan',
345             variables => { a => '- hostname' }
346             }
347              
348             If you need to combine possibilities from several hash, use the "C<+>"
349             token to separate 2 paths:
350              
351             value_type => 'reference',
352             computed_refer_to => {
353             formula => '! host:$a lan + ! host:foobar lan',
354             variables => { a => '- hostname' }
355             }
356              
357             You can specify C<refer_to> or C<computed_refer_to> with a C<choice>
358             argument so the possible enum value will be the combination of the
359             specified choice and the referred_to values.
360              
361             =head1 Methods
362              
363             =head2 reference_info
364              
365             Returns a human readable string with explains how is retrieved the
366             reference. This method is mostly used to construct an error messages.
367              
368             =head1 AUTHOR
369              
370             Dominique Dumont, (ddumont at cpan dot org)
371              
372             =head1 SEE ALSO
373              
374             L<Config::Model>, L<Config::Model::Value>,
375             L<Config::Model::AnyId>, L<Config::Model::CheckList>
376              
377             =head1 AUTHOR
378              
379             Dominique Dumont
380              
381             =head1 COPYRIGHT AND LICENSE
382              
383             This software is Copyright (c) 2005-2022 by Dominique Dumont.
384              
385             This is free software, licensed under:
386              
387             The GNU Lesser General Public License, Version 2.1, February 1999
388              
389             =cut