File Coverage

lib/Net/ISC/DHCPd/Config/Role.pm
Criterion Covered Total %
statement 184 207 88.8
branch 66 88 75.0
condition 6 9 66.6
subroutine 429 431 99.5
pod 7 7 100.0
total 692 742 93.2


line stmt bran cond sub pod time code
1             package Net::ISC::DHCPd::Config::Role;
2              
3             =head1 NAME
4              
5             Net::ISC::DHCPd::Config::Role - Role with generic config methods and attributes
6              
7             =head1 DESCRIPTION
8              
9             This role contains common methods and attributes for each of the config
10             classes in the L<Net::ISC::DHCPd::Config> namespace.
11              
12             =head1 WARNINGS
13              
14             This module will warn when a line in the input config could not be parsed.
15             This can be turned off by adding the line below before calling L</parse>.
16              
17             no warnings 'net_isc_dhcpd_config_parse';
18              
19             =cut
20              
21 25     25   15256 use Class::Load;
  25         44  
  25         1249  
22 25     25   118 use Moose::Role;
  25         38  
  25         204  
23              
24             requires 'generate';
25              
26             =head1 ATTRIBUTES
27              
28             =head2 parent
29              
30             The parent node in the config tree. This must be an object which does
31             this role.
32              
33             =cut
34              
35             has parent => (
36             is => 'rw',
37             does => 'Net::ISC::DHCPd::Config::Role',
38             weak_ref => 1,
39             );
40              
41             =head2 root
42              
43             The root node in the config tree.
44              
45             =cut
46              
47             has root => (
48             is => 'ro',
49             isa => 'Object',
50             weak_ref => 1,
51             default => sub { $_[0] },
52             );
53              
54             =head2 depth
55              
56             Integer value that counts how far this node is from the root node.
57              
58             =cut
59              
60             has depth => (
61             is => 'ro',
62             isa => 'Int',
63             lazy => 1,
64             builder => '_build_depth',
65             );
66              
67             sub _build_depth {
68 0     0   0 my $self = shift;
69 0         0 my $obj = $self;
70 0         0 my $i = 0;
71              
72 0         0 while($obj = $obj->parent) {
73 0         0 $i++;
74 0 0       0 last if($obj == $self->root);
75             }
76              
77 0         0 return $i;
78             }
79              
80             =head2 children
81              
82             Holds a list of possible child objects as objects. This list is used
83             when L</parse> or L</generate_config_from_children> is called.
84             The child list has a default value set from L</create_children> in each
85             of the config modules. This is a static list, which reflects the actual
86             documentation from C<dhcpd.conf(5)>. Example:
87              
88             package Net::ISC::DHCPd::Config::Foo;
89             __PACKAGE__->create_children("Net::ISC::DHCPd::Config::Host");
90              
91             package main;
92             $config = Net::ISC::DHCPd::Config::Foo->new;
93             $config->add_host({ ... });
94             @host_objects = $config->find_hosts({ ... });
95             $config->remove_host({ ... });
96             @host_objects = $config->hosts;
97              
98             The L</create_children> method will autogenerate three methods and an
99             attribute. The name of the attribute and methods will be the last part
100             of the config class, with "s" at the end in some cases.
101              
102             =over 4
103              
104             =item foos
105              
106             C<foos> is the name the attribute as well as the accessor. The accessor
107             will auto-deref the array-ref to a list if called in list context. (yes:
108             be aware of this!).
109              
110             =item add_foo
111              
112             Instead of pushing values directly to the C<foos> list, an C<add_foo>
113             method is available. It can take either a hash, hash-ref or an object
114             to add/construct a new child.
115              
116             =item find_foos
117              
118             This method will return zero or more objects as a list. It takes
119             a hash-ref which will be matched against the object attributes of
120             the children.
121              
122             =item remove_foo
123              
124             This method will remove zero or more children from the C<foos> attribute.
125             The method takes a hash-ref which will be used to match against the
126             child list. It returns the number of child nodes actually matched and
127             removed.
128              
129             =back
130              
131             =cut
132              
133             # should be overridden by anything that has children
134 172     172 1 414 sub children { }
135              
136             # actual children
137             has _children => (
138             is => 'ro',
139             isa => 'ArrayRef',
140             default => sub { [] },
141             );
142              
143             =head2 comments
144              
145             @str = $self->comments;
146              
147             Will return all the comments before this element appeared in the config file.
148             The comments will not contain leading hash symbol spaces, nor trailing newline.
149              
150             =cut
151              
152             has _comments => (
153             is => 'ro',
154             traits => ['Array'],
155             init_arg => 'comments',
156             default => sub { [] },
157             handles => {
158             comments => 'elements',
159             },
160             );
161              
162             =head2 regex
163              
164             Regex used to scan a line of config text, which then spawns an
165             a new node to the config tree. This is used inside l</parse>.
166              
167             THIS IS A STATIC METHOD. SELF is not used.
168              
169             =cut
170              
171             has _filehandle => (
172             is => 'ro',
173             lazy_build => 1,
174             );
175              
176             sub _build__filehandle {
177 19     19   38 my $self = shift;
178 19         28 my $file;
179              
180             # get filehandle from parent to prevent seeking file from beginning
181 19 50       1100 if(my $parent = $self->parent) {
182 0         0 return $parent->_filehandle;
183             }
184              
185 19 100       503 if ($self->fh) {
186 17         457 return $self->fh;
187             }
188              
189 2         44 $file = $self->file;
190              
191 2 50 33     68 if($file->is_relative and !-e $file) {
192 0         0 $file = Path::Class::File->new($self->root->file->dir, $file);
193             }
194              
195 2         224 return $file->openr;
196             }
197              
198             =head2 filename_callback
199              
200             Callback for changing file paths when include files are on different relative paths.
201              
202             # here is an example:
203             my $cb = sub {
204             my $file = shift;
205             print "We're in callback and file is $file\n";
206             if ($file =~ /catphotos/) {
207             return "/dog.conf";
208             }
209             };
210              
211             =cut
212              
213             has filename_callback => (
214             is => 'rw',
215             isa => 'CodeRef',
216             );
217              
218              
219             =head1 METHODS
220              
221             =head2 BUILD
222              
223             Used to convert input arguments to child nodes.
224              
225             =cut
226              
227             sub BUILD {
228 294     294 1 25973 my($self, $args) = @_;
229             # skip the expensive method check if called through the parser. The only
230             # time this code applies is with code like add_method( submethod => { } );
231 294 100       6609 return if ($args->{'parse'});
232 40         228 my $meta = $self->meta;
233              
234 40         1812 for my $key (sort keys %$args) {
235 86         121 my $list = $args->{$key};
236 86         122 my $method = "add_$key";
237 86         130 $method =~ s/s$//;
238 86 100 66     1257 if(ref $list eq 'ARRAY' and $meta->has_method($method)) {
239 7         196 for my $element (@$list) {
240 7         25 $self->$method($element);
241             }
242             }
243             }
244             }
245              
246             =head2 parse
247              
248             Will read a line of the time from the current config
249             L<file|Net::ISC::DHCPd::Config::Root/file>. For each line, this method
250             will loop though each object in L</children> and try to match the line
251             against a given child and create a new node in the object graph if it
252             match the L</regex>. This method is called recursively for each child
253             when possible.
254              
255             =cut
256              
257             sub parse {
258 101     101 1 868 my $self = shift;
259 101         115 my $fh = $_[1];
260 101         107 my $linebuf = $_[2];
261 101         111 my($n, @comments);
262 0         0 my $lines;
263 101         94 my $line_from_array=0;
264             # if $fh is a File::Temp or IO::File object then comparing on assignment
265             # above makes it convert to a number (refaddr), which takes 4µs/call
266 101 100       238 if (!defined($fh)) {
267 24         710 $fh = $self->_filehandle;
268             }
269              
270             LINE:
271 101         108 while(1) {
272 465         415 my $line;
273 465 100       746 if (defined($linebuf->[0])) {
274 46         35 $line = pop(@{$linebuf});
  46         66  
275 46         43 $line_from_array=1;
276             } else {
277 419 100       1541 defined($line = readline $fh) or last LINE;
278 395         355 $n++;
279 395         461 chomp $line;
280 395         351 $line_from_array=0;
281             # From here we need to preprocess the line to see if it can be broken
282             # into multiple lines. Something like group { option test; }
283             # lines with comments can't be handled by this so we do them first
284 395 100       850 if($line =~ /^\s*\#\s*(.*)/) {
285 26         116 push @comments, $1;
286 26         35 next LINE;
287             }
288              
289             # after semicolon or braces if there isn't a semicolon or return insert a newline
290 369 100       855 if ($line =~ s/([;\{\}])([^;\n\r])/$1\n$2/g) {
291 13         14 push(@{$linebuf}, reverse split(/\n/, $line));
  13         43  
292 13         47 next LINE;
293             }
294             }
295              
296              
297 402 100       1528 if ($line =~ /^(?:\s*|\s*\{\s*)$/) {
    100          
298 52         70 next LINE;
299             }
300             elsif($line =~ /^\s*\}\s*$/) {
301 77 50       1945 next LINE if($self->root == $self);
302 77         492 last LINE;
303             }
304              
305             # this is how we handle incomplete lines
306             # we need a space for lines like 'option\ndomain-name-servers'
307 273 100       365 if ($lines) {
308 19         31 $lines .= ' '.$line;
309             } else {
310 254         301 $lines = $line;
311             }
312              
313             CHILD:
314 273         782 for my $child ($self->children) {
315 25     25   124036 no strict 'refs';
  25         71  
  25         33014  
316 1867         1307 my $regex = ${"$child".'::regex'};
  1867         4740  
317 1867 100       7149 my @c = $lines =~ $regex or next CHILD;
318 254         1291 my $add = 'add_' .lc +($child =~ /::(\w+)$/)[0];
319 254         1602 my $method = $child->can('captured_to_args');
320 254         736 my $args = $method->(@c);
321 254         4629 my $obj;
322              
323 254         462 $args->{'comments'} = [@comments];
324 254         342 $args->{'parse'} = 1;
325 254         290 @comments = ();
326 254         268 undef $lines;
327 254         732 $obj = $self->$add($args);
328 254 100       1246 $n += $obj->_parse_slurp($fh, $linebuf) if ($obj->can('slurp'));
329              
330             # the recursive statement is used for Include.pm
331 254 100       621 $n += $obj->parse('recursive', $fh, $linebuf) if(@_ = $obj->children);
332              
333 254         994 next LINE;
334             }
335              
336             # if we get here that means our parse failed. If the incoming line
337             # doesn't have a semicolon then we can guess it's a partial line and
338             # append the next line to it.
339             # we could do this with Slurp but then everything would need to
340             # support slurp and odd semicolon handling. If we figure out a way to
341             # merge the lines then the normal parser should be able to cover it.
342 19 50       49 if ($lines !~ /;/) {
343 19         21 next LINE;
344             }
345              
346 0 0       0 if(warnings::enabled('net_isc_dhcpd_config_parse')) {
347 0         0 warn sprintf qq[Could not parse "%s" at %s line %s\n],
348             $line,
349             $self->root->file,
350             $fh->input_line_number
351             ;
352             }
353             }
354              
355 101 100       381 return $n ? $n : '0e0';
356             }
357              
358             =head2 _parse_slurp
359              
360             This is a simplified parser for the slurp method. It's only used when slurp
361             is available in a child method.
362              
363             =cut
364              
365              
366             sub _parse_slurp {
367 7     7   10 my $self = shift;
368 7         8 my $fh = shift;
369 7         7 my $linebuf = shift;
370 7         9 my($n, @comments);
371              
372             LINE:
373 7         8 while(1) {
374 35         35 my $line;
375 35 50       52 if (defined($linebuf->[0])) {
376 0         0 $line = pop(@{$linebuf});
  0         0  
377             } else {
378 35 50       96 defined($line = readline $fh) or last LINE;
379 35         40 $n++;
380 35         49 chomp $line;
381             }
382              
383              
384 35 50       111 if($self->can('slurp')) {
385 35         70 my $action = $self->slurp($line); # next or last
386 35 100       71 if($action eq 'next') {
    50          
    0          
387 28         44 next LINE;
388             }
389             elsif($action eq 'last') {
390 7         14 last LINE;
391             }
392             elsif($action eq 'backtrack') {
393 0         0 push(@{$linebuf}, $line);
  0         0  
394 0         0 last LINE;
395             }
396             }
397             }
398 7         16 return $n;
399             }
400              
401             =head2 captured_to_args
402              
403             $hash_ref = $self->captured_to_args(@list);
404              
405             Called when a L</regex> matches, with a list of captured strings.
406             This method then returns a hash-ref passed on to the constructor when
407             a new node in the object graph is constructed.
408              
409             THIS IS A STATIC METHOD. SELF is not used.
410              
411             =cut
412              
413             sub captured_to_args {
414 11     11 1 24 return {};
415             }
416              
417             =head2 create_children
418              
419             This method takes a list of classes, and creates builder method for
420             the L</children> attribute, an attribute and helper methods. See
421             L</children> for more details.
422              
423             =cut
424              
425             sub create_children {
426 275     275 1 719 my $self = shift;
427 275         1285 my $meta = $self->meta;
428 275         5920 my @children = @_;
429              
430 275         713 for my $class (@children) {
431 2575         81085 my $name = lc (($class =~ /::(\w+)$/)[0]);
432 2575         4353 my $attr = $name .'s';
433              
434             # hack so the child method for class is classes instead of classs
435 2575 100       6812 $attr = $name . 'es' if ($name =~ /s$/);
436              
437              
438 2575         7447 Class::Load::load_class($class);
439              
440 2575 50       76032 unless($meta->find_method_by_name($attr)) {
441 2575     269   253628 $meta->add_method("add_${name}" => sub { shift->_add_child($class, @_) });
  269     269   1525  
        269      
        269      
        269      
        269      
        269      
        269      
        269      
        269      
        269      
        269      
        269      
        269      
        269      
        269      
        269      
        269      
        269      
        269      
        269      
        269      
        269      
        269      
        269      
        269      
        269      
        269      
        269      
        269      
        269      
        269      
        269      
        269      
        269      
        269      
        269      
        269      
        269      
        269      
        269      
        269      
        269      
        269      
        269      
        269      
        269      
        269      
        269      
        269      
        269      
        269      
        269      
        269      
        269      
        269      
        269      
        269      
        269      
        269      
        269      
        269      
        269      
        269      
        269      
        269      
        269      
        269      
        269      
        269      
        269      
        269      
        269      
        269      
        269      
        269      
        269      
        269      
        269      
        269      
        269      
        269      
        269      
        269      
        269      
        269      
        269      
        269      
        269      
        269      
        269      
        269      
        269      
        269      
        269      
        269      
        269      
        269      
        269      
442 2575     3   97772 $meta->add_method("find_${attr}" => sub { shift->_find_children($class, @_) });
  3     3   363  
        3      
        3      
        3      
        3      
        3      
        3      
        3      
        3      
        3      
        3      
        3      
        3      
        3      
        3      
        3      
        3      
        3      
        3      
        3      
        3      
        3      
        3      
        3      
        3      
        3      
        3      
        3      
        3      
        3      
        3      
        3      
        3      
        3      
        3      
        3      
        3      
        3      
        3      
        3      
        3      
        3      
        3      
        3      
        3      
        3      
        3      
        3      
        3      
        3      
        3      
        3      
        3      
        3      
        3      
        3      
        3      
        3      
        3      
        3      
        3      
        3      
        3      
        3      
        3      
        3      
        3      
        3      
        3      
        3      
        3      
        3      
        3      
        3      
        3      
        3      
        3      
        3      
        3      
        3      
        3      
        3      
        3      
        3      
        3      
        3      
        3      
        3      
        3      
        3      
        3      
        3      
        3      
        3      
        3      
        3      
        3      
        3      
        3      
        3      
        3      
        3      
        3      
443 2575     10   82708 $meta->add_method("remove_${attr}" => sub { shift->_remove_children($class, @_) });
  10     10   38  
        10      
        10      
        10      
        10      
        10      
        10      
        10      
        10      
        10      
        10      
        10      
        10      
        10      
        10      
        10      
        10      
        10      
        10      
        10      
        10      
        10      
        10      
        10      
        10      
        10      
        10      
        10      
        10      
        10      
        10      
        10      
        10      
        10      
        10      
        10      
        10      
        10      
        10      
        10      
        10      
        10      
        10      
        10      
        10      
        10      
        10      
        10      
        10      
        10      
        10      
        10      
        10      
        10      
        10      
        10      
        10      
        10      
        10      
        10      
        10      
        10      
        10      
        10      
        10      
        10      
        10      
        10      
        10      
        10      
        10      
        10      
        10      
        10      
        10      
        10      
        10      
        10      
        10      
        10      
        10      
        10      
        10      
        10      
        10      
        10      
        10      
        10      
        10      
        10      
        10      
        10      
        10      
        10      
        10      
        10      
        10      
        10      
        10      
        10      
        10      
        10      
        10      
444             $meta->add_method($attr => sub {
445 157     157   1646 my $self = shift;
        157      
        157      
        157      
        157      
        157      
        157      
        157      
        157      
        157      
        157      
        157      
        157      
        157      
        157      
        157      
        157      
        157      
        157      
        157      
        157      
        157      
        157      
        157      
        157      
        157      
        157      
        157      
        157      
        157      
        157      
        157      
        157      
        157      
        157      
        157      
        157      
        157      
        157      
        157      
        157      
        157      
        157      
        157      
        157      
        157      
        157      
        157      
        157      
        157      
        157      
        157      
        157      
        157      
        157      
        157      
        157      
        157      
        157      
        157      
        157      
        157      
        157      
        157      
        157      
        157      
        157      
        157      
        157      
        157      
        157      
        157      
        157      
        157      
        157      
        157      
        157      
        157      
        157      
        157      
        157      
        157      
        157      
        157      
        157      
        157      
        157      
        157      
        157      
        157      
        157      
        157      
        157      
        157      
        157      
        157      
        157      
        157      
        157      
        157      
        157      
        157      
        157      
        157      
446 157 50       351 return $self->_set_children($class, @_) if(@_);
447 157         444 return $self->_get_children_by_class($class);
448 2575         79316 });
449             }
450             }
451              
452 275         9025 return \@children;
453             }
454              
455             sub _set_children {
456 0     0   0 my($self, $attr, $class, $children) = @_;
457              
458 0         0 for my $child (@$children) {
459 0 0       0 $child = $class->new(parent => $self, root => $self->root, %$child) if(ref $child eq 'HASH');
460             }
461              
462 0         0 @{ $self->_children } = @$children;
  0         0  
463             }
464              
465             sub _get_children_by_class {
466 157     157   191 my($self, $class) = @_;
467 157         146 my @children = grep { $class eq ref $_ } @{ $self->_children };
  885         1361  
  157         4624  
468              
469 157 100       2573 return wantarray ? @children : \@children;
470             }
471              
472             sub _add_child {
473 269     269   255 my $self = shift;
474 269         284 my $class = shift;
475 269 100       516 my $child = @_ == 1 ? $_[0] : {@_};
476 269         7428 my $children = $self->_children;
477              
478 269 50       637 if(ref $child eq 'HASH') {
479 269         5943 $child = $class->new(parent => $self, root => $self->root, %$child);
480             }
481              
482 269         636 push @$children, $child;
483 269         487 return $child;
484             }
485              
486             sub _find_children {
487 3     3   6 my($self, $class, $query) = @_;
488 3         4 my @children;
489              
490 3 50       58 if(ref $query ne 'HASH') {
491 0         0 return;
492             }
493              
494             CHILD:
495 3         4 for my $child (@{ $self->_children }) {
  3         89  
496 38 100       181 if($class ne ref $child) {
497 36         29 next CHILD;
498             }
499 2         5 for my $key (keys %$query) {
500 2 100       47 next CHILD unless($child->$key eq $query->{$key});
501             }
502 1         118 push @children, $child;
503             }
504              
505 3         15 return @children;
506             }
507              
508             sub _remove_children {
509 10     10   53 my $self = shift;
510 10         11 my $class = shift;
511 10 50       23 my $query = shift or return;
512 10         282 my $children = $self->_children;
513 10         13 my $i = 0;
514 10         11 my @removed;
515              
516             CHILD:
517 10         24 while($i < @$children) {
518 72 100       115 if($class ne ref $children->[$i]) {
519 57         42 next CHILD;
520             }
521 15         33 for my $key (keys %$query) {
522 13 100       288 next CHILD unless($children->[$i]->$key eq $query->{$key});
523             }
524 11         682 push @removed, splice @$children, $i, 1;
525 11         12 $i--;
526             } continue {
527 72         94 $i++;
528             }
529              
530 10         187 return @removed;
531             }
532              
533              
534             =head2 find_all_children
535              
536             Loops through all child nodes with recursion looking for nodes of "class"
537             type. Returns an array of those nodes. You can use the full classname or
538             just the end part. For subclasses like Host::FixedAddress you would need to
539             use the whole name.
540              
541             my @subnet = $config->find_all_children('subnet');
542              
543             =cut
544              
545             sub find_all_children {
546 7     7 1 9 my $self = shift;
547 7         8 my $class = shift;
548 7         5 my @children;
549              
550 7 100       16 if ($class !~ /::/) {
551             # strip plural if they put it.
552 1         6 $class =~ s/s\z//;
553 1         1 $class =~ s/(class|address)e/$1/;
554 1         5 $class = 'Net::ISC::DHCPd::Config::' . ucfirst(lc($class));
555             }
556              
557 7         5 for my $child (@{ $self->_children }) {
  7         181  
558 6 100       11 if (ref($child) eq $class) {
559 3         4 push(@children, $child);
560             }
561              
562 6 50       161 if ($child->_children) {
563 6         15 push(@children, $child->find_all_children($class));
564             }
565             }
566 7         19 return @children;
567             }
568              
569             =head2 generate_config_from_children
570              
571             Loops all child nodes in reverse order and calls L</generate> on each
572             of them. Each L</generate> method must return a list of strings which
573             will be indented correctly and concatenated with newline inside this
574             method, before returned as one string.
575              
576             =cut
577              
578             sub generate_config_from_children {
579 26     26 1 91 return join "\n", shift->_generate_config_from_children;
580             }
581              
582             sub _generate_config_from_children {
583 49     49   3737 my $self = shift;
584 49         70 my $indent = '';
585 49         57 my @text;
586              
587 49 100 100     1288 if($self->parent and !$self->can('generate_with_include')) {
588 30         38 $indent = ' ' x 4;
589             }
590              
591 49         57 for my $child (@{ $self->_children }) {
  49         1192  
592 112         3581 push @text, map { "$indent# $_" } $child->comments;
  17         43  
593 112         315 push @text, map { "$indent$_" } $child->generate;
  232         1094  
594             }
595              
596 49         394 return @text;
597             }
598              
599             =head2 generate
600              
601             A C<generate()> must be defined in the consuming class. This method
602             must return a list of lines (zero or more), which will be indented
603             and concatenated inside L</generate_config_from_children>.
604              
605             =head1 COPYRIGHT & LICENSE
606              
607             =head1 AUTHOR
608              
609             See L<Net::ISC::DHCPd>.
610              
611             =cut
612              
613             package # hack to register a new warnings category
614             net_isc_dhcpd_config_parse;
615 25     25   151 use warnings::register;
  25         32  
  25         4072  
616              
617             1;