#!/usr/bin/python '''Take an org-mode file as input and print a timeclock file as output. This can then be read directly by Ledger for fancy time reporting and querying. Fields :BILLCODE: and :TASKCODE: are parsed to generate lines compatible with the format expected by ledger ("billcode taskcode"). See also http://ledger-cli.org/2.6/ledger.html#Using-timeclock-to-record-billable-time © 2011-2016 John Wiegly © 2016-2017 Antoine Beaupré ''' from __future__ import print_function import argparse import locale locale.setlocale(locale.LC_ALL, '') import sys import re import time iso_date_fmt = "%Y-%m-%d %H:%M:%S" def parse_org_time(s): return time.strptime(s, "%Y-%m-%d %a %H:%M") def parse_timestamp(s): return time.strptime(s, iso_date_fmt) events = [] last_heading = None clocks = [] parser = argparse.ArgumentParser(description='convert org clocks into timeclock', epilog=__doc__ + '''Note that TIME is provided in the following format: %s''' % iso_date_fmt) parser.add_argument('orgfile', help='Org file to process') parser.add_argument('-s', '--start', metavar='TIME', help='process only entries from this date') parser.add_argument('-e', '--end', metavar='TIME', help='process only entries to this date') parser.add_argument('-r', '--regex', help='process only entries matching this regex') parser.add_argument('-o', '--output', help='output file (default: stdout)', type=argparse.FileType('w'), default=sys.stdout) args = parser.parse_args() data = args.orgfile range_start = parse_timestamp(args.start) if args.start else None range_end = parse_timestamp(args.end) if args.end else None regex = args.regex fd = open(data, "r") headings = [None] * 9 acct = "" (billcode, taskcode) = ("", None) def add_events(): # XXX: those globals should really be cleaned up, maybe through a clock object or named tuple global acct, clocks, billcode, taskcode, events, todo_keyword, last_heading if clocks: for (clock_in, clock_out, billcode, taskcode) in clocks: if billcode and ":" not in billcode and taskcode: acct = "%s:%s" % (billcode, taskcode) events.append((clock_in, clock_out, todo_keyword, ("%s %s" % (acct, last_heading)) if acct else last_heading)) clocks = [] for line in fd: match = re.search("^(\*+)\s*(.+)", line) if match: depth = len(match.group(1)) headings[depth] = match.group(2) depth = 0 match = re.search("^(\*+)\s+(TODO|DONE)?(\s+\[#[ABC]\])?\s*(.+)", line) if match: add_events() depth = len(match.group(1)) todo_keyword = match.group(2) last_heading = match.group(4) match = re.search("(.+?)\s+:\S+:$", last_heading) if match: last_heading = match.group(1) match = re.search("\[\[.*\]\]\s+(.+?)$", last_heading) if match: last_heading = match.group(1) headings[depth] = last_heading i = 0 prefix = "" while i < depth: if prefix: prefix += ":" + headings[i] else: prefix = headings[i] i += 1 if prefix: #last_heading = prefix + " " + last_heading last_heading = prefix + ":" + last_heading if regex and not (prefix and re.search(regex, prefix)): last_heading = None if last_heading: match = re.search("CLOCK:\s+\[(.+?)\](--\[(.+?)\])?", line) if match: clock_in = parse_org_time(match.group(1)) clock_out = match.group(3) # optional if clock_out: clock_out = parse_org_time(clock_out) else: #clock_out = time.localtime() clock_out = None if (not range_start or clock_in >= range_start) and \ (not range_end or clock_in < range_end): clocks.append((clock_in, clock_out, billcode, taskcode)) elif clock_in < range_start and clock_out > range_start: clocks.append((range_start, clock_out, billcode, taskcode)) elif clock_in < range_end and clock_out > range_end: clocks.append((clock_in, range_end, billcode, taskcode)) match = re.search(":BILLCODE:\s+(.+)", line) if match: billcode = match.group(1) taskcode = None match = re.search(":TASKCODE:\s+(.+)", line) if match: taskcode = match.group(1) fd.close() add_events() events.sort(key=lambda x: time.mktime(x[0])) for event in events: print("i %s %s" % (time.strftime(iso_date_fmt, event[0]), event[3]), file=args.output) if event[1]: print("%s %s" % ('O' if event[2] == 'DONE' else 'o', time.strftime(iso_date_fmt, event[1])), file=args.output) # org2tc ends here