diff --git a/CmdWindow.py b/CmdWindow.py deleted file mode 100644 index 46ea3fd..0000000 --- a/CmdWindow.py +++ /dev/null @@ -1,103 +0,0 @@ - -import gtk -import gobject -from datetime import datetime - -class CmdWindow: - def __init__(self, handle_activated): - self.starttime = None - self.handle_activated = handle_activated - - self.window = gtk.Window(gtk.WINDOW_TOPLEVEL) - self.window.set_geometry_hints(min_width = 350); - self.window.connect("delete_event", self.delete_event) - self.window.connect("destroy", self.destroy_event) - self.entry = gtk.Entry() - self.entry.connect("activate", self.activate_event) - self.button = gtk.Button(stock = gtk.STOCK_OK) - self.button.connect("clicked", self.activate_event) - - self.status_hbox = gtk.HBox() - self.status_label = gtk.Label('') - self.elapsed_label = gtk.Label('') - self.status_hbox.pack_start(self.status_label, expand = False) - self.status_hbox.pack_end(self.elapsed_label, expand = False) - - self.error_label = gtk.Label('') - - self.formatted_label = gtk.Label('') - - vbox = gtk.VBox() - vbox.pack_start(self.status_hbox) - vbox.pack_start(self.error_label) - vbox.pack_start(self.formatted_label) - gobject.timeout_add(1000, self.updateElapsed) - - hbox = gtk.HBox() - hbox.pack_start(self.entry) - hbox.pack_start(self.button, expand = False) - hbox.show_all() - vbox.pack_start(hbox) - - vbox.show() - - self.window.add(vbox) - - def main(self): - self.window.show() - gtk.main() - - def delete_event(self, widget, event, data=None): - return False - - def destroy_event(self, widget, data=None): - gtk.main_quit() - - def activate_event(self, widget, data=None): - if not self.handle_activated(self.entry.get_text(), self): - self.window.destroy() - self.entry.set_text('') - - def getElapsed(self): - if self.starttime is None: - return '' - delta = datetime.now() - self.starttime - days = delta.days - dsecs = delta.seconds - hours = dsecs / (60 * 60) - dsecs -= hours * 60 * 60 - minutes = dsecs / 60 - dsecs -= minutes * 60 - seconds = dsecs - elapsed = '' - if days != 0: - elapsed = '%dd ' % days - elapsed += '%d:%02d:%02d' % (hours, minutes, seconds) - return elapsed - - def updateElapsed(self): - self.elapsed_label.set_text(self.getElapsed()) - return True - - def setStatus(self, status, starttime): - if status != '': - self.status_label.set_text(status) - self.starttime = starttime - self.updateElapsed() - self.status_hbox.show_all() - else: - self.status_hbox.hide_all() - - def setError(self, error): - self.error_label.set_text(error) - if error != '': - self.error_label.show() - else: - self.error_label.hide() - - def setFormatted(self, formatted): - self.formatted_label.set_text(formatted) - if formatted != '': - self.formatted_label.show() - else: - self.formatted_label.hide() diff --git a/Command.py b/Command.py deleted file mode 100644 index 709be3d..0000000 --- a/Command.py +++ /dev/null @@ -1,116 +0,0 @@ - -from datetime import datetime, timedelta -import re - -class Command: - def __init__(self, cmdline, default_time = None): - if default_time is None: - self.time = datetime.now() - else: - self.time = default_time - self.command = 'start' - self.argstr = '' - self.parseCommandLine(cmdline) - - def __str__(self): - return "{'time' => '%s', 'command' => '%s', 'argstr' => '%s'}" % \ - (self.time, self.command, self.argstr) - - def parseCommandLine(self, cmdline): - COMMANDS = { - 'out' : 1, - 'report' : 1, - 'status' : 1, - 'fill' : 1, - 'adjust' : 1, - 'start' : 1, - 'task' : 1 - } - ALIASES = { - 'rpt' : 'report', - 'adj' : 'adjust', - 'end' : 'out', - 'st' : 'status', - 'show' : 'status', - 'f' : 'fill' - } - args = [] - foundArgs = False - - while True: - cmdline = cmdline.strip() - if len(cmdline) < 1: - break - parts = cmdline.split(None, 1) - token = parts[0] - if not foundArgs: - if self.parseDate(token): - pass - elif self.parseTime(token): - pass - elif token in COMMANDS: - self.command = token - foundArgs = True - elif token in ALIASES: - self.command = ALIASES[token] - foundArgs = True - else: - foundArgs = True - args.append(token) - else: - args.append(token) - if len(parts) < 2: - break - cmdline = parts[1] - - self.argstr = ' '.join(args) - - def parseDate(self, dt): - if dt.lower() == "yesterday": - today = datetime.today() - y = today - timedelta(days = 1) - self.time = self.time.replace( - year = y.year, month = y.month, day = y.day) - return True - m = re.match('^(?:(\d{4})[-/])?(\d{1,2})[-/](\d{1,2})$', dt) - if m is not None: - # dt was a date string - if m.group(1) is not None: - self.time = self.time.replace(year = int(m.group(1))) - month, day = int(m.group(2)), int(m.group(3)) - self.time = self.time.replace(month = month, day = day) - return True - return False - - def parseTime(self, timespec): - m = re.match('^(\d{1,2}):?(\d{2})?(am?|pm?)?$', timespec, re.I) - if m is not None: - # an absolute time was given - h = int(m.group(1)) - mins = 0 if m.group(2) is None else int(m.group(2)) - am_pm = '' if m.group(3) is None else m.group(3)[0].lower() - if am_pm == 'p' and h < 12: - h += 12 - elif am_pm == 'a' and h == 12: - h = 0 - self.time = self.time.replace(hour = h, minute = mins, second = 0) - return True - m = re.match('^([-+])(\d+(?:\.\d+)?)([hms])?$', timespec, re.I) - if m is not None: - # a relative time was given - plus_minus = m.group(1) - hms = '' if m.group(3) is None else m.group(3) - count = int(m.group(2)) - if hms == 'm': - seconds = count * 60 - elif hms == 's': - seconds = count - else: # hours - seconds = count * 60 * 60 - delta = timedelta(seconds = seconds) - if plus_minus == '-': - self.time -= delta - else: - self.time += delta - return True - return False diff --git a/DataStore.py b/DataStore.py deleted file mode 100644 index da30f6b..0000000 --- a/DataStore.py +++ /dev/null @@ -1,267 +0,0 @@ - -import sqlite3 -import os -from datetime import datetime - -HISTORY_DT_FMT = '%Y-%m-%d %H:%M:%S' - -class Task: - def __init__(self, taskid, name, longname, parentid): - self.taskid = taskid - self.name = name - self.longname = longname - self.parentid = parentid - -class TaskRef: - def __init__(self, taskid, time): - self.taskid = taskid - self.time = time - -class Entry: - def __init__(self, date, seconds, taskid): - self.date = date - self.seconds = seconds - self.taskid = taskid - -class DataStore: - def __init__(self, dbfile): - if not os.path.exists(dbfile): - self.createdb(dbfile) - self.conn = sqlite3.connect(dbfile) - - def __del__(self): - self.conn.close() - - def createdb(self, dbfile): - conn = sqlite3.connect(dbfile) - c = conn.cursor() - c.execute(''' -CREATE TABLE tasks ( - id INTEGER PRIMARY KEY, - name TEXT, - longname TEXT, - parentid INTEGER, - FOREIGN KEY (parentid) REFERENCES tasks(id) -)''') - c.execute(''' -CREATE TABLE entries ( - date TEXT, - taskid INTEGER, - seconds INTEGER, - PRIMARY KEY (date, taskid), - FOREIGN KEY (taskid) REFERENCES tasks(id) -)''') - c.execute(''' -CREATE TABLE history ( - id INTEGER PRIMARY KEY, - taskid INTEGER, - datetime TEXT, - FOREIGN KEY (taskid) REFERENCES tasks(id) -)''') - conn.commit() - c.close() - conn.close() - - def getCurrentTask(self): - c = self.conn.cursor() - c.execute(''' -SELECT taskid, datetime -FROM history -WHERE id = 0 -''') - ct = None - for row in c: - taskid = row[0] - dt = datetime.strptime(row[1], HISTORY_DT_FMT) - ct = TaskRef(taskid, dt) - c.close() - self.conn.commit() - return ct - - def updateCurrentTask(self, ct): - self.clearCurrentTask() - c = self.conn.cursor() - c.execute(''' -INSERT INTO history -VALUES (0, ?, ?) -''', (ct.taskid, ct.time.strftime(HISTORY_DT_FMT))) - c.close() - self.conn.commit() - - def clearCurrentTask(self): - c = self.conn.cursor() - c.execute(''' -DELETE FROM history -WHERE id = 0 -''') - c.close() - self.conn.commit() - - def updateTask(self, taskid, name, longname): - c = self.conn.cursor() - c.execute(''' -UPDATE tasks -SET name = ?, longname = ? -WHERE id = ? -''', (name, longname, taskid)) - c.close() - self.conn.commit() - return True - - def getTaskByID(self, taskid): - t = None - c = self.conn.cursor() - c.execute(''' -SELECT name, longname, parentid -FROM tasks -WHERE id = ? -''', (taskid,)) - for row in c: - t = Task(taskid, row[0], row[1], row[2]) - c.close() - self.conn.commit() - return t - - def getTaskByNameParent(self, name, parentid): - t = None - c = self.conn.cursor() - name = name.strip() - if parentid is None: - c.execute(''' -SELECT id, name, longname, parentid -FROM tasks -WHERE name = ? AND parentid IS NULL -''', (name,)) - else: - c.execute(''' -SELECT id, name, longname, parentid -FROM tasks -WHERE name = ? AND parentid = ? -''', (name, parentid)) - for row in c: - t = Task(*row) - c.close() - self.conn.commit() - return t - - def getTaskByName(self, name): - parts = name.split(':') - parentid = None - task = None - for p in parts: - p = p.strip() - task = self.getTaskByNameParent(p, parentid) - if task is None: - break - parentid = task.taskid - return task - - def getTaskByShortName(self, name): - if name.find(':') >= 0: - return self.getTaskByName(name) - task = None - c = self.conn.cursor() - c.execute(''' -SELECT id, name, longname, parentid -FROM tasks -WHERE name = ? -''', (name,)) - count = 0 - for row in c: - count += 1 - task = Task(*row) - if count > 1: - return None - c.close() - self.conn.commit() - return task - - def getParentTaskByName(self, name): - parts = name.split(':') - parentname = ':'.join(parts[:-1]) - return self.getTaskByName(parentname) - - def getParentTaskByShortName(self, name): - parts = name.split(':') - parentname = ':'.join(parts[:-1]) - return self.getTaskByShortName(parentname) - - def getTaskPath(self, task): - path = task.name - if task.parentid is not None: - parenttask = self.getTaskByID(task.parentid) - if parenttask is not None: - parentpath = self.getTaskPath(parenttask) - path = parentpath + ':' + path - return path - - def createTask(self, name, longname, parentid): - c = self.conn.cursor() - if parentid is not None and parentid != '': - c.execute(''' -SELECT * -FROM tasks -WHERE id = ? -''', (parentid,)) - found = False - for row in c: - found = True - if not found: - return 0 - c.execute(''' -SELECT MAX(id) -FROM tasks -''') - nextid = 1 - for row in c: - if row[0] is not None: - nextid = row[0] + 1 - c.execute(''' -INSERT INTO tasks -VALUES (?, ?, ?, ?) -''', (nextid, name.strip(), longname.strip(), parentid)) - c.close() - self.conn.commit() - return nextid - - def addTime(self, date, taskid, seconds): - c = self.conn.cursor() - exists = False - oldseconds = 0 - c.execute(''' -SELECT seconds -FROM entries -WHERE date = ? AND taskid = ? -''', (date, taskid)) - for row in c: - if row[0] is not None: - exists = True - oldseconds = row[0] - if exists: - c.execute(''' -UPDATE entries -SET seconds = ? -WHERE date = ? AND taskid = ? -''', (oldseconds + seconds, date, taskid)) - else: - c.execute(''' -INSERT INTO entries -VALUES(?, ?, ?) -''', (date, taskid, seconds)) - c.close() - self.conn.commit() - - def getEntriesInDateRange(self, date1, date2): - entries = [] - c = self.conn.cursor() - c.execute(''' -SELECT date, seconds, taskid -FROM entries -WHERE date >= ? AND date <= ? -ORDER BY taskid, date -''', (date1.strftime('%Y-%m-%d'), date2.strftime('%Y-%m-%d'))) - for row in c: - entries.append(Entry(*row)) - c.close() - self.conn.commit() - return entries diff --git a/Window.py b/Window.py new file mode 100644 index 0000000..78d3c54 --- /dev/null +++ b/Window.py @@ -0,0 +1,20 @@ + +import gtk +import gobject + +class Window: + def __init__(self): + self.window = gtk.Window(gtk.WINDOW_TOPLEVEL) + self.window.set_geometry_hints(); + self.window.connect("delete_event", self.delete_event) + self.window.connect("destroy", self.destroy_event) + + def main(self): + self.window.show() + gtk.main() + + def delete_event(self, widget, event, data=None): + return False + + def destroy_event(self, widget, data=None): + gtk.main_quit() diff --git a/dwtt b/dwtt index 995678e..377de42 100755 --- a/dwtt +++ b/dwtt @@ -3,11 +3,8 @@ import os import sys import getopt -from datetime import datetime, timedelta -from CmdWindow import CmdWindow -from Command import Command -from DataStore import DataStore, TaskRef +from Window import * PROGRAM_NAME = 'dwtt' @@ -32,135 +29,8 @@ def main(argv): usage() sys.exit(3) - timedbfile = os.path.expanduser('~') + os.path.sep + '.dwtt.db' - if len(args) >= 1: - timedbfile = args[0] - - ds = DataStore(timedbfile) - - def handleActivated(cmdline, cw): - if cmdline.strip() == '': - return False - ct = ds.getCurrentTask() - if ct is not None: - task = ds.getTaskByID(ct.taskid) - status = 'Task: ' + ds.getTaskPath(task) - if task.longname != '': - status += ' (%s)' % task.longname - cw.setStatus(status, ct.time) - else: - cw.setStatus('', None) - cmd = Command(cmdline) - res = processCommand(cmd, ds) - if not res.keepwindow: - return False - cw.setError(res.error) - cw.setFormatted(res.formatted) - return True - - cw = CmdWindow(handleActivated) - ct = ds.getCurrentTask() - if ct is not None: - task = ds.getTaskByID(ct.taskid) - status = 'Task: ' + ds.getTaskPath(task) - if task.longname != '': - status += ' (%s)' % task.longname - cw.setStatus(status, ct.time) - cw.main() - -class Result: - def __init__(self): - self.keepwindow = False - self.error = '' - self.message = '' - self.formatted = '' - -def processStart(cmd, store): - res = Result() - task = store.getTaskByShortName(cmd.argstr) - if task is None: - parent = store.getParentTaskByShortName(cmd.argstr) - if parent is None: - res.error = 'Could not find task "%s"' % \ - (':'.join(cmd.argstr.split(':')[:-1])) - res.keepwindow = True - return res - taskid = store.createTask(cmd.argstr.split(':')[-1], '', parent.taskid) - else: - taskid = task.taskid - processOut(cmd, store) - store.updateCurrentTask(TaskRef(taskid, cmd.time)) - return res - -def processOut(cmd, store): - res = Result() - ct = store.getCurrentTask() - if ct is None: - res.error = 'No current task defined' - res.keepwindow = True - return res - seconds = (cmd.time - ct.time).seconds - if seconds > 0: - store.addTime(cmd.time.strftime('%Y-%m-%d'), ct.taskid, seconds) - store.clearCurrentTask() - return res - -def processTask(cmd, store): - res = Result() - res.keepwindow = True - parts = cmd.argstr.split(',', 1) - fullname = parts[0].strip() - longname = '' if len(parts) < 2 else parts[1].strip() - nameparts = fullname.split(':') - task = store.getTaskByName(fullname) - if task is not None: - # the task already exists, update it - store.updateTask(task.taskid, nameparts[-1], longname) - res.message = 'Task "%s" updated' % fullname - return res - if len(nameparts) > 1: - parenttask = store.getParentTaskByShortName(fullname) - if parenttask is None: - res.error = 'Parent task of "%s" not found' % fullname - return res - store.createTask(nameparts[-1].strip(), longname, parenttask.taskid) - else: - store.createTask(nameparts[-1].strip(), longname, None) - res.message = 'Task "%s" created' % fullname - return res - -def processStatus(cmd, store): - res = Result() - res.keepwindow = True - now = datetime.now() - monday = now - timedelta(now.weekday()) - sunday = monday + timedelta(6) - entries = store.getEntriesInDateRange(monday, sunday) - prevtask = 0 - for ent in entries: - if ent.taskid != prevtask: - prevtask = ent.taskid - task = store.getTaskByID(ent.taskid) - print store.getTaskPath(task) + ':' - hours = round(float(ent.seconds) / (60 * 60), 1) - print ' %s: %0.1f' % (ent.date, hours) - return res - -COMMAND_HANDLERS = { - 'start' : processStart, - 'out' : processOut, - 'task' : processTask, - 'status' : processStatus - } - -# Returns boolean for whether the command prompt should be displayed again -def processCommand(cmd, store): - if cmd.command in COMMAND_HANDLERS: - return COMMAND_HANDLERS[cmd.command](cmd, store) - res = Result() - res.error = 'Unknown command: %s' % cmd.command - res.keepwindow = True - return res + w = Window() + w.main() if __name__ == "__main__": main(sys.argv) diff --git a/test b/test deleted file mode 100755 index 3aa0cc3..0000000 --- a/test +++ /dev/null @@ -1,49 +0,0 @@ -#!/usr/bin/env python - -from Command import Command -from datetime import datetime - -n_tests = 0 -n_pass = 0 - -def testcmd(cmdline, cmd, argstr): - global n_tests, n_pass - n_tests += 1 - print "Testing command line '%s'" % cmdline - c = Command(cmdline) - if c.command == cmd and c.argstr == argstr: - n_pass += 1 - else: - print " **** testcmd FAILED ****" - print " command:" - print " expected: '%s'" % cmd - print " actual: '%s'" % c.command - print " argstr:" - print " expected: '%s'" % argstr - print " actual: '%s'" % c.argstr - print " ************************" - -def main(): - global n_tests, n_pass - testcmd('out', 'out', '') - testcmd('fill', 'fill', '') - testcmd('adjust', 'adjust', '') - testcmd('2 report', 'report', '') - testcmd('show', 'status', '') - testcmd('5pm out', 'out', '') - testcmd('st 45h', 'status', '45h') - testcmd('fill 40h', 'fill', '40h') - testcmd('-10m arlx', 'start', 'arlx') - testcmd(' adjust -10m ', 'adjust', '-10m') - testcmd(' 9:45 wr: nog: pbit ram test ', - 'start', 'wr: nog: pbit ram test') - testcmd('12/14 3pm start arlx', 'start', 'arlx') - testcmd('yesterday 7P out', 'out', '') - if n_tests == n_pass: - print " >= SUCCESS <=" - else: - print " >= FAIL <=" - print "%d of %d tests pass" % (n_pass, n_tests) - -if __name__ == "__main__": - main();