File Coverage

blib/lib/YAML/yq/Helper.pm
Criterion Covered Total %
statement 14 228 6.1
branch 0 154 0.0
condition 0 6 0.0
subroutine 5 21 23.8
pod 15 16 93.7
total 34 425 8.0


line stmt bran cond sub pod time code
1             package YAML::yq::Helper;
2              
3 1     1   68471 use 5.006;
  1         4  
4 1     1   5 use strict;
  1         2  
  1         105  
5 1     1   10 use warnings;
  1         2  
  1         43  
6 1     1   508 use YAML;
  1         9776  
  1         56  
7 1     1   577 use File::Slurp qw (read_file write_file);
  1         32188  
  1         3221  
8              
9             =head1 NAME
10              
11             YAML::yq::Helper - Wrapper for yq for various common tasks so YAML files can be manipulated in a manner to preserve comments and version header.
12              
13             =head1 VERSION
14              
15             Version 0.0.1
16              
17             =cut
18              
19             our $VERSION = '0.0.1';
20              
21             =head1 SYNOPSIS
22              
23             use YAML::yq::Helper;
24              
25             my $yq = YAML::yq::Helper->new(file='/etc/suricata/suricata-ids.yaml');
26              
27             $yq->set_array(var=>'rule-files', vals=>['suricata.rules','custom.rules'])
28              
29             =head1 METHODS
30              
31             =head2 new
32              
33             Inits the object and check if a version header is present for use with the
34             ensure method.
35              
36             Will make sure the file specified exists, is a file, is readable, and is
37             writable. Otherwise it will die.
38              
39             Will also die if yq is not in the path.
40              
41             - file :: The YAML file to operate on.
42              
43             =cut
44              
45             sub new {
46 0     0 1   my ( $blank, %opts ) = @_;
47              
48 0           my $exists = `/bin/sh -c "which yq"`;
49 0 0         if ( $? != 0 ) {
50 0           die("yq not found in the path");
51             }
52              
53 0 0         if ( !defined( $opts{file} ) ) {
54 0           die('No file specified');
55             }
56              
57 0 0         if ( !-e $opts{file} ) {
58 0           die( '"' . $opts{file} . '" does not exist' );
59             }
60              
61 0 0         if ( !-f $opts{file} ) {
62 0           die( '"' . $opts{file} . '" is not a file' );
63             }
64              
65 0 0         if ( !-r $opts{file} ) {
66 0           die( '"' . $opts{file} . '" is not readable' );
67             }
68              
69             my $self = {
70             file => $opts{file},
71 0           qfile => quotemeta( $opts{file} ),
72             ensure => 0,
73             ver => undef,
74             };
75 0           bless $self;
76              
77 0           my $raw = read_file( $self->{file} );
78 0 0         if ( $raw =~ /^\%YAML\ 1\.1/ ) {
    0          
79 0           $self->{ensure} = 1;
80 0           $self->{ver} = '1.1';
81             }
82             elsif ( $raw =~ /^\%YAML\ 1\.1/ ) {
83 0           $self->{ensure} = 1;
84 0           $self->{ver} = '1.2';
85             }
86              
87 0           return $self;
88             }
89              
90             =head2 clear_array
91              
92             Clears the entries in a array, but does not delete the array.
93              
94             Will die if called on a item that is not a array.
95              
96             - var :: Variable to check. If not matching /^\./,
97             a period will be prepended.
98              
99             $yq->clear_array(var=>'rule-files');
100              
101             =cut
102              
103             sub clear_array {
104 0     0 1   my ( $self, %opts ) = @_;
105              
106 0 0         if ( !defined( $opts{var} ) ) {
    0          
107 0           die('Nothing specified for var to check');
108             }
109             elsif ( $opts{var} !~ /^\./ ) {
110 0           $opts{var} = '.' . $opts{var};
111             }
112              
113 0 0         if ( $self->is_array_clear( var => $opts{var} ) ) {
114 0           return;
115             }
116              
117 0 0         if ( $opts{var} !~ /\[\]$/ ) {
118 0           $opts{var} = $opts{var} . '[]';
119             }
120              
121 0           my $string = `yq -i "del $opts{var}" $self->{qfile}`;
122              
123 0           $self->ensure;
124             }
125              
126             =head2 clear_hash
127              
128             Clears the entries in a hash, but does not delete the hash.
129              
130             Will die if called on a item that is not a hash.
131              
132             - var :: Variable to check. If not matching /^\./,
133             a period will be prepended.
134              
135             $yq->clear_hash(var=>'rule-files');
136              
137             =cut
138              
139             sub clear_hash {
140 0     0 1   my ( $self, %opts ) = @_;
141              
142 0 0         if ( !defined( $opts{var} ) ) {
    0          
143 0           die('Nothing specified for var to check');
144             }
145             elsif ( $opts{var} !~ /^\./ ) {
146 0           $opts{var} = '.' . $opts{var};
147             }
148              
149 0 0         if ( $self->is_hash_clear( var => $opts{var} ) ) {
150 0           return;
151             }
152              
153 0 0         if ( $opts{var} !~ /\[\]$/ ) {
154 0           $opts{var} = $opts{var} . '[]';
155             }
156              
157 0           my $string = `yq -i "del $opts{var}" $self->{qfile}`;
158              
159 0           $self->ensure;
160             }
161              
162             =head2 create_array
163              
164             Creates a empty array. Unlike set_array, vals is optional.
165              
166             Will die if it already exists.
167              
168             - var :: Variable to operate on. If not matching /^\./,
169             a period will be prepended.
170              
171             - vals :: Array of values to set the array to.
172              
173             $yq->clear_array(var=>'rule-files');
174              
175             =cut
176              
177             sub create_array {
178 0     0 1   my ( $self, %opts ) = @_;
179              
180 0 0         if ( !defined( $opts{var} ) ) {
    0          
181 0           die('Nothing specified for var to check');
182             }
183             elsif ( $opts{var} !~ /^\./ ) {
184 0           $opts{var} = '.' . $opts{var};
185             }
186              
187 0           my $string;
188 0 0         if ( !$self->is_defined( var => $opts{var} ) ) {
189 0           $string = `yq -i '$opts{var}=[]' $self->{qfile}`;
190             }
191             else {
192 0           die( '"' . $opts{var} . '" already exists' );
193             }
194              
195 0 0         if ( $opts{var} !~ /\[\]$/ ) {
196 0           $opts{var} =~ s/\[\]$//;
197             }
198              
199 0           my $int = 0;
200 0           while ( defined( $opts{vals}[$int] ) ) {
201 0           my $insert = $opts{var} . '[' . $int . ']="' . $opts{vals}[$int] . '"';
202 0           $string = `yq -i '$insert' $self->{qfile}`;
203 0           $int++;
204             }
205              
206 0           $self->ensure;
207             }
208              
209             =head2 create_array
210              
211             Creates a empty array.
212              
213             Will die if it already exists.
214              
215             - var :: Variable to operate on. If not matching /^\./,
216             a period will be prepended.
217              
218             $yq->clear_array(var=>'rule-files');
219              
220             =cut
221              
222             sub create_hash {
223 0     0 0   my ( $self, %opts ) = @_;
224              
225 0 0         if ( !defined( $opts{var} ) ) {
    0          
226 0           die('Nothing specified for var to check');
227             }
228             elsif ( $opts{var} !~ /^\./ ) {
229 0           $opts{var} = '.' . $opts{var};
230             }
231              
232 0           my $string;
233 0 0         if ( !$self->is_defined( var => $opts{var} ) ) {
234 0           $string = `yq -i '$opts{var}={}' $self->{qfile}`;
235             }
236             else {
237 0           die( '"' . $opts{var} . '" already exists' );
238             }
239              
240 0           $self->ensure;
241             }
242              
243             =head2 delete
244              
245             Deletes an variable. If it is already undef, it will just return.
246              
247             - var :: Variable to check. If not matching /^\./,
248             a period will be prepended.
249              
250             $yq->delete_array(var=>'rule-files');
251              
252             =cut
253              
254             sub delete {
255 0     0 1   my ( $self, %opts ) = @_;
256              
257 0 0         if ( !defined( $opts{var} ) ) {
    0          
258 0           die('Nothing specified for var to check');
259             }
260             elsif ( $opts{var} !~ /^\./ ) {
261 0           $opts{var} = '.' . $opts{var};
262             }
263              
264 0 0         if ( !$self->is_defined( var => $opts{var} ) ) {
265 0           return;
266             }
267              
268 0           my $string = `yq -i "del $opts{var}" $self->{qfile}`;
269              
270 0           $self->ensure;
271             }
272              
273             =head2 delete_array
274              
275             Deletes an array. If it is already undef, it will just return.
276              
277             Will die if called on a item that is not a array.
278              
279             - var :: Variable to check. If not matching /^\./,
280             a period will be prepended.
281              
282             $yq->delete_array(var=>'rule-files');
283              
284             =cut
285              
286             sub delete_array {
287 0     0 1   my ( $self, %opts ) = @_;
288              
289 0 0         if ( !defined( $opts{var} ) ) {
    0          
290 0           die('Nothing specified for var to check');
291             }
292             elsif ( $opts{var} !~ /^\./ ) {
293 0           $opts{var} = '.' . $opts{var};
294             }
295              
296 0 0         if ( !$self->is_defined( var => $opts{var} ) ) {
297 0           return;
298             }
299              
300 0 0         if ( !$self->is_array( var => $opts{var} ) ) {
301 0           die( '"' . $opts{var} . '" is not a array' );
302             }
303              
304 0 0         if ( $opts{var} =~ /\[\]$/ ) {
305 0           $opts{var} =~ s/\[\]$//;
306             }
307              
308 0           my $string = `yq -i "del $opts{var}" $self->{qfile}`;
309              
310 0           $self->ensure;
311             }
312              
313             =head2 delete_hash
314              
315             Deletes an hash. If it is already undef, it will just return.
316              
317             Will die if called on a item that is not a hash.
318              
319             - var :: Variable to check. If not matching /^\./,
320             a period will be prepended.
321              
322             $yq->delete_hash(var=>'vars');
323              
324             =cut
325              
326             sub delete_hash {
327 0     0 1   my ( $self, %opts ) = @_;
328              
329 0 0         if ( !defined( $opts{var} ) ) {
    0          
330 0           die('Nothing specified for var to check');
331             }
332             elsif ( $opts{var} !~ /^\./ ) {
333 0           $opts{var} = '.' . $opts{var};
334             }
335              
336 0 0         if ( !$self->is_defined( var => $opts{var} ) ) {
337 0           return;
338             }
339              
340 0 0         if ( !$self->is_hash( var => $opts{var} ) ) {
341 0           die( '"' . $opts{var} . '" is not a hash or is undef' );
342             }
343              
344 0 0         if ( $opts{var} =~ /\[\]$/ ) {
345 0           $opts{var} =~ s/\[\]$//;
346             }
347              
348 0           my $string = `yq -i "del $opts{var}" $self->{qfile}`;
349              
350 0           $self->ensure;
351             }
352              
353             =head2 ensure
354              
355             Makes sure that the YAML file has the
356             version at the top.
357              
358             $yq->ensure;
359              
360             =cut
361              
362             sub ensure {
363 0     0 1   my ($self) = @_;
364              
365 0 0         if ( !$self->{ensure} ) {
366 0           return;
367             }
368              
369 0           my $raw = read_file( $self->{file} );
370              
371             # starts
372 0 0         if ( $raw =~ /^\%YANL/ ) {
373 0           return;
374             }
375              
376             # add dashes to the start of the raw if it is missing
377 0 0         if ( $raw !~ /^\-\-\-\n/ ) {
378 0           $raw = "---\n\n" . $raw;
379             }
380              
381             # adds the yaml version
382 0           $raw = '%YAML ' . $self->{ver} . "\n" . $raw;
383              
384 0 0         write_file( $self->{file}, $raw ) or die($@);
385              
386 0           return;
387             }
388              
389             =head2 is_array
390              
391             Checks if the specified variable in a array.
392              
393             - var :: Variable to check. If not matching /^\./,
394             a period will be prepended.
395              
396             if ( $yq->is_array(var=>'rule-files') ){
397             print "array...\n:";
398             }
399              
400             =cut
401              
402             sub is_array {
403 0     0 1   my ( $self, %opts ) = @_;
404              
405 0 0         if ( !defined( $opts{var} ) ) {
    0          
406 0           die('Nothing specified for var to check');
407             }
408             elsif ( $opts{var} !~ /^\./ ) {
409 0           $opts{var} = '.' . $opts{var};
410             }
411              
412 0           my $string = `yq "$opts{var}" $self->{qfile} 2> /dev/null`;
413 0 0         if ( $string =~ /\[\]/ ) {
    0          
    0          
414 0           return 1;
415             }
416             elsif ( $string =~ /\{\}/ ) {
417 0           return 0;
418             }
419             elsif ( $string eq "null\n" ) {
420 0           return 0;
421             }
422              
423 0           my $yaml;
424 0           eval { $yaml = Load($string); };
  0            
425 0 0         if ($@) {
426 0           die($@);
427             }
428              
429 0 0         if ( ref($yaml) eq 'ARRAY' ) {
430 0           return 1;
431             }
432              
433 0           return 0;
434             }
435              
436             =head2 is_array_clear
437              
438             Checks if a array is clear or not.
439              
440             - var :: Variable to check. If not matching /^\./,
441             a period will be prepended.
442              
443             if ( $yq->is_array_clear(var=>'rule-files') ){
444             print "clear...\n:";
445             }
446              
447             =cut
448              
449             sub is_array_clear {
450 0     0 1   my ( $self, %opts ) = @_;
451              
452 0 0         if ( !defined( $opts{var} ) ) {
    0          
453 0           die('Nothing specified for var to check');
454             }
455             elsif ( $opts{var} !~ /^\./ ) {
456 0           $opts{var} = '.' . $opts{var};
457             }
458              
459 0 0         if ( !$self->is_array( var => $opts{var} ) ) {
460 0           die( '"' . $opts{var} . '" is not a array or is undef' );
461             }
462              
463 0           my $string = `yq "$opts{var}" $self->{qfile} 2> /dev/null`;
464 0 0         if ( $string =~ /\[\]/ ) {
465 0           return 1;
466             }
467              
468 0           return 0;
469             }
470              
471             =head2 is_defined
472              
473             Checks if the specified variable is defined or not.
474              
475             Will die if called on a item that is not a array.
476              
477             - var :: Variable to check. If not matching /^\./,
478             a period will be prepended.
479              
480             if ( $yq->is_defined('vars.address-groups') ){
481             print "defined...\n:";
482             }
483              
484             =cut
485              
486             sub is_defined {
487 0     0 1   my ( $self, %opts ) = @_;
488              
489 0 0         if ( !defined( $opts{var} ) ) {
    0          
490 0           die('Nothing specified for var to check');
491             }
492             elsif ( $opts{var} !~ /^\./ ) {
493 0           $opts{var} = '.' . $opts{var};
494             }
495              
496 0           my $string = `yq "$opts{var}" $self->{qfile} 2> /dev/null`;
497              
498 0 0         if ( $string eq "null\n" ) {
499 0           return 0;
500             }
501              
502 0           return 1;
503             }
504              
505             =head2 is_hash
506              
507             Checks if the specified variable in a hash.
508              
509             Will die if called on a item that is not a array.
510              
511             - var :: Variable to check. If not matching /^\./,
512             a period will be prepended.
513              
514             if ( $yq->is_hash('vars.address-groups') ){
515             print "hash...\n:";
516             }
517              
518             =cut
519              
520             sub is_hash {
521 0     0 1   my ( $self, %opts ) = @_;
522              
523 0 0         if ( !defined( $opts{var} ) ) {
    0          
524 0           die('Nothing specified for var to check');
525             }
526             elsif ( $opts{var} !~ /^\./ ) {
527 0           $opts{var} = '.' . $opts{var};
528             }
529              
530 0           my $string = `yq "$opts{var}" $self->{qfile} 2> /dev/null`;
531              
532 0 0         if ( $string =~ /\[\]/ ) {
    0          
    0          
533 0           return 0;
534             }
535             elsif ( $string =~ /\{\}/ ) {
536 0           return 1;
537             }
538             elsif ( $string eq "null\n" ) {
539 0           return 0;
540             }
541              
542 0           my $yaml = Load($string);
543              
544 0 0         if ( ref($yaml) eq 'HASH' ) {
545 0           return 1;
546             }
547              
548 0           return 0;
549             }
550              
551             =head2 is_hash_clear
552              
553             Checks if a hash is clear or not.
554              
555             Will die if called on a item that is not a hash.
556              
557             - var :: Variable to check. If not matching /^\./,
558             a period will be prepended.
559              
560             if ( ! $yq->is_hash_clear(var=>'vars') ){
561             print "not clear...\n:";
562             }
563              
564             =cut
565              
566             sub is_hash_clear {
567 0     0 1   my ( $self, %opts ) = @_;
568              
569 0 0         if ( !defined( $opts{var} ) ) {
    0          
570 0           die('Nothing specified for var to check');
571             }
572             elsif ( $opts{var} !~ /^\./ ) {
573 0           $opts{var} = '.' . $opts{var};
574             }
575              
576 0 0         if ( !$self->is_hash( var => $opts{var} ) ) {
577 0           die( '"' . $opts{var} . '" is not a hash or is undef' );
578             }
579              
580 0           my $string = `yq "$opts{var}" $self->{qfile} 2> /dev/null`;
581 0 0         if ( $string =~ /\{\}/ ) {
582 0           return 1;
583             }
584              
585 0           return 0;
586             }
587              
588             =head2 set_array
589              
590             Creates an array and sets it to the values.
591              
592             If the array is already defined, it will clear it and set
593             the values to those specified.
594              
595             Will die if called on a item that is not a array.
596              
597             - var :: Variable to check. If not matching /^\./,
598             a period will be prepended.
599              
600             - vals :: Array of values to set the array to.
601              
602             $yq->set_array(var=>'rule-files',vals=>\@vals);
603              
604             =cut
605              
606             sub set_array {
607 0     0 1   my ( $self, %opts ) = @_;
608              
609 0 0         if ( !defined( $opts{vals} ) ) {
610 0           die('Nothing specified for vars');
611             }
612              
613 0 0         if ( !defined( $opts{var} ) ) {
    0          
614 0           die('Nothing specified for vals');
615             }
616             elsif ( $opts{var} !~ /^\./ ) {
617 0           $opts{var} = '.' . $opts{var};
618             }
619              
620 0           my $string;
621 0 0         if ( $self->is_defined( var => $opts{var} ) ) {
622 0           $string = `yq -i '$opts{var}=[]' $self->{qfile}`;
623             }
624             else {
625 0           $self->clear_array( var => $opts{var} );
626             }
627              
628 0 0         if ( $opts{var} !~ /\[\]$/ ) {
629 0           $opts{var} =~ s/\[\]$//;
630             }
631              
632 0           my $int = 0;
633 0           while ( defined( $opts{vals}[$int] ) ) {
634 0           my $insert = $opts{var} . '[' . $int . ']="' . $opts{vals}[$int] . '"';
635 0           $string = `yq -i '$insert' $self->{qfile}`;
636 0           $int++;
637             }
638              
639 0           $self->ensure;
640             }
641              
642             =head2 set_hash
643              
644             Creates an hash and sets it to the values.
645              
646             If the hash is already defined, it will clear it and set
647             the values to those specified.
648              
649             Will die if called on a item that is not a array.
650              
651             - var :: Variable to check. If not matching /^\./,
652             a period will be prepended.
653              
654             - hash :: A hash to use for generating the hash to be
655             added. Any undef value will be set to null.
656              
657             $yq->set_array(var=>'vars',hash=>{a=>33,bar=>undef});
658              
659             =cut
660              
661             sub set_hash {
662 0     0 1   my ( $self, %opts ) = @_;
663              
664 0           my @keys;
665 0 0         if ( !defined( $opts{hash} ) ) {
666 0           die('Nothing specified for hash');
667             }
668             else {
669 0 0         if ( ref( $opts{hash} ) ne 'HASH' ) {
670 0           die( 'The passed value for hash is a ' . ref( $opts{hash} ) . ' and not HASH' );
671             }
672              
673 0           @keys = keys( %{ $opts{hash} } );
  0            
674              
675 0           foreach my $key (@keys) {
676 0 0 0       if ( defined( $opts{hash}{$key} )
      0        
677             && ref( $opts{hash}{$key} ) ne 'SCALAR'
678             && ref( $opts{hash}{$key} ) ne '' )
679             {
680             die( 'The passed value for the key "'
681             . $key
682             . '" for the hash is a '
683 0           . ref( $opts{hash}{$key} )
684             . ' and not SCALAR or undef' );
685             }
686             }
687             }
688              
689 0 0         if ( !defined( $opts{var} ) ) {
    0          
690 0           die('Nothing specified for vals');
691             }
692             elsif ( $opts{var} !~ /^\./ ) {
693 0           $opts{var} = '.' . $opts{var};
694             }
695              
696 0 0         if ( $opts{var} =~ /\[\]$/ ) {
697 0           die( 'vars, "' . $opts{var} . '", may not contains []' );
698             }
699              
700 0 0         if ( $opts{var} !~ /\.$/ ) {
701 0           $opts{var} =~ s/\.$//;
702             }
703              
704 0           my $string;
705 0 0         if ( !$self->is_defined( var => $opts{var} ) ) {
706 0           $string = `yq -i '$opts{var}={}' $self->{qfile}`;
707             }
708             else {
709 0           $self->clear_hash( var => $opts{var} );
710             }
711              
712 0           foreach my $key (@keys) {
713 0           my $insert;
714 0 0         if ( defined( $opts{hash}{$key} ) ) {
715 0           $insert = $opts{var} . '.' . $key . '="' . $opts{hash}{$key} . '"';
716             }
717             else {
718 0           $insert = $opts{var} . '.' . $key . '=null';
719             }
720 0           $string = `yq -i '$insert' $self->{qfile}`;
721             }
722              
723 0           $self->ensure;
724             }
725              
726             =head1 AUTHOR
727              
728             Zane C. Bowers-Hadley, C<< >>
729              
730             =head1 BUGS
731              
732             Please report any bugs or feature requests to C, or through
733             the web interface at L. I will be notified, and then you'll
734             automatically be notified of progress on your bug as I make changes.
735              
736              
737              
738              
739             =head1 SUPPORT
740              
741             You can find documentation for this module with the perldoc command.
742              
743             perldoc YAML::yq::Helper
744              
745              
746             You can also look for information at:
747              
748             =over 4
749              
750             =item * RT: CPAN's request tracker (report bugs here)
751              
752             L
753              
754             =item * CPAN Ratings
755              
756             L
757              
758             =item * Search CPAN
759              
760             L
761              
762             =back
763              
764              
765             =head1 ACKNOWLEDGEMENTS
766              
767              
768             =head1 LICENSE AND COPYRIGHT
769              
770             This software is Copyright (c) 2022 by Zane C. Bowers-Hadley.
771              
772             This is free software, licensed under:
773              
774             The Artistic License 2.0 (GPL Compatible)
775              
776              
777             =cut
778              
779             1; # End of YAML::yq::Helper