File Coverage

blib/lib/Tasks.pm
Criterion Covered Total %
statement 10 12 83.3
branch n/a
condition n/a
subroutine 4 4 100.0
pod n/a
total 14 16 87.5


line stmt bran cond sub pod time code
1             package Tasks;
2              
3             #
4             # Tasks - module & application for the tasks / projects and time tracking
5             #
6             # See POD documentation in this file for more info
7             #
8             # Copyright (c) 2001 Sergey Gribov
9             # This is free software with ABSOLUTELY NO WARRANTY.
10             # You can redistribute and modify it freely, but please leave
11             # this message attached to this file.
12             #
13             # Subject to terms of GNU General Public License (www.gnu.org)
14             #
15             # Last update: $Date: 2001/07/30 15:11:54 $ by $Author: sergey $
16             # Revision: $Revision: 1.3 $
17              
18 1     1   625 use strict;
  1         2  
  1         31  
19 1     1   4 no strict "refs";
  1         1  
  1         26  
20              
21 1     1   4 use vars qw($VERSION @ISA @EXPORT @EXPORT_OK);
  1         4  
  1         78  
22              
23 1     1   1346 use XML::Parser;
  0            
  0            
24              
25             require Exporter;
26             require DynaLoader;
27             require AutoLoader;
28              
29             @ISA = qw(Exporter DynaLoader);
30             # Items to export into callers namespace by default. Note: do not export
31             # names by default without a very good reason. Use EXPORT_OK instead.
32             # Do not simply export all your public functions/methods/constants.
33             @EXPORT = qw();
34             $VERSION = '1.2';
35              
36             #bootstrap Tasks $VERSION;
37              
38             # Preloaded methods go here.
39              
40             my $ident_prefix = ' ';
41              
42             # Create object
43             sub new {
44             my $proto = shift;
45             my $class = ref($proto) || $proto;
46             my $self = {};
47             bless($self, $class);
48              
49             $self->init();
50             return $self;
51             }
52              
53              
54             # initialize everything
55             sub init {
56             my $self = shift;
57             $self->{FNAME} = $ENV{'HOME'} ? $ENV{'HOME'}.'/' : '';
58             $self->{FNAME} .= '.tasks.xml';
59             $self->{max_id} = 0;
60             ($self->{pkgname}) = caller;
61             $self->{tree} = undef;
62             $self->{tasklist} = ();
63             $self->{tasks_hash} = undef;
64             }
65              
66             # Read task list
67             sub read {
68             my ($self, $fname) = @_;
69             my $buf = '';
70              
71             $self->{FNAME} = $fname if $fname;
72             open(F, $self->{FNAME}) or return "Can't open task list $self->{FNAME}: $!";
73             while () { $buf .= $_; }
74             close(F);
75              
76             my $parser = XML::Parser->new(Style => 'Objects', Pkg => $self->{pkgname});
77             $self->{tree} = $parser->parse($buf);
78              
79             $self->tree2tasklist();
80             $self->{tree} = undef;
81            
82             return '';
83             }
84              
85             # Convert objects tree to tasklist
86             sub tree2tasklist {
87             my $self = shift;
88              
89             my ($task, $i, $tl, $el);
90             for ($i = 0; $i < scalar(@{ $self->{tree} }); $i++) {
91             $tl = $self->{tree}[$i];
92             next unless (lc(ref($tl)) eq lc($self->{pkgname}.'::tasklist'));
93             map {
94             $self->{tasklist}[$i]{$_} = $tl->{$_} unless (ref($tl->{$_}));
95             } keys %{ $tl };
96             # if (lc(ref($_)) eq lc($self->{pkgname}.'::Characters')) {
97              
98             foreach $task (@{$self->{tree}[$i]{Kids}}) {
99             $el = $self->convert_task($task);
100             push(@{ $self->{tasklist}[$i]{_tasks} }, $el) if $el;
101             }
102             }
103             }
104              
105             # Convert task $task from tree to the tasklist format
106             sub convert_task {
107             my ($self, $task) = @_;
108            
109             return undef unless (lc(ref($task)) eq lc($self->{pkgname}.'::task'));
110              
111             my %ret = ();
112             map {$ret{$_} = $task->{$_} unless ref($task->{$_});} keys %{$task};
113            
114             my ($tmp, $el);
115             $ret{_text} = '';
116             foreach (@{$task->{Kids}}) {
117             if (lc(ref($_)) eq lc($self->{pkgname}.'::task')) {
118             $el = $self->convert_task($_);
119             $el->{_parent} = $ret{id} if $ret{id};
120             push(@{$ret{_tasks}}, $el) if $el;
121             }
122             elsif (lc(ref($_)) eq lc($self->{pkgname}.'::time')) {
123             $el = $self->convert_time($_);
124             push(@{$ret{_times}}, $el) if $el;
125             }
126             elsif (lc(ref($_)) eq lc($self->{pkgname}.'::Characters')) {
127             $tmp = clean_empty_spaces($_->{Text});
128             $ret{_text} .= "$tmp" unless ($tmp =~ /^\s*$/);
129             }
130             }
131             return \%ret;
132             }
133              
134             # Convert time tree element to the simple hash
135             sub convert_time {
136             my ($self, $time) = @_;
137             return undef unless (lc(ref($time)) eq lc($self->{pkgname}.'::time'));
138              
139             my ($tmp);
140             my %ret = ();
141             map {$ret{$_} = $time->{$_} unless ref($time->{$_});} keys %{$time};
142             $ret{_text} = '';
143             foreach (@{$time->{Kids}}) {
144             if (lc(ref($_)) eq lc($self->{pkgname}.'::Characters')) {
145             $tmp = clean_empty_spaces($_->{Text});
146             $ret{_text} .= "$tmp" unless ($tmp =~ /^\s*$/);
147             }
148             }
149              
150             return \%ret;
151             }
152              
153              
154             sub print_tasks {
155             my $self = shift;
156              
157             my ($task, $tasklist);
158             foreach $tasklist (@{ $self->{tasklist} }) {
159             print "==== Task list $tasklist->{name} v$tasklist->{version}\n";
160             foreach $task (@{$tasklist->{_tasks}}) {
161             print_task($task, $ident_prefix);
162             }
163             }
164             }
165              
166             # Add new tasklist
167             # Parameter:
168             # $attr - pointer to the hash with task attributes
169             # Returns pointer to the new tasklist or undef in case of error
170             sub add_tasklist {
171             my ($self, $attr) = @_;
172             my %tl = ();
173             map { $tl{$_} = $attr->{$_}; } keys %{$attr};
174             push(@{$self->{tasklist}}, \%tl);
175             return \%tl;
176             }
177              
178             # Add new task
179             # Parameter:
180             # $parent - pointer to parent task
181             # $attr - pointer to the hash with task attributes
182             # Returns pointer to the new task or undef in case of error
183             sub add_task {
184             my ($self, $parent, $attr) = @_;
185            
186             unless ($parent) {
187             $self->add_tasklist({'name' => 'tasklist'})
188             unless scalar(@{$self->{tasklist}});
189             $parent = $self->{tasklist}[0];
190             }
191             return undef unless $parent;
192            
193             my %task = ();
194             $task{id} = $self->new_id();
195             $task{_parent} = $parent->{id} if $parent->{id};
196             map { $task{$_} = $attr->{$_}; } keys %{$attr};
197             push(@{ $parent->{_tasks} }, \%task);
198             return \%task;
199             }
200              
201             # Generate the new task ID
202             # Parameter: $prefix - optional ID prefix
203             sub new_id {
204             my ($self, $prefix) = @_;
205             my $id = undef;
206            
207             my ($task, $tasklist);
208             foreach $tasklist (@{ $self->{tasklist} }) {
209             foreach $task (@{$tasklist->{_tasks}}) {
210             $id = find_max_id($task, $id);
211             }
212             }
213             $id = 1 unless $id;
214             $id++;
215             return $prefix.$id;
216             }
217              
218             # Save tasks to the file
219             # Parameter:
220             # $fname - (optional) file name to save to. If not supplied, the same
221             # file is used as in read()
222             # Returns: '' if Ok, error in case of error
223             sub save {
224             my ($self, $fname) = @_;
225              
226             my ($buf, $tasklist);
227             $self->{FNAME} = $fname if $fname;
228             open(F, ">$self->{FNAME}") or
229             return "Can't open file $self->{FNAME} for writing: $!";
230             foreach $tasklist (@{ $self->{tasklist} }) {
231             $buf = form_attr_line('tasklist', $tasklist);
232             print F "$buf\n";
233             print F &StrToXML("\n$tasklist->{_text}\n") if $tasklist->{_text};
234             foreach (@{$tasklist->{_tasks}}) {
235             save_task(\*F, $_, $ident_prefix);
236             }
237             print F "\n\n";
238             }
239              
240             close(F);
241             return '';
242             }
243              
244             # Add times to task.
245             # Parameters:
246             # $task - pointer to task
247             # $start - start time (in sec. since 1/1/70
248             # $finish - finish time (if undef the current time will be used)
249             # $desc - description (optional)
250             sub add_time {
251             my ($self, $task, $start, $finish, $desc) = @_;
252             return undef unless ($task && $start);
253             my $time = {};
254             $time->{start} = $start;
255             $time->{finish} = $finish ? $finish : time();
256             $time->{_text} = $desc;
257             push(@{$task->{_times}}, $time);
258             }
259              
260             # Create hash with back pointers to parents
261             # Parameters:
262             # $parent - pointer to parent, if undef, initialize the list
263             # $task - pointer to task
264             sub create_task_hash {
265             my ($self, $parent, $task) = @_;
266              
267             if ($parent) {
268             if ($task) {
269             $self->{tasks_hash}{$task->{id}}{name} = $task->{name};
270             $self->{tasks_hash}{$task->{id}}{parent} = $parent;
271             }
272             }
273             else {
274             $self->{tasks_hash} = {};
275             }
276             $task = $self->{tasklist}[0] unless $task;
277             map { $self->create_task_hash($task, $_) if $_; } @{$task->{_tasks}};
278             return $self->{tasks_hash};
279             }
280              
281             # Get task pointer by task ID
282             # Parameters:
283             # $id - task ID
284             # Returns: list ($parent, $task) if task found, undef otherwise
285             sub get_task {
286             my ($self, $id) = @_;
287             my ($task, $tasklist, $ret, $par);
288             foreach $tasklist (@{ $self->{tasklist} }) {
289             foreach $task (@{$tasklist->{_tasks}}) {
290             return ($tasklist, $task) if ($id eq $task->{id});
291             ($par, $ret) = $self->get_task_by_id($id, $task);
292             return ($par, $ret) if $ret;
293             }
294             }
295             return undef;
296             }
297              
298             # Get task pointer by task ID (internal function)
299             # Parameters:
300             # $id - task ID
301             # $task - task to start from
302             # Returns: list ($parent, $task) if task found, undef otherwise
303             sub get_task_by_id {
304             my ($self, $id, $task) = @_;
305             return undef unless ($id && $task);
306             my ($par, $ret);
307             foreach (@{$task->{_tasks}}) {
308             return ($task, $_) if ($id eq $_->{id});
309             ($par, $ret) = $self->get_task_by_id($id, $_);
310             return ($par, $ret) if $ret;
311             }
312             return undef;
313             }
314              
315             # Reset / Zero all the times for the task.
316             # Parameters:
317             # $task - pointer to task, if undef, resets all the times
318             # $this_only - flag. If to zero this task only without subtasks
319             # $sec - time (in sec.) till which to times should be reset
320             # (all times entries with 'start' time less than this will be zeroed)
321             # Returns: void
322             sub zero_time {
323             my ($self, $task, $this_only, $till) = @_;
324            
325             if ($task) {
326             unless ($this_only) {
327             $self->traverse_task_tree($task, \&zero_task_time, $till);
328             }
329             zero_task_time($task);
330             return;
331             }
332            
333             # else
334             my ($tasklist);
335             foreach $tasklist (@{ $self->{tasklist} }) {
336             $self->traverse_task_tree($tasklist, \&zero_task_time, $till);
337             }
338             return;
339              
340             sub zero_task_time {
341             my ($task, $till) = @_;
342             my ($tmp, $t);
343             if ($till) {
344             $tmp = $task->{_times};
345             $task->{_times} = undef;
346             foreach $t (@$tmp) {
347             push(@{$task->{_times}}, $t) if ($t->{start} > $till);
348             }
349             }
350             else {
351             $task->{_times} = undef;
352             }
353             return $till;
354             }
355             }
356              
357             # Get total time spend on this task
358             # Parameters:
359             # $task - pointer to task, if undef, calculates total
360             # $options - pointer to options, possible options:
361             # this_only - flag. If set when calculate this task only without subtasks
362             # start - start time to take in account (in sec. from 1970)
363             # private - take into account private tasks
364             # Returns: time in seconds
365             sub get_total_time {
366             my ($self, $task, $options) = @_;
367            
368             $options->{ret} = 0;
369             $options->{private} = 1 unless (defined($options->{private}));
370             if ($task) {
371             if ($options->{this_only}) {
372             $options = calc_time($task, $options);
373             }
374             else {
375             $options = $self->traverse_task_tree($task, \&calc_time, $options);
376             }
377             return $options->{ret};
378             }
379            
380             # else
381             my $ret = 0;
382             my ($tasklist);
383             foreach $tasklist (@{ $self->{tasklist} }) {
384             $options = $self->traverse_task_tree($tasklist, \&calc_time, $options);
385             $ret += $options->{ret};
386             }
387             return $ret;
388              
389             sub calc_time {
390             my ($task, $ret) = @_;
391             return $ret unless $task;
392             return $ret if (!$ret->{private} && $task->{private});
393             foreach (@{$task->{_times}}) {
394             next if ($ret->{start} && ($ret->{start} > $_->{start}));
395             next if ($ret->{finish} && ($ret->{finish} > $_->{finish}));
396             $ret->{ret} += ($_->{finish} - $_->{start});
397             }
398             return $ret;
399             }
400             }
401              
402             # Traverse task tree and use supplied function on any task
403             # Parameters:
404             # $task - task to start from
405             # $func - pointer to function to apply for every task
406             # function should get two arguments: $task, $ret, where $ret is
407             # result of this function for previous tasks
408             # $ret - result of this function for previous tasks
409             # $level - level in the tree
410             sub traverse_task_tree {
411             my ($self, $task, $func, $ret, $level) = @_;
412             $level = 0 unless $level;
413             return undef unless ($task && $func);
414             $ret = &$func($task, $ret, $level);
415             foreach my $t (@{$task->{_tasks}}) {
416             $ret = $self->traverse_task_tree($t, $func, $ret, ($level+1));
417             }
418             # return &$func($task, $ret);
419             return $ret;
420             }
421              
422             ##########################################################################
423             # functions
424              
425             # Returns maximal task ID found in the tasks.
426             sub find_max_id {
427             my ($task, $id) = @_;
428             return $id unless $task;
429             $_ = $task->{id};
430             s/[^0-9]//g;
431             $id = $_ if ($_ > $id);
432             foreach my $t (@{$task->{_tasks}}) {
433             $id = find_max_id($t, $id);
434             }
435             return $id;
436             }
437              
438             # Form XML line of type "<$label attr1="value1"... >"
439             # Parameters:
440             # $label - XML label
441             # $hash - pointer to hash with values
442             sub form_attr_line {
443             my ($label, $hash) = @_;
444            
445             my $buf = "<$label";
446             map {
447             $buf .= &StrToXML(qq( $_="$hash->{$_}"))
448             unless (ref($hash->{$_}) || $_ =~ /^_/); } keys %{$hash};
449             return "$buf>";
450             }
451              
452             # Save the task and call itself recursively for the 'Kids' tasks
453             # Parameters:
454             # $fh - file handle
455             # $task - pointer to the task
456             # $prefix - prefix to print before any line (typically some number of spaces
457             sub save_task {
458             my ($fh, $task, $prefix) = @_;
459              
460             return undef unless ($task && $task->{id});
461              
462             my ($time, $buf);
463             $buf = $prefix.form_attr_line('task', $task);;
464             print $fh "\n$buf\n";
465             print $fh &StrToXML(qq($prefix$ident_prefix$task->{_text}\n))
466             if $task->{_text};
467             foreach $time (@{$task->{_times}}) {
468             $buf = $prefix.$ident_prefix.form_attr_line('time', $time);
469             print $fh "$buf";
470             print $fh &StrToXML(qq($time->{_text})) if $time->{_text};
471             print $fh "\n";
472             }
473             foreach (@{$task->{_tasks}}) {
474             save_task($fh, $_, $prefix.$ident_prefix);
475             }
476             print $fh "$prefix\n";
477             return '';
478             }
479              
480             # Print the task and call itself recursively for the 'Kids' tasks
481             # Parameters:
482             # $task - pointer to the task
483             # $prefix - prefix to print before any line (typically some number of spaces
484             sub print_task {
485             my ($task, $prefix) = @_;
486              
487             return undef unless $task;
488            
489             print "\n".$prefix."Task name: $task->{name}, id: $task->{id}\n";
490             print $prefix.$task->{_text}."\n" if $task->{_text};
491              
492             foreach (@{$task->{_times}}) {
493             print $prefix."time start=$_->{start} finish=$_->{finish} $_->{_text}\n";
494             }
495             if (scalar(@{$task->{_tasks}})) {
496             print $prefix."Sub-tasks:\n";
497             foreach (@{$task->{_tasks}}) {
498             print_task($_, $prefix.$ident_prefix);
499             }
500             }
501             }
502              
503             # Clean leading and tailing empty lines
504             sub cleanr_empty_spaces(\$) {
505             my $s = shift;
506             $$s =~ s/^(\s*\n*)+//gs;
507             $$s =~ s/\n(\s*\n*)+$//gs;
508             return $$s;
509             }
510             sub clean_empty_spaces {
511             my $str = shift;
512             return cleanr_empty_spaces($str);
513             }
514              
515             sub StrToXML {
516             my $str = shift;
517             my(@chars) = split(//, $str);
518             local $_;
519              
520             $_ = "";
521             foreach my $char (@chars) {
522             if ($char eq '&') {
523             $_ .= "&";
524             }
525             elsif ($char eq '<') {
526             $_ .= "<";
527             }
528             elsif ((ord($char) < 32 || ord($char) > 127)
529             && (ord($char) != 9 && ord($char) != 10 && ord($char) != 13)) {
530             $_ .= "&#" . ord($char) . ";";
531             } else {
532             $_ .= $char;
533             }
534             }
535              
536             return $_;
537             }
538              
539             ##########################################################################
540             # Create properties access methods
541             my ($prop, $sub);
542              
543             foreach $prop ('FNAME', 'max_id', 'pkgname', 'tasklist') {
544             *$prop = sub {
545             my ($self, $val) = @_;
546             $self->{$prop} = $val if $val;
547             return $self->{$prop};
548             }
549             }
550              
551              
552             1;
553             __END__