File Coverage

blib/lib/App/HWD/Task.pm
Criterion Covered Total %
statement 105 114 92.1
branch 35 40 87.5
condition 6 7 85.7
subroutine 28 30 93.3
pod 27 27 100.0
total 201 218 92.2


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