sawine@6
|
1 |
#!/usr/local/bin/python
|
sawine@13
|
2 |
# -*- coding: utf-8 -*-
|
sawine@0
|
3 |
|
sawine@0
|
4 |
"""
|
sawine@7
|
5 |
tim - A time recording tool, because time is money.
|
sawine@0
|
6 |
Author: Eugen Sawin (sawine@me73.com)
|
sawine@7
|
7 |
Dependencies: Python 2.7 and libsqlite3-dev
|
sawine@0
|
8 |
"""
|
sawine@0
|
9 |
|
sawine@1
|
10 |
import os
|
sawine@2
|
11 |
import sys
|
sawine@0
|
12 |
import argparse
|
sawine@2
|
13 |
from datetime import datetime
|
sawine@3
|
14 |
|
sawine@3
|
15 |
import db
|
sawine@0
|
16 |
|
sawine@1
|
17 |
WD = "working_path"
|
sawine@1
|
18 |
CONFIG = {WD: str}
|
sawine@1
|
19 |
CONFIG_SEP = "="
|
sawine@12
|
20 |
DB_FILE = "tim.db"
|
sawine@1
|
21 |
|
sawine@13
|
22 |
CURRENCIES = {("EUR", "EURO", "€"): "EUR",
|
sawine@13
|
23 |
("US DOLLAR", "USD", "$"): "USD"}
|
sawine@13
|
24 |
|
sawine@13
|
25 |
RATE_TYPES = {("h", "hourly"): "hourly"}
|
sawine@13
|
26 |
|
sawine@1
|
27 |
config = {}
|
sawine@1
|
28 |
|
sawine@7
|
29 |
# Try to get the user's home directory path
|
sawine@1
|
30 |
try: # Windows
|
sawine@1
|
31 |
from win32com.shell import shellcon, shell
|
sawine@1
|
32 |
HOMEDIR = shell.SHGetFolderPath(0, shellcon.CSIDL_LOCAL_APPDATA, 0, 0)
|
sawine@2
|
33 |
except ImportError: # Linux (hopefully)
|
sawine@1
|
34 |
HOMEDIR = os.path.expanduser("~")
|
sawine@1
|
35 |
|
sawine@12
|
36 |
CONFIG_FILE = "%s/.timrc" % HOMEDIR
|
sawine@1
|
37 |
|
sawine@10
|
38 |
DATETIME_FORMAT = "%Y-%m-%d %H:%M:%S"
|
sawine@10
|
39 |
|
sawine@1
|
40 |
def read_config():
|
sawine@1
|
41 |
config = {}
|
sawine@1
|
42 |
with open(CONFIG_FILE, "r") as config_stream:
|
sawine@1
|
43 |
config_lines = [l.split(CONFIG_SEP) for l in config_stream.readlines()
|
sawine@1
|
44 |
if CONFIG_SEP in l]
|
sawine@1
|
45 |
for key, value in config_lines:
|
sawine@1
|
46 |
key = key.strip().lower()
|
sawine@1
|
47 |
value = value.strip()
|
sawine@1
|
48 |
if key in CONFIG:
|
sawine@1
|
49 |
config[key] = CONFIG[key](value)
|
sawine@1
|
50 |
return config
|
sawine@1
|
51 |
|
sawine@1
|
52 |
def write_config(config):
|
sawine@1
|
53 |
with open(CONFIG_FILE, "w") as config_input:
|
sawine@1
|
54 |
config_input.write("\n".join([CONFIG_SEP.join((k, v))
|
sawine@1
|
55 |
for k, v in config.iteritems() if k in CONFIG]))
|
sawine@1
|
56 |
|
sawine@3
|
57 |
def db_file():
|
sawine@3
|
58 |
global config
|
sawine@3
|
59 |
if WD not in config:
|
sawine@3
|
60 |
print "Working directory path is not configured. \
|
sawine@3
|
61 |
Please use the init command."
|
sawine@3
|
62 |
return None
|
sawine@3
|
63 |
return config[WD] + "/" + DB_FILE
|
sawine@3
|
64 |
|
sawine@1
|
65 |
def init(args):
|
sawine@2
|
66 |
global config
|
sawine@1
|
67 |
last_wd = None
|
sawine@1
|
68 |
if WD in config:
|
sawine@1
|
69 |
last_wd = config[WD]
|
sawine@2
|
70 |
config[WD] = args.working_path
|
sawine@1
|
71 |
write_config(config)
|
sawine@2
|
72 |
path_exists = os.path.exists(config[WD]) and os.path.isdir(config[WD])
|
sawine@3
|
73 |
db_exists = path_exists and os.path.exists(db_file())\
|
sawine@3
|
74 |
and os.path.isfile(db_file())
|
sawine@2
|
75 |
if last_wd != config[WD]:
|
sawine@2
|
76 |
print "Changed working directory from %s to %s." % (last_wd, config[WD])
|
sawine@1
|
77 |
else:
|
sawine@2
|
78 |
print "Working directory remains at %s." % config[WD]
|
sawine@2
|
79 |
if not path_exists:
|
sawine@2
|
80 |
print "Warning: working directory %s does not exist." % config[WD]
|
sawine@2
|
81 |
os.makedirs(config[WD])
|
sawine@2
|
82 |
print "Working directory %s created." % config[WD]
|
sawine@2
|
83 |
elif db_exists:
|
sawine@3
|
84 |
print "Database file %s already exists, please delete it before \
|
sawine@3
|
85 |
initiating a new database at this location." % db_file()
|
sawine@2
|
86 |
if not db_exists:
|
sawine@3
|
87 |
db.init(db_file())
|
sawine@1
|
88 |
|
sawine@10
|
89 |
def parse_label(label):
|
sawine@10
|
90 |
label = label.strip()
|
sawine@2
|
91 |
if ":" in label:
|
sawine@2
|
92 |
project, activity = label.split(":")
|
sawine@2
|
93 |
else:
|
sawine@10
|
94 |
project, activity = (label, None)
|
sawine@10
|
95 |
return project, activity
|
sawine@10
|
96 |
|
sawine@10
|
97 |
def begin(args):
|
sawine@10
|
98 |
project, activity = parse_label(args.label)
|
sawine@8
|
99 |
time = datetime.now()
|
sawine@8
|
100 |
db.resume(db_file(), None, None, time)
|
sawine@8
|
101 |
db.begin(db_file(), project, activity, time)
|
sawine@0
|
102 |
|
sawine@0
|
103 |
def end(args):
|
sawine@10
|
104 |
project, activity = parse_label(args.label)
|
sawine@10
|
105 |
log = args.m
|
sawine@8
|
106 |
time = datetime.now()
|
sawine@9
|
107 |
db.resume(db_file(), db.find_active_task(db_file()), time, log)
|
sawine@9
|
108 |
db.end(db_file(), project, activity, time, log)
|
sawine@8
|
109 |
|
sawine@8
|
110 |
def pause(args):
|
sawine@11
|
111 |
db.pause(db_file(), datetime.now())
|
sawine@8
|
112 |
|
sawine@8
|
113 |
def resume(args):
|
sawine@11
|
114 |
db.resume(db_file(), None, datetime.now(), args.m)
|
sawine@2
|
115 |
|
sawine@10
|
116 |
def add_task(args):
|
sawine@10
|
117 |
project, activity = parse_label(args.label)
|
sawine@10
|
118 |
log = args.m
|
sawine@10
|
119 |
begin = datetime.strptime(args.begin, DATETIME_FORMAT)
|
sawine@10
|
120 |
end = datetime.strptime(args.end, DATETIME_FORMAT)
|
sawine@10
|
121 |
db.begin(db_file(), project, activity, begin)
|
sawine@10
|
122 |
db.end(db_file(), project, activity, end, log)
|
sawine@10
|
123 |
|
sawine@11
|
124 |
def add_break(args):
|
sawine@11
|
125 |
log = args.m
|
sawine@11
|
126 |
begin = datetime.strptime(args.begin, DATETIME_FORMAT)
|
sawine@11
|
127 |
end = datetime.strptime(args.end, DATETIME_FORMAT)
|
sawine@11
|
128 |
db.pause(db_file(), begin)
|
sawine@11
|
129 |
db.resume(db_file(), db.find_active_task(db_file(), begin), end, log)
|
sawine@11
|
130 |
|
sawine@12
|
131 |
def set_customer(args):
|
sawine@12
|
132 |
db.update_where(db_file(), "projects", "name", args.project, "customer",
|
sawine@12
|
133 |
args.customer)
|
sawine@12
|
134 |
|
sawine@13
|
135 |
def map_name(map_base, name):
|
sawine@13
|
136 |
mapped = None
|
sawine@13
|
137 |
if name not in map_base.itervalues():
|
sawine@13
|
138 |
for v in map_base.iterkeys():
|
sawine@13
|
139 |
if name in v:
|
sawine@13
|
140 |
mapped = map_base[v]
|
sawine@13
|
141 |
else:
|
sawine@13
|
142 |
mapped = name
|
sawine@13
|
143 |
return mapped
|
sawine@13
|
144 |
|
sawine@13
|
145 |
def set_rate(args):
|
sawine@13
|
146 |
project, activity = parse_label(args.label)
|
sawine@13
|
147 |
currency = map_name(CURRENCIES, args.currency)
|
sawine@13
|
148 |
type = map_name(RATE_TYPES, args.type)
|
sawine@13
|
149 |
print "setting rate for %s at %f %s %s" % (project, args.rate, currency, type)
|
sawine@13
|
150 |
db.set_rate(db_file(), project, activity, args.rate, currency, type)
|
sawine@13
|
151 |
|
sawine@7
|
152 |
def status(args):
|
sawine@8
|
153 |
task = db.status(db_file())
|
sawine@8
|
154 |
if task:
|
sawine@8
|
155 |
print "Currently active task"
|
sawine@8
|
156 |
print "Project: %s Activity: %s" % task
|
sawine@7
|
157 |
else:
|
sawine@8
|
158 |
print "There is no active task."
|
sawine@7
|
159 |
|
sawine@2
|
160 |
def main():
|
sawine@2
|
161 |
global config
|
sawine@1
|
162 |
config = read_config()
|
sawine@1
|
163 |
parser = argparse.ArgumentParser(description="Records time.")
|
sawine@1
|
164 |
subs = parser.add_subparsers()
|
sawine@10
|
165 |
|
sawine@10
|
166 |
sub_init = subs.add_parser("init")
|
sawine@12
|
167 |
sub_init.add_argument("working_path")
|
sawine@10
|
168 |
sub_init.set_defaults(func=init)
|
sawine@0
|
169 |
|
sawine@1
|
170 |
sub_begin = subs.add_parser("begin")
|
sawine@12
|
171 |
sub_begin.add_argument("label")
|
sawine@1
|
172 |
sub_begin.set_defaults(func=begin)
|
sawine@0
|
173 |
|
sawine@1
|
174 |
sub_end = subs.add_parser("end")
|
sawine@12
|
175 |
sub_end.add_argument("label", nargs="?", default="all:all")
|
sawine@12
|
176 |
sub_end.add_argument("-m")
|
sawine@1
|
177 |
sub_end.set_defaults(func=end)
|
sawine@0
|
178 |
|
sawine@10
|
179 |
sub_pause = subs.add_parser("pause")
|
sawine@10
|
180 |
sub_pause.set_defaults(func=pause)
|
sawine@8
|
181 |
|
sawine@10
|
182 |
sub_resume = subs.add_parser("resume")
|
sawine@12
|
183 |
sub_resume.add_argument("-m")
|
sawine@10
|
184 |
sub_resume.set_defaults(func=resume)
|
sawine@8
|
185 |
|
sawine@10
|
186 |
sub_add = subs.add_parser("add")
|
sawine@10
|
187 |
sub_add_subs = sub_add.add_subparsers()
|
sawine@11
|
188 |
|
sawine@10
|
189 |
sub_add_task = sub_add_subs.add_parser("task")
|
sawine@12
|
190 |
sub_add_task.add_argument("label")
|
sawine@12
|
191 |
sub_add_task.add_argument("begin")
|
sawine@12
|
192 |
sub_add_task.add_argument("end")
|
sawine@12
|
193 |
sub_add_task.add_argument("-m")
|
sawine@10
|
194 |
sub_add_task.set_defaults(func=add_task)
|
sawine@11
|
195 |
|
sawine@11
|
196 |
sub_add_break = sub_add_subs.add_parser("break")
|
sawine@12
|
197 |
sub_add_break.add_argument("begin")
|
sawine@12
|
198 |
sub_add_break.add_argument("end")
|
sawine@12
|
199 |
sub_add_break.add_argument("-m")
|
sawine@11
|
200 |
sub_add_break.set_defaults(func=add_break)
|
sawine@10
|
201 |
|
sawine@12
|
202 |
sub_set = subs.add_parser("set")
|
sawine@12
|
203 |
sub_set_subs = sub_set.add_subparsers()
|
sawine@12
|
204 |
|
sawine@12
|
205 |
sub_set_customer = sub_set_subs.add_parser("customer")
|
sawine@12
|
206 |
sub_set_customer.add_argument("project")
|
sawine@12
|
207 |
sub_set_customer.add_argument("customer")
|
sawine@12
|
208 |
sub_set_customer.set_defaults(func=set_customer)
|
sawine@12
|
209 |
|
sawine@13
|
210 |
sub_set_rate = sub_set_subs.add_parser("rate")
|
sawine@13
|
211 |
sub_set_rate.add_argument("label")
|
sawine@13
|
212 |
sub_set_rate.add_argument("rate", type=float)
|
sawine@13
|
213 |
sub_set_rate.add_argument("currency")
|
sawine@13
|
214 |
sub_set_rate.add_argument("type")
|
sawine@13
|
215 |
sub_set_rate.set_defaults(func=set_rate)
|
sawine@13
|
216 |
|
sawine@10
|
217 |
sub_status = subs.add_parser("status")
|
sawine@10
|
218 |
sub_status.set_defaults(func=status)
|
sawine@7
|
219 |
|
sawine@1
|
220 |
args = parser.parse_args()
|
sawine@1
|
221 |
args.func(args)
|
sawine@2
|
222 |
|
sawine@2
|
223 |
if __name__ == "__main__":
|
sawine@2
|
224 |
main()
|