1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
1.2 +++ b/tim.py Mon Jul 18 01:03:37 2011 +0200
1.3 @@ -0,0 +1,226 @@
1.4 +#!/usr/local/bin/python
1.5 +# -*- coding: utf-8 -*-
1.6 +
1.7 +"""
1.8 +Name: tim - A time recording tool, because time is money.
1.9 +Author: Eugen Sawin <sawine@me73.com>
1.10 +Dependencies: Python 2.7 and libsqlite3-dev
1.11 +"""
1.12 +
1.13 +import os
1.14 +import sys
1.15 +import argparse
1.16 +from datetime import datetime
1.17 +
1.18 +import db
1.19 +
1.20 +def main():
1.21 + global config
1.22 + config = read_config()
1.23 + parser = argparse.ArgumentParser(description="Records time.")
1.24 + subs = parser.add_subparsers()
1.25 +
1.26 + sub_init = subs.add_parser("init")
1.27 + sub_init.add_argument("working_path")
1.28 + sub_init.set_defaults(func=init)
1.29 +
1.30 + sub_begin = subs.add_parser("begin")
1.31 + sub_begin.add_argument("label")
1.32 + sub_begin.set_defaults(func=begin)
1.33 +
1.34 + sub_end = subs.add_parser("end")
1.35 + sub_end.add_argument("label", nargs="?", default="all:all")
1.36 + sub_end.add_argument("-m")
1.37 + sub_end.set_defaults(func=end)
1.38 +
1.39 + sub_pause = subs.add_parser("pause")
1.40 + sub_pause.set_defaults(func=pause)
1.41 +
1.42 + sub_resume = subs.add_parser("resume")
1.43 + sub_resume.add_argument("-m")
1.44 + sub_resume.set_defaults(func=resume)
1.45 +
1.46 + sub_add = subs.add_parser("add")
1.47 + sub_add_subs = sub_add.add_subparsers()
1.48 +
1.49 + sub_add_task = sub_add_subs.add_parser("task")
1.50 + sub_add_task.add_argument("label")
1.51 + sub_add_task.add_argument("begin")
1.52 + sub_add_task.add_argument("end")
1.53 + sub_add_task.add_argument("-m")
1.54 + sub_add_task.set_defaults(func=add_task)
1.55 +
1.56 + sub_add_break = sub_add_subs.add_parser("break")
1.57 + sub_add_break.add_argument("begin")
1.58 + sub_add_break.add_argument("end")
1.59 + sub_add_break.add_argument("-m")
1.60 + sub_add_break.set_defaults(func=add_break)
1.61 +
1.62 + sub_set = subs.add_parser("set")
1.63 + sub_set_subs = sub_set.add_subparsers()
1.64 +
1.65 + sub_set_customer = sub_set_subs.add_parser("customer")
1.66 + sub_set_customer.add_argument("project")
1.67 + sub_set_customer.add_argument("customer")
1.68 + sub_set_customer.set_defaults(func=set_customer)
1.69 +
1.70 + sub_set_rate = sub_set_subs.add_parser("rate")
1.71 + sub_set_rate.add_argument("label")
1.72 + sub_set_rate.add_argument("rate", type=float)
1.73 + sub_set_rate.add_argument("currency")
1.74 + sub_set_rate.add_argument("type")
1.75 + sub_set_rate.set_defaults(func=set_rate)
1.76 +
1.77 + sub_status = subs.add_parser("status")
1.78 + sub_status.set_defaults(func=status)
1.79 +
1.80 + args = parser.parse_args()
1.81 + args.func(args)
1.82 +
1.83 +WD = "working_path"
1.84 +CONFIG = {WD: str}
1.85 +CONFIG_SEP = "="
1.86 +DB_FILE = "tim.db"
1.87 +
1.88 +CURRENCIES = {("EUR", "EURO", "€"): "EUR",
1.89 +("US DOLLAR", "USD", "$"): "USD"}
1.90 +
1.91 +RATE_TYPES = {("h", "hourly"): "hourly"}
1.92 +
1.93 +config = {}
1.94 +
1.95 +# Try to get the user's home directory path
1.96 +try: # Windows
1.97 + from win32com.shell import shellcon, shell
1.98 + HOMEDIR = shell.SHGetFolderPath(0, shellcon.CSIDL_LOCAL_APPDATA, 0, 0)
1.99 +except ImportError: # Linux (hopefully)
1.100 + HOMEDIR = os.path.expanduser("~")
1.101 +
1.102 +CONFIG_FILE = "%s/.timrc" % HOMEDIR
1.103 +
1.104 +DATETIME_FORMAT = "%Y-%m-%d %H:%M:%S"
1.105 +
1.106 +def read_config():
1.107 + config = {}
1.108 + if not os.path.isfile(CONFIG_FILE):
1.109 + write_config(config)
1.110 + with open(CONFIG_FILE, "r") as config_stream:
1.111 + config_lines = [l.split(CONFIG_SEP) for l in config_stream.readlines()
1.112 + if CONFIG_SEP in l]
1.113 + for key, value in config_lines:
1.114 + key = key.strip().lower()
1.115 + value = value.strip()
1.116 + if key in CONFIG:
1.117 + config[key] = CONFIG[key](value)
1.118 + return config
1.119 +
1.120 +def write_config(config):
1.121 + with open(CONFIG_FILE, "w") as config_input:
1.122 + config_input.write("\n".join([CONFIG_SEP.join((k, v))
1.123 + for k, v in config.iteritems() if k in CONFIG]))
1.124 +
1.125 +def db_file():
1.126 + global config
1.127 + if WD not in config:
1.128 + print "Working directory path is not configured. \
1.129 +Please use the init command."
1.130 + return None
1.131 + return config[WD] + "/" + DB_FILE
1.132 +
1.133 +def init(args):
1.134 + global config
1.135 + last_wd = None
1.136 + if WD in config:
1.137 + last_wd = config[WD]
1.138 + config[WD] = args.working_path
1.139 + write_config(config)
1.140 + path_exists = os.path.exists(config[WD]) and os.path.isdir(config[WD])
1.141 + db_exists = path_exists and os.path.exists(db_file())\
1.142 + and os.path.isfile(db_file())
1.143 + if last_wd != config[WD]:
1.144 + print "Changed working directory from %s to %s." % (last_wd, config[WD])
1.145 + else:
1.146 + print "Working directory remains at %s." % config[WD]
1.147 + if not path_exists:
1.148 + print "Warning: working directory %s does not exist." % config[WD]
1.149 + os.makedirs(config[WD])
1.150 + print "Working directory %s created." % config[WD]
1.151 + elif db_exists:
1.152 + print "Database file %s already exists, please delete it before \
1.153 +initiating a new database at this location." % db_file()
1.154 + if not db_exists:
1.155 + db.init(db_file())
1.156 +
1.157 +def parse_label(label):
1.158 + label = label.strip()
1.159 + if ":" in label:
1.160 + project, activity = label.split(":")
1.161 + else:
1.162 + project, activity = (label, None)
1.163 + return project, activity
1.164 +
1.165 +def begin(args):
1.166 + project, activity = parse_label(args.label)
1.167 + time = datetime.now()
1.168 + db.resume(db_file(), None, None, time)
1.169 + db.begin(db_file(), project, activity, time)
1.170 +
1.171 +def end(args):
1.172 + project, activity = parse_label(args.label)
1.173 + log = args.m
1.174 + time = datetime.now()
1.175 + db.resume(db_file(), db.find_active_task(db_file()), time, log)
1.176 + db.end(db_file(), project, activity, time, log)
1.177 +
1.178 +def pause(args):
1.179 + db.pause(db_file(), datetime.now())
1.180 +
1.181 +def resume(args):
1.182 + db.resume(db_file(), None, datetime.now(), args.m)
1.183 +
1.184 +def add_task(args):
1.185 + project, activity = parse_label(args.label)
1.186 + log = args.m
1.187 + begin = datetime.strptime(args.begin, DATETIME_FORMAT)
1.188 + end = datetime.strptime(args.end, DATETIME_FORMAT)
1.189 + db.begin(db_file(), project, activity, begin)
1.190 + db.end(db_file(), project, activity, end, log)
1.191 +
1.192 +def add_break(args):
1.193 + log = args.m
1.194 + begin = datetime.strptime(args.begin, DATETIME_FORMAT)
1.195 + end = datetime.strptime(args.end, DATETIME_FORMAT)
1.196 + db.pause(db_file(), begin)
1.197 + db.resume(db_file(), db.find_active_task(db_file(), begin), end, log)
1.198 +
1.199 +def set_customer(args):
1.200 + db.update_where(db_file(), "projects", "name", args.project, "customer",
1.201 + args.customer)
1.202 +
1.203 +def map_name(map_base, name):
1.204 + mapped = None
1.205 + if name not in map_base.itervalues():
1.206 + for v in map_base.iterkeys():
1.207 + if name in v:
1.208 + mapped = map_base[v]
1.209 + else:
1.210 + mapped = name
1.211 + return mapped
1.212 +
1.213 +def set_rate(args):
1.214 + project, activity = parse_label(args.label)
1.215 + currency = map_name(CURRENCIES, args.currency)
1.216 + type = map_name(RATE_TYPES, args.type)
1.217 + print "setting rate for %s at %f %s %s" % (project, args.rate, currency, type)
1.218 + db.set_rate(db_file(), project, activity, args.rate, currency, type)
1.219 +
1.220 +def status(args):
1.221 + task = db.status(db_file())
1.222 + if task:
1.223 + print "Currently active task"
1.224 + print "Project: %s Activity: %s" % task
1.225 + else:
1.226 + print "There is no active task."
1.227 +
1.228 +if __name__ == "__main__":
1.229 + main()