File Coverage

blib/lib/App/HWD/Task.pm
Criterion Covered Total %
statement 102 111 91.8
branch 30 36 83.3
condition 6 7 85.7
subroutine 27 29 93.1
pod 26 26 100.0
total 191 209 91.3


line stmt bran cond sub pod time code
1             package App::HWD::Task;
2              
3             =head1 NAME
4              
5             App::HWD::Task - Tasks for HWD
6              
7             =head1 SYNOPSIS
8              
9             Used only by the F application.
10              
11             Note that these functions are pretty fragile, and do almost no data
12             checking.
13              
14             =head1 FUNCTIONS
15              
16             =head2 App::HWD::Task->parse( $input_line, $parent_task )
17              
18             Returns an App::HWD::Task object from an input line
19              
20             =cut
21              
22 7     7   25452 use warnings;
  7         15  
  7         252  
23 7     7   44 use strict;
  7         13  
  7         266  
24 7     7   8740 use DateTime::Format::Strptime;
  7         1854862  
  7         12086  
25              
26             my $line_regex = qr/
27             ^
28             (-+|\*+) # leading dashes or stars
29             \s* # whitespace
30             (.+) # everything else
31             $
32             /x;
33              
34             sub parse {
35 45     45 1 5797 my $class = shift;
36 45         72 my $line = shift;
37 45         78 my $parent = shift;
38 45         61 my $where = shift;
39              
40 45 100       401 if ( $line =~ $line_regex ) {
41 44         106 my $level = length $1;
42 44         113 my $name = $2;
43 44         70 my $id;
44             my $estimate;
45 0         0 my %date;
46              
47 44 100       444 if ( $name =~ s/\s*\(([^)]+)\)\s*$// ) {
48 30         73 my $parens = $1;
49 30         177 my $parser = DateTime::Format::Strptime->new( pattern => '%Y-%m-%d' );
50              
51 30         15703 my @subfields = split /,/, $parens;
52 30         147 for ( @subfields ) {
53             # Strip whitespace
54 56         263 s/^\s+//;
55 56         121 s/\s+$//;
56              
57             # ID?
58 56 100       309 /^#(\d+)$/ and $id = $1, next;
59              
60             # Estimate in hours or minutes?
61 29 100       303 /^((\d*\.)?\d+)h$/ and $estimate = $1, next;
62 5 100       30 /^(\d+)m$/ and $estimate = $1/60, next;
63              
64             # Add or delete dates
65 4 50       31 /^(added|deleted) (\S+)$/i and do {
66 4         16 my ($type,$date) = ($1,$2);
67 4         23 $date{$type} = $parser->parse_datetime($date);
68 4 50       5086 next if $date{$type};
69             };
70 0         0 warn qq{Can't parse $where: $_\n};
71             }
72             }
73              
74 44         627 my $task = $class->new( {
75             level => $level,
76             name => $name,
77             id => $id,
78             where => $where,
79             estimate => $estimate,
80             date_added_obj => $date{added},
81             date_deleted_obj => $date{deleted},
82             parent => $parent,
83             } );
84             }
85             else {
86 1         5 return;
87             }
88             }
89              
90             =head2 App::HWD::Task->new( { args } )
91              
92             Creates a new task from the args passed in. They should include at
93             least I, I and I, even if I is C.
94              
95             my $task = App::HWD::Task->new( {
96             level => $level,
97             name => $name,
98             id => $id,
99             estimate => $estimate,
100             } );
101              
102             =cut
103              
104             sub new {
105 44     44 1 82 my $class = shift;
106 44         71 my $args = shift;
107              
108 44         442 my $self = bless {
109             %$args,
110             work => [],
111             }, $class;
112              
113 44         378 return $self;
114             }
115              
116             =head2 $task->level()
117              
118             Returns the level of the task
119              
120             =head2 $task->name()
121              
122             Returns the name of the task
123              
124             =head2 $task->id()
125              
126             Returns the ID of the task, or the empty string if there isn't one.
127              
128             =head2 $task->where()
129              
130             Returns a string describing the location of the task's line, as in "line 45 of foo.hwd"
131              
132             =head2 $task->estimate()
133              
134             Returns the estimate, or 0 if it's not set.
135              
136             =head2 $task->notes()
137              
138             Returns the list of notes for the task.
139              
140             =head2 $task->date_added()
141              
142             Returns a string showing the date the task was added, or empty string if it's not set.
143              
144             =head2 $task->date_added_obj()
145              
146             Returns a DateTime object representing the date the task was added, or C if it's not set.
147              
148             =head2 $task->date_deleted()
149              
150             Returns a string showing the date the task was deleted, or empty string if it's not set.
151              
152             =head2 $task->date_deleted_obj()
153              
154             Returns a DateTime object representing the date the task was deleted, or C if it's not set.
155              
156             =head2 $task->parent()
157              
158             Returns the parent of the task, or C if it's a top-level task.
159              
160             =head2 $task->children()
161              
162             Returns a list of child tasks.
163              
164             =head2 $task->work()
165              
166             Returns the array of App::HWD::Work applied to the task.
167              
168             =cut
169              
170 9     9 1 893 sub level { return shift->{level} }
171 33     33 1 14021 sub name { return shift->{name} }
172 99   100 99 1 686 sub id { return shift->{id} || "" }
173 1     1 1 5 sub where { return shift->{where} }
174 85   100 85 1 630 sub estimate { return shift->{estimate} || 0 }
175 4 50   4 1 10 sub work { return @{shift->{work}||[]} }
  4         35  
176 5 100   5 1 11 sub notes { return @{shift->{notes}||[]} }
  5         56  
