--- /dev/null
+General:
+===========================
+
+trk is a simple time tracker with support for sub-tasks/activities.
+It is implemented using perl, and should work with no additional
+modules using perl >= 5.8 on a Unix/Linux based system.
+
+trk was inspired by several similar simple console based trackers, like:
+ * ti (http://ti.sharats.me/)
+ * timed (http://adeel.github.io/timed/),
+ * timetrap (https://github.com/samg/timetrap)
+ * wtime (http://wtime.sourceforge.net/)
+
+Example session:
+===========================
+
+ # Start tracking time usage:
+ trk start Big coding-project
+
+ # Track a sub-task, e.g. separate tracking for fixes:
+ trk task Bugfix BUG#3247
+
+ # Return from sub-task, keep tracking the main project/activity:
+ trk main
+
+ # Check what is currently going on
+ trk status
+
+ # Stop tracking
+ trk stop
+
+ # Get a tracking report
+ trk report verbose
+
+Usage:
+===========================
+
+With default execution of trk, data will be stored in, and read from
+the directory $HOME/.trk This path may be overridden by setting the
+environment variable TRK_DIR, e.g. "env TRK_DIR=/foo/bar trk ..."
+
+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.
+
+ If a project was already tracking, that project will be stopped
+ at the startingpoint of the new tracking, i.e. implicitly switching.
+
+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 list [verbose]
+trk projects [verbose]
+
+ Lists all known project names. If a project/track is currently
+ active, thata project will be indicated with a chevron (>).
+ Adding verbose to the command will display ID hashes along
+ with the project/track names.
+
+trk task <task>
+trk task at <YYYY-MM-DD hh:mm> <task>
+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.
+
+ 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 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 report
+trk report terse <project>
+trk report standard <project>
+trk report verbose <project>
+tkr report details <project>
+
+ Gives a tracking report for the given project.
+ Using "report" with no options provide a standard report for
+ the last (or current) project tracked as a shortcut.
+ Note: reports include currently active session. There is currently
+ no plan to add complexity by adding an option to filter that out..
+
+ Terse reports include:
+ * project name
+ * charged customer (not implemented)
+ * total time accumulated
+
+ Standard reports include:
+ * project name
+ * charged customer (not implemented)
+ * 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
+ * time accumulated per named task
+ * note texts w/datetime (not implemented)
+ * total time accumulated
+
+ Not implemented yet:
+ Detailed reports include:
+ * project name
+ * charged customer (not implemented)
+ * 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 (not implemented)
+ * note texts w/datetime (not implemented)
+ * time accumulated per named task
+ * total time accumulated
+
+The following are not implemented, but planned (ordered by priority:
+
+trk tasks
+trk activities
+
+ Lists all known task/activity names for currently active project/track
+
+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 charge <customer>
+
+ Will set a customer name as meta-information. Setting the
+ charged customer will replace any previously set value.
+
+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.
+
+Storage format:
+===========================
+
+The directory used for storing tracking data will be $HOME/.trk unless
+overridden with the environment variable TRK_DIR. In this description,
+$TRK_DIR will represent the active tracking directory.
+
+Inside $TRK_DIR each track/project will be stored in separate
+sub-directories, with a random generated ID as directory name.
+The ID of the currently active project/track is stored (single-line)
+in the file $TRK_DIR/current, and the last activated track (current,
+if one is tracking) is stored in $TRK_DIR/last.
+
+Meta-information, e.g. Title of the current track is stored in the
+file $TRK_DIR/<id-hash>/info as a colon-separated key-value list
+(currently only title is used).
+
+Time-tracking data is stored as $TRK_DIR/<id-hash>/tracking, as a
+very simple format, with start-and-stop times line-by-line:
+
+[YYYY-MM-DD hh:mm] to [YYYY-MM-DD hh:mm]
+
+If a task is currently tracking, the line will be non-ended (i.e. no newline)
+with only the start-time in brackets.
+
+Sub-tasks are stored using the same princible as above, with the only
+difference that they are contained within the given track's directory.
+So, the ID of the last and current tasks will be stored in
+$TRK_DIR/<id-hash>/current and $TRK_DIR/<id-hash>/last, the activity
+it self in $TRK_DIR/<id-hash>/<id-hash>, with meta-info and tracking in
+$TRK_DIR/<id-hash>/<id-hash>/info and $TRK_DIR/<id-hash>/<id-hash>/tracking.
+
+An example of the structure:
+
+ $HOME/.trk
+ ├── 1e2ad171
+ │ ├── info
+ │ └── tracking
+ ├── 373fcd96
+ │ ├── info
+ │ └── tracking
+ ├── 3b5974b0
+ │ ├── current
+ │ ├── ef917c75
+ │ │ ├── info
+ │ │ └── tracking
+ │ ├── f657d08e
+ │ │ ├── info
+ │ │ └── tracking
+ │ ├── info
+ │ ├── last
+ │ └── tracking
+ ├── 87670109
+ │ ├── info
+ │ └── tracking
+ ├── current
+ └── last
+
+Licencing:
+===========================
+
+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.
+++ /dev/null
-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
-
-
+++ /dev/null
-Handle Activities within a project like projects are handled from "root":
- Separate hash-ID directory for each activity
- Separate info-file with optional meta-data per activiy
- Separate tracking-file per activity.
- Activities stored as sub-directories of related project
- Keep track of current and last activity
-
-Change functions/subs that handle projects currently to be
-generic, and take PROJECT id as optional argument, following
-the logic: if an argument is passed, the function should
-operate on an activity, if no argument is passed, on project.
-
-Fix code so start = stop->start can work
-Make sure stop stops current activity, if any
-Allow activities to toggle (activity = main->activity)
-
-Be naive: assume that tracking files are consistent :)
-Maybe: get inspiration from ti: read until last line, and regex new info onto it :)
-
#!/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;
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;
- }
+ printf("Error in invocation. Syntax summary:\n\n");
+ printf(" %s {start|on} [at yyyy-mm-dd hh:mm] <trackname>\n", $0);
+ printf(" %s {stop|off} [at yyyy-mm-dd hh:mm] \n", $0);
+ printf(" %s {activity|task} [at yyyy-mm-dd hh:mm] <taskname>\n", $0);
+ printf(" %s main [at yyyy-mm-dd hh:mm]\n", $0);
+ printf(" %s {projects|list} [verbose]\n", $0);
+ printf(" %s report [{terse|standard|verbose|details}] [<trackname>]\n", $0);
+ printf(" %s edit <trackname>\n", $0);
+ printf(" %s status\n", $0);
+ printf("\nSee README.txt for more information\n");
+
exit(-1);
}
return strftime("%Y-%m-%d %H:%M", localtime($t));
}
+sub delta2str ($)
+{
+ my $delta = shift;
+ my $t = $delta;
+ my $hours = $t / 3600;
+ $t = $delta % 3600;
+ my $minutes = $t / 60;
+ return sprintf("%d hours %d minutes", $hours, $minutes);
+}
+
sub parse_arguments ($)
{
}
-sub report ($;$)
+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);
+ printf("# Report for '%s':\n\n", $name) unless $silent;
+
+ my $check = get_current_id( $trk_id );
open (TRACK, "<" . $wrk_dir . "/" . $current . "/tracking" ) or die ("Unable to open file, $!");
while ( <TRACK> )
my $t_end = str2time( $end );
my $delta = $t_end - $t_start;
- 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);
-
+ if ( not $silent )
+ {
+ printf(" %s to %s => %s\n", $start, $end, delta2str($delta) );
+ }
$total += $delta;
+
}
close ( TRACK );
- my $t = $total;
- my $hours = $t / 3600;
- $t = $total % 3600;
- my $minutes = $t / 60;
+ if ( $check eq $current )
+ {
+ my $t;
+ if ( $t = current_starttime( $trk_id ) )
+ {
+ my $delta = time - $t;
+ printf(" %s to NOW (active) => %s\n", time2str( $t ), delta2str( $delta ) ) if not $silent;
+ $total += $delta;
+ }
+ }
+
+ return $total;
- printf("\nTotal: %d hours %d minutes\n", $hours, $minutes);
}
+sub current_starttime (;$)
+{
+ my $trk_id = shift;
+ my $wrk_dir = $trk_dir;
+ $wrk_dir = $trk_dir . "/" . $trk_id if $trk_id;
+
+ my $line = undef;
+
+ my $current = get_current_id( $trk_id );
+ return 0 if not $current;
+
+ open (TRACK, "<" . $wrk_dir . "/" . $current . "/tracking" ) or die ("Unable to open file, $!");
+ while ( <TRACK> )
+ {
+ $line = $_;
+ }
+ close ( TRACK );
+ return 0 if not $line =~ m/^\[(\d\d\d\d-\d\d-\d\d \d\d:\d\d)\]$/;
+ return str2time($1);
+}
+
############################################################
if ( ! -d $trk_dir )
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 )
exit(1);
}
+ my $total = 0;
+ my $subtotals = 0;
my $activities = get_tracks( $track );
if ( keys %$activities )
{
- printf("# Reporting for sub-task/activities:\n\n");
+ 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 )
{
- report( $id, $track );
- printf("# --------------------------------------------------------------\n");
- print("\n");
+ $subtotals += report( $id, ( $output >= 2 ? 0 : 1 ), $track );
+ printf("# --------------------------------------------------------------\n\n") if $output >= 2;
}
}
- printf("# Reporting for main track/project/task\n");
- report($track);
- printf("\n# ==============================================================\n\n");
+ 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;
- print("# End of report\n");
+ printf("Total: %s\n", delta2str($total) );
+
+ if ( $output >= 2 )
+ {
+ printf("Time logged on tasks: %s\n", delta2str($subtotals) );
+ }
+
+ print("# End of report\n") if $output >= 1;
}
+elsif ( $command eq "status" )
+{
+
+ my $trk_id = get_current_id();
+ if ( not $trk_id )
+ {
+ printf("Not currently tracking anything.\n");
+ $trk_id = get_last_id();
+ if ( $trk_id )
+ {
+ printf("Last track was: %s\n", get_track_name( $trk_id ) );
+ }
+ exit(1);
+ }
+ printf("Currently tracking: %s\n", get_track_name( $trk_id ) );
+ my $t = current_starttime();
+ printf("Tracking started at %s\n", scalar localtime $t);
+ printf("Time elapsed since start of session: %s\n", delta2str(time - $t) );
+ my $activity = get_current_id( $trk_id );
+ if ( $activity )
+ {
+ printf("\nCurrent sub-task/activity is: %s\n", get_track_name( $activity, $trk_id ) );
+ my $t = current_starttime($trk_id);
+ printf("Activity started at %s\n", scalar localtime $t);
+ printf("Time elapsed since start of activity: %s\n", delta2str(time - $t) );
+ }
+ else
+ {
+ $activity = get_last_id( $trk_id );
+ if ( $activity )
+ {
+ printf("\nLast track was: %s\n", get_track_name( $activity, $trk_id ) );
+ }
+ }
+}
elsif ( $command eq "edit" )
{