# HG changeset patch # User Eugen Sawin # Date 1310943817 -7200 # Node ID 1b45f8231179aa16cea24e8e8d2baaf9c144c28e # Parent 4ef9c214205981aa9422bab98b365a000607acf2 Renamed and added default config file. diff -r 4ef9c2142059 -r 1b45f8231179 db.py --- a/db.py Wed Oct 06 16:07:36 2010 +0200 +++ b/db.py Mon Jul 18 01:03:37 2011 +0200 @@ -1,5 +1,5 @@ """ -Database lib for cronrec. +Description: Database lib for cronrec. Author: Eugen Sawin (sawine@me73.com) Dependencies: libsqlite3-dev """ diff -r 4ef9c2142059 -r 1b45f8231179 tim.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tim.py Mon Jul 18 01:03:37 2011 +0200 @@ -0,0 +1,226 @@ +#!/usr/local/bin/python +# -*- coding: utf-8 -*- + +""" +Name: tim - A time recording tool, because time is money. +Author: Eugen Sawin +Dependencies: Python 2.7 and libsqlite3-dev +""" + +import os +import sys +import argparse +from datetime import datetime + +import db + +def main(): + global config + config = read_config() + parser = argparse.ArgumentParser(description="Records time.") + subs = parser.add_subparsers() + + sub_init = subs.add_parser("init") + sub_init.add_argument("working_path") + sub_init.set_defaults(func=init) + + sub_begin = subs.add_parser("begin") + sub_begin.add_argument("label") + sub_begin.set_defaults(func=begin) + + sub_end = subs.add_parser("end") + sub_end.add_argument("label", nargs="?", default="all:all") + sub_end.add_argument("-m") + sub_end.set_defaults(func=end) + + sub_pause = subs.add_parser("pause") + sub_pause.set_defaults(func=pause) + + sub_resume = subs.add_parser("resume") + sub_resume.add_argument("-m") + sub_resume.set_defaults(func=resume) + + sub_add = subs.add_parser("add") + sub_add_subs = sub_add.add_subparsers() + + sub_add_task = sub_add_subs.add_parser("task") + sub_add_task.add_argument("label") + sub_add_task.add_argument("begin") + sub_add_task.add_argument("end") + sub_add_task.add_argument("-m") + sub_add_task.set_defaults(func=add_task) + + sub_add_break = sub_add_subs.add_parser("break") + sub_add_break.add_argument("begin") + sub_add_break.add_argument("end") + sub_add_break.add_argument("-m") + sub_add_break.set_defaults(func=add_break) + + sub_set = subs.add_parser("set") + sub_set_subs = sub_set.add_subparsers() + + sub_set_customer = sub_set_subs.add_parser("customer") + sub_set_customer.add_argument("project") + sub_set_customer.add_argument("customer") + sub_set_customer.set_defaults(func=set_customer) + + sub_set_rate = sub_set_subs.add_parser("rate") + sub_set_rate.add_argument("label") + sub_set_rate.add_argument("rate", type=float) + sub_set_rate.add_argument("currency") + sub_set_rate.add_argument("type") + sub_set_rate.set_defaults(func=set_rate) + + sub_status = subs.add_parser("status") + sub_status.set_defaults(func=status) + + args = parser.parse_args() + args.func(args) + +WD = "working_path" +CONFIG = {WD: str} +CONFIG_SEP = "=" +DB_FILE = "tim.db" + +CURRENCIES = {("EUR", "EURO", "€"): "EUR", +("US DOLLAR", "USD", "$"): "USD"} + +RATE_TYPES = {("h", "hourly"): "hourly"} + +config = {} + +# Try to get the user's home directory path +try: # Windows + from win32com.shell import shellcon, shell + HOMEDIR = shell.SHGetFolderPath(0, shellcon.CSIDL_LOCAL_APPDATA, 0, 0) +except ImportError: # Linux (hopefully) + HOMEDIR = os.path.expanduser("~") + +CONFIG_FILE = "%s/.timrc" % HOMEDIR + +DATETIME_FORMAT = "%Y-%m-%d %H:%M:%S" + +def read_config(): + config = {} + if not os.path.isfile(CONFIG_FILE): + write_config(config) + with open(CONFIG_FILE, "r") as config_stream: + config_lines = [l.split(CONFIG_SEP) for l in config_stream.readlines() + if CONFIG_SEP in l] + for key, value in config_lines: + key = key.strip().lower() + value = value.strip() + if key in CONFIG: + config[key] = CONFIG[key](value) + return config + +def write_config(config): + with open(CONFIG_FILE, "w") as config_input: + config_input.write("\n".join([CONFIG_SEP.join((k, v)) + for k, v in config.iteritems() if k in CONFIG])) + +def db_file(): + global config + if WD not in config: + print "Working directory path is not configured. \ +Please use the init command." + return None + return config[WD] + "/" + DB_FILE + +def init(args): + global config + last_wd = None + if WD in config: + last_wd = config[WD] + config[WD] = args.working_path + write_config(config) + path_exists = os.path.exists(config[WD]) and os.path.isdir(config[WD]) + db_exists = path_exists and os.path.exists(db_file())\ + and os.path.isfile(db_file()) + if last_wd != config[WD]: + print "Changed working directory from %s to %s." % (last_wd, config[WD]) + else: + print "Working directory remains at %s." % config[WD] + if not path_exists: + print "Warning: working directory %s does not exist." % config[WD] + os.makedirs(config[WD]) + print "Working directory %s created." % config[WD] + elif db_exists: + print "Database file %s already exists, please delete it before \ +initiating a new database at this location." % db_file() + if not db_exists: + db.init(db_file()) + +def parse_label(label): + label = label.strip() + if ":" in label: + project, activity = label.split(":") + else: + project, activity = (label, None) + return project, activity + +def begin(args): + project, activity = parse_label(args.label) + time = datetime.now() + db.resume(db_file(), None, None, time) + db.begin(db_file(), project, activity, time) + +def end(args): + project, activity = parse_label(args.label) + log = args.m + time = datetime.now() + db.resume(db_file(), db.find_active_task(db_file()), time, log) + db.end(db_file(), project, activity, time, log) + +def pause(args): + db.pause(db_file(), datetime.now()) + +def resume(args): + db.resume(db_file(), None, datetime.now(), args.m) + +def add_task(args): + project, activity = parse_label(args.label) + log = args.m + begin = datetime.strptime(args.begin, DATETIME_FORMAT) + end = datetime.strptime(args.end, DATETIME_FORMAT) + db.begin(db_file(), project, activity, begin) + db.end(db_file(), project, activity, end, log) + +def add_break(args): + log = args.m + begin = datetime.strptime(args.begin, DATETIME_FORMAT) + end = datetime.strptime(args.end, DATETIME_FORMAT) + db.pause(db_file(), begin) + db.resume(db_file(), db.find_active_task(db_file(), begin), end, log) + +def set_customer(args): + db.update_where(db_file(), "projects", "name", args.project, "customer", + args.customer) + +def map_name(map_base, name): + mapped = None + if name not in map_base.itervalues(): + for v in map_base.iterkeys(): + if name in v: + mapped = map_base[v] + else: + mapped = name + return mapped + +def set_rate(args): + project, activity = parse_label(args.label) + currency = map_name(CURRENCIES, args.currency) + type = map_name(RATE_TYPES, args.type) + print "setting rate for %s at %f %s %s" % (project, args.rate, currency, type) + db.set_rate(db_file(), project, activity, args.rate, currency, type) + +def status(args): + task = db.status(db_file()) + if task: + print "Currently active task" + print "Project: %s Activity: %s" % task + else: + print "There is no active task." + +if __name__ == "__main__": + main()