]>
git.defcon.no Git - trk/blob - trk
3 # Copyright © Jon Langseth
5 # Permission is hereby granted, free of charge, to any person obtaining a copy
6 # of this software and associated documentation files (the "Software"), to deal
7 # in the Software without restriction, including without limitation the rights
8 # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 # copies of the Software, and to permit persons to whom the Software is
10 # furnished to do so, subject to the following conditions:
12 # The above copyright notice and this permission notice shall be included in
13 # all copies or substantial portions of the Software.
15 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24 use Digest
::MD5
qw(md5_hex);
29 my $trk_dir = "$ENV{HOME}/.trk";
32 if ( -d
$ENV{TRK_DIR
} ) { $trk_dir = $ENV{TRK_DIR
} if -d
$ENV{TRK_DIR
}; }
33 else { printf("Environment variable TRK_DIR is not a directory\n"); exit(1); }
48 printf("Error in invocation. Syntax summary:\n\n");
49 printf(" %s {start|on} [at yyyy-mm-dd hh:mm] <trackname>\n", $0);
50 printf(" %s {stop|off} [at yyyy-mm-dd hh:mm] \n", $0);
51 printf(" %s {activity|task} [at yyyy-mm-dd hh:mm] <taskname>\n", $0);
52 printf(" %s main [at yyyy-mm-dd hh:mm]\n", $0);
53 printf(" %s {projects|list} [verbose]\n", $0);
54 printf(" %s report [{terse|standard|verbose|details}] [<trackname>]\n", $0);
55 printf(" %s edit <trackname>\n", $0);
56 printf(" %s status\n", $0);
57 printf("\nSee README.txt for more information\n");
65 my $id_length = shift;
66 $id_length = 32 if not defined $id_length;
70 for(my $i=0 ; $i<128;)
72 my $tc = chr(int(rand(127)));
73 if($tc =~ /[a-zA-Z0-9]/)
81 while ( length($w) < $id_length )
83 $w .= gen_puuid
( $id_length - length( $w ) );
86 $w = substr( $w, 0, $id_length );
90 # Input to parse_time is:
91 # * date -> date-string in the form YYYY-MM-DD
92 # * time -> time-string in the form HH:MM
93 # Return value is a unix timestamp, as returned by time()
96 my ( $Y, $M, $D ) = split ("-", shift );
97 my ( $h, $m ) = split(":", shift );
98 return timelocal
(0, $m, $h, $D, ($M-1), $Y);
104 return 0 if not $i =~ m/(\d\d\d\d-\d\d-\d\d) (\d\d:\d\d)/;
105 return parse_time
($1, $2);
111 return strftime
("%Y-%m-%d %H:%M", localtime($t));
118 my $hours = $t / 3600;
120 my $minutes = $t / 60;
121 return sprintf("%d hours %d minutes", $hours, $minutes);
124 sub parse_arguments
($)
129 my $start_time = time;
132 if (( $#ARGV >= 1) && ( $ARGV[1] eq "at" ))
134 # Start and Activity require a title to be present.
135 # All other (stop, main...) do not ^^.
136 if ( ($step == START
) || ($step == TASK
) )
138 # TODO: Allow no title!
139 # If no title is given, read ID of previously used track in stead :)
140 help
($step) unless $#ARGV > 3;
141 $title = join(" ", @ARGV[4..$#ARGV]);
143 help
(TIMEFORMAT
) unless ( $ARGV[2] =~ m/\d\d\d\d-\d\d-\d\d/ && $ARGV[3] =~ m/\d\d:\d\d/);
145 $start_time = parse_time
( $ARGV[2], $ARGV[3] );
147 elsif ( ($step == START
) || ($step == TASK
) || ($step == EDIT
))
150 $title = join(" ", @ARGV);
153 if ( not defined $title )
159 return ( $start_time, $title );
166 my $wrk_dir = $trk_dir;
167 $wrk_dir = $trk_dir . "/" . $trk_id if $trk_id;
169 return undef if ( ! -f
$wrk_dir . "/last" );
170 open ( CUR
, "<" . $wrk_dir . "/last" ) or die ("Unable to read last track file");
177 sub get_current_id
(;$)
180 my $wrk_dir = $trk_dir;
181 $wrk_dir = $trk_dir . "/" . $trk_id if $trk_id;
183 return undef if ( ! -f
$wrk_dir . "/current" );
184 open ( CUR
, "<" . $wrk_dir . "/current" ) or die ("Unable to read current track file");
191 sub set_current_id
($;$)
195 my $wrk_dir = $trk_dir;
196 $wrk_dir = $trk_dir . "/" . $trk_id if $trk_id;
198 return undef if ( -f
$wrk_dir . "/current" );
199 open ( CUR
, ">" . $wrk_dir . "/current" ) or die ("Unable to write current track file");
200 printf(CUR
"%s\n", $id );
203 open ( LAST
, ">" . $wrk_dir . "/last" ) or die ("Unable to write last track file");
204 printf(LAST
"%s\n", $id );
211 my $wrk_dir = $trk_dir;
212 $wrk_dir = $trk_dir . "/" . $trk_id if $trk_id;
217 foreach my $d ( <$wrk_dir/*> )
220 next if not -f
$d . "/info";
222 my $id = basename
($d);
223 my $title = get_track_name
( $id, $trk_id );
225 $tracks{$id} = $title unless not defined $title;
232 sub get_track_id
($;$)
237 # Get hash of track-id's and -names from get_tracks
238 my $tracks = get_tracks
($trk_id);
240 # Look up name in list
241 foreach my $id ( keys %$tracks )
244 return $id if ( $tracks->{$id} eq $title )
247 # If no match, return undef.
251 sub get_track_name
($;$)
255 my $wrk_dir = $trk_dir;
256 $wrk_dir = $trk_dir . "/" . $trk_id if $trk_id;
258 open(PRO
, "<" . $wrk_dir . "/" . $id . "/info" ) or die ("Unable to read track medatata file!");
262 next if not $_ =~ /^title:(.*)/;
269 sub create_track
($;$)
273 my $wrk_dir = $trk_dir;
274 $wrk_dir = $trk_dir . "/" . $trk_id if $trk_id;
281 } while ( -d
$wrk_dir . "/" . $id );
282 mkdir ( $wrk_dir . "/" . $id );
284 open(PRO
, ">" . $wrk_dir . "/" . $id . "/info" ) or die ("Unable to create track medatata file!");
285 printf(PRO
"title:%s\n", $title);
291 sub start_track
($$;$)
293 my $start_time = shift;
297 my $wrk_dir = $trk_dir;
298 $wrk_dir = $trk_dir . "/" . $trk_id if $trk_id;
300 my $current = get_current_id
($trk_id);
305 $current = get_last_id
( $trk_id );
309 $current = get_track_id
( $title, $trk_id );
312 printf("No track by that name! Creating a new one.\n");
313 $current = create_track
($title, $trk_id);
317 # Break off here if we haven't gotten an ID yet.
318 return undef if not $current;
320 set_current_id
($current, $trk_id);
322 # First iteration is VERY naive: simply add the start time to the bottom of the tracking file
323 # Will have to do more logic: if the start point is before one of the times already in the track,
324 # the file will have to be manipulated to get coherent tracking!
325 open (TRACK
, ">>" . $wrk_dir . "/" . $current . "/tracking" ) or die ("Unable to open file, $!");
326 printf(TRACK
"[%s]", time2str
($start_time));
335 sub close_track
($;$)
338 my $stop_time = shift;
340 my $wrk_dir = $trk_dir;
341 $wrk_dir = $trk_dir . "/" . $trk_id if $trk_id;
343 my $current = get_current_id
( $trk_id );
345 die ("Project exists, but tracking file does not!") if ( not -f
$wrk_dir . "/" . $current . "/tracking" );
347 # First iteration is VERY naive: simply add the stop time to the bottom line of the tracking file
348 # Will have to do more logic: if the start point is before one of the times already in the track,
349 # the file will have to be manipulated to get coherent tracking!
350 # In addtion to this: actually do some file sanity checking!
351 open (TRACK
, ">>" . $wrk_dir . "/" . $current . "/tracking" ) or die ("Unable to open file, $!");
352 printf(TRACK
" to [%s]\n", time2str
($stop_time));
355 unlink ( $wrk_dir . "/current" );
364 my $wrk_dir = $trk_dir;
365 $wrk_dir = $trk_dir . "/" . $trk_id if $trk_id;
369 my $name = get_track_name
( $current, $trk_id );
370 printf("# Report for '%s':\n\n", $name) unless $silent;
372 my $check = get_current_id
( $trk_id );
374 open (TRACK
, "<" . $wrk_dir . "/" . $current . "/tracking" ) or die ("Unable to open file, $!");
377 next if not $_ =~ m/\[(.*)\] to \[(.*)\]/;
380 my $t_start = str2time
( $start );
381 my $t_end = str2time
( $end );
382 my $delta = $t_end - $t_start;
386 printf(" %s to %s => %s\n", $start, $end, delta2str
($delta) );
390 if ( $check eq $current )
393 if ( $t = current_starttime
( $trk_id ) )
395 my $delta = time - $t;
396 printf(" %s to NOW (active) => %s\n", time2str
( $t ), delta2str
( $delta ) ) if not $silent;
407 sub current_starttime
(;$)
410 my $wrk_dir = $trk_dir;
411 $wrk_dir = $trk_dir . "/" . $trk_id if $trk_id;
415 my $current = get_current_id
( $trk_id );
416 return 0 if not $current;
418 open (TRACK
, "<" . $wrk_dir . "/" . $current . "/tracking" ) or die ("Unable to open file, $!");
424 return 0 if not $line =~ m/^\[(\d\d\d\d-\d\d-\d\d \d\d:\d\d)\]$/;
428 ############################################################
432 mkdir $trk_dir or die("Unable to create data directory");
440 my $command = $ARGV[0];
442 if ( ( $command eq "start") || ($command eq "on" ) )
449 my ( $start_time, $title ) = parse_arguments
(START
);
451 my $current = get_current_id
();
454 $current = start_track
( $start_time, $title );
458 printf("Something weird happened.\n");
464 printf("A project is being tracked: %s\n", get_track_name
( $current ) );
465 close_track
($start_time);
466 $current = start_track
( $start_time, $title );
469 printf("Started tracking of '%s' at %s\n\n", $title, scalar localtime $start_time);
471 elsif ( ( $command eq "stop") || ($command eq "off" ) )
479 my $stop_time = parse_arguments
(STOP
);
481 my $current = get_current_id
();
484 printf("No project is currently tracked. To stop, please start first\n");
487 my $title = get_track_name
( $current );
489 my $activity = get_current_id
($current);
492 printf("An active subtask is running: '%s'. Closing it.\n", get_track_name
( $activity, $current ));
493 close_track
($stop_time, $current);
495 close_track
($stop_time);
497 printf("Stopped tracking of '%s' at %s\n\n", $title, scalar localtime $stop_time);
499 elsif ( ( $command eq "activity") || ($command eq "task" ) )
506 my ( $start_time, $title ) = parse_arguments
(START
);
508 my $trk_id = get_current_id
();
511 printf("Starting a task/activity requires an active main track.\n");
517 my $current = get_current_id
( $trk_id );
520 $current = start_track
( $start_time, $title, $trk_id );
524 printf("Something weird happened.\n");
530 printf("A task/activity is being tracked: %s\n", get_track_name
( $current, $trk_id ) );
531 close_track
($start_time, $trk_id);
532 $current = start_track
( $start_time, $title, $trk_id );
535 printf("Started tracking of '%s' at %s\n\n", $title, scalar localtime $start_time);
541 elsif ( $command eq "main" )
550 my $stop_time = parse_arguments
(STOP
);
552 my $trk_id = get_current_id
();
555 printf("Stopping a task/activity requires an active main track.\n");
560 my $current = get_current_id
( $trk_id );
563 printf("No activity/task is currently tracked. To stop, please start first\n");
566 my $title = get_track_name
( $current, $trk_id );
567 close_track
($stop_time, $trk_id);
569 printf("Stopped tracking of '%s' at %s\n\n", $title, scalar localtime $stop_time);
572 elsif ( ( $command eq "projects" ) || ( $command eq "list" ) )
574 # Todo/future extensions:
575 # TODO: Sort list of names alphabetically
576 # TODO: Get total-hours for projects
578 my $tracks = get_tracks
();
579 printf("Currently tracked project names:\n\n");
580 my $current = get_current_id
();
582 #foreach my $id ( keys %$tracks )
583 foreach my $id ( sort { $tracks->{$a} cmp $tracks->{$b} || $a cmp $b } keys %$tracks )
585 printf(" %s %s\n", ($id eq $current ?
">" : " " ),$tracks->{$id} );
589 elsif ( $command eq "report" )
592 my $format = "standard";
595 if (( $#ARGV >= 1) &&
596 ( ( $ARGV[1] eq "standard" )
597 || ( $ARGV[1] eq "terse" )
598 || ( $ARGV[1] eq "verbose" )
599 || ( $ARGV[1] eq "details" ) ) )
605 $output = 0 if $format eq "terse";
606 $output = 1 if $format eq "standard";
607 $output = 2 if $format eq "verbose";
608 $output = 3 if $format eq "details";
610 my ( undef, $title ) = parse_arguments
(START
);
612 printf("Report format: %s\nTitle: %s\n", $format, $title);
618 $track = get_track_id
( $title );
622 $track = get_last_id
();
627 printf ("Unable to get info for that track\n");
634 my $activities = get_tracks
( $track );
635 if ( keys %$activities )
637 printf("# Reporting for sub-task/activities:\n\n") if $output >= 2;
639 foreach my $id ( sort { $activities->{$a} cmp $activities->{$b} || $a cmp $b } keys %$activities )
640 #foreach my $id ( keys %$activities )
642 $subtotals += report
( $id, ( $output >= 2 ?
0 : 1 ), $track );
643 printf("# --------------------------------------------------------------\n\n") if $output >= 2;
647 printf("# Reporting for main track/project/task\n") if $output >= 2;
648 $total += report
($track, ( ( $output >= 1 ?
0 : 1 ) ) );
649 printf("\n# ==============================================================\n\n") if $output >= 2;
650 print("\n") if $output >= 1;
653 printf("Total: %s\n", delta2str
($total) );
657 printf("Time logged on tasks: %s\n", delta2str
($subtotals) );
660 print("# End of report\n") if $output >= 1;
663 elsif ( $command eq "status" )
666 my $trk_id = get_current_id
();
669 printf("Not currently tracking anything.\n");
670 $trk_id = get_last_id
();
673 printf("Last track was: %s\n", get_track_name
( $trk_id ) );
677 printf("Currently tracking: %s\n", get_track_name
( $trk_id ) );
678 my $t = current_starttime
();
679 printf("Tracking started at %s\n", scalar localtime $t);
680 printf("Time elapsed since start of session: %s\n", delta2str
(time - $t) );
682 my $activity = get_current_id
( $trk_id );
685 printf("\nCurrent sub-task/activity is: %s\n", get_track_name
( $activity, $trk_id ) );
686 my $t = current_starttime
($trk_id);
687 printf("Activity started at %s\n", scalar localtime $t);
688 printf("Time elapsed since start of activity: %s\n", delta2str
(time - $t) );
692 $activity = get_last_id
( $trk_id );
695 printf("\nLast track was: %s\n", get_track_name
( $activity, $trk_id ) );
699 elsif ( $command eq "edit" )
702 my ( undef, $title ) = parse_arguments
(EDIT
);
703 my $id = get_last_id
();
707 $id = get_track_id
($title);
710 printf("No project by that name. Try 'list'\n");
715 system( "/usr/bin/editor " . $trk_dir . "/" . $id . "/tracking" );