File Coverage

blib/lib/YAML/yq/Helper.pm
Criterion Covered Total %
statement 23 394 5.8
branch 0 246 0.0
condition 0 20 0.0
subroutine 8 29 27.5
pod 21 21 100.0
total 52 710 7.3


line stmt bran cond sub pod time code
1             package YAML::yq::Helper;
2              
3 1     1   91740 use 5.006;
  1         3  
4 1     1   9 use strict;
  1         1  
  1         31  
5 1     1   3 use warnings;
  1         1  
  1         55  
6 1     1   432 use YAML;
  1         8247  
  1         63  
7 1     1   821 use File::Slurp qw (read_file write_file);
  1         32991  
  1         99  
8 1     1   7 use File::Temp qw/ tempfile tempdir /;
  1         2  
  1         43  
9 1     1   612 use File::Copy;
  1         3178  
  1         57  
10 1     1   11 use Cwd;
  1         2  
  1         4946  
11              
12             =head1 NAME
13              
14             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.
15              
16             =head1 VERSION
17              
18             Version 0.2.0
19              
20             =cut
21              
22             our $VERSION = '0.2.0';
23              
24             =head1 SYNOPSIS
25              
26             use YAML::yq::Helper;
27              
28             my $yq = YAML::yq::Helper->new(file='/etc/suricata/suricata-ids.yaml');
29              
30             $yq->set_array(var=>'rule-files', vals=>['suricata.rules','custom.rules'])
31              
32             =head1 METHODS
33              
34             =head2 new
35              
36             Inits the object and check if a version header is present for use with the
37             ensure method.
38              
39             Will make sure the file specified exists, is a file, is readable, and is
40             writable. Otherwise it will die.
41              
42             Will also die if yq is not in the path.
43              
44             - file :: The YAML file to operate on.
45              
46             =cut
47              
48             sub new {
49 0     0 1   my ( $blank, %opts ) = @_;
50              
51 0           my $exists = `/bin/sh -c "which yq"`;
52 0 0         if ( $? != 0 ) {
53 0           die("yq not found in the path");
54             }
55              
56 0 0         if ( !defined( $opts{file} ) ) {
57 0           die('No file specified');
58             }
59              
60 0 0         if ( !-e $opts{file} ) {
61 0           die( '"' . $opts{file} . '" does not exist' );
62             }
63              
64 0 0         if ( !-f $opts{file} ) {
65 0           die( '"' . $opts{file} . '" is not a file' );
66             }
67              
68 0 0         if ( !-r $opts{file} ) {
69 0           die( '"' . $opts{file} . '" is not readable' );
70             }
71              
72             my $self = {
73             file => $opts{file},
74 0           qfile => quotemeta( $opts{file} ),
75             ensure => 0,
76             ver => '1.1',
77             };
78 0           bless $self;
79              
80 0           my $raw = read_file( $self->{file} );
81 0 0         if ( $raw =~ /^\%YAML\ 1\.1/ ) {
    0          
    0          
    0          
    0          
    0          
82 0           $self->{ensure} = 1;
83 0           $self->{ver} = '1.1';
84             } elsif ( $raw =~ /^\%YAML\ 1\.2/ ) {
85 0           $self->{ensure} = 1;
86 0           $self->{ver} = '1.2';
87             } elsif ( $raw =~ /^\%YAML\ 1\.2\.0/ ) {
88 0           $self->{ensure} = 1;
89 0           $self->{ver} = '1.2.0';
90             } elsif ( $raw =~ /^\%YAML\ 1\.2\.1/ ) {
91 0           $self->{ensure} = 1;
92 0           $self->{ver} = '1.2.1';
93             } elsif ( $raw =~ /^\%YAML\ 1\.2\.2/ ) {
94 0           $self->{ensure} = 1;
95 0           $self->{ver} = '1.2.2';
96             } elsif ( $raw =~ /^\%YAML\ 1\.0/ ) {
97 0           $self->{ensure} = 1;
98 0           $self->{ver} = '1.0';
99             }
100              
101 0           return $self;
102             } ## end sub new
103              
104             =head2 clear_array
105              
106             Clears the entries in a array, but does not delete the array.
107              
108             Will die if called on a item that is not a array.
109              
110             - var :: Variable to check. If not matching /^\./,
111             a period will be prepended.
112              
113             $yq->clear_array(var=>'rule-files');
114              
115             =cut
116              
117             sub clear_array {
118 0     0 1   my ( $self, %opts ) = @_;
119              
120 0 0         if ( !defined( $opts{var} ) ) {
    0          
121 0           die('Nothing specified for var to check');
122             } elsif ( $opts{var} !~ /^\./ ) {
123 0           $opts{var} = '.' . $opts{var};
124             }
125              
126 0 0         if ( $self->is_array_clear( var => $opts{var} ) ) {
127 0           return;
128             }
129              
130 0 0         if ( $opts{var} !~ /\[\]$/ ) {
131 0           $opts{var} = $opts{var} . '[]';
132             }
133              
134 0           my $string = `yq -i "del $opts{var}" $self->{qfile}`;
135              
136 0           $self->ensure;
137             } ## end sub clear_array
138              
139             =head2 clear_hash
140              
141             Clears the entries in a hash, but does not delete the hash.
142              
143             Will die if called on a item that is not a hash.
144              
145             - var :: Variable to check. If not matching /^\./,
146             a period will be prepended.
147              
148             $yq->clear_hash(var=>'rule-files');
149              
150             =cut
151              
152             sub clear_hash {
153 0     0 1   my ( $self, %opts ) = @_;
154              
155 0 0         if ( !defined( $opts{var} ) ) {
    0          
156 0           die('Nothing specified for var to check');
157             } elsif ( $opts{var} !~ /^\./ ) {
158 0           $opts{var} = '.' . $opts{var};
159             }
160              
161 0 0         if ( $self->is_hash_clear( var => $opts{var} ) ) {
162 0           return;
163             }
164              
165 0 0         if ( $opts{var} !~ /\[\]$/ ) {
166 0           $opts{var} = $opts{var} . '[]';
167             }
168              
169 0           my $string = `yq -i "del $opts{var}" $self->{qfile}`;
170              
171 0           $self->ensure;
172             } ## end sub clear_hash
173              
174             =head2 create_array
175              
176             Creates a empty array. Unlike set_array, vals is optional.
177              
178             Will die if it already exists.
179              
180             - var :: Variable to operate on. If not matching /^\./,
181             a period will be prepended.
182              
183             - vals :: Array of values to set the array to.
184              
185             $yq->create_array(var=>'rule-files');
186              
187             =cut
188              
189             sub create_array {
190 0     0 1   my ( $self, %opts ) = @_;
191              
192 0 0         if ( !defined( $opts{var} ) ) {
    0          
193 0           die('Nothing specified for var to check');
194             } elsif ( $opts{var} !~ /^\./ ) {
195 0           $opts{var} = '.' . $opts{var};
196             }
197              
198 0           my $string;
199 0 0         if ( !$self->is_defined( var => $opts{var} ) ) {
200 0           $string = `yq -i '$opts{var}=[]' $self->{qfile}`;
201             } else {
202 0           die( '"' . $opts{var} . '" already exists' );
203             }
204              
205 0 0         if ( $opts{var} !~ /\[\]$/ ) {
206 0           $opts{var} =~ s/\[\]$//;
207             }
208              
209 0           my $int = 0;
210 0           while ( defined( $opts{vals}[$int] ) ) {
211 0           my $insert = $opts{var} . '[' . $int . ']="' . $opts{vals}[$int] . '"';
212 0           $string = `yq -i '$insert' $self->{qfile}`;
213 0           $int++;
214             }
215              
216 0           $self->ensure;
217             } ## end sub create_array
218              
219             =head2 create_hash
220              
221             Creates a empty hash.
222              
223             Will die if it already exists.
224              
225             - var :: Variable to operate on. If not matching /^\./,
226             a period will be prepended.
227              
228             $yq->clear_hash(var=>'rule-files');
229              
230             =cut
231              
232             sub create_hash {
233 0     0 1   my ( $self, %opts ) = @_;
234              
235 0 0         if ( !defined( $opts{var} ) ) {
    0          
236 0           die('Nothing specified for var to check');
237             } elsif ( $opts{var} !~ /^\./ ) {
238 0           $opts{var} = '.' . $opts{var};
239             }
240              
241 0           my $string;
242 0 0         if ( !$self->is_defined( var => $opts{var} ) ) {
243 0           $string = `yq -i '$opts{var}={}' $self->{qfile}`;
244             } else {
245 0           die( '"' . $opts{var} . '" already exists' );
246             }
247              
248 0           $self->ensure;
249             } ## end sub create_hash
250              
251             =head2 dedup_array
252              
253             Dedup the specified array.
254              
255             Will die if called on a item that is not a array or the array
256             does not exist.
257              
258             - var :: Variable to check. If not matching /^\./,
259             a period will be prepended.
260              
261             $yq->dedup_array(var=>'rule-files');
262              
263             =cut
264              
265             sub dedup_array {
266 0     0 1   my ( $self, %opts ) = @_;
267              
268 0 0         if ( !defined( $opts{dedup} ) ) {
269 0           $opts{dedup} = 1;
270             }
271              
272 0 0         if ( !defined( $opts{var} ) ) {
    0          
273 0           die('Nothing specified for vals');
274             } elsif ( $opts{var} !~ /^\./ ) {
275 0           $opts{var} = '.' . $opts{var};
276             }
277              
278 0 0         if ( $opts{var} =~ /\[\]$/ ) {
279 0           $opts{var} =~ s/\[\]$//;
280             }
281              
282 0           my $string;
283 0 0         if ( !$self->is_array( var => $opts{var} ) ) {
284 0           die( '"' . $opts{var} . '" is not a array or is undef' );
285             }
286              
287 0           $string = `yq "$opts{var}" $self->{qfile} 2> /dev/null`;
288 0           my $yaml;
289 0 0         if ( $string =~ /\[\]/ ) {
290 0           print "blank\n";
291 0           $yaml = [];
292             } else {
293 0           eval { $yaml = Load($string); }
294 0 0         || eval { $string = 'foo: ' . $string; $yaml = Load($string); $yaml = $yaml->{foo} };
  0            
  0            
  0            
295             }
296              
297 0           my $int = 0;
298 0           my $existing = {};
299 0           my @new_array;
300 0           while ( defined( $yaml->[$int] ) ) {
301 0 0         if ( !defined( $existing->{ $yaml->[$int] } ) ) {
302 0           $existing->{ $yaml->[$int] } = 1;
303 0           push( @new_array, $yaml->[$int] );
304             }
305              
306 0           $int++;
307             }
308              
309 0           $self->set_array( var => $opts{var}, vals => \@new_array );
310             } ## end sub dedup_array
311              
312             =head2 delete
313              
314             Deletes an variable. If it is already undef, it will just return.
315              
316             - var :: Variable to check. If not matching /^\./,
317             a period will be prepended.
318              
319             $yq->delete_array(var=>'rule-files');
320              
321             =cut
322              
323             sub delete {
324 0     0 1   my ( $self, %opts ) = @_;
325              
326 0 0         if ( !defined( $opts{var} ) ) {
    0          
327 0           die('Nothing specified for var to check');
328             } elsif ( $opts{var} !~ /^\./ ) {
329 0           $opts{var} = '.' . $opts{var};
330             }
331              
332 0 0         if ( !$self->is_defined( var => $opts{var} ) ) {
333 0           return;
334             }
335              
336 0           my $string = `yq -i "del $opts{var}" $self->{qfile}`;
337              
338 0           $self->ensure;
339             } ## end sub delete
340              
341             =head2 delete_array
342              
343             Deletes an array. If it is already undef, it will just return.
344              
345             Will die if called on a item that is not a array.
346              
347             - var :: Variable to check. If not matching /^\./,
348             a period will be prepended.
349              
350             $yq->delete_array(var=>'rule-files');
351              
352             =cut
353              
354             sub delete_array {
355 0     0 1   my ( $self, %opts ) = @_;
356              
357 0 0         if ( !defined( $opts{var} ) ) {
    0          
358 0           die('Nothing specified for var to check');
359             } elsif ( $opts{var} !~ /^\./ ) {
360 0           $opts{var} = '.' . $opts{var};
361             }
362              
363 0 0         if ( !$self->is_defined( var => $opts{var} ) ) {
364 0           return;
365             }
366              
367 0 0         if ( !$self->is_array( var => $opts{var} ) ) {
368 0           die( '"' . $opts{var} . '" is not a array' );
369             }
370              
371 0 0         if ( $opts{var} =~ /\[\]$/ ) {
372 0           $opts{var} =~ s/\[\]$//;
373             }
374              
375 0           my $string = `yq -i "del $opts{var}" $self->{qfile}`;
376              
377 0           $self->ensure;
378             } ## end sub delete_array
379              
380             =head2 delete_hash
381              
382             Deletes an hash. If it is already undef, it will just return.
383              
384             Will die if called on a item that is not a hash.
385              
386             - var :: Variable to check. If not matching /^\./,
387             a period will be prepended.
388              
389             $yq->delete_hash(var=>'vars');
390              
391             =cut
392              
393             sub delete_hash {
394 0     0 1   my ( $self, %opts ) = @_;
395              
396 0 0         if ( !defined( $opts{var} ) ) {
    0          
397 0           die('Nothing specified for var to check');
398             } elsif ( $opts{var} !~ /^\./ ) {
399 0           $opts{var} = '.' . $opts{var};
400             }
401              
402 0 0         if ( !$self->is_defined( var => $opts{var} ) ) {
403 0           return;
404             }
405              
406 0 0         if ( !$self->is_hash( var => $opts{var} ) ) {
407 0           die( '"' . $opts{var} . '" is not a hash or is undef' );
408             }
409              
410 0 0         if ( $opts{var} =~ /\[\]$/ ) {
411 0           $opts{var} =~ s/\[\]$//;
412             }
413              
414 0           my $string = `yq -i "del $opts{var}" $self->{qfile}`;
415              
416 0           $self->ensure;
417             } ## end sub delete_hash
418              
419             =head2 ensure
420              
421             Makes sure that the YAML file has the
422             version at the top.
423              
424             If the file none originally, no action will be taken
425             unless force=>1 is also set for this. At which point
426             it will it to 1.1.
427              
428             $yq->ensure;
429             $yq->ensure(force=>1);
430              
431             =cut
432              
433             sub ensure {
434 0     0 1   my ( $self, %opts ) = @_;
435              
436 0 0         if ( $opts{force} ) {
437 0           $self->{ensure} = 1;
438             }
439              
440 0 0         if ( !$self->{ensure} ) {
441 0           return;
442             }
443              
444 0           my $raw = read_file( $self->{file} );
445              
446             # starts
447 0 0         if ( $raw =~ /^\%YAML/ ) {
448 0           return;
449             }
450              
451             # add dashes to the start of the raw if it is missing
452 0 0         if ( $raw !~ /^\-\-\-\n/ ) {
453 0           $raw = "---\n\n" . $raw;
454             }
455              
456             # adds the yaml version
457 0           $raw = '%YAML ' . $self->{ver} . "\n" . $raw;
458              
459 0 0         write_file( $self->{file}, $raw ) or die($@);
460              
461 0           return;
462             } ## end sub ensure
463              
464             =head2 is_array
465              
466             Checks if the specified variable in a array.
467              
468             - var :: Variable to check. If not matching /^\./,
469             a period will be prepended.
470              
471             if ( $yq->is_array(var=>'rule-files') ){
472             print "array...\n:";
473             }
474              
475             =cut
476              
477             sub is_array {
478 0     0 1   my ( $self, %opts ) = @_;
479              
480 0 0         if ( !defined( $opts{var} ) ) {
    0          
481 0           die('Nothing specified for var to check');
482             } elsif ( $opts{var} !~ /^\./ ) {
483 0           $opts{var} = '.' . $opts{var};
484             }
485              
486 0           my $string = `yq "$opts{var}" $self->{qfile} 2> /dev/null`;
487 0 0         if ( $string =~ /\[\]/ ) {
    0          
    0          
488 0           return 1;
489             } elsif ( $string =~ /\{\}/ ) {
490 0           return 0;
491             } elsif ( $string eq "null\n" ) {
492 0           return 0;
493             }
494              
495 0           my $yaml;
496 0           eval { $yaml = Load($string); };
  0            
497 0 0         if ($@) {
498 0           $string = 'foo: ' . $string;
499 0   0       $yaml = Load($string) || die( $string . "\n\n" . $@ );
500 0 0         if ( ref( $yaml->{foo} ) eq 'ARRAY' ) {
501 0           return 1;
502             }
503 0           return 0;
504             }
505              
506 0 0         if ( ref($yaml) eq 'ARRAY' ) {
507 0           return 1;
508             }
509              
510 0           return 0;
511             } ## end sub is_array
512              
513             =head2 is_array_clear
514              
515             Checks if a array is clear or not.
516              
517             - var :: Variable to check. If not matching /^\./,
518             a period will be prepended.
519              
520             if ( $yq->is_array_clear(var=>'rule-files') ){
521             print "clear...\n:";
522             }
523              
524             =cut
525              
526             sub is_array_clear {
527 0     0 1   my ( $self, %opts ) = @_;
528              
529 0 0         if ( !defined( $opts{var} ) ) {
    0          
530 0           die('Nothing specified for var to check');
531             } elsif ( $opts{var} !~ /^\./ ) {
532 0           $opts{var} = '.' . $opts{var};
533             }
534              
535 0 0         if ( !$self->is_array( var => $opts{var} ) ) {
536 0           die( '"' . $opts{var} . '" is not a array or is undef' );
537             }
538              
539 0           my $string = `yq "$opts{var}" $self->{qfile} 2> /dev/null`;
540 0 0         if ( $string =~ /\[\]/ ) {
541 0           return 1;
542             }
543              
544 0           return 0;
545             } ## end sub is_array_clear
546              
547             =head2 is_defined
548              
549             Checks if the specified variable is defined or not.
550              
551             Will die if called on a item that is not a array.
552              
553             - var :: Variable to check. If not matching /^\./,
554             a period will be prepended.
555              
556             if ( $yq->is_defined('vars.address-groups') ){
557             print "defined...\n:";
558             }
559              
560             =cut
561              
562             sub is_defined {
563 0     0 1   my ( $self, %opts ) = @_;
564              
565 0 0         if ( !defined( $opts{var} ) ) {
    0          
566 0           die('Nothing specified for var to check');
567             } elsif ( $opts{var} !~ /^\./ ) {
568 0           $opts{var} = '.' . $opts{var};
569             }
570              
571 0           my $string = `yq "$opts{var}" $self->{qfile} 2> /dev/null`;
572              
573 0 0         if ( $string eq "null\n" ) {
574 0           return 0;
575             }
576              
577 0           return 1;
578             } ## end sub is_defined
579              
580             =head2 is_hash
581              
582             Checks if the specified variable in a hash.
583              
584             Will die if called on a item that is not a array.
585              
586             - var :: Variable to check. If not matching /^\./,
587             a period will be prepended.
588              
589             if ( $yq->is_hash('vars.address-groups') ){
590             print "hash...\n:";
591             }
592              
593             =cut
594              
595             sub is_hash {
596 0     0 1   my ( $self, %opts ) = @_;
597              
598 0 0         if ( !defined( $opts{var} ) ) {
    0          
599 0           die('Nothing specified for var to check');
600             } elsif ( $opts{var} !~ /^\./ ) {
601 0           $opts{var} = '.' . $opts{var};
602             }
603              
604 0           my $string = `yq "$opts{var}" $self->{qfile} 2> /dev/null`;
605              
606 0 0         if ( $string =~ /\[\]/ ) {
    0          
    0          
607 0           return 0;
608             } elsif ( $string =~ /\{\}/ ) {
609 0           return 1;
610             } elsif ( $string eq "null\n" ) {
611 0           return 0;
612             }
613              
614 0           my $yaml = Load($string);
615              
616 0 0         if ( ref($yaml) eq 'HASH' ) {
617 0           return 1;
618             }
619              
620 0           return 0;
621             } ## end sub is_hash
622              
623             =head2 is_hash_clear
624              
625             Checks if a hash is clear or not.
626              
627             Will die if called on a item that is not a hash.
628              
629             - var :: Variable to check. If not matching /^\./,
630             a period will be prepended.
631              
632             if ( ! $yq->is_hash_clear(var=>'vars') ){
633             print "not clear...\n:";
634             }
635              
636             =cut
637              
638             sub is_hash_clear {
639 0     0 1   my ( $self, %opts ) = @_;
640              
641 0 0         if ( !defined( $opts{var} ) ) {
    0          
642 0           die('Nothing specified for var to check');
643             } elsif ( $opts{var} !~ /^\./ ) {
644 0           $opts{var} = '.' . $opts{var};
645             }
646              
647 0 0         if ( !$self->is_hash( var => $opts{var} ) ) {
648 0           die( '"' . $opts{var} . '" is not a hash or is undef' );
649             }
650              
651 0           my $string = `yq "$opts{var}" $self->{qfile} 2> /dev/null`;
652 0 0         if ( $string =~ /\{\}/ ) {
653 0           return 1;
654             }
655              
656 0           return 0;
657             } ## end sub is_hash_clear
658              
659             =head2 merge_yaml
660              
661             Murges the specified YAML into the current YAML.
662              
663             - yaml :: The YAML to merge into into the current one.
664             This value may not match /[\'\"]/.
665             - Default :: undef
666              
667             - merge_mode :: Merge mode to use.
668             - Default :: deeply
669              
670             $yq->merge_yaml(yaml=>'./some_other_file.yaml');
671              
672             The merge modes are as below.
673              
674             - deeply :: Deeply merge arrays.
675             - yq :: '. *d load("file2.yml")'
676              
677             - replace :: Replace arrays.
678             - yq :: '. *= load("file2.yml")'
679              
680             - append :: Append arrays.
681             - yq :: '. *+ load("file2.yml")'
682              
683             - existing :: Only merge existing fields.
684             - yq :: '. *? load("file2.yml")'
685              
686             - new :: Only merge new fields.
687             - yq :: '. *n load("file2.yml")'
688              
689             =cut
690              
691             sub merge_yaml {
692 0     0 1   my ( $self, %opts ) = @_;
693              
694 0 0         if ( !defined( $opts{yaml} ) ) {
    0          
    0          
695 0           die('Nothing specified for yaml');
696             } elsif ( !-f $opts{yaml} ) {
697 0           die( '"' . $opts{yaml} . '" does not exist' );
698             } elsif ( $opts{yaml} =~ /[\"\']/ ) {
699 0           die( '"' . $opts{yaml} . '" may not match /[\\\"\\\']/' );
700             }
701              
702 0 0 0       if ( !defined( $opts{merge_mode} ) ) {
    0 0        
      0        
      0        
703 0           $opts{merge_mode} = 'deeply';
704             } elsif ( $opts{merge_mode} ne 'deeply'
705             && $opts{merge_mode} ne 'replace'
706             && $opts{merge_mode} ne 'append'
707             && $opts{merge_mode} ne 'existing'
708             && $opts{merge_mode} ne 'new' )
709             {
710 0           die( '"' . $opts{merge_mode} . '" is not a recognized merge mode' );
711             }
712              
713             # default to deeply
714 0           my $mode = '*d';
715 0 0         if ( $opts{merge_mode} eq 'replace' ) {
    0          
    0          
    0          
716 0           $mode = '*=';
717             } elsif ( $opts{merge_mode} eq 'append' ) {
718 0           $mode = '*+';
719             } elsif ( $opts{merge_mode} eq 'existing' ) {
720 0           $mode = '*?';
721             } elsif ( $opts{merge_mode} eq 'new' ) {
722 0           $mode = '*n';
723             }
724              
725 0           my $toMerge = $opts{yaml};
726 0           my $string = `yq -i '. $mode load("$toMerge")' $self->{qfile}`;
727              
728 0           $self->ensure;
729             } ## end sub merge_yaml
730              
731             =head2 push_array
732              
733             Pushes the passed array onto the specified array.
734              
735             Will die if called on a item that is not a array or the array
736             does not exist.
737              
738             - var :: Variable to check. If not matching /^\./,
739             a period will be prepended.
740              
741             - vals :: Array of values to set the array to.
742              
743             $yq->push_array(var=>'rule-files',vals=>\@new_rules_files);
744              
745             =cut
746              
747             sub push_array {
748 0     0 1   my ( $self, %opts ) = @_;
749              
750 0 0         if ( !defined( $opts{vals} ) ) {
751 0           die('Nothing specified for vars');
752             } else {
753 0 0         if ( !defined $opts{vals}[0] ) {
754 0           return;
755             }
756             }
757              
758 0 0         if ( !defined( $opts{dedup} ) ) {
759 0           $opts{dedup} = 1;
760             }
761              
762 0 0         if ( !defined( $opts{var} ) ) {
    0          
763 0           die('Nothing specified for vals');
764             } elsif ( $opts{var} !~ /^\./ ) {
765 0           $opts{var} = '.' . $opts{var};
766             }
767              
768 0 0         if ( $opts{var} =~ /\[\]$/ ) {
769 0           $opts{var} =~ s/\[\]$//;
770             }
771              
772 0           my $string;
773 0 0         if ( !$self->is_array( var => $opts{var} ) ) {
774 0           die( '"' . $opts{var} . '" is not a array or is undef' );
775             }
776              
777 0           $string = `yq "$opts{var}" $self->{qfile} 2> /dev/null`;
778 0           my $yaml;
779 0 0         if ( $string =~ /\[\]/ ) {
780 0           print "blank\n";
781 0           $yaml = [];
782             } else {
783 0           eval { $yaml = Load($string); }
784 0 0         || eval { $string = 'foo: ' . $string; $yaml = Load($string); $yaml = $yaml->{foo} };
  0            
  0            
  0            
785             }
786              
787 0           my @new_array;
788 0           push( @new_array, @{$yaml} );
  0            
789 0           push( @new_array, @{ $opts{vals} } );
  0            
790              
791 0           $self->set_array( var => $opts{var}, vals => \@new_array );
792             } ## end sub push_array
793              
794             =head2 set_array
795              
796             Creates an array and sets it to the values.
797              
798             If the array is already defined, it will clear it and set
799             the values to those specified.
800              
801             Will die if called on a item that is not a array.
802              
803             - var :: Variable to check. If not matching /^\./,
804             a period will be prepended.
805              
806             - vals :: Array of values to set the array to.
807              
808             $yq->set_array(var=>'rule-files',vals=>\@vals);
809              
810             =cut
811              
812             sub set_array {
813 0     0 1   my ( $self, %opts ) = @_;
814              
815 0 0         if ( !defined( $opts{vals} ) ) {
816 0           die('Nothing specified for vars');
817             }
818              
819 0 0         if ( !defined( $opts{var} ) ) {
    0          
820 0           die('Nothing specified for vals');
821             } elsif ( $opts{var} !~ /^\./ ) {
822 0           $opts{var} = '.' . $opts{var};
823             }
824              
825 0           my $string;
826 0 0         if ( $self->is_defined( var => $opts{var} ) ) {
827 0           $string = `yq -i '$opts{var}=[]' $self->{qfile}`;
828             } else {
829 0           $self->clear_array( var => $opts{var} );
830             }
831              
832 0 0         if ( $opts{var} !~ /\[\]$/ ) {
833 0           $opts{var} =~ s/\[\]$//;
834             }
835              
836 0           my $int = 0;
837 0           while ( defined( $opts{vals}[$int] ) ) {
838 0           my $insert = $opts{var} . '[' . $int . ']="' . $opts{vals}[$int] . '"';
839 0           $string = `yq -i '$insert' $self->{qfile}`;
840 0           $int++;
841             }
842              
843 0           $self->ensure;
844             } ## end sub set_array
845              
846             =head2 set_hash
847              
848             Creates an hash and sets it to the values.
849              
850             If the hash is already defined, it will clear it and set
851             the values to those specified.
852              
853             Will die if called on a item that is not a array.
854              
855             - var :: Variable to check. If not matching /^\./,
856             a period will be prepended.
857              
858             - hash :: A hash to use for generating the hash to be
859             added. Any undef value will be set to null.
860              
861             $yq->set_hash(var=>'vars',hash=>{a=>33,bar=>undef});
862              
863             =cut
864              
865             sub set_hash {
866 0     0 1   my ( $self, %opts ) = @_;
867              
868 0           my @keys;
869 0 0         if ( !defined( $opts{hash} ) ) {
870 0           die('Nothing specified for hash');
871             } else {
872 0 0         if ( ref( $opts{hash} ) ne 'HASH' ) {
873 0           die( 'The passed value for hash is a ' . ref( $opts{hash} ) . ' and not HASH' );
874             }
875              
876 0           @keys = keys( %{ $opts{hash} } );
  0            
877              
878 0           foreach my $key (@keys) {
879 0 0 0       if ( defined( $opts{hash}{$key} )
      0        
880             && ref( $opts{hash}{$key} ) ne 'SCALAR'
881             && ref( $opts{hash}{$key} ) ne '' )
882             {
883             die( 'The passed value for the key "'
884             . $key
885             . '" for the hash is a '
886 0           . ref( $opts{hash}{$key} )
887             . ' and not SCALAR or undef' );
888             } ## end if ( defined( $opts{hash}{$key} ) && ref( ...))
889             } ## end foreach my $key (@keys)
890             } ## end else [ if ( !defined( $opts{hash} ) ) ]
891              
892 0 0         if ( !defined( $opts{var} ) ) {
    0          
893 0           die('Nothing specified for vals');
894             } elsif ( $opts{var} !~ /^\./ ) {
895 0           $opts{var} = '.' . $opts{var};
896             }
897              
898 0 0         if ( $opts{var} =~ /\[\]$/ ) {
899 0           die( 'vars, "' . $opts{var} . '", may not contains []' );
900             }
901              
902 0 0         if ( $opts{var} !~ /\.$/ ) {
903 0           $opts{var} =~ s/\.$//;
904             }
905              
906 0           my $string;
907 0 0         if ( !$self->is_defined( var => $opts{var} ) ) {
908 0           $string = `yq -i '$opts{var}={}' $self->{qfile}`;
909             } else {
910 0           $self->clear_hash( var => $opts{var} );
911             }
912              
913 0           foreach my $key (@keys) {
914 0           my $insert;
915 0 0         if ( defined( $opts{hash}{$key} ) ) {
916 0           $insert = $opts{var} . '.' . $key . '="' . $opts{hash}{$key} . '"';
917             } else {
918 0           $insert = $opts{var} . '.' . $key . '=null';
919             }
920 0           $string = `yq -i '$insert' $self->{qfile}`;
921             }
922              
923 0           $self->ensure;
924             } ## end sub set_hash
925              
926             =head2 set_in_array
927              
928             Ensures the values specified exist at any point in the array.
929              
930             Will create the array if it does not already exist.
931              
932             Will die if called on a item that is not a array.
933              
934             - var :: Variable to check. If not matching /^\./,
935             a period will be prepended.
936              
937             - vals :: Array of values to set the array to.
938              
939             - dedup :: If it should deduplicate the existing items
940             in the array or not.
941             Default :: 1
942              
943             $yq->set_in_array(var=>'rule-files',vals=>\@vals);
944              
945             =cut
946              
947             sub set_in_array {
948 0     0 1   my ( $self, %opts ) = @_;
949              
950 0           my $to_exist = {};
951 0 0         if ( !defined( $opts{vals} ) ) {
952 0           die('Nothing specified for vars');
953             } else {
954 0 0         if ( !defined $opts{vals}[0] ) {
955 0           return;
956             }
957              
958 0           my $int = 0;
959 0           while ( defined( $opts{vals}[$int] ) ) {
960 0           $to_exist->{ $opts{vals}[$int] } = 1;
961 0           $int++;
962             }
963              
964             } ## end else [ if ( !defined( $opts{vals} ) ) ]
965              
966 0 0         if ( !defined( $opts{dedup} ) ) {
967 0           $opts{dedup} = 1;
968             }
969              
970 0 0         if ( !defined( $opts{var} ) ) {
    0          
971 0           die('Nothing specified for vals');
972             } elsif ( $opts{var} !~ /^\./ ) {
973 0           $opts{var} = '.' . $opts{var};
974             }
975              
976 0 0         if ( $opts{var} =~ /\[\]$/ ) {
977 0           $opts{var} =~ s/\[\]$//;
978             }
979              
980 0           my $string;
981 0 0         if ( !$self->is_defined( var => $opts{var} ) ) {
982 0           $string = `yq -i '$opts{var}=[]' $self->{qfile}`;
983             } else {
984 0 0         if ( !$self->is_array( var => $opts{var} ) ) {
985 0           die( '"' . $opts{var} . '" is not a array or is undef' );
986             }
987              
988             }
989              
990 0           $string = `yq "$opts{var}" $self->{qfile} 2> /dev/null`;
991 0           my $yaml;
992 0 0         if ( $string =~ /\[\]/ ) {
993 0           print "blank\n";
994 0           $yaml = [];
995             } else {
996 0           eval { $yaml = Load($string); };
  0            
997             }
998              
999 0           my $int = 0;
1000 0           my @exiting_a;
1001 0           my $existing_h = {};
1002 0           while ( defined( $yaml->[$int] ) ) {
1003 0 0         if ( defined( $to_exist->{ $yaml->[$int] } ) ) {
1004 0           delete( $to_exist->{ $yaml->[$int] } );
1005             }
1006              
1007 0           push( @exiting_a, $yaml->[$int] );
1008              
1009 0           $existing_h->{ $yaml->[$int] } = 1;
1010              
1011 0           $int++;
1012             } ## end while ( defined( $yaml->[$int] ) )
1013              
1014 0           my @new_array;
1015 0 0         if ( $opts{dedup} ) {
1016 0           push( @new_array, keys( %{$existing_h} ) );
  0            
1017 0           push( @new_array, keys( %{$to_exist} ) );
  0            
1018             } else {
1019 0           push( @new_array, @exiting_a );
1020 0           push( @new_array, keys( %{$to_exist} ) );
  0            
1021             }
1022              
1023 0           $self->set_array( var => $opts{var}, vals => \@new_array );
1024             } ## end sub set_in_array
1025              
1026             =head2 yaml_diff
1027              
1028             This returns a diff between both YAMLs.
1029              
1030             The two YAMLs are are copied to the a temp dir.
1031              
1032             - yaml :: The YAML use for the new side of the diff.
1033             - Default :: undef
1034              
1035             =cut
1036              
1037             sub yaml_diff {
1038 0     0 1   my ( $self, %opts ) = @_;
1039              
1040 0 0         if ( !defined( $opts{yaml} ) ) {
    0          
    0          
1041 0           die('Nothing specified for yaml');
1042             } elsif ( !-f $opts{yaml} ) {
1043 0           die( '"' . $opts{yaml} . '" does not exist' );
1044             } elsif ( !-r $opts{yaml} ) {
1045 0           die( '"' . $opts{yaml} . '" is not readable' );
1046             }
1047              
1048 0           my $dir = tempdir( CLEANUP => 1 );
1049              
1050 0           my $yaml1 = $dir . '/old';
1051 0           copy( $self->{file}, $yaml1 );
1052 0           my $string = `yq -i -P 'sort_keys(..) | ... comments=""' -o=props $yaml1`;
1053              
1054 0           my $yaml2 = $dir . '/new';
1055 0           copy( $opts{yaml}, $yaml2 );
1056 0           $string = `yq -i -P 'sort_keys(..) | ... comments=""' -o=props $yaml2`;
1057              
1058 0           my $old_dir = getcwd;
1059              
1060 0           chdir($dir);
1061 0           $string = `diff -u old new`;
1062 0           chdir($old_dir);
1063              
1064 0           return $string;
1065             } ## end sub yaml_diff
1066              
1067             =head1 AUTHOR
1068              
1069             Zane C. Bowers-Hadley, C<< >>
1070              
1071             =head1 BUGS
1072              
1073             Please report any bugs or feature requests to C, or through
1074             the web interface at L. I will be notified, and then you'll
1075             automatically be notified of progress on your bug as I make changes.
1076              
1077              
1078              
1079              
1080             =head1 SUPPORT
1081              
1082             You can find documentation for this module with the perldoc command.
1083              
1084             perldoc YAML::yq::Helper
1085              
1086              
1087             You can also look for information at:
1088              
1089             =over 4
1090              
1091             =item * RT: CPAN's request tracker (report bugs here)
1092              
1093             L
1094              
1095             =item * Search CPAN
1096              
1097             L
1098              
1099             =back
1100              
1101              
1102             =head1 ACKNOWLEDGEMENTS
1103              
1104              
1105             =head1 LICENSE AND COPYRIGHT
1106              
1107             This software is Copyright (c) 2022 by Zane C. Bowers-Hadley.
1108              
1109             This is free software, licensed under:
1110              
1111             The Artistic License 2.0 (GPL Compatible)
1112              
1113              
1114             =cut
1115              
1116             1; # End of YAML::yq::Helper