#!/usr/bin/perl use Time::Local; use Digest::MD5 qw(md5_hex); use File::Basename; my $trk_dir = "$ENV{HOME}/.trk"; use constant { START => 1, TIMEFORMAT => 2, STOP => 3, }; sub help { my $code = shift; printf("It seems you require assistance\n"); if ( $code ) { printf("How to start\n") if $code == START; printf("How to time\n") if $code == TIMEFORMAT; } exit(-1); } # Input to parse_time is: # * date -> date-string in the form YYYY-MM-DD # * time -> time-string in the form HH:MM # Return value is a unix timestamp, as returned by time() sub parse_time ($$) { my ( $Y, $M, $D ) = split ("-", shift ); my ( $h, $m ) = split(":", shift ); return timelocal(0, $m, $h, $D, ($M-1), $Y); } sub gen_puuid (;$) { my $id_length = shift; $id_length = 32 if not defined $id_length; my $w = time; for(my $i=0 ; $i<128;) { my $tc = chr(int(rand(127))); if($tc =~ /[a-zA-Z0-9]/) { $w .=$tc; $i++; } } $w = md5_hex( $w ); while ( length($w) < $id_length ) { $w .= gen_puuid( $id_length - length( $w ) ); } $w = substr( $w, 0, $id_length ); return $w; } sub parse_arguments ($) { my $step = shift; my $start_time = time; my $title = undef; if (( $#ARGV >= 1) && ( $ARGV[1] eq "at" )) { # Start and Activity require a title to be present. # All other (stop, main...) do not ^^. if ( ($step == START) || ($step == TASK) ) { # TODO: Allow no title! # If no title is given, read ID of previously used project in stead :) help($step) unless $#ARGV > 3; $title = join(" ", @ARGV[4..$#ARGV]); } help(TIMEFORMAT) unless ( $ARGV[2] =~ m/\d\d\d\d-\d\d-\d\d/ && $ARGV[3] =~ m/\d\d:\d\d/); $start_time = parse_time( $ARGV[2], $ARGV[3] ); } elsif ( ($step == START) || ($step == TASK) ) { shift(@ARGV); $title = join(" ", @ARGV); } if ( not defined $title ) { return $start_time; } else { return ( $start_time, $title ); } } sub get_current_project { return undef if ( ! -f $trk_dir . "/current" ); open ( CUR, "<" . $trk_dir . "/current" ) or die ("Unable to read current project file"); my $id = ; chomp($id); close(CUR); return $id; } sub set_current_project ($) { my $id = shift; return undef if ( -f $trk_dir . "/current" ); open ( CUR, ">" . $trk_dir . "/current" ) or die ("Unable to write current project file"); printf(CUR "%s\n", $id ); close(CUR); open ( LAST, ">" . $trk_dir . "/last" ) or die ("Unable to write last project file"); printf(LAST "%s\n", $id ); close(LAST); } sub current_task { my $project = get_current_project(); return undef if not $project; open ( CUR, "<" . $trk_dir . "/current" ) or die ("Unable to read current project file"); ; my $id = ; chomp($id); close(CUR); return $id; } sub get_projects { my %projects; foreach my $d ( <$trk_dir/*> ) { next if not -d $d; next if not -f $d . "/info"; my $id = basename($d); my $title = get_project_name( $id ); $projects{$id} = $title unless not defined $title; } return \%projects; } sub get_project_id ($) { my $title = shift; # Get hash of project-id's and -names from get_projects my $projects = get_projects(); # Look up name in list foreach my $id ( keys $projects ) { # Return ID for name return $id if ( $projects->{$id} eq $title ) } # If no match, return undef. return undef; } sub get_project_name ($) { my $id = shift; open(PRO, "<" . $trk_dir . "/" . $id . "/info" ) or die ("Unable to read project medatata file!"); my $title = undef; while () { next if not $_ =~ /^title:(.*)/; $title = $1; } close(PRO); return $title; } sub create_project ($) { my $title = shift; my $id; do { $id = gen_puuid(8); } while ( -d $trk_dir . "/" . $id ); mkdir ( $trk_dir . "/" . $id ); open(PRO, ">" . $trk_dir . "/" . $id . "/info" ) or die ("Unable to create project medatata file!"); printf(PRO "title:%s", $title); close(PRO); return $id; } ############################################################ if ( ! -d $trk_dir ) { mkdir $trk_dir or die("Unable to create data directory"); } if ( $#ARGV < 0 ) { help(); } my $command = $ARGV[0]; if ( ( $command eq "start") || ($command eq "on" ) ) { if ( $#ARGV < 1) { help(START); } my ( $start_time, $title ) = parse_arguments(START); my $current = get_current_project(); if ( not $current ) { $current = get_project_id( $title ); if ( not $current ) { printf("No project by that name! Creating a new one.\n"); $current = create_project($title); } else { printf("Continuing tracking for existing project.\n"); } set_current_project($current); } else { printf("A project is being tracked: %s\n", get_project_name( $current ) ); printf("Stop current tracking before starting a new one\n"); exit(0); } # First iteration is VERY naive: simply add the start time to the bottom of the tracking file # Will have to do more logic: if the start point is before one of the times already in the track, # the file will have to be manipulated to get coherent tracking! open (TRACK, ">>" . $trk_dir . "/" . $current . "/tracking" ) or die ("Unable to open file, $!"); printf(TRACK "[%s]", $start_time); close (TRACK); printf("Started tracking of '%s' at %s\n\n", $title, scalar localtime $start_time); } elsif ( ( $command eq "stop") || ($command eq "off" ) ) { if ( $#ARGV < 0) { help(STOP); } my $stop_time = parse_arguments(STOP); my $current = get_current_project(); if ( not $current ) { printf("No project is currently tracked. To stop, please start first\n"); exit(0); } my $title = get_project_name( $current ); die ("Project exists, but tracking file does not!") if ( not -f $trk_dir . "/" . $current . "/tracking" ); # First iteration is VERY naive: simply add the stop time to the bottom line of the tracking file # Will have to do more logic: if the start point is before one of the times already in the track, # the file will have to be manipulated to get coherent tracking! # In addtion to this: actually do some file sanity checking! open (TRACK, ">>" . $trk_dir . "/" . $current . "/tracking" ) or die ("Unable to open file, $!"); printf(TRACK " to [%s]\n", $stop_time); close (TRACK); unlink ( $trk_dir . "/current" ); printf("Stopped tracking of '%s' at %s\n\n", $title, scalar localtime $stop_time); } else { help(); }