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