Skip to content

Instantly share code, notes, and snippets.

@mdwhatcott
Created October 30, 2014 04:57
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mdwhatcott/3f86f9dd4e278164f220 to your computer and use it in GitHub Desktop.
Save mdwhatcott/3f86f9dd4e278164f220 to your computer and use it in GitHub Desktop.
Gather all actions across all projects in a directory and group them according to their context. Helps with GTD.
"""
# Dependencies:
- https://github.com/russross/blackfriday-tool
- http://wkhtmltopdf.org/
TODO: this whole thing should be put under test.
TODO: it would be great to know how long a project (or task) is taking
"""
from os.path import join, exists, basename, isdir
import os
import time
import shutil
import collections
def main():
print 'Waiting for changes to project files...'
previous = -1
while True:
time.sleep(1)
latest = sum(checksums())
if latest != previous:
rebuild_context_lists()
previous = latest
def checksums():
for f in os.listdir(PROJECTS):
if f.endswith('.md'):
try:
stats = os.stat(os.path.join(PROJECTS, f))
yield stats.st_mtime + stats.st_size
except OSError:
pass
def rebuild_context_lists():
print 'Rebuilding context lists...'
tasks = []
projects = {}
contexts = collections.defaultdict(list)
stalled = set()
completed = []
urgent = []
# Collect tasks from projects:
for item in os.listdir(PROJECTS):
if isdir(item):
continue
if not item.endswith('.md'):
continue
project_name = item.title()\
.replace('-', ' ')\
.replace('Eqp', 'EQP')\
.replace('Ht', 'HT')\
.replace('.Md', '')
project = Project(project_name)
with open(join(PROJECTS, item)) as reader:
for line in reader:
if line.lower().startswith('- [x] ') or line.startswith('- [ ] '):
task = Task(line, project_name)
tasks.append(task)
project.include(task)
if task.due_date and not task.finished:
urgent.append(task.line)
if project.is_stalled():
stalled.add('- {0}'.format(project_name))
elif project.is_completed():
completed.append('- {0}'.format(project))
for task in tasks:
if task.finished:
continue
for context in task.contexts:
contexts[context].append(task.line)
total_tasks = len([t for t in tasks if not t.finished and t.contexts])
total_contexts = len(contexts)
# Account for stalled and completed projects:
if stalled:
contexts['**Stalled Projects**'] = stalled
if completed: # not seeing this ever...
contexts['**Completed Projects**'] = completed
if urgent:
contexts['**Urgent Tasks**'] = urgent
# Create the aggregated contexts report:
with open(CONTEXTS, 'w') as writer:
keys = sorted(contexts.keys())
writer.write("# Table of Contexts\n\n")
for key in keys:
writer.write(' {1:3} {0}\n'.format(key, len(contexts[key])))
writer.write(' ' + '=' * 36 + '\n')
writer.write(' {0:3} Planned tasks across {1} contexts\n'.format(
total_tasks, total_contexts))
writer.write('\n' + ('-' * 80) + '\n\n')
for context in keys:
writer.write('## {0} ({1} items)\n\n'.format(
context, len(contexts[context])))
for task in sorted(contexts[context]):
writer.write(task.replace(
context, '_{0}_'.format(context)).strip() + '\n')
writer.write('\n' + ('-' * 80) + '\n\n')
# Export to html and pdf:
if exists(HTML_PATH): os.remove(HTML_PATH)
if exists(PDF_PATH): os.remove(PDF_PATH)
blackfriday_command = './util/blackfriday-tool -page=true ' + \
'-css="../util/boilerplate/Clearness.css" ' + \
'"{0}" "{1}"'.format(CONTEXTS, HTML_PATH)
wkhtmltopdf_command = './util/wkhtmltopdf "{0}" "{1}"'.format(
HTML_PATH, PDF_PATH)
os.system(blackfriday_command)
os.system(wkhtmltopdf_command)
PROJECTS = './1-projects'
CONTEXTS = './0-runway/next_actions.md'
HTML_PATH = './0-runway/next_actions.html'
PDF_PATH = './0-runway/next_actions.pdf'
class Project(object):
def __init__(self, name):
self.name = name
self.tasks = []
def include(self, task):
self.tasks.append(task)
def is_completed(self):
if not self.tasks:
return False
for t in self.tasks:
if not t.finished:
return False
return True
def is_stalled(self):
if not len(self.tasks):
return True
for task in self.tasks:
if not task.finished and task.contexts:
return False
return True
class Task(object):
def __init__(self, line, project):
self.project = project
self.line = line.replace(
'- [ ] ',
'- **{0}**: '.format(project))
self.contexts = []
for word in self.line.split():
if word.startswith('@') and len(word) > 1:
self.contexts.append(word)
# TODO: This date detection is really naive and limited...
self.due_date = None
if '`2014-' in self.line or '`2015-' in self.line:
start_index = self.line.index('`201') + 1
stop_index = self.line[start_index:].index('`')
date = self.line[start_index:start_index + stop_index]
self.due_date = date
self.finished = self.line.lower().startswith('- [x]')
if __name__ == '__main__':
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment