Beefy Boxes and Bandwidth Generously Provided by pair Networks
We don't bite newbies here... much
 
PerlMonks  

ts - timesheet (stopwatch)

by Dylan (Monk)
on Jul 16, 2003 at 04:48 UTC ( #274687=sourcecode: print w/replies, xml ) Need Help??
Category: Database stuff
Author/Contact Info Email: dylanwh AT tampabay DOT rr DOT com
Email 2: dylan AT hardison DOT net
Jabber: dylan@myjabber.net
Description: (This was copied from an email. Sorry, I am lazy. ;)
I wrote a little timesheet/stopwatch script that uses MySQL,
and I wanted to share it.

It is fairly short and auto-configures itself, 
and automatically creates the mysql table that it needs. <g>

It is also not very readable.

it requires mysql and perl (of course),
and (optionally) uses the perl module Time::Duration (if found).

It's a command line program, and it has no documentation (But hey,
I did it only *three* hours!).

After you put it someplace (call it 'ts')
and chmod 755 it, run it and it'll auto-configure itself,
unless there is a bug, which is very likely.


Simple Usage:
  When you start working on something,
  type at the prompt:
  $ ts start <name>

  When you're done, type:
  $ ts stop <name>

  to list all projects, type:
  $ ts list
  you can say:
  $ ts ls
  if you want to type less. :)

  to get the total of everything, type:
  $ ts total

  To get the total of a specific thing, type:
  $ ts total <name>

  You can get averages too:
  $ ts average
  or
  $ ts average <name>
  You can use avg instead of average, if you want.

  You can list 'active' things, by typing:
  $ ts active

There's a few more features, but they're not really that useful.


It's GPL'd, of course, which means no warranty of any kind.

If you install the Time::Duration perl module,
it'll print time durations in a more human-readable way...
(By default it displays time durations in seconds.)

I've just gzip'd it, not tar'd, because it is only one file. 

Hopefully, someone else will find it useful.


If it has any bugs, I *might* fix them, 
if I'm not too busy.
I'd love some questions and comments about it.

Enjoy!

- -- 
I'm sure that that could be indented more 
readably, but I'm scared of the awk parser.
              -- Larry Wall
- -
GPG Fingerprint=D67D 2B75 53C6 9769 30E4
D390 239F C833 F32C F6F6

GPG KeyID=F32CF6F6
#!/usr/bin/perl
use strict;
use warnings;
use DBI;
use File::Temp qw( tempfile );


# This program is under the GPL.
# Copyright (C) 2003 by Dylan William Hardison.

eval {
    require Time::Duration;
    1;
};


my ($cmd, $project, $sql, $db, $did_config) = (shift, shift);

while(1) {
    eval {
        $sql = require("$ENV{HOME}/.timesheetrc.pl");
    };
    if ($@) {
        write_config_file("$ENV{HOME}/.timesheetrc.pl");
        $did_config = 1;
    } else {
        last;
    }
}

eval {
    $db = DBI->connect("dbi:mysql:$sql->{db}",
        $sql->{user},
        $sql->{pass}, 
        { RaiseError => 1, PrintError => 0}
    );
};

if ($@) {
    warn "Error: " . DBI->errstr . "\n\n";
    if (DBI->err == 1044) {
        print <<"PRT";
    Why this happened:
You don't have permission to access the database...
This is because either the database doesn't exist,
or because you're not allowed to use it.

Create a database named $sql->{db},
with user $sql->{user} and password $sql->{pass}.
(You can configure those in by deleting $ENV{HOME}/.timesheetrc.pl
and re-running this program.)

PRT
    }
    exit(255);
}
        


check_db_table();
unless (defined $cmd) {
    die "Usage: ts <cmd> [project|project id] [args]\n" if not $did_co
+nfig;
    exit(0);
}



