Skip to content

Instantly share code, notes, and snippets.

@mdaniel
Created March 20, 2014 20:49
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mdaniel/9673503 to your computer and use it in GitHub Desktop.
Save mdaniel/9673503 to your computer and use it in GitHub Desktop.
Using ``htmlize-buffer`` one can convert a clock-ed org file into a series of PivotalTracker chores
#! /usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import print_function, unicode_literals
__docformat__ = 'reStructuredText'
"""
To use this:
1. Inside emacs issue ``(require 'htmlize)`` followed by ``M-x htmlize-buffer``
1. Save that buffer to a file
1. Run this script on the file
1. Profit!
"""
from datetime import datetime
import json
import os.path
import re
import sys
import time
import requests
TOKEN = ''
PROJECT_ID = 0
ENDPOINT = 'https://www.pivotaltracker.com' \
'/services/v5/projects/%s' % PROJECT_ID
def create_chore(chore_dict):
"""
:type chore_dict: dict from unicode to object
"""
headers = {
# requests fills in C-Len
'Content-Type': 'application/json;charset=UTF-8',
'X-TrackerToken': TOKEN,
}
create_url = '%s/stories' % ENDPOINT
# copy for mutation
chore = dict(chore_dict)
# ensure description ends with a newline
chore['description'] = '%s\n' % chore['description']
if 'created_at' not in chore:
chore['created_at'] = datetime.utcnow()
#: :type: datetime
c_datetime = chore['created_at']
created_at_str = re.sub(r'\.\d+$', 'Z', c_datetime.isoformat())
if not created_at_str.endswith('Z'):
created_at_str = '%sZ' % created_at_str
chore['created_at'] = created_at_str
del created_at_str, c_datetime
if chore['accepted_at'] is None:
chore['accepted_at'] = datetime.utcnow()
#: :type: datetime
a_datetime = chore['accepted_at']
accepted_at_str = re.sub(r'\.\d+$', 'Z', a_datetime.isoformat())
if not accepted_at_str.endswith('Z'):
accepted_at_str = '%sZ' % accepted_at_str
chore['accepted_at'] = accepted_at_str
del accepted_at_str, a_datetime
chore_defaults = {
# this comes back in the response but is r/o in the POST
# 'kind': 'story',
'story_type': 'chore',
'estimate': 1,
'current_state': 'started',
}
chore_defaults.update(chore)
chore = chore_defaults
payload = json.dumps(chore, encoding='utf-8')
# print(payload)
#: :type: requests.Response
res = requests.post(create_url, headers=headers, data=payload)
if not res.ok:
print('Bogus: %s (%s)\n%s' %
(res.status_code, res.reason, res.content), file=sys.stderr)
return 1
print('OK!: %s (%s)\n%s\n%s' %
(res.status_code, res.reason, res.headers, res.text))
return 0
def convert_time(txt):
try:
result = datetime.utcfromtimestamp(long(txt))
except ValueError:
from pytz import timezone
us_pdt = timezone('US/Pacific')
#: :type: datetime
result = us_pdt.localize(datetime.strptime(txt, '%Y-%m-%d %H:%M:%S'))
result = datetime.utcfromtimestamp(time.mktime(result.timetuple()))
return result
def ingest_file(filename):
l2re = re.compile(r'<span class="org-level-2">\*\*\s+([^<]+)</span>')
org_date_re = re.compile(
r'<span class="org-date">'
r'\[(\d{4}-\d{2}-\d{2}) ... (\d\d:\d\d)\]'
r'--'
r'\[(\d{4}-\d{2}-\d{2}) ... (\d\d:\d\d)\]</span>')
org_fixups = [
(re.compile(r'<span class="org-verbatim">=(.+?)=</span>'), r'``\1``'),
]
def publish_chore(chore_dict):
print('== Publish Chore ==\n%r' % chore_dict)
create_chore(chore_dict)
fh = open(filename)
chore = {}
for line in fh.readlines():
if 'class="org-level-2' in line:
ma = l2re.search(line)
if not ma:
raise ValueError('Unable to decipher <<%r>>' % line)
if chore:
publish_chore(chore)
chore = {
'name': ma.group(1)
}
elif 'name' not in chore:
continue
elif 'class="org-level-1' in line:
break
elif 'org-special-keyword' in line and 'org-date' in line:
ma = org_date_re.search(line)
if not ma:
raise ValueError('Unable to deconstruct org-date\n%r' % line)
d0 = ma.group(1)
t0 = ma.group(2)
d1 = ma.group(3)
t1 = ma.group(4)
chore['created_at'] = convert_time('%s %s:00' % (d0, t0))
chore['accepted_at'] = convert_time('%s %s:00' % (d1, t1))
chore['current_state'] = 'accepted'
else:
desc = chore.get('description')
line = line.strip()
for fix_re, fix_sub in org_fixups:
line = fix_re.sub(fix_sub, line)
if not desc:
chore['description'] = line
else:
chore['description'] = '%s\n%s' % (desc, line)
fh.close()
if chore:
publish_chore(chore)
def main(argv):
fn = argv[1]
if not os.path.exists(fn):
print('Your file "%s" is 404' % fn, file=sys.stderr)
sys.exit(1)
if not os.path.isfile(fn):
print('Expected "%s" to be a file' % fn, file=sys.stderr)
sys.exit(1)
ingest_file(fn)
if __name__ == '__main__':
main(sys.argv)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment