]>
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) );
394 if ( $check eq $current )
397 if ( $t = current_starttime
( $trk_id ) )
399 my $delta = time - $t;
400 printf(" %s to NOW (active) => %s\n", time2str
( $t ), delta2str
( $delta ) ) if not $silent;
409 sub current_starttime
(;$)
412 my $wrk_dir = $trk_dir;
413 $wrk_dir = $trk_dir . "/" . $trk_id if $trk_id;
417 my $current = get_current_id
( $trk_id );
418 return 0 if not $current;
420 open (TRACK
, "<" . $wrk_dir . "/" . $current . "/tracking" ) or die ("Unable to open file, $!");
426 return 0 if not $line =~ m/^\[(\d\d\d\d-\d\d-\d\d \d\d:\d\d)\]$/;
430 ############################################################
434 mkdir $trk_dir or die("Unable to create data directory");
442 my $command = $ARGV[0];
444 if ( ( $command eq "start") || ($command eq "on" ) )
451 my ( $start_time, $title ) = parse_arguments
(START
);
453 my $current = get_current_id
();
456 $current = start_track
( $start_time, $title );
460 printf("Something weird happened.\n");
466 printf("A project is being tracked: %s\n", get_track_name
( $current ) );
467 close_track
($start_time);
468 $current = start_track
( $start_time, $title );
471 printf("Started tracking of '%s' at %s\n\n", $title, scalar localtime $start_time);
473 elsif ( ( $command eq "stop") || ($command eq "off" ) )
481 my $stop_time = parse_arguments
(STOP
);
483 my $current = get_current_id
();
486 printf("No project is currently tracked. To stop, please start first\n");
489 my $title = get_track_name
( $current );
491 my $activity = get_current_id
($current);
494 printf("An active subtask is running: '%s'. Closing it.\n", get_track_name
( $activity, $current ));
495 close_track
($stop_time, $current);
497 close_track
($stop_time);
499 printf("Stopped tracking of '%s' at %s\n\n", $title, scalar localtime $stop_time);
501 elsif ( ( $command eq "activity") || ($command eq "task" ) )
508 my ( $start_time, $title ) = parse_arguments
(START
);
510 my $trk_id = get_current_id
();
513 printf("Starting a task/activity requires an active main track.\n");
519 my $current = get_current_id
( $trk_id );
522 $current = start_track
( $start_time, $title, $trk_id );
526 printf("Something weird happened.\n");
532 printf("A task/activity is being tracked: %s\n", get_track_name
( $current, $trk_id ) );
533 close_track
($start_time, $trk_id);
534 $current = start_track
( $start_time, $title, $trk_id );
537 printf("Started tracking of '%s' at %s\n\n", $title, scalar localtime $start_time);
543 elsif ( $command eq "main" )
552 my $stop_time = parse_arguments
(STOP
);
554 my $trk_id = get_current_id
();
557 printf("Stopping a task/activity requires an active main track.\n");
562 my $current = get_current_id
( $trk_id );
565 printf("No activity/task is currently tracked. To stop, please start first\n");
568 my $title = get_track_name
( $current, $trk_id );
569 close_track
($stop_time, $trk_id);
571 printf("Stopped tracking of '%s' at %s\n\n", $title, scalar localtime $stop_time);
574 elsif ( ( $command eq "projects" ) || ( $command eq "list" ) )
576 # Todo/future extensions:
577 # TODO: Sort list of names alphabetically
578 # TODO: Get total-hours for projects
580 my $tracks = get_tracks
();
581 printf("Currently tracked project names:\n\n");
582 my $current = get_current_id
();
584 #foreach my $id ( keys %$tracks )
585 foreach my $id ( sort { $tracks->{$a} cmp $tracks->{$b} || $a cmp $b } keys %$tracks )
587 printf(" %s %s\n", ($id eq $current ?
">" : " " ),$tracks->{$id} );
591 elsif ( $command eq "report" )
594 my $format = "standard";
597 if (( $#ARGV >= 1) &&
598 ( ( $ARGV[1] eq "standard" )
599 || ( $ARGV[1] eq "terse" )
600 || ( $ARGV[1] eq "verbose" )
601 || ( $ARGV[1] eq "details" ) ) )
607 $output = 0 if $format eq "terse";
608 $output = 1 if $format eq "standard";
609 $output = 2 if $format eq "verbose";
610 $output = 3 if $format eq "details";
612 my ( undef, $title ) = parse_arguments
(START
);
614 printf("Report format: %s\nTitle: %s\n", $format, $title);
620 $track = get_track_id
( $title );
624 $track = get_last_id
();
629 printf ("Unable to get info for that track\n");
636 my $activities = get_tracks
( $track );
637 if ( keys %$activities )
639 printf("# Reporting for sub-task/activities:\n\n") if $output >= 2;
641 foreach my $id ( sort { $activities->{$a} cmp $activities->{$b} || $a cmp $b } keys %$activities )
642 #foreach my $id ( keys %$activities )
644 $subtotals += report
( $id, ( $output >= 2 ?
0 : 1 ), $track );
645 printf("# --------------------------------------------------------------\n\n") if $output >= 2;
649 printf("# Reporting for main track/project/task\n") if $output >= 2;
650 $total += report
($track, ( ( $output >= 1 ?
0 : 1 ) ) );
651 printf("\n# ==============================================================\n\n") if $output >= 2;
652 print("\n") if $output >= 1;
655 printf("Total: %s\n", delta2str
($total) );
659 printf("Time logged on tasks: %s\n", delta2str
($subtotals) );
662 print("# End of report\n") if $output >= 1;
665 elsif ( $command eq "status" )
668 my $trk_id = get_current_id
();
671 printf("Not currently tracking anything.\n");
672 $trk_id = get_last_id
();
675 printf("Last track was: %s\n", get_track_name
( $trk_id ) );
679 printf("Currently tracking: %s\n", get_track_name
( $trk_id ) );
680 my $t = current_starttime
();
681 printf("Tracking started at %s\n", scalar localtime $t);
682 printf("Time elapsed since start of session: %s\n", delta2str
(time - $t) );
684 my $activity = get_current_id
( $trk_id );
687 printf("\nCurrent sub-task/activity is: %s\n", get_track_name
( $activity, $trk_id ) );
688 my $t = current_starttime
($trk_id);
689 printf("Activity started at %s\n", scalar localtime $t);
690 printf("Time elapsed since start of activity: %s\n", delta2str
(time - $t) );
694 $activity = get_last_id
( $trk_id );
697 printf("\nLast track was: %s\n", get_track_name
( $activity, $trk_id ) );
701 elsif ( $command eq "edit" )
704 my ( undef, $title ) = parse_arguments
(EDIT
);
705 my $id = get_last_id
();
709 $id = get_track_id
($title);
712 printf("No project by that name. Try 'list'\n");
717 system( "/usr/bin/editor " . $trk_dir . "/" . $id . "/tracking" );