File Coverage

blib/lib/Cron/Sequencer/CLI.pm
Criterion Covered Total %
statement 101 102 99.0
branch 51 52 98.0
condition 20 20 100.0
subroutine 8 8 100.0
pod 0 2 0.0
total 180 184 97.8


line stmt bran cond sub pod time code
1             #!perl
2              
3 2     2   177079 use v5.20.0;
  2         14  
4 2     2   8 use warnings;
  2         3  
  2         42  
5              
6             # The parts of this that we use have been stable and unchanged since v5.20.0:
7 2     2   8 use feature qw(postderef);
  2         2  
  2         261  
8 2     2   10 no warnings 'experimental::postderef';
  2         2  
  2         113  
9              
10              
11             use parent qw(Exporter);
12 2     2   669 require DateTime;
  2         462  
  2         7  
13             use Getopt::Long qw(GetOptionsFromArray);
14 2     2   1184  
  2         15957  
  2         15  
15             our $VERSION = '0.04';
16             our @EXPORT_OK = qw(calculate_start_end parse_argv);
17              
18             my %known_json = map { $_, 1 } qw(seq split pretty canonical);
19              
20             my ($pod2usage, @argv) = @_;
21              
22 44     44 0 177948 my @groups;
23             my $current = [];
24 44         69 my $json;
25 44         52  
26 44         46 # Split the command line into sections:
27             for my $item (@argv) {
28             if ($item eq '--') {
29 44         57 push @groups, $current;
30 158 100 100     390 $current = [];
    100          
31 20         21 } elsif ($item =~ /\A--json(?:=(.*)|)\z/s && !@groups) {
32 20         24 # GetOpt::Long doesn't appear to have a way to specify to specify an
33             # option that can take an argument, but it so, the argument must be
34             # given in the '=' form.
35             # We'd like to support `--json` and `--json=pretty` but have
36             # `--json pretty` mean the same as `./pretty --json`
37              
38             if (length $1) {
39             for my $opt (split ',', $1) {
40 19 100       43 $pod2usage->(exitval => 255,
41 15         31 message => "Unknown --json option '$opt'")
42             unless $known_json{$opt};
43             $json->{$opt} = 1;
44 20 100       43 }
45 19         29 $pod2usage->(exitval => 255,
46             message => "Can't use --json=seq with --json=split")
47             if $json->{seq} && $json->{split};
48             } else {
49 14 100 100     42 # Flag that we saw --json
50             $json //= {};
51             }
52 4   100     12 } else {
53             push @$current, $item;
54             }
55 119         160 }
56             push @groups, $current;
57              
58 41         52 my %global_options = (
59             group => 1,
60 41         79 );
61              
62             Getopt::Long::Configure('pass_through', 'auto_version', 'auto_help');
63             unless(GetOptionsFromArray($groups[0], \%global_options,
64 41         96 'show=s',
65 41 50       1421 'from=s',
66             'to=s',
67             'hide-env',
68             'group!',
69             )) {
70             $pod2usage->(exitval => 255, verbose => 1);
71             }
72 0         0  
73             my ($start, $end) = calculate_start_end(\%global_options);
74              
75 37         11657 my @input;
76              
77 37         48 Getopt::Long::Configure('no_pass_through', 'no_auto_version');
78             for my $group (@groups) {
79 37         91 my %options;
80 37         1210 unless(GetOptionsFromArray($group, \%options,
81 55         60 'env=s@',
82 55 100       110 'ignore=s@'
83             )) {
84             $pod2usage->(exitval => 255, verbose => 1);
85             }
86 4         735 $pod2usage->(exitval => 255,
87             message => "--env and --hide-env options can't be used together")
88             if $global_options{'hide-env'} && $options{env};
89              
90 50 100 100     8904 push @input, map {{ source => $_, %options{qw(env ignore)} }} @$group;
91             }
92 48         80  
  42         138  
