#!/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
sub str2time ($)
{
my $i = shift;
- printf("%s\n", $i);
return 0 if not $i =~ m/(\d\d\d\d-\d\d-\d\d) (\d\d:\d\d)/;
return parse_time($1, $2);
}
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]);
}
}
}
-sub get_last_project
+sub get_last_id (;$)
{
- return undef if ( ! -f $trk_dir . "/last" );
- open ( CUR, "<" . $trk_dir . "/last" ) or die ("Unable to read last 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 = <CUR>;
chomp($id);
close(CUR);
return $id;
}
-sub get_current_project
+sub get_current_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 . "/current" );
+ open ( CUR, "<" . $wrk_dir . "/current" ) or die ("Unable to read current track file");
my $id = <CUR>;
chomp($id);
close(CUR);
return $id;
}
-sub set_current_project ($)
+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");
- <CUR>;
- my $id = <CUR>;
- 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 (<PRO>)
{
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!");
+ 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 ( <TRACK> )
+ {
+ 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 )
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);
+ printf("Something weird happened.\n");
+ exit(1);
}
- 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);
+ 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]", time2str($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" ) )
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", time2str($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);
+
+ 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);
+
+ }
- printf("Stopped tracking of '%s' at %s\n\n", $title, scalar localtime $stop_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: Sort list of names alphabetically
# TODO: Get total-hours for projects
# TODO:
- my $projects = get_projects();
+ my $tracks = get_tracks();
printf("Currently tracked project names:\n\n");
- my $current = get_current_project();
+ my $current = get_current_id();
- foreach my $id ( keys $projects )
+ #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 ? ">" : " " ),$projects->{$id} );
+ 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_project();
+ my $id = get_last_id();
if ( $title )
{
- $id = get_project_id($title);
+ $id = get_track_id($title);
if ( not $id )
{
printf("No project by that name. Try 'list'\n");