if ($cmd eq 'start' or $cmd eq 'begin') {
    die "Undef project!" unless defined $project;
    
    my $proj = $db->quote($project);
    my $time = time();
    my $sql = qq{ INSERT INTO $sql->{table}
                  (project,start)
                  VALUES ($proj, FROM_UNIXTIME($time));
    };
    $db->do($sql);
    print "Started working on $project\n";
} elsif ($cmd eq 'stop' or $cmd eq 'end') {
    die "Undef project!" unless defined $project;
    
    my ($select, $update, $result, $id, $start, $pname);
    if ($project =~ /\D/) {
        $select = qq{ SELECT id,UNIX_TIMESTAMP(start),project FROM $sq
+l->{table}
                      WHERE (project = ? AND stop IS NULL);
        };
    } else {
        $select = qq{ SELECT id,UNIX_TIMESTAMP(start),project FROM $sq
+l->{table}
                      WHERE (id = ? AND stop IS NULL);
        };
    }

    $update = qq{ UPDATE $sql->{table}
                  SET stop = FROM_UNIXTIME(?)
                  WHERE (id = ?)
    };
    $result = $db->prepare($select);
    $result->execute($project);
    
    ($id, $start, $pname) = $result->fetchrow_array;
    if (defined $id) {
        my $stop = time();
        my $diff = $stop - $start;
    
        print "Worked on $pname for ", duration($diff, 5) ,"\n";
        $result = $db->prepare($update);
        $result->execute($stop, $id);
    } else {
        if ($project =~ /\D/) {
            print "But, we haven't ever started $pname!\n";
        } else {
            print "We already stopped working on $pname!\n";
        }
    }
} elsif ($cmd eq 'total') {
    if (defined $project) {
        show_total($project);
    } else {
        show_total_all();
    }
} elsif ($cmd eq 'active' or $cmd eq 'act') {
    my $select = qq{
        SELECT project,id FROM $sql->{table}
        WHERE stop IS NULL;
    };
    my $result = $db->prepare($select);
    $result->execute();
    my ($p, $id);
    while (($p,$id) = $result->fetchrow_array) {
        print "Active: $p ($id)\n";
    }
} elsif ($cmd eq 'list' or $cmd eq 'ls') {
    my $select = qq{
        SELECT DISTINCT project FROM $sql->{table};
    };
    my $result = $db->prepare($select);
    $result->execute();
    my $list = $result->fetchall_arrayref;
    my $count = @$list;

    unless ($count == 0) {
        print "Found $count projects:\n";
        foreach my $item (@$list) {
            print "\t- $item->[0]\n";
        }
    } else {
        print "No projects found.\n";
    }
} elsif ($cmd eq 'index') {
    my $select = qq{
        SELECT id,project FROM $sql->{table};
    };
    my $result = $db->prepare($select);
    $result->execute();
    my $list = $result->fetchall_arrayref;
    my $count = @$list;

    unless ($count == 0) {
        print "Index of Projects:\n";
        foreach my $item (@$list) {
            print "\t- $item->[1] ($item->[0])\n";
        }
    } else {
        print "No projects found.\n";
    }
} elsif ($cmd eq 'average' or $cmd eq 'avg') {
    my $select = qq(
        SELECT project,AVG(UNIX_TIMESTAMP(stop) - UNIX_TIMESTAMP(start
+)) AS average
        FROM $sql->{table}
        #WHERE#
        GROUP BY project
        ORDER BY id ASC
    );
    if (defined $project) {
        my $p = $db->quote($project);
        $select =~ s/#WHERE#\n/WHERE (project = $p)\n/;
    } else {
        $select =~ s/#WHERE#\n//;
    }

    my $result = $db->prepare($select);
    $result->execute();
    my $item;

    my $did_loop = 0;
    while ($item = $result->fetchrow_hashref) {
        $did_loop++;
        print "$item->{project}: Average time was ", duration($item->{
+average}, 5), "\n";
    }

    unless ($did_loop) {
        print "No average time...\n";
    }
} elsif ($cmd eq 'purge') {
    my $e = prompt("Are you sure you want to purge timesheet?\n(yes/no
+, default no)");
    if ($e =~ /^y(es?)$/) {
        print "Deleted everything from database.\n";
        $db->do("DELETE FROM $sql->{table};");
    }
} elsif ($cmd eq 'delete' or $cmd eq 'del') {
    die "Undef project!" unless defined $project;
    my $delete;
    my $name;

    if ($project =~ /\D/) {
        # $project is project name.
        $delete = qq(
            DELETE FROM $sql->{table} WHERE project = ?
        );
        $name = $project;
    } else {
        # $project is project id.
        $name = id_to_project($project); # get name of project id.
        $delete = qq{
            DELETE FROM $sql->{table} WHERE id = ?
        };
    }
    my $result = $db->prepare($delete);
    $result->execute($project);
    print "Deleted $name from database.\n";
} else {    print "Unknown command: $cmd\n";
}
    

END {
    if ($db) {
        $db->disconnect;
    }
}

sub show_total {
    my ($project) = @_;
    my $select = qq{
        SELECT sum(UNIX_TIMESTAMP(stop) - UNIX_TIMESTAMP(start)) AS di
+ff_sum
        FROM $sql->{table}
        WHERE (project = ? AND start IS NOT NULL AND stop IS NOT NULL)
        GROUP BY project;
    };
    my ($total, $entry, $result);
    $total = 0;
    $result = $db->prepare($select);
    $result->execute($project);
    ($total) = $result->fetchrow_array() || 0;

    if ($total > 0) {
        print $project, ": Worked on for ", duration($total, 5),"\n";
    } else {
        print "Haven't ever worked on $project...\n";
    }
}

