File Coverage

blib/lib/App/Yabsm/Config/Parser.pm
Criterion Covered Total %
statement 258 260 99.2
branch 70 104 67.3
condition 24 41 58.5
subroutine 31 31 100.0
pod 0 18 0.0
total 383 454 84.3


line stmt bran cond sub pod time code
1             # Author: Nicholas Hubbard
2             # WWW: https://github.com/NicholasBHubbard/yabsm
3             # License: MIT
4              
5             # Provides functionality for Yabsm configuration parsing using the
6             # Parser::MGC library. Tests for the parser are located at
7             # src/t/Config.t.
8             #
9             # This parser produces a multi-dimensional hash data structure with
10             # the following skeleton:
11             #
12             # %config = ( yabsm_dir => '/.snapshots/yabsm'
13             #
14             # subvols => { foo => { mountpoint=/foo_dir }
15             # , bar => { mountpoint=/bar_dir }
16             # , ...
17             # },
18             # snaps => { foo_snap => { key=val, ... }
19             # , bar_snap => { key=val, ... }
20             # , ...
21             # },
22             # ssh_backups => { foo_ssh_backup => { key=val, ... }
23             # , bar_ssh_backup => { key=val, ... }
24             # , ...
25             # },
26             # local_backups => { foo_local_backup => { key=val, ... }
27             # , bar_local_backup => { key=val, ... }
28             # , ...
29             # }
30             # );
31              
32 2     2   514 use strict;
  2         7  
  2         45  
33 2     2   8 use warnings;
  2         3  
  2         34  
34 2     2   22 use v5.16.3;
  2         6  
35              
36             package App::Yabsm::Config::Parser;
37              
38 2     2   310 use App::Yabsm::Tools qw(arg_count_or_die);
  2         3  
  2         84  
39              
40 2     2   10 use Carp qw(confess);
  2         2  
  2         70  
41 2     2   725 use Array::Utils qw(array_minus);
  2         639  
  2         114  
42 2     2   806 use Regexp::Common qw(net);
  2         4013  
  2         5  
43 2     2   4564 use Feature::Compat::Try;
  2         10  
  2         15  
44              
45 2     2   1043 use Parser::MGC;
  2         8560  
  2         57  
46 2     2   14 use base 'Parser::MGC';
  2         2  
  2         231  
47              
48             ####################################
49             # EXPORTED #
50             ####################################
51              
52 2     2   11 use Exporter qw(import);
  2         3  
  2         6074  
