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   492 use strict;
  2         8  
  2         52  
33 2     2   9 use warnings;
  2         3  
  2         37  
34 2     2   26 use v5.16.3;
  2         6  
35              
36             package App::Yabsm::Config::Parser;
37              
38 2     2   357 use App::Yabsm::Tools qw(arg_count_or_die);
  2         3  
  2         93  
39              
40 2     2   10 use Carp qw(confess);
  2         3  
  2         77  
41 2     2   811 use Array::Utils qw(array_minus);
  2         728  
  2         127  
42 2     2   870 use Regexp::Common qw(net);
  2         4859  
  2         6  
43 2     2   5594 use Feature::Compat::Try;
  2         9  
  2         12  
44              
45 2     2   1117 use Parser::MGC;
  2         9731  
  2         56  
46 2     2   12 use base 'Parser::MGC';
  2         3  
  2         246  
47              
48             ####################################
49             # EXPORTED #
50             ####################################
51              
52 2     2   12 use Exporter qw(import);
  2         3  
  2         7757  
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 12898 arg_count_or_die(0, 1, @_);
60              
61 37   50     65 my $file = shift // '/etc/yabsm.conf';
62              
63 37 50       578 -f $file or die "yabsm: config error: no such file '$file'\n";
64 37 50       426 -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         91 );
72              
73 37         1695 my $config_ref = do {
74             try { $parser->from_file($file) }
75 37         104 catch ($e) { $e =~ s/\s+$// ; die "yabsm: config error: $e\n" }
76             };
77              
78 16         692 my ($config_valid, @error_msgs) = check_config($config_ref);
79              
80 16 100       30 if ($config_valid) {
81 7 50       112 return wantarray ? %{ $config_ref} : $config_ref;
  0         0  
82             }
83             else {
84 9         13 my $error_msg = join '', map { $_ = "$_\n" } @error_msgs;
  10         31  
85 9         97 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 582 arg_count_or_die(0, 0, @_);
99              
100 236         1721 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       53957 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 167 arg_count_or_die(0, 0, @_);
143              
144 4         34 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       19 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 80 arg_count_or_die(0, 0, @_);
179              
180 38         70 my %grammar = grammar();
181              
182             my %subvol_settings_grammar = (
183             mountpoint => $grammar{mountpoint}
184 38         130 );
185              
186 38 50       257 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 74 arg_count_or_die(0, 1, @_);
195              
196 36   100     78 my $include_tf = shift // 1;
197              
198 36         58 my %grammar = grammar();
199              
200             my %timeframe_sub_grammar =
201 36 100       86 $include_tf ? %{ $grammar{timeframe_sub_grammar} } : ();
  20         79  
202              
203             my %snap_settings_grammar = (
204             subvol => $grammar{subvol},
205             timeframes => $grammar{timeframes},
206 36         137 %timeframe_sub_grammar
207             );
208              
209 36 50       242 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 63 arg_count_or_die(0, 1, @_);
218              
219 26   100     58 my $include_tf = shift // 1;
220              
221 26         51 my %grammar = grammar();
222              
223             my %timeframe_sub_grammar =
224 26 100       67 $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         104 %timeframe_sub_grammar
232             );
233              
234 26 50       182 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 54 arg_count_or_die(0, 1, @_);
243              
244 25   100     50 my $include_tf = shift // 1;
245              
246 25         40 my %grammar = grammar();
247              
248             my %timeframe_sub_grammar =
249 25 100       58 $include_tf ? %{ $grammar{timeframe_sub_grammar} } : ();
  9         36  
250              
251             my %local_backup_settings_grammar = (
252             subvol => $grammar{subvol},
253             dir => $grammar{dir},
254             timeframes => $grammar{timeframes},
255 25         92 %timeframe_sub_grammar
256             );
257              
258 25 50       169 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 3775 arg_count_or_die(1, 1, @_);
270              
271 37         47 my $self = shift;
272              
273             # return this
274 37         49 my %config;
275              
276             # Define the parser
277              
278 37         76 my %grammar = grammar();
279              
280             $self->sequence_of( sub {
281 120     120   5342 $self->commit;
282             $self->any_of(
283             sub {
284 120         1354 $self->expect( 'yabsm_dir' );
285 35         1819 $self->commit;
286 35 50       262 exists $config{yabsm_dir} and $self->fail('yabsm_dir is already defined');
287 35   33     59 $self->maybe_expect('=') // $self->fail(q(expected '='));
288 35   33     983 my $dir = $self->maybe_expect($grammar{dir}) // $self->fail(grammar_msg->{dir});
289 35         1545 $config{yabsm_dir} = $dir;
290             },
291             sub {
292 85         6303 $self->expect( 'subvol' );
293 39         1618 $self->commit;
294 39         242 my $name = $self->maybe_expect( $grammar{name} );
295 39   33     1252 $name // $self->fail('expected subvol name');
296 39 100       96 exists $config{subvols}{$name} and $self->fail("already have a subvol named '$name'");
297 38 50       66 exists $config{snaps}{$name} and $self->fail("already have a snap named '$name'");
298 38 50       66 exists $config{ssh_backups}{$name} and $self->fail("already have a ssh_backup named '$name'");
299 38 50       69 exists $config{local_backups}{$name} and $self->fail("already have a local_backup named '$name'");
300 38         77 my $kvs = $self->scope_of('{', 'subvol_settings_parser' ,'}');
301 36         1474 $config{subvols}{$name} = $kvs;
302             },
303             sub {
304 46         3478 $self->expect( 'snap' );
305 23         928 $self->commit;
306 23         145 my $name = $self->maybe_expect( $grammar{name} );
307 23   66     708 $name // $self->fail('expected snap name');
308 22 50       50 exists $config{subvols}{$name} and $self->fail("already have a subvol named '$name'");
309 22 100       40 exists $config{snaps}{$name} and $self->fail("already have a snap named '$name'");
310 21 50       34 exists $config{ssh_backups}{$name} and $self->fail("already have a ssh_backup named '$name'");
311 21 50       59 exists $config{local_backups}{$name} and $self->fail("already have a local_backup named '$name'");
312 21         47 my $kvs = $self->scope_of('{', 'snap_settings_parser', '}');
313 13         551 $config{snaps}{$name} = $kvs;
314             },
315             sub {
316 23         1576 $self->expect( 'ssh_backup' );
317 12         481 $self->commit;
318 12         76 my $name = $self->maybe_expect( $grammar{name} );
319 12   66     368 $name // $self->fail('expected ssh_backup name');
320 11 50       24 exists $config{subvols}{$name} and $self->fail("already have a subvol named '$name'");
321 11 50       23 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       15 exists $config{local_backups}{$name} and $self->fail("already have a local_backup named '$name'");
324 10         22 my $kvs = $self->scope_of('{', 'ssh_backup_settings_parser', '}');
325 8         348 $config{ssh_backups}{$name} = $kvs;
326             },
327             sub {
328 11         763 $self->expect( 'local_backup' );
329 10         402 $self->commit;
330 10         63 my $name = $self->maybe_expect( $grammar{name} );
331 10   66     304 $name // $self->fail('expected local_backup name');
332 9 50       23 exists $config{subvols}{$name} and $self->fail("already have a subvol named '$name'");
333 9 50       18 exists $config{snaps}{$name} and $self->fail("already have a snap named '$name'");
334 9 50       16 exists $config{ssh_backups}{$name} and $self->fail("already have a ssh_backup named '$name'");
335 9 50       14 exists $config{local_backups}{$name} and $self->fail("already have a local_backup named '$name'");
336 9         19 my $kvs = $self->scope_of('{', 'local_backup_settings_parser', '}');
337 7         288 $config{local_backups}{$name} = $kvs;
338             },
339             sub {
340 1         65 $self->commit;
341 1         7 $self->skip_ws; # skip_ws also skips comments
342 1         12 $self->fail(q(expected one of 'subvol', 'snap', 'ssh_backup', or 'local_backup'));
343             }
344 120         1517 );
345 37         242 });
346              
347 16 50       808 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 212 arg_count_or_die(3, 3, @_);
359              
360 77         87 my $self = shift;
361 77         88 my $type = shift;
362 77         84 my $grammar = shift;
363              
364 77         87 my @settings = keys %{ $grammar };
  77         197  
365 77         189 my $setting_regex = join '|', @settings;
366              
367             # return this
368 77         115 my %kvs;
369              
370             $self->sequence_of( sub {
371 226     226   8025 $self->commit;
372              
373 226   66     3866 my $setting = $self->maybe_expect( qr/$setting_regex/ )
374             // $self->fail("expected a $type setting");
375              
376 218   33     11981 $self->maybe_expect('=') // $self->fail('expected "="');
377              
378             my $value = $self->maybe_expect($grammar->{$setting})
379 218   66     6258 // $self->fail('expected ' . grammar_msg->{$setting});
380              
381 214         11662 $kvs{$setting} = $value;
382 77         418 });
383              
384 65 50       2305 return wantarray ? %kvs : \%kvs;
385             }
386              
387             sub subvol_settings_parser {
388 38     38 0 2079 arg_count_or_die(1, 1, @_);
389 38         47 my $self = shift;
390 38         61 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 1047 arg_count_or_die(1, 1, @_);
396 20         25 my $self = shift;
397 20         37 my $snap_settings_grammar = snap_settings_grammar();
398 20         46 $self->settings_parser('snap', $snap_settings_grammar);
399             }
400              
401             sub ssh_backup_settings_parser {
402 10     10 0 526 arg_count_or_die(1, 1, @_);
403 10         13 my $self = shift;
404 10         20 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 835 arg_count_or_die(1, 1, @_);
410 9         11 my $self = shift;
411 9         17 my $local_backup_settings_grammar = local_backup_settings_grammar();
412 9         21 $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 52 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       36 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     41 unless ($config_ref->{snaps} || $config_ref->{ssh_backups} || $config_ref->{local_backups}) {
      33        
437 1         3 push @error_msgs, 'yabsm: config error: no defined snaps, ssh_backups, or local_backups';
438             }
439              
440 16         32 push @error_msgs, snap_errors($config_ref);
441 16         31 push @error_msgs, ssh_backup_errors($config_ref);
442 16         33 push @error_msgs, local_backup_errors($config_ref);
443              
444 16 100       27 if (@error_msgs) {
445 9         28 return (0, @error_msgs);
446             }
447             else {
448 7         18 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 36 arg_count_or_die(1, 1, @_);
459              
460 16         19 my $config_ref = shift;
461              
462             # return this
463 16         17 my @error_msgs;
464              
465             # Base required settings. Passing 0 to snap_settings_grammar excludes
466             # timeframe settings from the returned hash.
467 16         22 my @base_required_settings = keys %{ snap_settings_grammar(0) };
  16         28  
468              
469 16         36 foreach my $snap (keys %{ $config_ref->{snaps} }) {
  16         48  
470              
471             # Make sure that the subvol being snapped exists
472 11         20 my $subvol = $config_ref->{snaps}{$snap}{subvol};
473 11 50       24 if (defined $subvol) {
474 11 100       13 unless (grep { $subvol eq $_ } keys %{ $config_ref->{subvols} }) {
  15         40  
  11         26  
475 1         4 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         21 my @required_settings = @base_required_settings;
481 11         18 my $timeframes = $config_ref->{snaps}{$snap}{timeframes};
482 11 100       21 if (defined $timeframes) {
483 10         21 push @required_settings, required_timeframe_settings($timeframes);
484             }
485 11         15 my @defined_settings = keys %{ $config_ref->{snaps}{$snap} };
  11         32  
486 11         36 my @missing_settings = array_minus(@required_settings, @defined_settings);
487 11         176 foreach my $missing (@missing_settings) {
488 2         8 push @error_msgs, "yabsm: config error: snap '$snap' missing required setting '$missing'";
489             }
490             }
491              
492 16 50       40 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 43 arg_count_or_die(1, 1, @_);
502              
503 16         19 my $config_ref = shift;
504              
505             # return this
506 16         20 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         19 my @base_required_settings = keys %{ ssh_backup_settings_grammar(0) };
  16         31  
511              
512 16         37 foreach my $ssh_backup (keys %{ $config_ref->{ssh_backups} }) {
  16         45  
513              
514             # Make sure that the subvol being backed up exists
515 7         15 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         28  
  7         14  
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         18 my @required_settings = @base_required_settings;
524 7         11 my $timeframes = $config_ref->{ssh_backups}{$ssh_backup}{timeframes};
525 7 50       12 if (defined $timeframes) {
526 7         15 push @required_settings, required_timeframe_settings($timeframes);
527             }
528 7         10 my @defined_settings = keys %{ $config_ref->{ssh_backups}{$ssh_backup} };
  7         21  
529 7         21 my @missing_settings = array_minus(@required_settings, @defined_settings);
530 7         126 foreach my $missing (@missing_settings) {
531 1         4 push @error_msgs, "yabsm: config error: ssh_backup '$ssh_backup' missing required setting '$missing'";
532             }
533             }
534              
535 16 50       40 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 44 arg_count_or_die(1, 1, @_);
545              
546 16         21 my $config_ref = shift;
547              
548             # return this
549 16         21 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         22 my @base_required_settings = keys %{ local_backup_settings_grammar(0) };
  16         27  
554              
555 16         40 foreach my $local_backup (keys %{ $config_ref->{local_backups} }) {
  16         44  
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         24  
  7         14  
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         16 my @required_settings = @base_required_settings;
567 7         12 my $timeframes = $config_ref->{local_backups}{$local_backup}{timeframes};
568 7 50       13 if (defined $timeframes) {
569 7         14 push @required_settings, required_timeframe_settings($timeframes);
570             }
571 7         10 my @defined_settings = keys %{ $config_ref->{local_backups}{$local_backup} };
  7         20  
572 7         16 my @missing_settings = array_minus(@required_settings, @defined_settings);
573 7         115 foreach my $missing (@missing_settings) {
574 1         6 push @error_msgs, "yabsm: config error: local_backup '$local_backup' missing required setting '$missing'";
575             }
576             }
577              
578 16 50       55 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 63 arg_count_or_die(1, 1, @_);
588              
589 24         36 my $tframes = shift;
590              
591 24         56 my @timeframes = split ',', $tframes;
592              
593             # return this
594 24         33 my @required;
595              
596 24         36 foreach my $tframe (@timeframes) {
597 51 100       103 if ($tframe eq '5minute') { push @required, qw(5minute_keep) }
  22 100       34  
    100          
    100          
    50          
598 10         13 elsif ($tframe eq 'hourly') { push @required, qw(hourly_keep) }
599 7         11 elsif ($tframe eq 'daily') { push @required, qw(daily_keep daily_times) }
600 6         11 elsif ($tframe eq 'weekly') { push @required, qw(weekly_keep weekly_time weekly_day) }
601 6         11 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;