Source

timetracker / timetracker.py

Full commit
#!/usr/bin/env python

'''timetracker

Tracks the time spent on a project.
'''

from mercurial import hg, extensions, commands, localrepo
from datetime import datetime, timedelta
import time
import ConfigParser

def timedelta_str(diff):
    """
    Returns string representing "time delta" e.g.
    3 days, 5 hours 3 minutes, etc.
    """
    def int_sub(a, b):
        if b >= a:
            return 0
        else:
            return a - b

    periods = (
        (diff.seconds // 3600, "hour", "hours", 60),
        (diff.seconds // 60, "minute", "minutes", 60),
        (diff.seconds, "second", "seconds", 60),
    )

    ret_periods = []
    count = 0
    previous_period = None
    for period, singular, plural, factor in periods:
        if previous_period:
            period -= previous_period * previous_factor
        if period:
            ret_periods += ["%d %s" % (period, singular if period == 1 else plural)]
            count += 1
        if count >= 2:
            break
        previous_period = period
        previous_factor = factor

    return ", ".join(ret_periods)


def uisetup(ui):
    entry = extensions.wrapcommand(commands.table, 'commit', wrap_commit)
    extensions.wrapfunction(localrepo.localrepository, "commit", wrap_localrepo_commit)

def readconfig(repo):
    extra = dict()
    config = ConfigParser.SafeConfigParser()
    config.read(repo.join("timetracker"))
    if not config.has_section("main"):
        config.add_section("main")
    config_time_spent = None
    config_time_spent_start = None
    if config.has_option("main", "time-spent"):
        extra["time-spent"] = timedelta(seconds=int(config.get("main",
            "time-spent", "0")))
    if config.has_option("main", "time-spent-start"):
        extra["time-spent-start"] = datetime.fromtimestamp(float(config.get(
            "main", "time-spent-start", "0.0")))
    return extra, config

def saveconfig(config, extra, repo):
    if not extra.has_key("time-spent") and\
        config.has_option("main", "time-spent"):
        config.remove_option("main", "time-spent")
    elif extra.has_key("time-spent"):
        time_spent = str(int(extra["time-spent"].total_seconds()))
        config.set("main", "time-spent", time_spent)

    if not extra.has_key("time-spent-start") and\
        config.has_option("main", "time-spent-start"):
        config.remove_option("main", "time-spent-start")
    elif extra.has_key("time-spent-start"):
        time_spent_start = str(time.mktime(extra["time-spent-start"].timetuple()))
        config.set("main", "time-spent-start", str(time_spent_start))

    filep = repo.opener('timetracker', 'w')
    config.write(filep)
    filep.close()

def timetracker_cmd(ui, repo, *args, **opts):
    """Tracks the time spent on a project"""

    extra, config = readconfig(repo)

    if opts['reset'] and extra.has_key("time-spent"):
        extra = dict()
        saveconfig(config, extra, repo)

    elif opts['start']:
        extra["time-spent-start"] = datetime.now()
        saveconfig(config, extra, repo)

    elif opts['stop'] and extra.get("time-spent-start", False):
        start = extra.pop("time-spent-start")
        delta = datetime.now() - start
        if extra.get("time-spent", False):
            extra["time-spent"] += delta
        else:
            extra["time-spent"] = delta
        saveconfig(config, extra, repo)

    elif opts['set']:
        extra = {"time-spent": timedelta(seconds=int(args[0])*60)}
        saveconfig(config, extra, repo)

    elif opts['current']:
        time_spent = extra.get("time-spent", timedelta(0))
        now = datetime.now()
        time_spent_current = now - extra.get("time-spent-start", now)
        time_str = timedelta_str(time_spent + time_spent_current)
        print "time spent in next commit: %s" % time_str

    elif opts['summary']:
        print "TODO"

def wrap_commit(orig, ui, repo, *pats, **opts):
    '''
    Adds the timespent extra metadata to the changeset of this commit
    '''
    # do a stop
    extra, config = readconfig(repo)
    if extra.get("time-spent-start", False):
        start = extra.pop("time-spent-start")
        delta = datetime.now() - start
        if extra.get("time-spent", False):
            extra["time-spent"] += delta
        else:
            extra["time-spent"] = delta
        saveconfig(config, extra, repo)

    return orig(ui, repo, *pats, **opts)

def wrap_localrepo_commit(origfunc, self, *args, **kwargs):
    extra_config, config = readconfig(self)

    # only adds time-spent extra if timetracker was put to use for this commit
    if extra_config.get("time-spent", False):
        if not kwargs["extra"]:
            kwargs["extra"] = extra_config
        else:
            kwargs["extra"].update(extra_config)
    ret = origfunc(self, *args, **kwargs)

    extra_config = {} # starts again from zero, once the commit has been done
    saveconfig(config, extra_config, self)

    return ret

cmdtable = {
    # cmd name        function call
    "timetracker": (timetracker_cmd,
        # see mercurial/fancyopts.py for all of the command
        # flag options.
        [('s', 'start', None, 'start/continue counting time spent'),
        ('p', 'stop', None, 'stop/pause counting time spent'),
        ('r', 'reset', None, 'continue counting time spent'),
        ('e', 'set', None, 'set time spent in minutes'),
        ('c', 'current', None, 'time spent currently in next commit'),
        ('o', 'summary', None, 'show summary of total time spent'),
        ('b', 'branch', None, '(in combination with --summary) filters for a given branch'),
        ('u', 'username', None, '(in combination with --summary) filters for a given username')],
        "[options]")
}