sub show_total_all {
    my ($project) = @_;
    my $select = qq{
        SELECT project,sum(UNIX_TIMESTAMP(stop) - UNIX_TIMESTAMP(start
+)) AS diff_sum
        FROM $sql->{table}
        WHERE (start IS NOT NULL AND stop IS NOT NULL)
        GROUP BY project
        ORDER BY diff_sum DESC
    };
    my ($total, $result, $name);
    $total = 0;
    $result = $db->prepare($select);
    $result->execute();
    
    while (($name, $total) = $result->fetchrow_array) {
        if ($total > 0) {
            print $name, ": Worked on for ", duration($total, 5),"\n";
        } else {
            print "Haven't ever worked on $name...\n";
        }
    }
}


sub id_to_project {
    my ($id) = @_;
    my $select = qq{
        SELECT project
        FROM $sql->{table}
        WHERE id = $id;
    };
    my $result = $db->prepare($select);
    $result->execute();
    my ($name) = $result->fetchrow_array;

    return $name;
}

sub write_config_file {
    my ($file) = @_;
    my $cfg = <<CONFIG;
#!/usr/bin/perl

{
    table => "timesheet", # Table name. DO NOT CHANGE!
    db => "DBNAME",       # Database name. Change.
    user => "USER",       # Username. Change.
    pass => "PASSWORD",   # Password. Change.
};
CONFIG

    if (not -f $file) {
        while (1) {
            my $in = prompt(
                "Config file doesn't exist. Do you want to create it n
+ow?\n"
                ."(yes/no)"
            );
            if ($in =~ /^y(es)?$/) {
                my ($dbname, $user, $password);
                print "\nOkay, I'm going to ask you some questions abo
+ut ",
                "your mysql database account,\n",
                "I need this information to function.\n";
                $user     = prompt("User name");
                $password = prompt("Password");
                $dbname   = prompt("Name of database", $user);
                $cfg =~ s/DBNAME/$dbname/g;
                $cfg =~ s/USER/$user/g;
                $cfg =~ s/PASSWORD/$password/g;
                write_str_to_file($cfg, $file);
                my $edit = prompt("Edit config file? (yes/no, default:
+ no)");
                if ($edit =~ /^y(es)?$/) {
                    edit_file($file);
                }
                print "Thanks!\n";
                last;
            } elsif ($in =~ /^no?$/) {
                last;
            } else {
                print "I don't understand that.\n";
                next;
            }
        }
    }
}

sub write_str_to_file {
    my ($s, $file) = @_;
    open my $fh, ">$file" or die "Can't open $file for writting: $!";
    print $fh $s;
    close $fh;
    chmod 0600, $file;
}

sub prompt {
    my ($prompt, $default) = @_;
    my $value;

    if (have("Term::ReadLine")) {
        
    } else {
        if (defined $default) {
            print "$prompt (default: $default): ";
        } else {
            print $prompt, ": ";
        }
        $value = readline STDIN;
        chomp $value;
        return $value || $default || '';
    }
}

sub duration {
    if (have("Time::Duration")) {
        goto &Time::Duration::duration;
    } else {
        $_[0] += 0;
        return "$_[0] seconds";
    }
}

sub have {
    my ($file) = @_;
    $file =~ s!::!/!;
    $file .= ".pm";

    return exists $INC{$file};
}

sub check_db_table {
    eval {
        $db->do("SELECT id FROM $sql->{table} LIMIT 1");
    };
    if ($@) {
        if ($db->errstr =~ /Table '$sql->{db}\.$sql->{table}' doesn't 
+exist/i) {
            configure_db_table();
        }
    }
}

sub configure_db_table {
    print "Configuring database table automagically.\n";
    local $/ = undef;
    my $s = readline DATA;
    $s =~ s/#TABLE#/$sql->{table}/g;
    $db->do($s);

}

sub edit_file {
    my ($file) = @_;
    system($ENV{VISUAL} || $ENV{EDITOR} || "vi", $file);
}

__DATA__

CREATE TABLE #TABLE# (
    id INTEGER NOT NULL AUTO_INCREMENT,
    project VARCHAR(80),
    start DATETIME,
    stop DATETIME,
    PRIMARY KEY (id)
);

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: sourcecode [id://274687]
help
Chatterbox?
and the web crawler heard nothing...

How do I use this? | Other CB clients
Other Users?
Others making s'mores by the fire in the courtyard of the Monastery: (3)
As of 2023-03-21 08:40 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?
    Which type of climate do you prefer to live in?






    Results (59 votes). Check out past polls.

    Notices?