tim.py
changeset 14 1b45f8231179
     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()