]> git.defcon.no Git - trk/commitdiff
A rough starting point
authorJon Langseth <jonl@p06076.(none)>
Tue, 30 Apr 2013 18:01:01 +0000 (20:01 +0200)
committerJon Langseth <jonl@p06076.(none)>
Tue, 30 Apr 2013 18:01:01 +0000 (20:01 +0200)
concept.txt [new file with mode: 0644]
trk [new file with mode: 0755]

diff --git a/concept.txt b/concept.txt
new file mode 100644 (file)
index 0000000..111f65e
--- /dev/null
@@ -0,0 +1,102 @@
+trk start <project> (if tracking project: stop-and-start, aka switch)
+trk start at <YYYY-MM-DD hh:mm> <project>
+trk on <project> (alias for start)
+
+  Starts tracking of a project. If 'at' is given, start-time
+  is overridden to the given time.
+
+  Future expansion:
+  If a project was already tracking, that project will be stopped
+  at the startingpoint of the new tracking, i.e. implicitly switching.
+
+trk note Comment will be timestamped
+
+  Adds a comment/note to the tracking. The note will not
+  affect tracked time, but will be included in detailed reports.
+
+trk activity <task>
+trk activity at <YYYY-MM-DD hh:mm> <task>
+
+  Starts tracking of an activity within the project; think task
+  or sub-projects. Activities will be tallied within the project
+  like the "main project", while time counts to both activity
+  and project. 
+
+  Future:
+  Only one activity can be ongoing at the same time,
+  so starting a new activity while one is ongoing stops the current
+  and starts the new one.
+
+trk main
+trk main at <YYYY-MM-DD hh:mm>
+
+  Stops tracking ongoing activity, and keeps tracking project.
+
+trk pause <optional description>
+trk back
+
+  Adds an activity to the project that is not counted on
+  the project. Pauses will be listed on detailed reports,
+  but not on terse, standard and verbose reports.
+
+trk charge <customer>
+
+  Will set a customer name as meta-information. Setting the
+  charged customer will replace any previously set value.
+
+trk status
+
+  Displays current project, charged customer (if any),
+  start time for this session, time spent on this session,
+  time spent on project total,
+  current activity with start time and time spent this session.
+
+trk stop
+trk stop at <YYYY-MM-DD hh:mm>
+trk off (alias for stop)
+
+  Stops tracking of project, with implicit stopping of any
+  current activity.
+
+trk report terse <project>
+trk report <project>
+trk report verbose <project>
+tkr report details <project>
+
+   Gives a tracking report for the given project.
+
+   Terse reports include:
+     * project name
+     * charged customer
+     * total time accumulated
+
+   Standard reports include:
+     * project name
+     * charged customer
+     * session start and end datetimes and time elapsed on session
+     * total time accumulated
+
+   Verbose reports include:
+     * project name
+     * charged customer
+     * session start and end datetimes and time elapsed on session
+     * activity start and end datetimes and time elapesd on activity
+     * note texts w/datetime
+     * time accumulated per named task
+     * total time accumulated
+
+   Detailed reports include:
+     * project name
+     * charged customer
+     * session start and end datetimes and time elapsed on session
+     * activity start and end datetimes and time elapesd on activity
+     * pauses with start, end, comment and elapsed time
+     * note texts w/datetime
+     * time accumulated per named task
+     * total time accumulated
+
+trk projects
+
+   Lists all known project names
+
+
diff --git a/trk b/trk
new file mode 100755 (executable)
index 0000000..f52f49a
--- /dev/null
+++ b/trk
@@ -0,0 +1,301 @@
+#!/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 = <CUR>;
+       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");
+       <CUR>;
+       my $id = <CUR>;
+       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 (<PRO>)
+       {
+               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();
+}