X-Git-Url: https://git.defcon.no/?a=blobdiff_plain;f=trk;h=bd10e9ec45c9f2724e2d1ba855130282b16f739c;hb=084aecf8f8d0ab3854c43a42769bd8d6fc6eee62;hp=f52f49af68b73b821f19f1fa117ebfe2b128cdcc;hpb=d4fa7880c3b0bfa66915534941fba898e47c2520;p=trk diff --git a/trk b/trk index f52f49a..bd10e9e 100755 --- a/trk +++ b/trk @@ -1,15 +1,44 @@ #!/usr/bin/perl +# +# Copyright © Jon Langseth +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + use Time::Local; use Digest::MD5 qw(md5_hex); use File::Basename; - +use POSIX; +use strict; my $trk_dir = "$ENV{HOME}/.trk"; +if ( $ENV{TRK_DIR} ) +{ + if ( -d $ENV{TRK_DIR} ) { $trk_dir = $ENV{TRK_DIR} if -d $ENV{TRK_DIR}; } + else { printf("Environment variable TRK_DIR is not a directory\n"); exit(1); } +} use constant { START => 1, TIMEFORMAT => 2, STOP => 3, + EDIT => 4, + TASK => 5, }; sub help @@ -25,16 +54,6 @@ sub help 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 (;$) { @@ -63,6 +82,30 @@ sub gen_puuid (;$) return $w; } +# 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 str2time ($) +{ + my $i = shift; + return 0 if not $i =~ m/(\d\d\d\d-\d\d-\d\d) (\d\d:\d\d)/; + return parse_time($1, $2); +} + +sub time2str ($) +{ + my $t = shift; + return strftime("%Y-%m-%d %H:%M", localtime($t)); +} + sub parse_arguments ($) { @@ -78,7 +121,7 @@ sub parse_arguments ($) if ( ($step == START) || ($step == TASK) ) { # TODO: Allow no title! - # If no title is given, read ID of previously used project in stead :) + # If no title is given, read ID of previously used track in stead :) help($step) unless $#ARGV > 3; $title = join(" ", @ARGV[4..$#ARGV]); } @@ -86,7 +129,7 @@ sub parse_arguments ($) $start_time = parse_time( $ARGV[2], $ARGV[3] ); } - elsif ( ($step == START) || ($step == TASK) ) + elsif ( ($step == START) || ($step == TASK) || ($step == EDIT)) { shift(@ARGV); $title = join(" ", @ARGV); @@ -102,84 +145,102 @@ sub parse_arguments ($) } } -sub get_current_project +sub get_last_id (;$) { - return undef if ( ! -f $trk_dir . "/current" ); - open ( CUR, "<" . $trk_dir . "/current" ) or die ("Unable to read current project file"); + my $trk_id = shift; + my $wrk_dir = $trk_dir; + $wrk_dir = $trk_dir . "/" . $trk_id if $trk_id; + + return undef if ( ! -f $wrk_dir . "/last" ); + open ( CUR, "<" . $wrk_dir . "/last" ) or die ("Unable to read last track file"); my $id = ; chomp($id); close(CUR); return $id; } -sub set_current_project ($) +sub get_current_id (;$) +{ + my $trk_id = shift; + my $wrk_dir = $trk_dir; + $wrk_dir = $trk_dir . "/" . $trk_id if $trk_id; + + return undef if ( ! -f $wrk_dir . "/current" ); + open ( CUR, "<" . $wrk_dir . "/current" ) or die ("Unable to read current track file"); + my $id = ; + chomp($id); + close(CUR); + return $id; +} + +sub set_current_id ($;$) { my $id = shift; - return undef if ( -f $trk_dir . "/current" ); - open ( CUR, ">" . $trk_dir . "/current" ) or die ("Unable to write current project file"); + my $trk_id = shift; + my $wrk_dir = $trk_dir; + $wrk_dir = $trk_dir . "/" . $trk_id if $trk_id; + + return undef if ( -f $wrk_dir . "/current" ); + open ( CUR, ">" . $wrk_dir . "/current" ) or die ("Unable to write current track file"); printf(CUR "%s\n", $id ); close(CUR); - open ( LAST, ">" . $trk_dir . "/last" ) or die ("Unable to write last project file"); + open ( LAST, ">" . $wrk_dir . "/last" ) or die ("Unable to write last track file"); printf(LAST "%s\n", $id ); close(LAST); } -sub current_task +sub get_tracks (;$) { - 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; + my $trk_id = shift; + my $wrk_dir = $trk_dir; + $wrk_dir = $trk_dir . "/" . $trk_id if $trk_id; -} -sub get_projects -{ - my %projects; + my %tracks; - foreach my $d ( <$trk_dir/*> ) + foreach my $d ( <$wrk_dir/*> ) { next if not -d $d; next if not -f $d . "/info"; my $id = basename($d); - my $title = get_project_name( $id ); + my $title = get_track_name( $id, $trk_id ); - $projects{$id} = $title unless not defined $title; + $tracks{$id} = $title unless not defined $title; } - return \%projects; + return \%tracks; } -sub get_project_id ($) +sub get_track_id ($;$) { my $title = shift; + my $trk_id = shift; - # Get hash of project-id's and -names from get_projects - my $projects = get_projects(); + # Get hash of track-id's and -names from get_tracks + my $tracks = get_tracks($trk_id); # Look up name in list - foreach my $id ( keys $projects ) + foreach my $id ( keys %$tracks ) { # Return ID for name - return $id if ( $projects->{$id} eq $title ) + return $id if ( $tracks->{$id} eq $title ) } # If no match, return undef. return undef; } -sub get_project_name ($) +sub get_track_name ($;$) { my $id = shift; - open(PRO, "<" . $trk_dir . "/" . $id . "/info" ) or die ("Unable to read project medatata file!"); + my $trk_id = shift; + my $wrk_dir = $trk_dir; + $wrk_dir = $trk_dir . "/" . $trk_id if $trk_id; + + open(PRO, "<" . $wrk_dir . "/" . $id . "/info" ) or die ("Unable to read track medatata file!"); my $title = undef; while () { @@ -190,25 +251,135 @@ sub get_project_name ($) return $title; } -sub create_project ($) +sub create_track ($;$) { my $title = shift; + my $trk_id = shift; + my $wrk_dir = $trk_dir; + $wrk_dir = $trk_dir . "/" . $trk_id if $trk_id; my $id; do { $id = gen_puuid(8); - } while ( -d $trk_dir . "/" . $id ); - mkdir ( $trk_dir . "/" . $id ); + } while ( -d $wrk_dir . "/" . $id ); + mkdir ( $wrk_dir . "/" . $id ); - open(PRO, ">" . $trk_dir . "/" . $id . "/info" ) or die ("Unable to create project medatata file!"); - printf(PRO "title:%s", $title); + open(PRO, ">" . $wrk_dir . "/" . $id . "/info" ) or die ("Unable to create track medatata file!"); + printf(PRO "title:%s\n", $title); close(PRO); return $id; } +sub start_track ($$;$) +{ + my $start_time = shift; + my $title = shift; + + my $trk_id = shift; + my $wrk_dir = $trk_dir; + $wrk_dir = $trk_dir . "/" . $trk_id if $trk_id; + + my $current = get_current_id($trk_id); + if ( not $current ) + { + if ( not $title ) + { + $current = get_last_id( $trk_id ); + } + else + { + $current = get_track_id( $title, $trk_id ); + if ( not $current ) + { + printf("No track by that name! Creating a new one.\n"); + $current = create_track($title, $trk_id); + } + } + + # Break off here if we haven't gotten an ID yet. + return undef if not $current; + + set_current_id($current, $trk_id); + + # 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, ">>" . $wrk_dir . "/" . $current . "/tracking" ) or die ("Unable to open file, $!"); + printf(TRACK "[%s]", time2str($start_time)); + close (TRACK); + + return $current; + } + + return undef; +} + +sub close_track ($;$) +{ + + my $stop_time = shift; + my $trk_id = shift; + my $wrk_dir = $trk_dir; + $wrk_dir = $trk_dir . "/" . $trk_id if $trk_id; + + my $current = get_current_id( $trk_id ); + + die ("Project exists, but tracking file does not!") if ( not -f $wrk_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, ">>" . $wrk_dir . "/" . $current . "/tracking" ) or die ("Unable to open file, $!"); + printf(TRACK " to [%s]\n", time2str($stop_time)); + close (TRACK); + + unlink ( $wrk_dir . "/current" ); +} + + +sub report ($$;$) +{ + my $current = shift; + my $silent = shift; + my $trk_id = shift; + my $wrk_dir = $trk_dir; + $wrk_dir = $trk_dir . "/" . $trk_id if $trk_id; + + my $total = 0; + + my $name = get_track_name( $current, $trk_id ); + printf("# Report for '%s':\n\n", $name) unless $silent; + + open (TRACK, "<" . $wrk_dir . "/" . $current . "/tracking" ) or die ("Unable to open file, $!"); + while ( ) + { + next if not $_ =~ m/\[(.*)\] to \[(.*)\]/; + my $start = $1; + my $end = $2; + my $t_start = str2time( $start ); + my $t_end = str2time( $end ); + my $delta = $t_end - $t_start; + + if ( not $silent ) + { + my $t = $delta; + my $hours = $t / 3600; + $t = $delta % 3600; + my $minutes = $t / 60; + printf(" %s to %s => %d hours %d minutes\n", $start, $end, $hours, $minutes); + } + $total += $delta; + + } + close ( TRACK ); + return $total; + +} + ############################################################ if ( ! -d $trk_dir ) @@ -232,35 +403,24 @@ if ( ( $command eq "start") || ($command eq "on" ) ) my ( $start_time, $title ) = parse_arguments(START); - my $current = get_current_project(); + my $current = get_current_id(); if ( not $current ) { - $current = get_project_id( $title ); + $current = start_track( $start_time, $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"); + printf("Something weird happened.\n"); + exit(1); } - 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); + printf("A project is being tracked: %s\n", get_track_name( $current ) ); + close_track($start_time); + $current = start_track( $start_time, $title ); } - # 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" ) ) @@ -273,27 +433,214 @@ elsif ( ( $command eq "stop") || ($command eq "off" ) ) my $stop_time = parse_arguments(STOP); - my $current = get_current_project(); + my $current = get_current_id(); if ( not $current ) { printf("No project is currently tracked. To stop, please start first\n"); exit(0); } - my $title = get_project_name( $current ); + my $title = get_track_name( $current ); - die ("Project exists, but tracking file does not!") if ( not -f $trk_dir . "/" . $current . "/tracking" ); + my $activity = get_current_id($current); + if ( $activity ) + { + printf("An active subtask is running: '%s'. Closing it.\n", get_track_name( $activity, $current )); + close_track($stop_time, $current); + } + close_track($stop_time); - # 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); + printf("Stopped tracking of '%s' at %s\n\n", $title, scalar localtime $stop_time); +} +elsif ( ( $command eq "activity") || ($command eq "task" ) ) +{ + if ( $#ARGV < 1) + { + help(START); + } - unlink ( $trk_dir . "/current" ); + my ( $start_time, $title ) = parse_arguments(START); - printf("Stopped tracking of '%s' at %s\n\n", $title, scalar localtime $stop_time); + my $trk_id = get_current_id(); + if ( not $trk_id ) + { + printf("Starting a task/activity requires an active main track.\n"); + exit(1); + } + else + { + + my $current = get_current_id( $trk_id ); + if ( not $current ) + { + $current = start_track( $start_time, $title, $trk_id ); + + if ( not $current ) + { + printf("Something weird happened.\n"); + exit(1); + } + } + else + { + printf("A task/activity is being tracked: %s\n", get_track_name( $current, $trk_id ) ); + close_track($start_time, $trk_id); + $current = start_track( $start_time, $title, $trk_id ); + } + + printf("Started tracking of '%s' at %s\n\n", $title, scalar localtime $start_time); + + } + +} + +elsif ( $command eq "main" ) +{ + + if ( $#ARGV < 0) + { + help(STOP); + } + + + my $stop_time = parse_arguments(STOP); + + my $trk_id = get_current_id(); + if ( not $trk_id ) + { + printf("Stopping a task/activity requires an active main track.\n"); + exit(1); + } + else + { + my $current = get_current_id( $trk_id ); + if ( not $current ) + { + printf("No activity/task is currently tracked. To stop, please start first\n"); + exit(0); + } + my $title = get_track_name( $current, $trk_id ); + close_track($stop_time, $trk_id); + + printf("Stopped tracking of '%s' at %s\n\n", $title, scalar localtime $stop_time); + } +} +elsif ( ( $command eq "projects" ) || ( $command eq "list" ) ) +{ + # Todo/future extensions: + # TODO: Sort list of names alphabetically + # TODO: Get total-hours for projects + # TODO: + my $tracks = get_tracks(); + printf("Currently tracked project names:\n\n"); + my $current = get_current_id(); + + #foreach my $id ( keys %$tracks ) + foreach my $id ( sort { $tracks->{$a} cmp $tracks->{$b} || $a cmp $b } keys %$tracks ) + { + printf(" %s %s\n", ($id eq $current ? ">" : " " ),$tracks->{$id} ); + } + print("\n"); +} +elsif ( $command eq "report" ) +{ + + my $format = "standard"; + my $output = 0; + + if (( $#ARGV >= 1) && + ( ( $ARGV[1] eq "standard" ) + || ( $ARGV[1] eq "terse" ) + || ( $ARGV[1] eq "verbose" ) + || ( $ARGV[1] eq "details" ) ) ) + { + $format = $ARGV[1]; + shift @ARGV; + } + + $output = 0 if $format eq "terse"; + $output = 1 if $format eq "standard"; + $output = 2 if $format eq "verbose"; + $output = 3 if $format eq "details"; + + my ( undef, $title ) = parse_arguments(START); + + printf("Report format: %s\nTitle: %s\n", $format, $title); + + my $track = undef; + + if ( $title ) + { + $track = get_track_id( $title ); + } + else + { + $track = get_last_id(); + } + + if ( not $track ) + { + printf ("Unable to get info for that track\n"); + exit(1); + } + + my $total = 0; + my $subtotals = 0; + + my $activities = get_tracks( $track ); + if ( keys %$activities ) + { + printf("# Reporting for sub-task/activities:\n\n") if $output >= 2; + + foreach my $id ( sort { $activities->{$a} cmp $activities->{$b} || $a cmp $b } keys %$activities ) + #foreach my $id ( keys %$activities ) + { + $subtotals += report( $id, ( $output >= 2 ? 0 : 1 ), $track ); + printf("# --------------------------------------------------------------\n\n") if $output >= 2; + } + } + + printf("# Reporting for main track/project/task\n") if $output >= 2; + $total += report($track, ( ( $output >= 1 ? 0 : 1 ) ) ); + printf("\n# ==============================================================\n\n") if $output >= 2; + print("\n") if $output >= 1; + + + my $t = $total; + my $hours = $t / 3600; + $t = $total % 3600; + my $minutes = $t / 60; + + printf("Total: %d hours %d minutes\n", $hours, $minutes); + if ( $output >= 2 ) + { + my $t = $subtotals; + my $hours = $t / 3600; + $t = $subtotals % 3600; + my $minutes = $t / 60; + printf("Time logged on tasks: %d hours %d minutes\n", $hours, $minutes); + } + + print("# End of report\n") if $output >= 1; + +} + +elsif ( $command eq "edit" ) +{ + + my ( undef, $title ) = parse_arguments(EDIT); + my $id = get_last_id(); + + if ( $title ) + { + $id = get_track_id($title); + if ( not $id ) + { + printf("No project by that name. Try 'list'\n"); + exit(0); + } + } + + system( "/usr/bin/editor " . $trk_dir . "/" . $id . "/tracking" ); } else {