]>
git.defcon.no Git - trk/blob - trk
83bf87d41760e33283239de0817164f0835a7358
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("\nSee README.txt for more information\n");
64 my $id_length = shift;
65 $id_length = 32 if not defined $id_length;
69 for(my $i=0 ; $i<128;)
71 my $tc = chr(int(rand(127)));
72 if($tc =~ /[a-zA-Z0-9]/)
80 while ( length($w) < $id_length )
82 $w .= gen_puuid
( $id_length - length( $w ) );
85 $w = substr( $w, 0, $id_length );
89 # Input to parse_time is:
90 # * date -> date-string in the form YYYY-MM-DD
91 # * time -> time-string in the form HH:MM
92 # Return value is a unix timestamp, as returned by time()
95 my ( $Y, $M, $D ) = split ("-", shift );
96 my ( $h, $m ) = split(":", shift );
97 return timelocal
(0, $m, $h, $D, ($M-1), $Y);
103 return 0 if not $i =~ m/(\d\d\d\d-\d\d-\d\d) (\d\d:\d\d)/;
104 return parse_time
($1, $2);
110 return strftime
("%Y-%m-%d %H:%M", localtime($t));
113 sub parse_arguments
($)
118 my $start_time = time;
121 if (( $#ARGV >= 1) && ( $ARGV[1] eq "at" ))
123 # Start and Activity require a title to be present.
124 # All other (stop, main...) do not ^^.
125 if ( ($step == START
) || ($step == TASK
) )
127 # TODO: Allow no title!
128 # If no title is given, read ID of previously used track in stead :)
129 help
($step) unless $#ARGV > 3;
130 $title = join(" ", @ARGV[4..$#ARGV]);
132 help
(TIMEFORMAT
) unless ( $ARGV[2] =~ m/\d\d\d\d-\d\d-\d\d/ && $ARGV[3] =~ m/\d\d:\d\d/);
134 $start_time = parse_time
( $ARGV[2], $ARGV[3] );
136 elsif ( ($step == START
) || ($step == TASK
) || ($step == EDIT
))
139 $title = join(" ", @ARGV);
142 if ( not defined $title )
148 return ( $start_time, $title );
155 my $wrk_dir = $trk_dir;
156 $wrk_dir = $trk_dir . "/" . $trk_id if $trk_id;
158 return undef if ( ! -f
$wrk_dir . "/last" );
159 open ( CUR
, "<" . $wrk_dir . "/last" ) or die ("Unable to read last track file");
166 sub get_current_id
(;$)
169 my $wrk_dir = $trk_dir;
170 $wrk_dir = $trk_dir . "/" . $trk_id if $trk_id;
172 return undef if ( ! -f
$wrk_dir . "/current" );
173 open ( CUR
, "<" . $wrk_dir . "/current" ) or die ("Unable to read current track file");
180 sub set_current_id
($;$)
184 my $wrk_dir = $trk_dir;
185 $wrk_dir = $trk_dir . "/" . $trk_id if $trk_id;
187 return undef if ( -f
$wrk_dir . "/current" );
188 open ( CUR
, ">" . $wrk_dir . "/current" ) or die ("Unable to write current track file");
189 printf(CUR
"%s\n", $id );
192 open ( LAST
, ">" . $wrk_dir . "/last" ) or die ("Unable to write last track file");
193 printf(LAST
"%s\n", $id );
200 my $wrk_dir = $trk_dir;
201 $wrk_dir = $trk_dir . "/" . $trk_id if $trk_id;
206 foreach my $d ( <$wrk_dir/*> )
209 next if not -f
$d . "/info";
211 my $id = basename
($d);
212 my $title = get_track_name
( $id, $trk_id );
214 $tracks{$id} = $title unless not defined $title;
221 sub get_track_id
($;$)
226 # Get hash of track-id's and -names from get_tracks
227 my $tracks = get_tracks
($trk_id);
229 # Look up name in list
230 foreach my $id ( keys %$tracks )
233 return $id if ( $tracks->{$id} eq $title )
236 # If no match, return undef.
240 sub get_track_name
($;$)
244 my $wrk_dir = $trk_dir;
245 $wrk_dir = $trk_dir . "/" . $trk_id if $trk_id;
247 open(PRO
, "<" . $wrk_dir . "/" . $id . "/info" ) or die ("Unable to read track medatata file!");
251 next if not $_ =~ /^title:(.*)/;
258 sub create_track
($;$)
262 my $wrk_dir = $trk_dir;
263 $wrk_dir = $trk_dir . "/" . $trk_id if $trk_id;
270 } while ( -d
$wrk_dir . "/" . $id );
271 mkdir ( $wrk_dir . "/" . $id );
273 open(PRO
, ">" . $wrk_dir . "/" . $id . "/info" ) or die ("Unable to create track medatata file!");
274 printf(PRO
"title:%s\n", $title);
280 sub start_track
($$;$)
282 my $start_time = shift;
286 my $wrk_dir = $trk_dir;
287 $wrk_dir = $trk_dir . "/" . $trk_id if $trk_id;
289 my $current = get_current_id
($trk_id);
294 $current = get_last_id
( $trk_id );
298 $current = get_track_id
( $title, $trk_id );
301 printf("No track by that name! Creating a new one.\n");
302 $current = create_track
($title, $trk_id);
306 # Break off here if we haven't gotten an ID yet.
307 return undef if not $current;
309 set_current_id
($current, $trk_id);
311 # First iteration is VERY naive: simply add the start time to the bottom of the tracking file
312 # Will have to do more logic: if the start point is before one of the times already in the track,
313 # the file will have to be manipulated to get coherent tracking!
314 open (TRACK
, ">>" . $wrk_dir . "/" . $current . "/tracking" ) or die ("Unable to open file, $!");
315 printf(TRACK
"[%s]", time2str
($start_time));
324 sub close_track
($;$)
327 my $stop_time = shift;
329 my $wrk_dir = $trk_dir;
330 $wrk_dir = $trk_dir . "/" . $trk_id if $trk_id;
332 my $current = get_current_id
( $trk_id );
334 die ("Project exists, but tracking file does not!") if ( not -f
$wrk_dir . "/" . $current . "/tracking" );
336 # First iteration is VERY naive: simply add the stop time to the bottom line of the tracking file
337 # Will have to do more logic: if the start point is before one of the times already in the track,
338 # the file will have to be manipulated to get coherent tracking!
339 # In addtion to this: actually do some file sanity checking!
340 open (TRACK
, ">>" . $wrk_dir . "/" . $current . "/tracking" ) or die ("Unable to open file, $!");
341 printf(TRACK
" to [%s]\n", time2str
($stop_time));
344 unlink ( $wrk_dir . "/current" );
353 my $wrk_dir = $trk_dir;
354 $wrk_dir = $trk_dir . "/" . $trk_id if $trk_id;
358 my $name = get_track_name
( $current, $trk_id );
359 printf("# Report for '%s':\n\n", $name) unless $silent;
361 open (TRACK
, "<" . $wrk_dir . "/" . $current . "/tracking" ) or die ("Unable to open file, $!");
364 next if not $_ =~ m/\[(.*)\] to \[(.*)\]/;
367 my $t_start = str2time
( $start );
368 my $t_end = str2time
( $end );
369 my $delta = $t_end - $t_start;
374 my $hours = $t / 3600;
376 my $minutes = $t / 60;
377 printf(" %s to %s => %d hours %d minutes\n", $start, $end, $hours, $minutes);
387 ############################################################
391 mkdir $trk_dir or die("Unable to create data directory");
399 my $command = $ARGV[0];
401 if ( ( $command eq "start") || ($command eq "on" ) )
408 my ( $start_time, $title ) = parse_arguments
(START
);
410 my $current = get_current_id
();
413 $current = start_track
( $start_time, $title );
417 printf("Something weird happened.\n");
423 printf("A project is being tracked: %s\n", get_track_name
( $current ) );
424 close_track
($start_time);
425 $current = start_track
( $start_time, $title );
428 printf("Started tracking of '%s' at %s\n\n", $title, scalar localtime $start_time);
430 elsif ( ( $command eq "stop") || ($command eq "off" ) )
438 my $stop_time = parse_arguments
(STOP
);
440 my $current = get_current_id
();
443 printf("No project is currently tracked. To stop, please start first\n");
446 my $title = get_track_name
( $current );
448 my $activity = get_current_id
($current);
451 printf("An active subtask is running: '%s'. Closing it.\n", get_track_name
( $activity, $current ));
452 close_track
($stop_time, $current);
454 close_track
($stop_time);
456 printf("Stopped tracking of '%s' at %s\n\n", $title, scalar localtime $stop_time);
458 elsif ( ( $command eq "activity") || ($command eq "task" ) )
465 my ( $start_time, $title ) = parse_arguments
(START
);
467 my $trk_id = get_current_id
();
470 printf("Starting a task/activity requires an active main track.\n");
476 my $current = get_current_id
( $trk_id );
479 $current = start_track
( $start_time, $title, $trk_id );
483 printf("Something weird happened.\n");
489 printf("A task/activity is being tracked: %s\n", get_track_name
( $current, $trk_id ) );
490 close_track
($start_time, $trk_id);
491 $current = start_track
( $start_time, $title, $trk_id );
494 printf("Started tracking of '%s' at %s\n\n", $title, scalar localtime $start_time);
500 elsif ( $command eq "main" )
509 my $stop_time = parse_arguments
(STOP
);
511 my $trk_id = get_current_id
();
514 printf("Stopping a task/activity requires an active main track.\n");
519 my $current = get_current_id
( $trk_id );
522 printf("No activity/task is currently tracked. To stop, please start first\n");
525 my $title = get_track_name
( $current, $trk_id );
526 close_track
($stop_time, $trk_id);
528 printf("Stopped tracking of '%s' at %s\n\n", $title, scalar localtime $stop_time);
531 elsif ( ( $command eq "projects" ) || ( $command eq "list" ) )
533 # Todo/future extensions:
534 # TODO: Sort list of names alphabetically
535 # TODO: Get total-hours for projects
537 my $tracks = get_tracks
();
538 printf("Currently tracked project names:\n\n");
539 my $current = get_current_id
();
541 #foreach my $id ( keys %$tracks )
542 foreach my $id ( sort { $tracks->{$a} cmp $tracks->{$b} || $a cmp $b } keys %$tracks )
544 printf(" %s %s\n", ($id eq $current ?
">" : " " ),$tracks->{$id} );
548 elsif ( $command eq "report" )
551 my $format = "standard";
554 if (( $#ARGV >= 1) &&
555 ( ( $ARGV[1] eq "standard" )
556 || ( $ARGV[1] eq "terse" )
557 || ( $ARGV[1] eq "verbose" )
558 || ( $ARGV[1] eq "details" ) ) )
564 $output = 0 if $format eq "terse";
565 $output = 1 if $format eq "standard";
566 $output = 2 if $format eq "verbose";
567 $output = 3 if $format eq "details";
569 my ( undef, $title ) = parse_arguments
(START
);
571 printf("Report format: %s\nTitle: %s\n", $format, $title);
577 $track = get_track_id
( $title );
581 $track = get_last_id
();
586 printf ("Unable to get info for that track\n");
593 my $activities = get_tracks
( $track );
594 if ( keys %$activities )
596 printf("# Reporting for sub-task/activities:\n\n") if $output >= 2;
598 foreach my $id ( sort { $activities->{$a} cmp $activities->{$b} || $a cmp $b } keys %$activities )
599 #foreach my $id ( keys %$activities )
601 $subtotals += report
( $id, ( $output >= 2 ?
0 : 1 ), $track );
602 printf("# --------------------------------------------------------------\n\n") if $output >= 2;
606 printf("# Reporting for main track/project/task\n") if $output >= 2;
607 $total += report
($track, ( ( $output >= 1 ?
0 : 1 ) ) );
608 printf("\n# ==============================================================\n\n") if $output >= 2;
609 print("\n") if $output >= 1;
613 my $hours = $t / 3600;
615 my $minutes = $t / 60;
617 printf("Total: %d hours %d minutes\n", $hours, $minutes);
621 my $hours = $t / 3600;
622 $t = $subtotals % 3600;
623 my $minutes = $t / 60;
624 printf("Time logged on tasks: %d hours %d minutes\n", $hours, $minutes);
627 print("# End of report\n") if $output >= 1;
631 elsif ( $command eq "edit" )
634 my ( undef, $title ) = parse_arguments
(EDIT
);
635 my $id = get_last_id
();
639 $id = get_track_id
($title);
642 printf("No project by that name. Try 'list'\n");
647 system( "/usr/bin/editor " . $trk_dir . "/" . $id . "/tracking" );