]>
git.defcon.no Git - trk/blob - trk
bd10e9ec45c9f2724e2d1ba855130282b16f739c
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); }
47 printf("It seems you require assistance\n");
51 printf("How to start\n") if $code == START
;
52 printf("How to time\n") if $code == TIMEFORMAT
;
60 my $id_length = shift;
61 $id_length = 32 if not defined $id_length;
65 for(my $i=0 ; $i<128;)
67 my $tc = chr(int(rand(127)));
68 if($tc =~ /[a-zA-Z0-9]/)
76 while ( length($w) < $id_length )
78 $w .= gen_puuid
( $id_length - length( $w ) );
81 $w = substr( $w, 0, $id_length );
85 # Input to parse_time is:
86 # * date -> date-string in the form YYYY-MM-DD
87 # * time -> time-string in the form HH:MM
88 # Return value is a unix timestamp, as returned by time()
91 my ( $Y, $M, $D ) = split ("-", shift );
92 my ( $h, $m ) = split(":", shift );
93 return timelocal
(0, $m, $h, $D, ($M-1), $Y);
99 return 0 if not $i =~ m/(\d\d\d\d-\d\d-\d\d) (\d\d:\d\d)/;
100 return parse_time
($1, $2);
106 return strftime
("%Y-%m-%d %H:%M", localtime($t));
109 sub parse_arguments
($)
114 my $start_time = time;
117 if (( $#ARGV >= 1) && ( $ARGV[1] eq "at" ))
119 # Start and Activity require a title to be present.
120 # All other (stop, main...) do not ^^.
121 if ( ($step == START
) || ($step == TASK
) )
123 # TODO: Allow no title!
124 # If no title is given, read ID of previously used track in stead :)
125 help
($step) unless $#ARGV > 3;
126 $title = join(" ", @ARGV[4..$#ARGV]);
128 help
(TIMEFORMAT
) unless ( $ARGV[2] =~ m/\d\d\d\d-\d\d-\d\d/ && $ARGV[3] =~ m/\d\d:\d\d/);
130 $start_time = parse_time
( $ARGV[2], $ARGV[3] );
132 elsif ( ($step == START
) || ($step == TASK
) || ($step == EDIT
))
135 $title = join(" ", @ARGV);
138 if ( not defined $title )
144 return ( $start_time, $title );
151 my $wrk_dir = $trk_dir;
152 $wrk_dir = $trk_dir . "/" . $trk_id if $trk_id;
154 return undef if ( ! -f
$wrk_dir . "/last" );
155 open ( CUR
, "<" . $wrk_dir . "/last" ) or die ("Unable to read last track file");
162 sub get_current_id
(;$)
165 my $wrk_dir = $trk_dir;
166 $wrk_dir = $trk_dir . "/" . $trk_id if $trk_id;
168 return undef if ( ! -f
$wrk_dir . "/current" );
169 open ( CUR
, "<" . $wrk_dir . "/current" ) or die ("Unable to read current track file");
176 sub set_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 write current track file");
185 printf(CUR
"%s\n", $id );
188 open ( LAST
, ">" . $wrk_dir . "/last" ) or die ("Unable to write last track file");
189 printf(LAST
"%s\n", $id );
196 my $wrk_dir = $trk_dir;
197 $wrk_dir = $trk_dir . "/" . $trk_id if $trk_id;
202 foreach my $d ( <$wrk_dir/*> )
205 next if not -f
$d . "/info";
207 my $id = basename
($d);
208 my $title = get_track_name
( $id, $trk_id );
210 $tracks{$id} = $title unless not defined $title;
217 sub get_track_id
($;$)
222 # Get hash of track-id's and -names from get_tracks
223 my $tracks = get_tracks
($trk_id);
225 # Look up name in list
226 foreach my $id ( keys %$tracks )
229 return $id if ( $tracks->{$id} eq $title )
232 # If no match, return undef.
236 sub get_track_name
($;$)
240 my $wrk_dir = $trk_dir;
241 $wrk_dir = $trk_dir . "/" . $trk_id if $trk_id;
243 open(PRO
, "<" . $wrk_dir . "/" . $id . "/info" ) or die ("Unable to read track medatata file!");
247 next if not $_ =~ /^title:(.*)/;
254 sub create_track
($;$)
258 my $wrk_dir = $trk_dir;
259 $wrk_dir = $trk_dir . "/" . $trk_id if $trk_id;
266 } while ( -d
$wrk_dir . "/" . $id );
267 mkdir ( $wrk_dir . "/" . $id );
269 open(PRO
, ">" . $wrk_dir . "/" . $id . "/info" ) or die ("Unable to create track medatata file!");
270 printf(PRO
"title:%s\n", $title);
276 sub start_track
($$;$)
278 my $start_time = shift;
282 my $wrk_dir = $trk_dir;
283 $wrk_dir = $trk_dir . "/" . $trk_id if $trk_id;
285 my $current = get_current_id
($trk_id);
290 $current = get_last_id
( $trk_id );
294 $current = get_track_id
( $title, $trk_id );
297 printf("No track by that name! Creating a new one.\n");
298 $current = create_track
($title, $trk_id);
302 # Break off here if we haven't gotten an ID yet.
303 return undef if not $current;
305 set_current_id
($current, $trk_id);
307 # First iteration is VERY naive: simply add the start time to the bottom of the tracking file
308 # Will have to do more logic: if the start point is before one of the times already in the track,
309 # the file will have to be manipulated to get coherent tracking!
310 open (TRACK
, ">>" . $wrk_dir . "/" . $current . "/tracking" ) or die ("Unable to open file, $!");
311 printf(TRACK
"[%s]", time2str
($start_time));
320 sub close_track
($;$)
323 my $stop_time = shift;
325 my $wrk_dir = $trk_dir;
326 $wrk_dir = $trk_dir . "/" . $trk_id if $trk_id;
328 my $current = get_current_id
( $trk_id );
330 die ("Project exists, but tracking file does not!") if ( not -f
$wrk_dir . "/" . $current . "/tracking" );
332 # First iteration is VERY naive: simply add the stop time to the bottom line of the tracking file
333 # Will have to do more logic: if the start point is before one of the times already in the track,
334 # the file will have to be manipulated to get coherent tracking!
335 # In addtion to this: actually do some file sanity checking!
336 open (TRACK
, ">>" . $wrk_dir . "/" . $current . "/tracking" ) or die ("Unable to open file, $!");
337 printf(TRACK
" to [%s]\n", time2str
($stop_time));
340 unlink ( $wrk_dir . "/current" );
349 my $wrk_dir = $trk_dir;
350 $wrk_dir = $trk_dir . "/" . $trk_id if $trk_id;
354 my $name = get_track_name
( $current, $trk_id );
355 printf("# Report for '%s':\n\n", $name) unless $silent;
357 open (TRACK
, "<" . $wrk_dir . "/" . $current . "/tracking" ) or die ("Unable to open file, $!");
360 next if not $_ =~ m/\[(.*)\] to \[(.*)\]/;
363 my $t_start = str2time
( $start );
364 my $t_end = str2time
( $end );
365 my $delta = $t_end - $t_start;
370 my $hours = $t / 3600;
372 my $minutes = $t / 60;
373 printf(" %s to %s => %d hours %d minutes\n", $start, $end, $hours, $minutes);
383 ############################################################
387 mkdir $trk_dir or die("Unable to create data directory");
395 my $command = $ARGV[0];
397 if ( ( $command eq "start") || ($command eq "on" ) )
404 my ( $start_time, $title ) = parse_arguments
(START
);
406 my $current = get_current_id
();
409 $current = start_track
( $start_time, $title );
413 printf("Something weird happened.\n");
419 printf("A project is being tracked: %s\n", get_track_name
( $current ) );
420 close_track
($start_time);
421 $current = start_track
( $start_time, $title );
424 printf("Started tracking of '%s' at %s\n\n", $title, scalar localtime $start_time);
426 elsif ( ( $command eq "stop") || ($command eq "off" ) )
434 my $stop_time = parse_arguments
(STOP
);
436 my $current = get_current_id
();
439 printf("No project is currently tracked. To stop, please start first\n");
442 my $title = get_track_name
( $current );
444 my $activity = get_current_id
($current);
447 printf("An active subtask is running: '%s'. Closing it.\n", get_track_name
( $activity, $current ));
448 close_track
($stop_time, $current);
450 close_track
($stop_time);
452 printf("Stopped tracking of '%s' at %s\n\n", $title, scalar localtime $stop_time);
454 elsif ( ( $command eq "activity") || ($command eq "task" ) )
461 my ( $start_time, $title ) = parse_arguments
(START
);
463 my $trk_id = get_current_id
();
466 printf("Starting a task/activity requires an active main track.\n");
472 my $current = get_current_id
( $trk_id );
475 $current = start_track
( $start_time, $title, $trk_id );
479 printf("Something weird happened.\n");
485 printf("A task/activity is being tracked: %s\n", get_track_name
( $current, $trk_id ) );
486 close_track
($start_time, $trk_id);
487 $current = start_track
( $start_time, $title, $trk_id );
490 printf("Started tracking of '%s' at %s\n\n", $title, scalar localtime $start_time);
496 elsif ( $command eq "main" )
505 my $stop_time = parse_arguments
(STOP
);
507 my $trk_id = get_current_id
();
510 printf("Stopping a task/activity requires an active main track.\n");
515 my $current = get_current_id
( $trk_id );
518 printf("No activity/task is currently tracked. To stop, please start first\n");
521 my $title = get_track_name
( $current, $trk_id );
522 close_track
($stop_time, $trk_id);
524 printf("Stopped tracking of '%s' at %s\n\n", $title, scalar localtime $stop_time);
527 elsif ( ( $command eq "projects" ) || ( $command eq "list" ) )
529 # Todo/future extensions:
530 # TODO: Sort list of names alphabetically
531 # TODO: Get total-hours for projects
533 my $tracks = get_tracks
();
534 printf("Currently tracked project names:\n\n");
535 my $current = get_current_id
();
537 #foreach my $id ( keys %$tracks )
538 foreach my $id ( sort { $tracks->{$a} cmp $tracks->{$b} || $a cmp $b } keys %$tracks )
540 printf(" %s %s\n", ($id eq $current ?
">" : " " ),$tracks->{$id} );
544 elsif ( $command eq "report" )
547 my $format = "standard";
550 if (( $#ARGV >= 1) &&
551 ( ( $ARGV[1] eq "standard" )
552 || ( $ARGV[1] eq "terse" )
553 || ( $ARGV[1] eq "verbose" )
554 || ( $ARGV[1] eq "details" ) ) )
560 $output = 0 if $format eq "terse";
561 $output = 1 if $format eq "standard";
562 $output = 2 if $format eq "verbose";
563 $output = 3 if $format eq "details";
565 my ( undef, $title ) = parse_arguments
(START
);
567 printf("Report format: %s\nTitle: %s\n", $format, $title);
573 $track = get_track_id
( $title );
577 $track = get_last_id
();
582 printf ("Unable to get info for that track\n");
589 my $activities = get_tracks
( $track );
590 if ( keys %$activities )
592 printf("# Reporting for sub-task/activities:\n\n") if $output >= 2;
594 foreach my $id ( sort { $activities->{$a} cmp $activities->{$b} || $a cmp $b } keys %$activities )
595 #foreach my $id ( keys %$activities )
597 $subtotals += report
( $id, ( $output >= 2 ?
0 : 1 ), $track );
598 printf("# --------------------------------------------------------------\n\n") if $output >= 2;
602 printf("# Reporting for main track/project/task\n") if $output >= 2;
603 $total += report
($track, ( ( $output >= 1 ?
0 : 1 ) ) );
604 printf("\n# ==============================================================\n\n") if $output >= 2;
605 print("\n") if $output >= 1;
609 my $hours = $t / 3600;
611 my $minutes = $t / 60;
613 printf("Total: %d hours %d minutes\n", $hours, $minutes);
617 my $hours = $t / 3600;
618 $t = $subtotals % 3600;
619 my $minutes = $t / 60;
620 printf("Time logged on tasks: %d hours %d minutes\n", $hours, $minutes);
623 print("# End of report\n") if $output >= 1;
627 elsif ( $command eq "edit" )
630 my ( undef, $title ) = parse_arguments
(EDIT
);
631 my $id = get_last_id
();
635 $id = get_track_id
($title);
638 printf("No project by that name. Try 'list'\n");
643 system( "/usr/bin/editor " . $trk_dir . "/" . $id . "/tracking" );