0b4ae60b9015

Initial commit.
[view raw] [browse files]
author Steve Losh <steve@stevelosh.com>
date Wed, 26 Aug 2009 17:28:25 -0400
parents
children 906a8111a48a
branches/tags (none)
files .hgignore t.py

Changes

--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/.hgignore	Wed Aug 26 17:28:25 2009 -0400
@@ -0,0 +1,3 @@
+syntax: glob
+*.pyc
+.DS_Store
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/t.py	Wed Aug 26 17:28:25 2009 -0400
@@ -0,0 +1,147 @@
+#!/usr/bin/env python
+
+from __future__ import with_statement
+
+import os, sys, hashlib, operator
+from optparse import OptionParser
+
+
+class InvalidTaskfile(Exception):
+    pass
+class AmbiguousPrefix(Exception):
+    pass
+
+_hash = lambda s: hashlib.sha1(s).hexdigest()
+
+def _task_from_taskline(taskline):
+    """Parse a taskline (from a task file) and return a task.
+
+    A taskline should be in the format:
+
+        summary text ... | meta1:meta1_value,meta2:meta2_value,...
+
+    The task returned will be a dictionary such as:
+
+        { 'id': <hash id>,
+          'text': <summary text>,
+           ... other metadata ... }
+    
+    """
+    text, _, meta = taskline.partition('|')
+    task = {'text': text.strip()}
+    for piece in meta.strip().split(','):
+        k, v = piece.split(':')
+        task[k.strip()] = v.strip()
+    return task
+
+def _prefixes(ids):
+    """return a mapping of ids to prefixes
+
+    Each prefix will be the shortest possible substring of the ID that
+    can uniquely identify it among the given group of IDs.
+
+    """
+    
+    prefixes = {}
+    for id in ids:
+        others = set(ids).difference([id])
+        for i in range(1, len(id)+1):
+            prefix = id[:i]
+            if not any(map(lambda o: o.startswith(prefix), others)):
+                prefixes[id] = prefix
+                break
+    return prefixes
+
+class TaskDict(object):
+    def __init__(self, name='tasks'):
+        self.tasks = {}
+        self.done = {}
+        self.name = name
+        filemap = (('tasks', self.name), ('done', '.%s.done' % self.name))
+        for kind, filename in filemap:
+            if os.path.isdir(filename):
+                raise InvalidTaskfile
+            if os.path.exists(filename):
+                with open(filename, 'r') as tfile:
+                    tls = [tl.strip() for tl in tfile.xreadlines() if tl]
+                    tasks = map(_task_from_taskline, tls)
+                    for task in tasks:
+                        getattr(self, kind)[task['id']] = task
+    
+    def add_task(self, text):
+        id = _hash(text)
+        self.tasks[id] = {'id': id, 'text': text}
+
+    def write(self):
+        filemap = (('tasks', self.name), ('done', '.%s.done' % self.name))
+        for kind, filename in filemap:
+            if os.path.isdir(filename):
+                raise InvalidTaskfile
+            with open(filename, 'w') as tfile:
+                tasks = getattr(self, kind).values()
+                tasks.sort(key=operator.itemgetter('id'))
+                for task in tasks:
+                    meta = [m for m in task.items() if m[0] != 'text']
+                    meta_str = ', '.join('%s:%s' % m for m in meta)
+                    tfile.write('%s | %s\n' % (task['text'], meta_str))
+    
+    def print_list(self, kind='tasks', verbose=False):
+        tasks = dict(getattr(self, kind).items())
+        label = 'prefix' if not verbose else 'id'
+        if not verbose:
+            for id, prefix in _prefixes(tasks).items():
+                tasks[id]['prefix'] = prefix
+        plen = max(map(lambda t: len(t[label]), tasks.values())) if tasks else 0
+        for t in tasks.values():
+            print ('%-' + str(plen) + 's - %s') % (t[label], t['text'])
+    
+    def finish_task(self, prefix):
+        matched = filter(lambda id: id.startswith(prefix), self.tasks.keys())
+        if len(matched) == 1:
+            task = self.tasks.pop(matched[0])
+            self.done[task['id']] = task
+        else:
+            raise AmbiguousPrefix
+
+    def delete_finished(self):
+        self.done = {}
+
+
+def build_parser():
+    parser = OptionParser()
+    parser.add_option("-a", "--add",
+                      action="store_true", dest="add", default=True,
+                      help="add the text as a task (default)")
+    parser.add_option("-e", "--edit", dest="edit",
+                      help="edit TASK", metavar="TASK")
+    parser.add_option("-f", "--finish", dest="finish",
+                      help="mark TASK as finished", metavar="TASK")
+    parser.add_option("-l", "--list", dest="name", default="tasks",
+                      help="work on LIST", metavar="LIST")
+    parser.add_option("-t", "--task-dir", dest="name",
+                      help="work in DIR", metavar="DIR")
+    parser.add_option("-D", "--delete-finished", dest="delete_finished",
+                      action="store_true", default=False,
+                      help="delete finished items to save space")
+    parser.add_option("-v", "--verbose",
+                      action="store_true", dest="verbose", default=False,
+                      help="print more detailed output (full task ids, etc)")
+    return parser
+
+(options, args) = build_parser().parse_args()
+
+td = TaskDict(options.name)
+text = ' '.join(args)
+
+if options.finish:
+    td.finish_task(options.finish)
+    td.write()
+elif options.delete_finished:
+    td.delete_finished()
+    td.write()
+elif text:
+    td.add_task(text)
+    td.write()
+else:
+    td.print_list(verbose=options.verbose)
+