53             our @EXPORT_OK = qw(parse_config_or_die);
54              
55             sub parse_config_or_die {
56              
57             # Attempt to parse $file into a yabsm configuration data structure.
58              
59 37     37 0 16189 arg_count_or_die(0, 1, @_);
60              
61 37   50     87 my $file = shift // '/etc/yabsm.conf';
62              
63 37 50       815 -f $file or die "yabsm: config error: no such file '$file'\n";
64 37 50       436 -r $file or die "yabsm: config error: can not read file '$file'\n";
65              
66             # Initialize the Parser::MGC parser object
67             my $parser = __PACKAGE__->new( toplevel => 'config_parser'
68             , patterns => { comment => &grammar->{comment}
69             , ws => &grammar->{whitespace}
70             }
71 37         127 );
72              
73 37         1888 my $config_ref = do {
74             try { $parser->from_file($file) }
75 37         97 catch ($e) { $e =~ s/\s+$// ; die "yabsm: config error: $e\n" }
76             };
77              
78 16         807 my ($config_valid, @error_msgs) = check_config($config_ref);
79              
80 16 100       36 if ($config_valid) {
81 7 50       104 return wantarray ? %{ $config_ref} : $config_ref;
  0         0  
82             }
83             else {
84 9         17 my $error_msg = join '', map { $_ = "$_\n" } @error_msgs;
  10         33  
85 9         139 die $error_msg;
86             }
87             }
88              
89             ####################################
90             # GRAMMAR #
91             ####################################
92              
93             sub grammar {
94              
95             # Return a hash of all the atomic grammar elements of the yabsm config
96             # language.
97              
98 236     236 0 476 arg_count_or_die(0, 0, @_);
99              
100 236         1658 my %grammar = (
101             name => qr/[a-zA-Z][-_a-zA-Z0-9]*/,
102             subvol => qr/[a-zA-Z][-_a-zA-Z0-9]*/,
103             dir => qr/\/[a-zA-Z0-9._:\-\/]*/,
104             mountpoint => qr/\/[a-zA-Z0-9._:\-\/]*/,
105             # timeframes example: hourly,monthly,daily
106             timeframes => qr/((5minute|hourly|daily|weekly|monthly),)+(5minute|hourly|daily|weekly|monthly)|(5minute|hourly|daily|weekly|monthly)/,
107             ssh_dest => qr/([a-z_]([a-z0-9_-]{0,31}|[a-z0-9_-]{0,30}\$)@)?(([A-Za-z][A-Za-z0-9_-]*)|$RE{net}{IPv4}{strict}|$RE{net}{IPv6})/,
108             opening_brace => qr/{/,
109             closing_brace => qr/}/,
110             equals_sign => qr/=/,
111             comment => qr/[\s\t]*#.*/,
112             whitespace => qr/[\s\t\n]+/,
113             timeframe_sub_grammar => {
114              
115             '5minute_keep' => qr/[1-9][0-9]*/,
116             hourly_keep => qr/[1-9][0-9]*/,
117             daily_keep => qr/[1-9][0-9]*/,
118             weekly_keep => qr/[1-9][0-9]*/,
119             monthly_keep => qr/[1-9][0-9]*/,
120              
121             # comma seperated hh:mm's
122             daily_times => qr/(((0[0-9]|1[0-9]|2[0-3]):[0-5][0-9]),)+((0[0-9]|1[0-9]|2[0-3]):[0-5][0-9])|(0[0-9]|1[0-9]|2[0-3]):[0-5][0-9]/,
123              
124             # hh:mm
125             weekly_time => qr/(0[0-9]|1[0-9]|2[0-3]):[0-5][0-9]/,
126             monthly_time => qr/(0[0-9]|1[0-9]|2[0-3]):[0-5][0-9]/,
127              
128             weekly_day => qr/[1-7]|monday|tuesday|wednesday|thursday|friday|saturday|sunday/,
129             monthly_day => qr/3[01]|[12][0-9]|[1-9]/ # 1-31
130             }
131             );
132              
133 236 100       48709 return wantarray ? %grammar : \%grammar;
134             }
135              
136             sub grammar_msg {
137              
138             # Return a hash that associates grammar non-terminals to a linguistic
139             # description of their expected value. Used for generating meaningful error
140             # messages.
141              
142 4     4 0 148 arg_count_or_die(0, 0, @_);
143              
144 4         37 my %grammar_msg = (
145             name => 'thing name',
146             subvol => 'subvol name',
147             dir => 'absolute path',
148             mountpoint => 'absolute path',
149             timeframes => 'comma separated timeframes',
150             ssh_dest => 'SSH destination',
151             opening_brace => q('{'}),
152             closing_brace => q('}'),
153             equals_sign => q('='),
154             comment => 'comment',
155             whitespace => 'whitespace',
156             #keep
157             '5minute_keep' => 'positive integer',
158             hourly_keep => 'positive integer',
159             daily_keep => 'positive integer',
160             weekly_keep => 'positive integer',
161             monthly_keep => 'positive integer',
162             #time
163             daily_times => q(comma seperated list of times in 'hh:mm' form'),
164             weekly_time => q(time in 'hh:mm' form),
165             monthly_time => q(time in 'hh:mm' form),
166             #day
167             weekly_day => 'week day',
168             monthly_day => 'month day'
169             );
170              
171 4 50       18 return wantarray ? %grammar_msg : \%grammar_msg;
172             }
173              
174             sub subvol_settings_grammar {
175              
176             # Return a hash of a subvols key=val grammar.
177              
178 38     38 0 90 arg_count_or_die(0, 0, @_);
179              
180 38         66 my %grammar = grammar();
181              
182             my %subvol_settings_grammar = (
183             mountpoint => $grammar{mountpoint}
184 38         112 );
185              
186 38 50       215 return wantarray ? %subvol_settings_grammar : \%subvol_settings_grammar;
187             }
188              
189             sub snap_settings_grammar {
190              
191             # Return a hash of a snaps key=val grammar. Optionally takes a false value
192             # to exclude the timeframe subgrammar from the returned grammar.
193              
194 36     36 0 82 arg_count_or_die(0, 1, @_);
195              
196 36   100     84 my $include_tf = shift // 1;
197              
198 36         76 my %grammar = grammar();
199              
200             my %timeframe_sub_grammar =
201 36 100       112 $include_tf ? %{ $grammar{timeframe_sub_grammar} } : ();
  20         71  
202              
203             my %snap_settings_grammar = (
204             subvol => $grammar{subvol},
205             timeframes => $grammar{timeframes},
206 36         144 %timeframe_sub_grammar
207             );
208              
209 36 50       218 return wantarray ? %snap_settings_grammar : \%snap_settings_grammar;
210             }
211              
212             sub ssh_backup_settings_grammar {
213              
214             # Return a hash of a ssh_backups key=val grammar. Optionally takes a false
215             # value to exclude the timeframe subgrammar from the returned grammar.
216              
217 26     26 0 83 arg_count_or_die(0, 1, @_);
218              
219 26   100     84 my $include_tf = shift // 1;
220              
221 26         57 my %grammar = grammar();
222              
223             my %timeframe_sub_grammar =
224 26 100       83 $include_tf ? %{ $grammar{timeframe_sub_grammar} } : ();
  10         38  
225              
226             my %ssh_backup_settings_grammar = (
227             subvol => $grammar{subvol},
228             ssh_dest => $grammar{ssh_dest},
229             dir => $grammar{dir},
230             timeframes => $grammar{timeframes},
231 26         114 %timeframe_sub_grammar
232             );
233              
234 26 50       165 return wantarray ? %ssh_backup_settings_grammar : \%ssh_backup_settings_grammar;
235             }
236              
237             sub local_backup_settings_grammar {
238              
239             # Return a hash of a local_backups key=val grammar. Optionally takes a false
240             # value to exclude the timeframe subgrammar from the returned grammar.
241              
242 25     25 0 58 arg_count_or_die(0, 1, @_);
243              
244 25   100     59 my $include_tf = shift // 1;
245              
246 25         46 my %grammar = grammar();
247              
248             my %timeframe_sub_grammar =
249 25 100       77 $include_tf ? %{ $grammar{timeframe_sub_grammar} } : ();
  9         34  
250              
251             my %local_backup_settings_grammar = (
252             subvol => $grammar{subvol},
253             dir => $grammar{dir},
254             timeframes => $grammar{timeframes},
255 25         90 %timeframe_sub_grammar
256             );
257              
258 25 50       159 return wantarray ? %local_backup_settings_grammar : \%local_backup_settings_grammar;
259             }
260              
261             ####################################
262             # PARSER #
263             ####################################
264              
265             sub config_parser {
266              
267             # Top level parser
268              
269 37     37 0 4427 arg_count_or_die(1, 1, @_);
270              
271 37         48 my $self = shift;
272              
273             # return this
274 37         58 my %config;
275              
276             # Define the parser
277              
278 37         75 my %grammar = grammar();
279              
280             $self->sequence_of( sub {
281 120     120   5296 $self->commit;
282             $self->any_of(
283             sub {
284 120         1324 $self->expect( 'yabsm_dir' );
285 35         1720 $self->commit;
286 35 50       239 exists $config{yabsm_dir} and $self->fail('yabsm_dir is already defined');
287 35   33     63 $self->maybe_expect('=') // $self->fail(q(expected '='));
288 35   33     922 my $dir = $self->maybe_expect($grammar{dir}) // $self->fail(grammar_msg->{dir});
289 35         1476 $config{yabsm_dir} = $dir;
290             },
291             sub {
292 85         6104 $self->expect( 'subvol' );
293 39         1526 $self->commit;
294 39         234 my $name = $self->maybe_expect( $grammar{name} );
295 39   33     1114 $name // $self->fail('expected subvol name');
296 39 100       115 exists $config{subvols}{$name} and $self->fail("already have a subvol named '$name'");
297 38 50       79 exists $config{snaps}{$name} and $self->fail("already have a snap named '$name'");
298 38 50       78 exists $config{ssh_backups}{$name} and $self->fail("already have a ssh_backup named '$name'");
299 38 50       92 exists $config{local_backups}{$name} and $self->fail("already have a local_backup named '$name'");
300 38         88 my $kvs = $self->scope_of('{', 'subvol_settings_parser' ,'}');
301 36         1355 $config{subvols}{$name} = $kvs;
302             },
303             sub {
304 46         2723 $self->expect( 'snap' );
305 23         851 $self->commit;
306 23         130 my $name = $self->maybe_expect( $grammar{name} );
307 23   66     637 $name // $self->fail('expected snap name');
308 22 50       47 exists $config{subvols}{$name} and $self->fail("already have a subvol named '$name'");
309 22 100       48 exists $config{snaps}{$name} and $self->fail("already have a snap named '$name'");
310 21 50       32 exists $config{ssh_backups}{$name} and $self->fail("already have a ssh_backup named '$name'");
311 21 50       39 exists $config{local_backups}{$name} and $self->fail("already have a local_backup named '$name'");
312 21         43 my $kvs = $self->scope_of('{', 'snap_settings_parser', '}');
313 13         508 $config{snaps}{$name} = $kvs;
314             },
315             sub {
316 23         1682 $self->expect( 'ssh_backup' );
317 12         444 $self->commit;
318 12         68 my $name = $self->maybe_expect( $grammar{name} );
319 12   66     379 $name // $self->fail('expected ssh_backup name');
320 11 50       23 exists $config{subvols}{$name} and $self->fail("already have a subvol named '$name'");
321 11 50       29 exists $config{snaps}{$name} and $self->fail("already have a snap named '$name'");
322 11 100       24 exists $config{ssh_backups}{$name} and $self->fail("already have a ssh_backup named '$name'");
323 10 50       17 exists $config{local_backups}{$name} and $self->fail("already have a local_backup named '$name'");
324 10         26 my $kvs = $self->scope_of('{', 'ssh_backup_settings_parser', '}');
325 8         319 $config{ssh_backups}{$name} = $kvs;
326             },
327             sub {
328 11         704 $self->expect( 'local_backup' );
329 10         369 $self->commit;
330 10         60 my $name = $self->maybe_expect( $grammar{name} );
331 10   66     272 $name // $self->fail('expected local_backup name');
332 9 50       20 exists $config{subvols}{$name} and $self->fail("already have a subvol named '$name'");
333 9 50       25 exists $config{snaps}{$name} and $self->fail("already have a snap named '$name'");
334 9 50       14 exists $config{ssh_backups}{$name} and $self->fail("already have a ssh_backup named '$name'");
335 9 50       17 exists $config{local_backups}{$name} and $self->fail("already have a local_backup named '$name'");
336 9         21 my $kvs = $self->scope_of('{', 'local_backup_settings_parser', '}');
337 7         272 $config{local_backups}{$name} = $kvs;
338             },
339             sub {
340 1         57 $self->commit;
341 1         6 $self->skip_ws; # skip_ws also skips comments
342 1         11 $self->fail(q(expected one of 'subvol', 'snap', 'ssh_backup', or 'local_backup'));
343             }
344 120         1491 );
345 37         257 });
346              
347 16 50       873 return wantarray ? %config : \%config;
348             }
349              
350             sub settings_parser {
351              
352             # Abstract method that parses a sequence of key=val pairs based off of the
353             # input grammar %grammar. The arg $type is simply a string that is either
354             # 'subvol', 'snap', 'ssh_backup', or 'local_backup' and is only used for
355             # error message generation. This method should be called from a wrapper
356             # method.
357              
358 77     77 0 192 arg_count_or_die(3, 3, @_);
359              
360 77         93 my $self = shift;
361 77         89 my $type = shift;
362 77         78 my $grammar = shift;
363              
364 77         84 my @settings = keys %{ $grammar };
  77         182  
365 77         193 my $setting_regex = join '|', @settings;
366              
367             # return this
368 77         86 my %kvs;
369              
370             $self->sequence_of( sub {
371 226     226   7385 $self->commit;
372              
373 226   66     3700 my $setting = $self->maybe_expect( qr/$setting_regex/ )
374             // $self->fail("expected a $type setting");
375              
376 218   33     11005 $self->maybe_expect('=') // $self->fail('expected "="');
377              
378             my $value = $self->maybe_expect($grammar->{$setting})
379 218   66     5487 // $self->fail('expected ' . grammar_msg->{$setting});
380              
381 214         10515 $kvs{$setting} = $value;
382 77         382 });
383              
384 65 50       2136 return wantarray ? %kvs : \%kvs;
385             }
386              
387             sub subvol_settings_parser {
388 38     38 0 1943 arg_count_or_die(1, 1, @_);
389 38         43 my $self = shift;
390 38         78 my $subvol_settings_grammar = subvol_settings_grammar();
391 38         93 $self->settings_parser('subvol', $subvol_settings_grammar);
392             }
393              
394             sub snap_settings_parser {
395 20     20 0 953 arg_count_or_die(1, 1, @_);
396 20         31 my $self = shift;
397 20         43 my $snap_settings_grammar = snap_settings_grammar();
398 20         47 $self->settings_parser('snap', $snap_settings_grammar);
399             }
400              
401             sub ssh_backup_settings_parser {
402 10     10 0 480 arg_count_or_die(1, 1, @_);
403 10         15 my $self = shift;
404 10         22 my $ssh_backup_settings_grammar = ssh_backup_settings_grammar();
405 10         23 $self->settings_parser('ssh_backup', $ssh_backup_settings_grammar);
406             }
407              
408             sub local_backup_settings_parser {
409 9     9 0 470 arg_count_or_die(1, 1, @_);
410 9         13 my $self = shift;
411 9         18 my $local_backup_settings_grammar = local_backup_settings_grammar();
412 9         22 $self->settings_parser('local_backup', $local_backup_settings_grammar);
413             }
414              
415             ####################################
416             # ERROR ANALYSIS #
417             ####################################
418              
419             sub check_config {
420              
421             # Ensure that $config_ref references a valid yabsm configuration. If the
422             # config is valid return a list containing only the value 1, otherwise
423             # return multiple values where the first value is 0 and the rest of the
424             # values are the corresponding error messages.
425              
426 16     16 0 65 arg_count_or_die(1, 1, @_);
427              
428 16         22 my $config_ref = shift;
429              
430 16         22 my @error_msgs;
431              
432 16 100       43 unless ($config_ref->{yabsm_dir}) {
433 2         5 push @error_msgs, q(yabsm: config error: missing required setting 'yabsm_dir');
434             }
435              
436 16 50 66     57 unless ($config_ref->{snaps} || $config_ref->{ssh_backups} || $config_ref->{local_backups}) {
      33        
437 1         2 push @error_msgs, 'yabsm: config error: no defined snaps, ssh_backups, or local_backups';
438             }
439              
440 16         38 push @error_msgs, snap_errors($config_ref);
441 16         41 push @error_msgs, ssh_backup_errors($config_ref);
442 16         44 push @error_msgs, local_backup_errors($config_ref);
443              
444 16 100       90 if (@error_msgs) {
445 9         32 return (0, @error_msgs);
446             }
447             else {
448 7         17 return (1);
449             }
450             }
451              
452             sub snap_errors {
453              
454             # Ensure that all the snaps defined in the config referenced by $config_ref
455             # are not missing required snap settings and are snapshotting a defined
456             # subvol.
457              
458 16     16 0 41 arg_count_or_die(1, 1, @_);
459              
460 16         17 my $config_ref = shift;
461              
462             # return this
463 16         21 my @error_msgs;
464              
465             # Base required settings. Passing 0 to snap_settings_grammar excludes
466             # timeframe settings from the returned hash.
467 16         19 my @base_required_settings = keys %{ snap_settings_grammar(0) };
  16         36  
468              
469 16         41 foreach my $snap (keys %{ $config_ref->{snaps} }) {
  16         66  
470              
471             # Make sure that the subvol being snapped exists
472 11         18 my $subvol = $config_ref->{snaps}{$snap}{subvol};
473 11 50       24 if (defined $subvol) {
474 11 100       18 unless (grep { $subvol eq $_ } keys %{ $config_ref->{subvols} }) {
  15         39  
  11         23  
475 1         5 push @error_msgs, "yabsm: config error: snap '$snap' is snapshotting up a non-existent subvol '$subvol'";
476             }
477             }
478              
479             # Make sure all required settings are defined
480 11         24 my @required_settings = @base_required_settings;
481 11         19 my $timeframes = $config_ref->{snaps}{$snap}{timeframes};
482 11 100       20 if (defined $timeframes) {
483 10         25 push @required_settings, required_timeframe_settings($timeframes);
484             }
485 11         13 my @defined_settings = keys %{ $config_ref->{snaps}{$snap} };
  11         30  
486 11         42 my @missing_settings = array_minus(@required_settings, @defined_settings);
487 11         163 foreach my $missing (@missing_settings) {
488 2         9 push @error_msgs, "yabsm: config error: snap '$snap' missing required setting '$missing'";
489             }
490             }
491              
492 16 50       47 return wantarray ? @error_msgs : \@error_msgs;
493             }
494              
495             sub ssh_backup_errors {
496              
497             # Ensure that all the ssh_backups defined in the config referenced by
498             # $config_ref are not missing required ssh_backup settings and are backing
499             # up a defined subvol.
500              
501 16     16 0 47 arg_count_or_die(1, 1, @_);
502              
503 16         19 my $config_ref = shift;
504              
505             # return this
506 16         21 my @error_msgs;
507              
508             # Base required settings. Passing 0 to ssh_backup_settings_grammar excludes
509             # timeframe settings from the returned hash.
510 16         18 my @base_required_settings = keys %{ ssh_backup_settings_grammar(0) };
  16         33  
511              
512 16         46 foreach my $ssh_backup (keys %{ $config_ref->{ssh_backups} }) {
  16         48  
513              
514             # Make sure that the subvol being backed up exists
515 7         14 my $subvol = $config_ref->{ssh_backups}{$ssh_backup}{subvol};
516 7 50       15 if (defined $subvol) {
517 7 100       9 unless (grep { $subvol eq $_ } keys %{ $config_ref->{subvols} }) {
  10         27  
  7         17  
518 1         4 push @error_msgs, "yabsm: config error: ssh_backup '$ssh_backup' is backing up a non-existent subvol '$subvol'";
519             }
520             }
521              
522             # Make sure all required settings are defined
523 7         14 my @required_settings = @base_required_settings;
524 7         13 my $timeframes = $config_ref->{ssh_backups}{$ssh_backup}{timeframes};
525 7 50       11 if (defined $timeframes) {
526 7         18 push @required_settings, required_timeframe_settings($timeframes);
527             }
528 7         11 my @defined_settings = keys %{ $config_ref->{ssh_backups}{$ssh_backup} };
  7         20  
529 7         24 my @missing_settings = array_minus(@required_settings, @defined_settings);
530 7         116 foreach my $missing (@missing_settings) {
531 1         5 push @error_msgs, "yabsm: config error: ssh_backup '$ssh_backup' missing required setting '$missing'";
532             }
533             }
534              
535 16 50       43 return wantarray ? @error_msgs : \@error_msgs;
536             }
537              
538             sub local_backup_errors {
539              
540             # Ensure that all the local_backups defined in the config referenced by
541             # $config_ref are not missing required local_backup settings and are backing
542             # up a defined subvol
543              
544 16     16 0 45 arg_count_or_die(1, 1, @_);
545              
546 16         24 my $config_ref = shift;
547              
548             # return this
549 16         18 my @error_msgs;
550              
551             # Base required settings. Passing 0 to local_backup_settings_grammar
552             # excludes timeframe settings from the returned hash.
553 16         19 my @base_required_settings = keys %{ local_backup_settings_grammar(0) };
  16         28  
554              
555 16         38 foreach my $local_backup (keys %{ $config_ref->{local_backups} }) {
  16         45  
556              
557             # Make sure that the subvol being backed up exists
558 7         12 my $subvol = $config_ref->{local_backups}{$local_backup}{subvol};
559 7 50       15 if (defined $subvol) {
560 7 100       10 unless (grep { $subvol eq $_ } keys %{ $config_ref->{subvols} }) {
  10         25  
  7         15  
561 1         5 push @error_msgs, "yabsm: config error: local_backup '$local_backup' is backing up a non-existent subvol '$subvol'";
562             }
563             }
564              
565             # Make sure all required settings are defined
566 7         14 my @required_settings = @base_required_settings;
567 7         10 my $timeframes = $config_ref->{local_backups}{$local_backup}{timeframes};
568 7 50       14 if (defined $timeframes) {
569 7         12 push @required_settings, required_timeframe_settings($timeframes);
570             }
571 7         8 my @defined_settings = keys %{ $config_ref->{local_backups}{$local_backup} };
  7         19  
572 7         23 my @missing_settings = array_minus(@required_settings, @defined_settings);
573 7         107 foreach my $missing (@missing_settings) {
574 1         4 push @error_msgs, "yabsm: config error: local_backup '$local_backup' missing required setting '$missing'";
575             }
576             }
577              
578 16 50       39 return wantarray ? @error_msgs : \@error_msgs;
579             }
580              
581             sub required_timeframe_settings {
582              
583             # Given a timeframes value like 'hourly,daily,monthly' returns a list of
584             # required settings. This subroutine is used to dynamically determine what
585             # settings are required for certain config entities.
586              
587 24     24 0 66 arg_count_or_die(1, 1, @_);
588              
589 24         31 my $tframes = shift;
590              
591 24         78 my @timeframes = split ',', $tframes;
592              
593             # return this
594 24         31 my @required;
595              
596 24         36 foreach my $tframe (@timeframes) {
597 51 100       89 if ($tframe eq '5minute') { push @required, qw(5minute_keep) }
  22 100       37  
    100          
    100          
    50          
598 10         14 elsif ($tframe eq 'hourly') { push @required, qw(hourly_keep) }
599 7         13 elsif ($tframe eq 'daily') { push @required, qw(daily_keep daily_times) }
600 6         14 elsif ($tframe eq 'weekly') { push @required, qw(weekly_keep weekly_time weekly_day) }
601 6         10 elsif ($tframe eq 'monthly') { push @required, qw(monthly_keep monthly_time monthly_day) }
602             else {
603 0         0 confess("yabsm: internal error: no such timeframe '$tframe'");
604             }
605             }
606              
607 24         59 return @required;
608             }
609              
610             1;