File Coverage

blib/lib/Taskwarrior/Kusarigama/Plugin/Command/Progress.pm
Criterion Covered Total %
statement 29 69 42.0
branch 5 32 15.6
condition 0 5 0.0
subroutine 8 11 72.7
pod 0 4 0.0
total 42 121 34.7


line stmt bran cond sub pod time code
1             package Taskwarrior::Kusarigama::Plugin::Command::Progress;
2             our $AUTHORITY = 'cpan:YANICK';
3             # ABSTRACT: Record progress on a task
4             $Taskwarrior::Kusarigama::Plugin::Command::Progress::VERSION = '0.12.0';
5              
6 1     1   136987 use strict;
  1         6  
  1         25  
7 1     1   4 use warnings;
  1         2  
  1         19  
8              
9 1     1   11 use 5.10.0;
  1         3  
10              
11 1     1   414 use Moo;
  1         8762  
  1         4  
12              
13             extends 'Taskwarrior::Kusarigama::Plugin';
14              
15             with 'Taskwarrior::Kusarigama::Hook::OnCommand',
16             'Taskwarrior::Kusarigama::Hook::OnAdd',
17             'Taskwarrior::Kusarigama::Hook::OnModify';
18              
19 1     1   1531 use experimental 'postderef';
  1         2702  
  1         4  
20              
21             has custom_uda => (
22             is => 'ro',
23             default => sub{ +{
24             goal => 'quantifiable goal',
25             progress => "where we're at",
26             }},
27             );
28              
29             sub on_add {
30 0     0 0 0 goto &on_modify;
31             }
32              
33             sub on_modify {
34 0     0 0 0 my( $self, $task ) = @_;
35              
36 1     1   233 no warnings 'uninitialized';
  1         2  
  1         309  
37              
38 0 0       0 my $goal = $task->{goal} or return;
39              
40 0         0 my $progress = $task->{progress};
41              
42 0         0 $task->{description} =~ s#\(\d+\/\d+\)(.*?)$#$1#;
43 0         0 $task->{description} .= sprintf ' (%d/%d)', $progress, $goal;
44              
45 0         0 return $task;
46             }
47              
48             sub formatted_rate {
49 3     3 0 2369 my ( $self, $rate ) = @_;
50              
51 3         6 my $unit;
52              
53 3 100       13 if( $rate > 1 ) {
    100          
    50          
54 1         3 $unit = 'day';
55             }
56             elsif( $rate > 1/7 ) {
57 1         2 $rate *= 7;
58 1         3 $unit = 'week';
59             }
60             elsif( $rate > 1/30 ) {
61 1         3 $rate *= 30;
62 1         2 $unit = 'month';
63             }
64             else {
65 0         0 $rate *= 365;
66 0         0 $unit = 'year';
67             }
68              
69 3         17 return sprintf "%d/%s", $rate, $unit;
70             }
71              
72             sub on_command {
73 0     0 0   my $self = shift;
74              
75             # only grab goal'ed tasks
76 0           my @tasks = $self->run_task->export( $self->pre_command_args, { 'goal.any' => '' } );
77              
78 0 0         die "no tasks found\n" unless @tasks;
79              
80 0           my $note = $self->post_command_args =~ s/\s*(\??)(=?)(-?\d*)\s*//r;
81              
82 1     1   7 no warnings 'uninitialized';
  1         2  
  1         439  
83 0           for my $task ( @tasks ) {
84 0           my $save = !$1;
85              
86             my $progress = !$save ? $task->{progress}
87             : $2 ? $3
88 0 0 0       : ($3||1) + $task->{progress};
    0          
89              
90 0           my $goal = $task->{goal};
91              
92 0           my $ratio = $progress / $goal;
93              
94 0           print $task->{id}, ' ', '=' x ( 20 * $ratio ), '-' x ( 20 * ( 1 - $ratio ) ), ' ', $progress, '/', $goal, sprintf(" (%.1f%%)\n", $ratio*100), "\n";
95              
96 0           my $id = $task->{uuid};
97 0 0         $self->run_task->mod( [ $id ], { progress => $progress } ) if $save;
98 0 0 0       $self->run_task->annotate( [ $id ], $note ) if $note and $save;
99 0 0         if ( $progress >= $task->{goal} ) {
    0          
100 0           say "goal achieved!";
101 0 0         $self->run_task->done( [ $id ] ) if $save;
102             }
103             elsif( $task->{due} ) {
104 0           my ( $span ) = $self->calc( $task->{due}, '-', $task->{entry} ) =~ /(\d+)D/;
105 0           my ( $now ) = $self->calc( $task->{due}, '-', 'now' ) =~ /(\d+)D/g;
106 0           my $should_be_at = eval { $task->{goal} * ($span-$now) / $span };
  0            
107 0 0         if( $should_be_at ) {
108 0           my $comp = ('on track', 'ahead', 'behind')[ $progress <=> $should_be_at ];
109              
110 0           printf "%d days left, you are %s of schedule (%d vs %d)\n",
111             $now, $comp, $progress, $should_be_at;
112              
113 0           my @stats;
114              
115 0 0         if( my $so_far = eval { $progress / ($span-$now) } ) {
  0            
116 0           push @stats, sprintf "rate so far: %s",
117             $self->formatted_rate($so_far);
118             }
119              
120 0 0         if( my $needed = eval { ($task->{goal} - $progress) / $now } ) {
  0            
121 0           push @stats, sprintf "rate needed: %s",
122             $self->formatted_rate($needed);
123             }
124              
125 0 0         say join ', ', @stats if @stats;
126              
127             }
128             }
129             }
130              
131             };
132              
133             1;
134              
135             __END__
136              
137             =pod
138              
139             =encoding UTF-8
140              
141             =head1 NAME
142              
143             Taskwarrior::Kusarigama::Plugin::Command::Progress - Record progress on a task
144              
145             =head1 VERSION
146              
147             version 0.12.0
148              
149             =head1 SYNOPSIS
150              
151             $ task add read ten books goal:10
152              
153             ... later on ...
154              
155             $ task 'read ten books' progress
156              
157             =head1 DESCRIPTION
158              
159             Tasks get two new UDAs: C<goal>, which sets a
160             numeric goal to reach, and C<progress>, which is
161             the current state of progress.
162              
163             Progress can be updated or asked via the C<progress> command.
164              
165             # add 3 units toward the goal
166             $ task 123 progress 3
167              
168             # oops, two steps back
169             $ task 123 progress -2
170              
171             # set progress to an absolute value
172             $ task 123 progress =9
173              
174             # defaults to a +1 increment
175             $ task 123 progress
176              
177             # record progress and add a note
178             $ task 123 progress +3 I did a little bit of the thing
179              
180             # ask about current task progress
181             $ task 123 progress ?
182              
183             # ask about current task progress on three step forward
184             # real progress don't modified
185             $ task 123 progress ?3
186              
187             If the task has a due date, the progress command will
188             also show a short report of your actual rate of completion
189             version what is required to meet the goal on time.
190              
191             $ task 630 progress =170
192             630 =======------------ 170/475 (35.8%)
193             15 days left, you are ahead of schedule (170 vs 118)
194             rate so far: 34/day, rate needed: 20/day
195              
196             =head1 AUTHOR
197              
198             Yanick Champoux <yanick@cpan.org>
199              
200             =head1 COPYRIGHT AND LICENSE
201              
202             This software is copyright (c) 2019, 2018, 2017 by Yanick Champoux.
203              
204             This is free software; you can redistribute it and/or modify it under
205             the same terms as the Perl 5 programming language system itself.
206              
207             =cut