177 1     1 1 5 sub date_added_obj { return shift->{date_added_obj} }
178 0     0 1 0 sub date_deleted_obj { return shift->{date_added_obj} }
179 32     32 1 142 sub parent { return shift->{parent} }
180 45 100   45 1 59 sub children { return @{shift->{children}||[]} }
  45         340  
181              
182             sub date_added {
183 8     8 1 24 my $self = shift;
184 8 100       59 my $obj = $self->{date_added_obj} or return '';
185              
186 2         117 return $obj->strftime( "%F" );
187             }
188              
189             sub date_deleted {
190 24     24 1 467 my $self = shift;
191 24 100       121 my $obj = $self->{date_deleted_obj} or return '';
192              
193 7         342 return $obj->strftime( "%F" );
194             }
195              
196             =head2 $task->is_todo()
197              
198             Returns true if the task still has things to be done on it. If the task
199             has no estimates, because it's a roll-up or milestone task, this is false.
200              
201             =cut
202              
203             sub is_todo {
204 23     23 1 1420 my $self = shift;
205              
206 23 100       105 if ( $self->estimate ) {
207 16 100       44 return if $self->date_deleted;
208 12         33 return !$self->completed;
209             }
210              
211 7         25 for my $child ( $self->children ) {
212 7 100       60 return 1 if $child->is_todo;
213             }
214 4         21 return;
215             }
216              
217             =head2 $task->set( $key => $value )
218              
219             Sets the I<$key> field to I<$value>.
220              
221             =cut
222              
223             sub set {
224 0     0 1 0 my $self = shift;
225 0         0 my $key = shift;
226 0         0 my $value = shift;
227              
228 0 0       0 die "Dupe key $key" if exists $self->{$key};
229 0         0 $self->{$key} = $value;
230             }
231              
232             =head2 add_notes( @notes_lines )
233              
234             Adds the lines passed in to the notes lines for the task.
235              
236             =cut
237              
238             sub add_notes {
239 7     7 1 26 my $self = shift;
240              
241 7         12 push( @{$self->{notes}}, @_ );
  7         50  
242             }
243              
244             =head2 add_child( $task )
245              
246             Adds a child Task record to the task
247              
248             =cut
249              
250             sub add_child {
251 30     30 1 48 my $self = shift;
252 30         41 my $child = shift;
253              
254 30         43 push( @{$self->{children}}, $child );
  30         112  
255             }
256              
257             =head2 add_work( $work )
258              
259             Adds a Work record to the task, for later accumulating
260              
261             =cut
262              
263             sub add_work {
264 12     12 1 20 my $self = shift;
265 12         17 my $work = shift;
266              
267 12         17 push( @{$self->{work}}, $work );
  12         83  
268             }
269              
270             =head2 hours_worked()
271              
272             Returns the number of hours worked, but counting up all the work records added in L.
273              
274             =cut
275              
276             sub hours_worked {
277 7     7 1 11 my $self = shift;
278              
279 7         14 my $hours = 0;
280 7         13 for my $work ( @{$self->{work}} ) {
  7         26  
281 0         0 $hours += $work->hours;
282             }
283 7         54 return $hours;
284             }
285              
286             =head2 started()
287              
288             Returns whether the task has been started. Doesn't address the question
289             of whether the task is completed or not, just whether work has been done
290             on it.
291              
292             =cut
293              
294             sub started {
295 9     9 1 20 my $self = shift;
296              
297 9         13 return @{$self->{work}} > 0;
  9         53  
298             }
299              
300             =head2 completed()
301              
302             Returns whether the task has been completed.
303              
304             =cut
305              
306             sub completed {
307 21     21 1 495 my $self = shift;
308              
309 21         38 my $completed = 0;
310 21         30 for my $work ( @{$self->{work}} ) {
  21         64  
311 10         28 $completed = $work->completed;
312             }
313              
314 21         139 return $completed;
315             }
316              
317             =head2 summary
318              
319             Returns a simple one line description of the Work.
320              
321             =cut
322              
323             sub summary {
324 7     7 1 448 my $self = shift;
325 7         15 my $sum;
326 7 100       24 $sum = $self->id . " - " if $self->id;
327 7         25 $sum .= sprintf( "%s (%s/%s)", $self->name, $self->estimate, $self->hours_worked );
328 7         41 return $sum;
329             }
330              
331             =head2 sort_work
332              
333             Make sure all the work for a task is sorted so we can tell what was done when.
334              
335             =cut
336              
337             sub sort_work {
338 35     35 1 128 my $self = shift;
339              
340 35         52 my $work = $self->{work};
341              
342 8 50 66     191 @$work = sort {
343 35         220 $a->when cmp $b->when
344             ||
345             $a->completed cmp $b->completed
346             ||
347             $a->who cmp $b->who
348             } @$work;
349             }
350              
351             =head2 subtask_walk( $callback )
352              
353             Recursively walks the tree of subtasks for the task, calling C<$callback>
354             for each subtask, like so:
355              
356             $callback->( $subtask )
357              
358             =cut
359              
360             sub subtask_walk {
361 14     14 1 556 my $self = shift;
362 14         14 my $callback = shift;
363              
364 14         26 for my $child ( $self->children ) {
365 12         30 $callback->( $child );
366 12         63 $child->subtask_walk( $callback );
367             }
368             }
369              
370             =head1 AUTHOR
371              
372             Andy Lester, C<< >>
373              
374             =head1 COPYRIGHT & LICENSE
375              
376             Copyright 2006 Andy Lester, all rights reserved.
377              
378             This program is free software; you can redistribute it and/or modify it
379             under the same terms as Perl itself.
380              
381             =cut
382              
383             "You got a killer scene there, man..."; # End of App::HWD::Task