File Coverage

blib/lib/Config/Model/Role/Grab.pm
Criterion Covered Total %
statement 138 157 87.9
branch 78 98 79.5
condition 46 50 92.0
subroutine 17 18 94.4
pod 5 6 83.3
total 284 329 86.3


line stmt bran cond sub pod time code
1             #
2             # This file is part of Config-Model
3             #
4             # This software is Copyright (c) 2005-2022 by Dominique Dumont.
5             #
6             # This is free software, licensed under:
7             #
8             # The GNU Lesser General Public License, Version 2.1, February 1999
9             #
10              
11             # ABSTRACT: Role to grab data from elsewhere in the tree
12              
13             use Mouse::Role;
14 59     59   29192 use strict;
  59         137  
  59         359  
15 59     59   16098 use warnings;
  59         121  
  59         1166  
16 59     59   326 use Carp;
  59         131  
  59         1527  
17 59     59   360 use 5.20.0;
  59         134  
  59         3287  
18 59     59   743  
  59         239  
19             use List::MoreUtils qw/any/;
20 59     59   379 use Mouse::Util;
  59         118  
  59         589  
21 59     59   35702 use Log::Log4perl qw(get_logger :levels);
  59         123  
  59         1787  
22 59     59   4270  
  59         121  
  59         451  
23             with "Config::Model::Role::Utils";
24             use feature qw/signatures postderef/;
25 59     59   7248 no warnings qw/experimental::signatures experimental::postderef/;
  59         121  
  59         5943  
26 59     59   363  
  59         156  
  59         58179  
27              
28             my $logger = get_logger("Grab");
29              
30             ## Navigation
31              
32             # accept commands like
33             # item:b -> go down a node, create a new node if necessary
34             # - climbs up
35             # ! climbs up to the top
36              
37             # Now return an object and not a value !
38              
39             my %args = _resolve_arg_shortcut(\@args, 'steps');
40 3295     3295 1 99948 my ( $steps, $mode, $autoadd, $type, $grab_non_available, $check ) =
  3295         4527  
  3295         5967  
  3295         3822  