93             $pod2usage->(exitval => 255)
94             unless @input;
95 30 100       60  
96             my $output = [%global_options{qw(hide-env group)}, count => scalar @input];
97              
98 29         55 push @$output, json => $json
99             if $json;
100 29 100       49  
101             return ($start, $end, $output, @input);
102             }
103 29         188  
104             my $options = shift;
105              
106             my ($start, $end);
107 83     83 0 82936  
108             if (defined $options->{from} || defined $options->{to}) {
109 83         133 die "$0: Can't use --show with --from or --to\n"
110             if defined $options->{show};
111 83 100 100     356  
112             # Default is midnight gone
113 22 100       62 my $from = $options->{from} // '+0';
114             if ($from =~ /\A[1-9][0-9]*\z/) {
115             # Absolute epoch seconds
116 20   100     52 $start = $from;
117 20 100       104 } elsif ($from =~ /\A[-+](?:0|[1-9][0-9]*)\z/) {
    100          
118             # Seconds relative to midnight gone
119 8         13 $start = DateTime->today()->epoch() + $from;
120             } else {
121             die "$0: Can't parse '$from' for --from\n";
122 11         49 }
123              
124 1         7 # Default is to show 1 hour
125             my $to = $options->{to} // '+3600';
126             if ($to =~ /\A[1-9][0-9]+\z/) {
127             # Absolute epoch seconds
128 19   100     5326 $end = $to;
129 19 100       84 } elsif ($to =~ /\A\+[1-9][0-9]*\z/) {
    100          
130             # Seconds relative to from
131 9         20 # As $end >= $start, '+0' doesn't make sense
132             $end = $start + $to;
133             } else {
134             die "$0: Can't parse '$to' for --to\n";
135 9         19 }
136              
137 1         9 die "$0: End $end must be after start $start (--from=$from --to=$to)\n"
138             if $end <= $start;
139             } else {
140 18 100       46 my $show = $options->{show} // 'today';
141             if ($show =~ /\A\s*(last|this|next)\s+(minute|hour|day|week)\s*\z/) {
142             my $which = $1;
143 61   100     150 my $what = $2;
144 61 100       370 my $start_of_period = DateTime->now()->truncate(to => $what);
    100          
    100          
    100          
    100          
145 12         32 if ($which eq 'last') {
146 12         23 $end = $start_of_period->epoch();
147 12         51 $start_of_period->subtract($what . 's' => 1);
148 12 100       6262 $start = $start_of_period->epoch();
149 4         14 } else {
150 4         41 $start_of_period->add($what . 's' => 1)
151 4         3327 if $which eq 'next';
152             $start = $start_of_period->epoch();
153 8 100       36 $start_of_period->add($what . 's' => 1);
154             $end = $start_of_period->epoch();
155 8         2998 }
156 8         80 } elsif ($show =~ /\A\s*(last|next)\s+([1-9][0-9]*)\s+(minute|hour|day|week)s\s*\z/) {
157 8         5537 my $which = $1;
158             my $count = $2;
159             my $what = $3;
160 4         11  
161 4         8 # I was going to name this $now, but then realised that if I add or
162 4         9 # subtract on it, it won't be very "now" any more...
163             my $aargh_mutable = DateTime->now();
164              
165             if ($which eq 'last') {
166 4         16 $end = $aargh_mutable->epoch();
167             $aargh_mutable->subtract($what . 's' => $count);
168 4 100       993 $start = $aargh_mutable->epoch();
169 2         6 } else {
170 2         21 $start= $aargh_mutable->epoch();
171 2         1707 $aargh_mutable->add($what . 's' => $count);
172             $end = $aargh_mutable->epoch();
173 2         6 }
174 2         20 } elsif ($show =~ /\A\s*yesterday\s*\z/) {
175 2         1534 my $midnight = DateTime->today();
176             $end = $midnight->epoch();
177             $midnight->subtract(days => 1);
178 2         8 $start = $midnight->epoch();
179 2         842 } elsif ($show =~ /\A\s*(today|tomorrow)\s*\z/) {
180 2         38 my $midnight = DateTime->today();
181 2         1556 $midnight->add(days => 1)
182             if $1 eq 'tomorrow';
183 39         138 $start = $midnight->epoch();
184 39 100       15828 $midnight->add(days => 1);
185             $end = $midnight->epoch();
186 39         1518 } elsif ($show =~ /\A(?:last|this|next)\z/) {
187 39         293 die "$0: Unknown time period '$show' for --show (did you forget to escape the space after it?)\n";
188 39         25794 } else {
189             die "$0: Unknown time period '$show' for --show\n";
190 3         17 }
191             }
192 1         6  
193             return ($start, $end);
194             }
195              
196 74         588 =head1 NAME
197              
198             Cron::Sequencer::CLI
199              
200             =head1 SYNOPSIS
201              
202             This module exists to make it easy to test the command line option parsing
203             of L<bin/cron-sequencer>. It's for "internal use only", and subject to change
204             (or deletion) without warning.
205              
206             =head1 LICENSE
207              
208             This library is free software; you can redistribute it and/or modify it under
209             the same terms as Perl itself. If you would like to contribute documentation,
210             features, bug fixes, or anything else then please raise an issue / pull request:
211              
212             https://github.com/Humanstate/cron-sequencer
213              
214             =head1 AUTHOR
215              
216             Nicholas Clark - C<nick@ccl4.org>
217              
218             =cut
219              
220             1;