File Coverage

blib/lib/App/Countdown.pm
Criterion Covered Total %
statement 61 96 63.5
branch 18 34 52.9
condition 2 5 40.0
subroutine 15 19 78.9
pod 2 2 100.0
total 98 156 62.8


line stmt bran cond sub pod time code
1             package App::Countdown;
2             $App::Countdown::VERSION = '0.8.3';
3 1     1   108088 use 5.010;
  1         12  
4              
5 1     1   5 use strict;
  1         2  
  1         25  
6 1     1   23 use warnings FATAL => 'all';
  1         2  
  1         36  
7              
8 1     1   545 use DateTime::Format::Natural ();
  1         569067  
  1         47  
9 1     1   578 use Time::HiRes qw(sleep time);
  1         1664  
  1         5  
10 1     1   206 use POSIX qw();
  1         2  
  1         18  
11 1     1   578 use IO::Handle;
  1         6361  
  1         51  
12 1     1   790 use Getopt::Long qw(2.36 GetOptionsFromArray);
  1         11029  
  1         4  
13 1     1   822 use Pod::Usage;
  1         52380  
  1         137  
14 1     1   11 use Carp;
  1         2  
  1         1082  
15              
16              
17             sub new
18             {
19 1     1 1 142 my $class = shift;
20              
21 1         3 my $self = bless {}, $class;
22              
23 1         7 $self->_init(@_);
24              
25 1         3 return $self;
26             }
27              
28             sub _delay
29             {
30 1     1   3 my $self = shift;
31              
32 1 50       4 if (@_)
33             {
34 1         6 $self->{_delay} = shift;
35             }
36              
37 1         3 return $self->{_delay};
38             }
39              
40             sub _end
41             {
42 0     0   0 my $self = shift;
43              
44 0 0       0 if (@_)
45             {
46 0         0 $self->{_end} = shift;
47             }
48              
49 0         0 return $self->{_end};
50             }
51              
52             my $up_to_60_re = qr/[1-9]|[1-5][0-9]|0[0-9]?/;
53              
54             sub _get_up_to_60_val
55             {
56 13     13   26 my ($v) = @_;
57              
58 13   100     75 ( $v //= '' ) =~ s/\A0*//;
59              
60 13 100       89 return ( length($v) ? $v : 0 );
61             }
62              
63             sub _calc_delay
64             {
65 19     19   51 my ( $self, $delay_spec ) = @_;
66              
67 19 100       327 if ( my ( $n, $qualifier ) =
    100          
    50          
68             $delay_spec =~ /\A((?:[1-9][0-9]*(?:\.\d*)?)|(?:0\.\d+))([mhs]?)\z/ )
69             {
70             return int(
71 11 100       93 $n * (
    100          
72             $qualifier eq 'h' ? ( 60 * 60 )
73             : $qualifier eq 'm' ? 60
74             : 1
75             )
76             );
77             }
78             elsif ( my ( $min, $sec ) =
79             $delay_spec =~ /\A([1-9][0-9]*)m($up_to_60_re)s\z/ )
80             {
81 3         14 return $min * 60 + _get_up_to_60_val($sec);
82             }
83             elsif ( ( ( my $hour ), $min, $sec ) =
84             $delay_spec =~
85             /\A([1-9][0-9]*)h(?:($up_to_60_re)m)?(?:($up_to_60_re)s)?\z/ )
86             {
87 5         18 return ( ( $hour * 60 + _get_up_to_60_val($min) ) * 60 +
88             _get_up_to_60_val($sec) );
89             }
90             else
91             {
92 0         0 die
93             "Invalid delay. Must be a positive and possibly fractional number, possibly followed by s, m, or h";
94             }
95             }
96              
97             sub _init
98             {
99 1     1   11 my ( $self, $args ) = @_;
100              
101 1         2 my $argv = [ @{ $args->{argv} } ];
  1         4  
102              
103 1         2 my $help = 0;
104 1         2 my $man = 0;
105 1         2 my $version = 0;
106 1         2 my $end_str;
107 1 50       9 if (
108             !(
109             my $ret = GetOptionsFromArray(
110             $argv,
111             'help|h' => \$help,
112             man => \$man,
113             version => \$version,
114             'to=s' => \$end_str,
115             )
116             )
117             )
118             {
119 0         0 die "GetOptions failed!";
120             }
121              
122 1 50       465 if ($help)
123             {
124 0         0 pod2usage(1);
125             }
126              
127 1 50       4 if ($man)
128             {
129 0         0 pod2usage( -verbose => 2 );
130             }
131              
132 1 50       3 if ($version)
133             {
134 0         0 print "countdown version $App::Countdown::VERSION .\n";
135 0         0 exit(0);
136             }
137              
138 1 50       4 if ( defined $end_str )
139             {
140 0         0 my $parser = DateTime::Format::Natural->new(
141             prefer_future => 1,
142             time_zone => 'local',
143             );
144 0         0 my $dt = $parser->parse_datetime($end_str);
145 0 0       0 if ( not $parser->success )
146             {
147 0         0 die $parser->error;
148             }
149 0         0 $self->_end( $dt->epoch );
150             }
151             else
152             {
153 1         2 my $delay = shift(@$argv);
154              
155 1 50       4 if ( !defined $delay )
156             {
157 0         0 Carp::confess("You should pass a number of seconds.");
158             }
159              
160 1         4 $self->_delay( $self->_calc_delay($delay) );
161             }
162 1         3 return;
163             }
164              
165             sub _format
166             {
167 0     0     my $delay = shift;
168 0           return sprintf( "%d:%02d:%02d",
169             POSIX::floor( $delay / 3600 ),
170             POSIX::floor( $delay / 60 ) % 60,
171             $delay % 60 );
172             }
173              
174             sub _calc_end
175             {
176 0     0     my ( $self, $start ) = @_;
177              
178 0 0         return defined( $self->_end ) ? $self->_end : ( $start + $self->_delay );
179             }
180              
181             sub run
182             {
183 0     0 1   my ($self) = @_;
184              
185 0           STDOUT->autoflush(1);
186              
187 0           my $start = time();
188 0           my $end = $self->_calc_end($start);
189              
190 0           my $delay = $end - $start;
191              
192 0           my $hms_tot = _format($delay);
193 0           my $last_printed;
194 0           while ( ( my $t = time() ) < $end )
195             {
196 0           my $new_to_print = POSIX::floor( $end - $t );
197 0 0 0       if ( !defined($last_printed) or $new_to_print != $last_printed )
198             {
199 0           $last_printed = $new_to_print;
200 0           my $hms = _format($new_to_print);
201 0           print "Remaining $hms / $hms_tot ( $new_to_print/$delay )",
202             ' ' x 10, "\r";
203             }
204 0           sleep(0.1);
205             }
206              
207 0           return;
208             }
209              
210             1;
211              
212              
213             1; # End of App::Countdown
214              
215             __END__
216              
217             =pod
218              
219             =encoding UTF-8
220              
221             =head1 NAME
222              
223             App::Countdown - wait some specified time while displaying the remaining time.
224              
225             =head1 VERSION
226              
227             version 0.8.3
228              
229             =head1 SYNOPSIS
230              
231             use App::Countdown ();
232              
233             App::Countdown->new({ argv => [@ARGV] })->run();
234              
235             =head1 DESCRIPTION
236              
237             B<countdown> waits for a certain time to pass, in a similar fashion to the
238             UNIX sleep command, but unlike sleep, it displays the amount of time left to
239             sleep. I always found it frustrating that I've placed an alarm using
240             C<sleep $secs ; finish-client> and could not tell how much time left, so I
241             wrote B<countdown> for that.
242              
243             =head1 USAGE
244              
245             countdown [number of seconds]
246             countdown [minutes]m
247             countdown [hours]h
248             countdown [seconds]s
249             countdown [minutes]m[seconds]s
250             countdown [hours]h[minutes]m[seconds]s
251             countdown --to "20:30"
252              
253             =head1 SUBROUTINES/METHODS
254              
255             =head2 new
256              
257             A constructor. Accepts the argv named arguments.
258              
259             =head2 run
260              
261             Runs the program.
262              
263             =head1 OPTIONS
264              
265             --man - displays the man page.
266             --help - displays the help.
267             --version - displays the version.
268              
269             =head1 EXAMPLES
270              
271             $ countdown 30s # 30 seconds
272              
273             $ countdown 1m # 1 minute
274              
275             $ countdown 100 # 100 seconds
276              
277             $ countdown 2h # 2 hours
278              
279             $ countdown 2m30s # 2 minutes and 30 seconds.
280              
281             $ countdown 1h0m30s # 1 hour, 0 minutes and 30 seconds.
282              
283             =head1 AUTHOR
284              
285             Shlomi Fish, L<http://www.shlomifish.org/>, C<< <shlomif at cpan.org> >> .
286              
287             =head1 ACKNOWLEDGEMENTS
288              
289             =over 4
290              
291             =item * Neil Bowers
292              
293             Reporting a typo and a problem with the description not fitting on one line.
294              
295             =back
296              
297             =for :stopwords cpan testmatrix url bugtracker rt cpants kwalitee diff irc mailto metadata placeholders metacpan
298              
299             =head1 SUPPORT
300              
301             =head2 Websites
302              
303             The following websites have more information about this module, and may be of help to you. As always,
304             in addition to those websites please use your favorite search engine to discover more resources.
305              
306             =over 4
307              
308             =item *
309              
310             MetaCPAN
311              
312             A modern, open-source CPAN search engine, useful to view POD in HTML format.
313              
314             L<https://metacpan.org/release/App-Countdown>
315              
316             =item *
317              
318             RT: CPAN's Bug Tracker
319              
320             The RT ( Request Tracker ) website is the default bug/issue tracking system for CPAN.
321              
322             L<https://rt.cpan.org/Public/Dist/Display.html?Name=App-Countdown>
323              
324             =item *
325              
326             CPANTS
327              
328             The CPANTS is a website that analyzes the Kwalitee ( code metrics ) of a distribution.
329              
330             L<http://cpants.cpanauthors.org/dist/App-Countdown>
331              
332             =item *
333              
334             CPAN Testers
335              
336             The CPAN Testers is a network of smoke testers who run automated tests on uploaded CPAN distributions.
337              
338             L<http://www.cpantesters.org/distro/A/App-Countdown>
339              
340             =item *
341              
342             CPAN Testers Matrix
343              
344             The CPAN Testers Matrix is a website that provides a visual overview of the test results for a distribution on various Perls/platforms.
345              
346             L<http://matrix.cpantesters.org/?dist=App-Countdown>
347              
348             =item *
349              
350             CPAN Testers Dependencies
351              
352             The CPAN Testers Dependencies is a website that shows a chart of the test results of all dependencies for a distribution.
353              
354             L<http://deps.cpantesters.org/?module=App::Countdown>
355              
356             =back
357              
358             =head2 Bugs / Feature Requests
359              
360             Please report any bugs or feature requests by email to C<bug-app-countdown at rt.cpan.org>, or through
361             the web interface at L<https://rt.cpan.org/Public/Bug/Report.html?Queue=App-Countdown>. You will be automatically notified of any
362             progress on the request by the system.
363              
364             =head2 Source Code
365              
366             The code is open to the world, and available for you to hack on. Please feel free to browse it and play
367             with it, or whatever. If you want to contribute patches, please send me a diff or prod me to pull
368             from your repository :)
369              
370             L<https://github.com/shlomif/app-countdown>
371              
372             git clone https://github.com/shlomif/App-Countdown
373              
374             =head1 AUTHOR
375              
376             Shlomi Fish <shlomif@cpan.org>
377              
378             =head1 BUGS
379              
380             Please report any bugs or feature requests on the bugtracker website
381             L<https://github.com/shlomif/app-countdown/issues>
382              
383             When submitting a bug or request, please include a test-file or a
384             patch to an existing test-file that illustrates the bug or desired
385             feature.
386              
387             =head1 COPYRIGHT AND LICENSE
388              
389             This software is Copyright (c) 2020 by Shlomi Fish.
390              
391             This is free software, licensed under:
392              
393             The MIT (X11) License
394              
395             =cut