41 3295         8814 ( undef, 'strict', 1, undef, 0, 'yes' );
42 3295         8369  
43             $steps = delete $args{steps} // delete $args{step};
44             $mode = delete $args{mode} if defined $args{mode};
45 3295   100     11136 $autoadd = delete $args{autoadd} if defined $args{autoadd};
46 3295 100       8068 $grab_non_available = delete $args{grab_non_available}
47 3295 100       6699 if defined $args{grab_non_available};
48             $type = delete $args{type}; # node, leaf or undef
49 3295 100       6313 $check = $self->_check_check( delete $args{check} );
50 3295         4871  
51 3295         10375 if ( defined $args{strict} ) {
52             carp "grab: deprecated parameter 'strict'. Use mode";
53 3295 50       7307 $mode = delete $args{strict} ? 'strict' : 'adaptative';
54 0         0 }
55 0 0       0  
56             Config::Model::Exception::User->throw(
57             object => $self,
58             message => "grab: unexpected parameter: " . join( ' ', keys %args ) ) if %args;
59 3295 50       6279  
60             Config::Model::Exception::Internal->throw(
61             error => "grab: steps parameter must be a string " . "or an array ref" )
62 3295 100 100     10314 unless ref $steps eq 'ARRAY' || ! ref $steps;
63              
64             # accept commands, grep remove empty items left by spurious spaces
65             my $huge_string = ref $steps ? join( ' ', @$steps ) : $steps;
66             return $self unless $huge_string;
67 3294 100       7564  
68 3294 100       5955 my @command = (
69             $huge_string =~ m/
70 3243         20738 ( # begin of *one* command
71             (?: # group parts of a command (e.g ...:... )
72             [^\s"]+ # match anything but a space and a quote
73             (?: # begin quoted group
74             " # begin of a string
75             (?: # begin group
76             \\" # match an escaped quote
77             | # or
78             [^"] # anything but a quote
79             )* # lots of time
80             " # end of the string
81             ) # end of quoted group
82             ? # match if I got more than one group
83             )+ # can have several parts in one command
84             ) # end of *one* command
85             /gx
86             );
87              
88             my @saved = @command;
89              
90 3243         7148 $logger->trace(
91             "grab: executing '",
92 3243         11389 join( "' '", @command ),
93             "' on object '",
94             $self->name, "'"
95             );
96              
97             my @found = ($self);
98              
99 3243         24038 COMMAND:
100             while (@command) {
101             last if $mode eq 'step_by_step' and @saved > @command;
102 3243         7359  
103 5585 100 100     13809 my $cmd = shift @command;
104              
105 4879         7247 my $obj = $found[-1];
106             $logger->trace( "grab: executing cmd '$cmd' on object '", $obj->name, "($obj)'" );
107 4879         6451  
108 4879         12015 if ( $cmd eq '!' ) {
109             push @found, $obj->grab_root();
110 4879 100       32077 next;
111 449         1283 }
112 449         1159  
113             if ( $cmd =~ /^!([\w:]*)/ ) {
114             my $ancestor = $obj->grab_ancestor($1);
115 4430 100       9348 if ( defined $ancestor ) {
116 3         12 push @found, $ancestor;
117 3 50       8 next;
118 3         9 }
119 3         9 else {
120             Config::Model::Exception::AncestorClass->throw(
121             object => $obj,
122 0 0       0 info => "grab called from '"
123             . $self->name
124             . "' with steps '@saved' looking for class $1"
125             ) if $mode eq 'strict';
126             return;
127             }
128 0         0 }
129              
130             if ( $cmd =~ /^\?(\w[\w-]*)/ ) {
131             push @found, $obj->grab_ancestor_with_element_named($1);
132 4427 100       8233 $cmd =~ s/^\?//; #remove the go up part
133 5         29 unshift @command, $cmd;
134 4         24 next;
135 4         12 }
136 4         16  
137             if ( $cmd eq '-' ) {
138             if ( defined $obj->parent ) {
139 4422 100       7927 push @found, $obj->parent;
140 1570 50       4505 next;
141 1570         3361 }
142 1570         3796 else {
143             $logger->debug( "grab: ", $obj->name, " has no parent" );
144             return $mode eq 'adaptative' ? $obj : undef;
145 0         0 }
146 0 0       0 }
147              
148             unless ( $obj->isa('Config::Model::Node')
149             or $obj->isa('Config::Model::WarpedNode') ) {
150 2852 100 100     10555 Config::Model::Exception::Model->throw(
151             object => $obj,
152 3         24 message => "Cannot apply command '$cmd' on leaf item"
153             . " (full command is '@saved')"
154             );
155             }
156              
157             my ( $name, $action, $arg ) =
158             ( $cmd =~ /(\w[\-\w]*)(?:(:)((?:"[^\"]*")|(?:[\w:\/\.\-\+]+)))?/ );
159 2849         13344  
160             if ( defined $arg and $arg =~ /^"/ and $arg =~ /"$/ ) {
161             $arg =~ s/^"//; # remove leading quote
162 2849 100 100     8126 $arg =~ s/"$//; # remove trailing quote
      66        
163 10         39 }
164 10         32  
165             {
166             no warnings "uninitialized"; ## no critic (TestingAndDebugging::ProhibitNoWarnings)
167             $logger->debug("grab: cmd '$cmd' -> name '$name', action '$action', arg '$arg'");
168 59     59   447 }
  59         120  
  59         70152  
  2849         3798  
169 2849         10273  
170             unless ( $obj->has_element(name => $name, autoadd => $autoadd) ) {
171             if ( $mode eq 'step_by_step' ) {
172 2849 100       22087 return wantarray ? ( undef, @command ) : undef;
173 206 100       587 }
    100          
    100          
174 87 50       495 elsif ( $mode eq 'loose' ) {
175             return;
176             }
177 84         376 elsif ( $mode eq 'adaptative' ) {
178             last;
179             }
180 1         3 else {
181             Config::Model::Exception::UnknownElement->throw(
182             object => $obj,
183 34         86 element => $name,
184             function => 'grab',
185             info => "grab called from '" . $self->name . "' with steps '@saved'"
186             );
187             }
188             }
189              
190             unless (
191             $grab_non_available
192 2643 100 100     8963 or $obj->is_element_available(
193             name => $name,
194             )
195             ) {
196             if ( $mode eq 'step_by_step' ) {
197             return wantarray ? ( undef, @command ) : undef;
198 3 50       29 }
    50          
    0          
199 0 0       0 elsif ( $mode eq 'loose' ) {
200             return;
201             }
202 3         14 elsif ( $mode eq 'adaptative' ) {
203             last;
204             }
205 0         0 else {
206             Config::Model::Exception::UnavailableElement->throw(
207             object => $obj,
208 0         0 element => $name,
209             function => 'grab',
210             info => "grab called from '" . $self->name . "' with steps '@saved'"
211             );
212             }
213             }
214              
215             my $next_obj = $obj->fetch_element(
216             name => $name,
217 2640         7157 check => $check,
218             autoadd => $autoadd,
219             accept_hidden => $grab_non_available
220             );
221              
222             # create list or hash element only if autoadd is true
223             if ( defined $action
224             and $autoadd == 0
225 2640 100 100     7279 and not $next_obj->exists($arg) ) {
      100        
226             return if $mode eq 'loose';
227             Config::Model::Exception::UnknownId->throw(
228 11 100       42 object => $obj->fetch_element($name),
229 8 50       38 element => $name,
230             id => $arg,
231             function => 'grab'
232             ) unless $mode eq 'adaptative';
233             last;
234             }
235 0         0  
236             if ( defined $action and not $next_obj->isa('Config::Model::AnyId') ) {
237             return if $mode eq 'loose';
238 2629 100 100     7101 Config::Model::Exception::Model->throw(
239 7 50       63 object => $obj,
240 0         0 message => "Cannot apply command '$cmd' on non hash or non list item"
241             . " (full command is '@saved'). item is '"
242             . $next_obj->name . "'"
243             );
244             last;
245             }
246 0         0  
247             # action can only be :
248             $next_obj = $next_obj->fetch_with_id(index => $arg, check => $check) if defined $action;
249              
250 2622 100       5762 push @found, $next_obj;
251             }
252 2622         8577  
253             # check element type
254             if ( defined $type ) {
255             my @allowed = ref $type ? @$type : ($type);
256 3013 100       6348 while ( @found and not any {$found[-1]->get_type eq $_} @allowed ) {
257 68 100       231 Config::Model::Exception::WrongType->throw(
258 68   66 72   559 object => $found[-1],
  72         272  
259 12 100       66 function => 'grab',
260             got_type => $found[-1]->get_type,
261             expected_type => $type,
262             info => "requested with steps '$steps'"
263             ) if $mode ne 'adaptative';
264             pop @found;
265             }
266 1         5 }
267              
268             my $return = $found[-1];
269             $logger->debug( "grab: returning object '", $return->name, "($return)'" );
270 3002         4455 return wantarray ? ( $return, @command ) : $return;
271 3002         9359 }
272 3002 100       29942  
273             my %args = _resolve_arg_shortcut(\@args, 'steps');
274              
275 375     375 1 30331 my $obj = $self->grab(%args);
  375         599  
  375         710  
  375         517  
276 375         1213  
277             # Pb: may return a node. add another option to grab ??
278 375         1203 # to get undef value when needed?
279              
280             return if ( $args{mode} and $args{mode} eq 'loose' and not defined $obj );
281              
282             if (not $obj->isa("Config::Model::Value")
283 375 100 66     1148 and not $obj->isa("Config::Model::CheckList")
      100        
284             ) {
285 373 100 100     1410 Config::Model::Exception::User->throw(
286             object => $self,
287             message => "Cannot get a value from '". $obj->location . "'. ",
288 1         16 info => "grab arguments are '".join( "' '", @args ) . "'."
289             );
290             }
291              
292             my $value = $obj->fetch;
293             if ( $logger->is_debug ) {
294             my $str = defined $value ? $value : '<undef>';
295 372         1315 $logger->debug( "grab_value: returning value $str of object '", $obj->name );
296 372 100       873 }
297 32 100       132 return $value;
298 32         104 }
299              
300 372         3316 return $self->grab(@args)->annotation;
301             }
302              
303 0     0 1 0 my $self = shift;
  0         0  
  0         0  
  0         0  
304 0         0 return defined $self->parent
305             ? $self->parent->grab_root
306             : $self;
307             }
308 1162     1162 1 1561  
309 1162 100       4000 my $self = shift;
310             my $class = shift || die "grab_ancestor: missing ancestor class";
311              
312             return $self if $self->get_type eq 'node' and $self->config_class_name eq $class;
313              
314             return $self->{parent}->grab_ancestor($class) if defined $self->{parent};
315 9     9 1 12 return;
316 9   50     24 }
317              
318 9 100 100     22 #internal. Used by grab with '?xxx' steps
319             my ( $self, $search, $type ) = @_;
320 6 50       25  
321 0         0 my $obj = $self;
322              
323             while (1) {
324             $logger->debug(
325             "grab_ancestor_with_element_named: executing cmd '?$search' on object " . $obj->name );
326 5     5 0 28  
327             my $obj_element_name = $obj->element_name;
328 5         12  
329             if ( $obj->isa('Config::Model::Node')
330 5         10 and $obj->has_element( name => $search, type => $type ) ) {
331 18         50  
332             # object contains the search element, we need to grab the
333             # searched object (i.e. the '?foo' part is done
334 18         149 return $obj;
335             }
336 18 100 100     120 elsif ( defined $obj->parent ) {
    100          
337              
338             # going up
339             $obj = $obj->parent;
340             }
341 4         26 else {
342             # there's no more up to go to...
343             Config::Model::Exception::Model->throw(
344             object => $self,
345             error => "Error: cannot grab '?$search'" . "from " . $self->name
346 13         29 );
347             }
348             }
349             return; # should never be reached...
350 1         9 }
351              
352             1;
353              
354              
355             =pod
356 0            
357             =encoding UTF-8
358              
359             =head1 NAME
360              
361             Config::Model::Role::Grab - Role to grab data from elsewhere in the tree
362              
363             =head1 VERSION
364              
365             version 2.151
366              
367             =head1 SYNOPSIS
368              
369             $root->grab('foo:2 bar');
370             $root->grab(steps => 'foo:2 bar');
371             $root->grab(steps => 'foo:2 bar', type => 'leaf');
372             $root->grab_value(steps => 'foo:2 bar');
373              
374             =head1 DESCRIPTION
375              
376             Role used to let a tree item (i.e. node, hash, list or leaf) to grab
377             another item or value from the configuration tree using a path (a bit
378             like an xpath path with a different syntax).
379              
380             =head1 METHODS
381              
382             =head2 grab
383              
384             Grab an object from the configuration tree.
385              
386             Parameters are:
387              
388             =over
389              
390             =item C<steps> (or C<step>)
391              
392             A string indicating the steps to follow in the tree to find the
393             required item. (mandatory)
394              
395             =item C<mode>
396              
397             When set to C<strict>, C<grab> throws an exception if no object is found
398             using the passed string. When set to C<adaptative>, the object found last is
399             returned. For instance, for the steps C<good_step wrong_step>, only
400             the object held by C<good_step> is returned. When set to C<loose>, grab
401             returns undef in case of problem. (default is C<strict>)
402              
403             =item C<type>
404              
405             Either C<node>, C<leaf>, C<hash> or C<list> or an array ref containing these
406             values. Returns only an object of
407             requested type. Depending on C<strict> value, C<grab> either
408             throws an exception or returns the last object found with the requested type.
409             (optional, default to C<undef>, which means any type of object)
410              
411             Examples:
412              
413             $root->grab(steps => 'foo:2 bar', type => 'leaf')
414             $root->grab(steps => 'foo:2 bar', type => ['leaf','check_list'])
415              
416             =item C<autoadd>
417              
418             When set to 1, C<hash> or C<list> configuration element are created
419             when requested by the passed steps. (default is 1).
420              
421             =item grab_non_available
422              
423             When set to 1, grab returns an object even if this one is not
424             available. I.e. even if this element was warped out. (default is 0).
425              
426             =back
427              
428             The C<steps> parameters is made of the following items separated by
429             spaces:
430              
431             =over 8
432              
433             =item -
434              
435             Go up one node
436              
437             =item !
438              
439             Go to the root node.
440              
441             =item !Foo
442              
443             Go up the configuration tree until the C<Foo> configuration class is found. Raise an exception if
444             no C<Foo> class is found when root node is reached.
445              
446             =item xxx
447              
448             Go down using C<xxx> element.
449              
450             =item xxx:yy
451              
452             Go down using C<xxx> element and id C<yy> (valid for hash or list elements)
453              
454             =item ?xxx
455              
456             Go up the tree until a node containing element C<xxx> is found. Then go down
457             the tree like item C<xxx>.
458              
459             C<?xxx:yy> goes up the tree the same way. But no check is done to see
460             if id C<yy> key actually exists or not. Only the element C<xxx> is
461             considered when going up the tree.
462              
463             =back
464              
465             =head2 grab_value
466              
467             Like L</grab>, but returns the value of a leaf or check_list object, not
468             just the leaf object.
469              
470             C<grab_value> raises an exception if following the steps ends on anything but a
471             leaf or a check_list.
472              
473             =head2 grab_annotation
474              
475             Like L</grab>, but returns the annotation of an object.
476              
477             =head2 grab_root
478              
479             Returns the root of the configuration tree.
480              
481             =head2 grab_ancestor
482              
483             Parameter: a configuration class name
484              
485             Go up the configuration tree until a node using the configuration
486             class is found. Returns the found node or undef.
487              
488             Example:
489              
490             # returns a Config::Model::Node object for a Systemd::Service config class
491             $self->grab('Systemd::Service');
492              
493             =head1 AUTHOR
494              
495             Dominique Dumont
496              
497             =head1 COPYRIGHT AND LICENSE
498              
499             This software is Copyright (c) 2005-2022 by Dominique Dumont.
500              
501             This is free software, licensed under:
502              
503             The GNU Lesser General Public License, Version 2.1, February 1999
504              
505             =cut