File Coverage

blib/lib/Cron/Sequencer/CLI.pm
Criterion Covered Total %
statement 97 101 96.0
branch 44 50 88.0
condition 20 20 100.0
subroutine 8 8 100.0
pod 0 2 0.0
total 169 181 93.3


line stmt bran cond sub pod time code
1             #!perl
2              
3 2     2   199518 use v5.20.0;
  2         22  
4 2     2   9 use warnings;
  2         4  
  2         56  
5              
6             # The parts of this that we use have been stable and unchanged since v5.20.0:
7 2     2   10 use feature qw(postderef);
  2         3  
  2         291  
8 2     2   11 no warnings 'experimental::postderef';
  2         10  
  2         119  
9              
10              
11             use parent qw(Exporter);
12 2     2   773 require DateTime;
  2         553  
  2         9  
13             use Getopt::Long qw(GetOptionsFromArray);
14 2     2   1368  
  2         17677  
  2         8  
15             our $VERSION = '0.03';
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 224141 my @groups;
23             my $current = [];
24 44         96 my $json;
25 44         98  
26 44         78 # Split the command line into sections:
27             for my $item (@argv) {
28             if ($item eq '--') {
29 44         105 push @groups, $current;
30 158 100 100     723 $current = [];
    100          
31 20         41 } elsif ($item =~ /\A--json(?:=(.*)|)\z/s && !@groups) {
32 20         46 # 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       69 $pod2usage->(exitval => 255,
41 15         96 message => "Unknown --json option '$opt'")
42             unless $known_json{$opt};
43             $json->{$opt} = 1;
44 20 100       63 }
45 19         49 $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     63 # Flag that we saw --json
50             $json //= {};
51             }
52 4   100     17 } else {
53             push @$current, $item;
54             }
55 119         234 }
56             push @groups, $current;
57              
58 41         83 my %global_options = (
59             group => 1,
60 41         147 );
61              
62             Getopt::Long::Configure('pass_through', 'auto_version', 'auto_help');
63             unless(GetOptionsFromArray($groups[0], \%global_options,
64 41         188 'show=s',
65 41 50       2450 '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         16837 my @input;
76              
77 37         75 Getopt::Long::Configure('no_pass_through', 'no_auto_version');
78             for my $group (@groups) {
79 37         150 my %options;
80 37         1598 unless(GetOptionsFromArray($group, \%options,
81 55         85 'env=s@',
82 55 100       162 'ignore=s@'
83             )) {
84             $pod2usage->(exitval => 255, verbose => 1);
85             }
86 4         936 $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     10955 push @input, map {{ source => $_, %options{qw(env ignore)} }} @$group;
91             }
92 48         108  
  42         212  
93             $pod2usage->(exitval => 255)
94             unless @input;
95 30 100       86  
96             my $output = [%global_options{qw(hide-env group)}, count => scalar @input];
97              
98 29         85 push @$output, json => $json
99             if $json;
100 29 100       83  
101             return ($start, $end, $output, @input);
102             }
103 29         295  
104             my $options = shift;
105              
106             my ($start, $end);
107 70     70 0 74449  
108             if (defined $options->{from} || defined $options->{to}) {
109 70         126 die "$0: Can't use --show with --from or --to"
110             if defined $options->{show};
111 70 100 100     372  
112             # Default is midnight gone
113 17 50       43 my $from = $options->{from} // '+0';
114             if ($from =~ /\A[1-9][0-9]*\z/) {
115             # Absolute epoch seconds
116 17   100     47 $start = $from;
117 17 100       94 } elsif ($from =~ /\A[-+](?:0|[1-9][0-9]*)\z/) {
    50          
118             # Seconds relative to midnight gone
119 7         17 $start = DateTime->today()->epoch() + $from;
120             } else {
121             die "$0: Can't parse '$from' for --from\n";
122 10         33 }
123              
124 0         0 # 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 17   100     4528 $end = $to;
129 17 100       74 } elsif ($to =~ /\A\+[1-9][0-9]*\z/) {
    50          
130             # Seconds relative to from
131 8         15 # 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         17 }
136              
137 0         0 die "$0: End $end must be after start $start (--from=$from --to=$to)"
138             if $end <= $start;
139             } else {
140 17 50       40 my $show = $options->{show} // 'today';
141             if ($show =~ /\A\s*(last|this|next)\s+(hour|day|week)\s*\z/) {
142             my $which = $1;
143 53   100     204 my $what = $2;
144 53 100       437 my $start_of_period = DateTime->now()->truncate(to => $what);
    100          
    100          
    50          
145 9         21 if ($which eq 'last') {
146 9         13 $end = $start_of_period->epoch();
147 9         29 $start_of_period->subtract($what . 's' => 1);
148 9 100       6857 $start = $start_of_period->epoch();
149 3         9 } else {
150 3         26 $start_of_period->add($what . 's' => 1)
151 3         2503 if $which eq 'next';
152             $start = $start_of_period->epoch();
153 6 100       23 $start_of_period->add($what . 's' => 1);
154             $end = $start_of_period->epoch();
155 6         2178 }
156 6         51 } elsif ($show =~ /\A\s*(last|next)\s+([1-9][0-9]*)\s+(hour|day|week)s\s*\z/) {
157 6         4486 my $which = $1;
158             my $count = $2;
159             my $what = $3;
160 3         8  
161 3         6 # I was going to name this $now, but then realised that if I add or
162 3         4 # subtract on it, it won't be very "now" any more...
163             my $aargh_mutable = DateTime->now();
164              
165             if ($which eq 'last') {
166 3         10 $end = $aargh_mutable->epoch();
167             $aargh_mutable->subtract($what . 's' => $count);
168 3 100       712 $start = $aargh_mutable->epoch();
169 1         4 } else {
170 1         9 $start= $aargh_mutable->epoch();
171 1         889 $aargh_mutable->add($what . 's' => $count);
172             $end = $aargh_mutable->epoch();
173 2         6 }
174 2         18 } elsif ($show =~ /\A\s*yesterday\s*\z/) {
175 2         1483 my $midnight = DateTime->today();
176             $end = $midnight->epoch();
177             $midnight->subtract(days => 1);
178 2         6 $start = $midnight->epoch();
179 2         882 } elsif ($show =~ /\A\s*(today|tomorrow)\s*\z/) {
180 2         22 my $midnight = DateTime->today();
181 2         1671 $midnight->add(days => 1)
182             if $1 eq 'tomorrow';
183 39         233 $start = $midnight->epoch();
184 39 100       23679 $midnight->add(days => 1);
185             $end = $midnight->epoch();
186 39         1619 } else {
187 39         414 die "$0: Unknown time period '$show' for --show\n";
188 39         35774 }
189             }
190 0         0  
191             return ($start, $end);
192             }
193              
194 70         689 =head1 NAME
195              
196             Cron::Sequencer::CLI
197              
198             =head1 SYNOPSIS
199              
200             This module exists to make it easy to test the command line option parsing
201             of L<bin/cron-sequencer>. It's for "internal use only", and subject to change
202             (or deletion) without warning.
203              
204             =head1 LICENSE
205              
206             This library is free software; you can redistribute it and/or modify it under
207             the same terms as Perl itself. If you would like to contribute documentation,
208             features, bug fixes, or anything else then please raise an issue / pull request:
209              
210             https://github.com/Humanstate/cron-sequencer
211              
212             =head1 AUTHOR
213              
214             Nicholas Clark - C<nick@ccl4.org>
215              
216             =cut
217              
218             1;