Created
October 30, 2014 04:57
-
-
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
""" | |
# 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