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   29558 use strict;
  59         139  
  59         395  
15 59     59   16592 use warnings;
  59         137  
  59         1205  
16 59     59   293 use Carp;
  59         132  
  59         1474  
17 59     59   330 use 5.20.0;
  59         127  
  59         3449  
18 59     59   753  
  59         204  
19             use List::MoreUtils qw/any/;
20 59     59   350 use Mouse::Util;
  59         148  
  59         584  
21 59     59   36185 use Log::Log4perl qw(get_logger :levels);
  59         137  
  59         1761  
22 59     59   4310  
  59         148  
  59         457  
23             with "Config::Model::Role::Utils";
24             use feature qw/signatures postderef/;
25 59     59   7309 no warnings qw/experimental::signatures experimental::postderef/;
  59         127  
  59         6006  
26 59     59   378  
  59         146  
  59         58787  
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 92846 my ( $steps, $mode, $autoadd, $type, $grab_non_available, $check ) =
  3295         4867  
  3295         6126  
  3295         3780  
41 3295         8427 ( undef, 'strict', 1, undef, 0, 'yes' );
42 3295         8670  
43             $steps = delete $args{steps} // delete $args{step};
44             $mode = delete $args{mode} if defined $args{mode};
45 3295   100     11657 $autoadd = delete $args{autoadd} if defined $args{autoadd};
46 3295 100       8191 $grab_non_available = delete $args{grab_non_available}
47 3295 100       6551 if defined $args{grab_non_available};
48             $type = delete $args{type}; # node, leaf or undef
49 3295 100       6509 $check = $self->_check_check( delete $args{check} );
50 3295         5024  
51 3295         9592 if ( defined $args{strict} ) {
52             carp "grab: deprecated parameter 'strict'. Use mode";
53 3295 50       6929 $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       6238  
60             Config::Model::Exception::Internal->throw(
61             error => "grab: steps parameter must be a string " . "or an array ref" )
62 3295 100 100     10498 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       7386  
68 3294 100       6082 my @command = (
69             $huge_string =~ m/
70 3243         21355 ( # 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         6558 $logger->trace(
91             "grab: executing '",
92 3243         10759 join( "' '", @command ),
93             "' on object '",
94             $self->name, "'"
95             );
96              
97             my @found = ($self);
98              
99 3243         23756 COMMAND:
100             while (@command) {
101             last if $mode eq 'step_by_step' and @saved > @command;
102 3243         7253  
103 5585 100 100     14879 my $cmd = shift @command;
104              
105 4879         7371 my $obj = $found[-1];
106             $logger->trace( "grab: executing cmd '$cmd' on object '", $obj->name, "($obj)'" );
107 4879         6678  
108 4879         12155 if ( $cmd eq '!' ) {
109             push @found, $obj->grab_root();
110 4879 100       31906 next;
111 449         1362 }
112 449         1130  
113             if ( $cmd =~ /^!([\w:]*)/ ) {
114             my $ancestor = $obj->grab_ancestor($1);
115 4430 100       9038 if ( defined $ancestor ) {
116 3         10 push @found, $ancestor;
117 3 50       7 next;
118 3         5 }
119 3         10 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       7876 $cmd =~ s/^\?//; #remove the go up part
133 5         22 unshift @command, $cmd;
134 4         18 next;
135 4         10 }
136 4         14  
137             if ( $cmd eq '-' ) {
138             if ( defined $obj->parent ) {
139 4422 100       8045 push @found, $obj->parent;
140 1570 50       4399 next;
141 1570         3397 }
142 1570         3759 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     10906 Config::Model::Exception::Model->throw(
151             object => $obj,
152 3         27 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         13481  
160             if ( defined $arg and $arg =~ /^"/ and $arg =~ /"$/ ) {
161             $arg =~ s/^"//; # remove leading quote
162 2849 100 100     8591 $arg =~ s/"$//; # remove trailing quote
      66        
163 10         36 }
164 10         35  
165             {
166             no warnings "uninitialized"; ## no critic (TestingAndDebugging::ProhibitNoWarnings)
167             $logger->debug("grab: cmd '$cmd' -> name '$name', action '$action', arg '$arg'");
168 59     59   468 }
  59         128  
  59         71504  
  2849         3695  
169 2849         10357  
170             unless ( $obj->has_element(name => $name, autoadd => $autoadd) ) {
171             if ( $mode eq 'step_by_step' ) {
172 2849 100       22040 return wantarray ? ( undef, @command ) : undef;
173 206 100       700 }
    100          
    100          
174 87 50       535 elsif ( $mode eq 'loose' ) {
175             return;
176             }
177 84         381 elsif ( $mode eq 'adaptative' ) {
178             last;
179             }
180 1         3 else {
181             Config::Model::Exception::UnknownElement->throw(
182             object => $obj,
183 34         84 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     8878 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       22 }
    50          
    0          
199 0 0       0 elsif ( $mode eq 'loose' ) {
200             return;
201             }
202 3         12 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         7051 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     7652 and not $next_obj->exists($arg) ) {
      100        
226             return if $mode eq 'loose';
227             Config::Model::Exception::UnknownId->throw(
228 11 100       47 object => $obj->fetch_element($name),
229 8 50       45 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     7343 Config::Model::Exception::Model->throw(
239 7 50       54 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       6403 push @found, $next_obj;
251             }
252 2622         7809  
253             # check element type
254             if ( defined $type ) {
255             my @allowed = ref $type ? @$type : ($type);
256 3013 100       6182 while ( @found and not any {$found[-1]->get_type eq $_} @allowed ) {
257 68 100       229 Config::Model::Exception::WrongType->throw(
258 68   66 72   564 object => $found[-1],
  72         275  
259 12 100       61 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         4 }
267              
268             my $return = $found[-1];
269             $logger->debug( "grab: returning object '", $return->name, "($return)'" );
270 3002         4530 return wantarray ? ( $return, @command ) : $return;
271 3002         8401 }
272 3002 100       29642  
273             my %args = _resolve_arg_shortcut(\@args, 'steps');
274              
275 375     375 1 33713 my $obj = $self->grab(%args);
  375         563  
  375         731  
  375         504  
276 375         1192  
277             # Pb: may return a node. add another option to grab ??
278 375         1298 # 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     1244 and not $obj->isa("Config::Model::CheckList")
      100        
284             ) {
285 373 100 100     1422 Config::Model::Exception::User->throw(
286             object => $self,
287             message => "Cannot get a value from '". $obj->location . "'. ",
288 1         17 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         1196 $logger->debug( "grab_value: returning value $str of object '", $obj->name );
296 372 100       924 }
297 32 100       120 return $value;
298 32         97 }
299              
300 372         3687 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 1558  
309 1162 100       4095 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 14 return;
316 9   50     20 }
317              
318 9 100 100     19 #internal. Used by grab with '?xxx' steps
319             my ( $self, $search, $type ) = @_;
320 6 50       19  
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 19  
327             my $obj_element_name = $obj->element_name;
328 5         8  
329             if ( $obj->isa('Config::Model::Node')
330 5         7 and $obj->has_element( name => $search, type => $type ) ) {
331 18         45  
332             # object contains the search element, we need to grab the
333             # searched object (i.e. the '?foo' part is done
334 18         126 return $obj;
335             }
336 18 100 100     89 elsif ( defined $obj->parent ) {
    100          
337              
338             # going up
339             $obj = $obj->parent;
340             }
341 4         11 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         27 );
347             }
348             }
349             return; # should never be reached...
350 1         6 }
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.152
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