From mboxrd@z Thu Jan 1 00:00:00 1970 From: =?UTF-8?q?Antoine=20Beaupr=C3=A9?= Subject: [PATCH] import org2tc scripts from John Wiegly into org-mode Date: Fri, 20 Jan 2017 13:18:06 -0500 Message-ID: <20170120181806.7332-1-anarcat@debian.org> Mime-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: quoted-printable Return-path: Received: from eggs.gnu.org ([2001:4830:134:3::10]:41019) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1cUdlv-0004D8-Pz for emacs-orgmode@gnu.org; Fri, 20 Jan 2017 13:18:49 -0500 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1cUdlr-0007Ud-IT for emacs-orgmode@gnu.org; Fri, 20 Jan 2017 13:18:47 -0500 Received: from marcos.anarc.at ([206.248.172.91]:41116) by eggs.gnu.org with esmtps (TLS1.0:DHE_RSA_AES_256_CBC_SHA1:32) (Exim 4.71) (envelope-from ) id 1cUdlq-0007T8-Va for emacs-orgmode@gnu.org; Fri, 20 Jan 2017 13:18:43 -0500 List-Id: "General discussions about Org-mode." List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: emacs-orgmode-bounces+geo-emacs-orgmode=m.gmane.org@gnu.org Sender: "Emacs-orgmode" To: emacs-orgmode@gnu.org Cc: =?UTF-8?q?Antoine=20Beaupr=C3=A9?= this was taken from this Github repo with the author's approval: https://github.com/jwiegley/org2tc this is very useful to convert org-mode clock entries into the more easily parseable timeclock.el format, a fundamental step in automating billing with org-mode. --- contrib/scripts/org2tc | 150 +++++++++++++++++++++++++++++++++++++++++++= ++++++ 1 file changed, 150 insertions(+) create mode 100755 contrib/scripts/org2tc diff --git a/contrib/scripts/org2tc b/contrib/scripts/org2tc new file mode 100755 index 000000000..9ff6422d7 --- /dev/null +++ b/contrib/scripts/org2tc @@ -0,0 +1,150 @@ +#!/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 + +=C2=A9 2011-2016 John Wiegly=20 +=C2=A9 2016-2017 Antoine Beaupr=C3=A9 +''' + + +from __future__ import print_function + +import argparse +import locale +locale.setlocale(locale.LC_ALL, '') +import sys +import re +import time + +iso_date_fmt =3D "%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 =3D [] +last_heading =3D None +clocks =3D [] + +parser =3D argparse.ArgumentParser(description=3D'convert org clocks int= o timeclock', + epilog=3D__doc__ + + '''Note that TIME is provided in the fo= llowing format: %s''' + % iso_date_fmt) +parser.add_argument('orgfile', help=3D'Org file to process') +parser.add_argument('-s', '--start', metavar=3D'TIME', help=3D'process o= nly entries from this date') +parser.add_argument('-e', '--end', metavar=3D'TIME', help=3D'process onl= y entries to this date') +parser.add_argument('-r', '--regex', help=3D'process only entries matchi= ng this regex') +parser.add_argument('-o', '--output', help=3D'output file (default: stdo= ut)', + type=3Dargparse.FileType('w'), default=3Dsys.stdout) +args =3D parser.parse_args() + +data =3D args.orgfile +range_start =3D parse_timestamp(args.start) if args.start else None +range_end =3D parse_timestamp(args.end) if args.end else None +regex =3D args.regex +fd =3D open(data, "r") +headings =3D [None] * 9 +acct =3D "" + +(billcode, taskcode) =3D ("", None) + +def add_events(): + # XXX: those globals should really be cleaned up, maybe through a cl= ock 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 =3D "%s:%s" % (billcode, taskcode) + events.append((clock_in, clock_out, todo_keyword, + ("%s %s" % (acct, last_heading)) + if acct else last_heading)) + clocks =3D [] + +for line in fd: + match =3D re.search("^(\*+)\s*(.+)", line) + if match: + depth =3D len(match.group(1)) + headings[depth] =3D match.group(2) + + depth =3D 0 + match =3D re.search("^(\*+)\s+(TODO|DONE)?(\s+\[#[ABC]\])?\s*(.+)", = line) + if match: + add_events() + + depth =3D len(match.group(1)) + todo_keyword =3D match.group(2) + last_heading =3D match.group(4) + match =3D re.search("(.+?)\s+:\S+:$", last_heading) + if match: + last_heading =3D match.group(1) + match =3D re.search("\[\[.*\]\]\s+(.+?)$", last_heading) + if match: + last_heading =3D match.group(1) + + headings[depth] =3D last_heading + + i =3D 0 + prefix =3D "" + while i < depth: + if prefix: + prefix +=3D ":" + headings[i] + else: + prefix =3D headings[i] + i +=3D 1 + + if prefix: + #last_heading =3D prefix + " " + last_heading + last_heading =3D prefix + ":" + last_heading + + if regex and not (prefix and re.search(regex, prefix)): + last_heading =3D None + + if last_heading: + match =3D re.search("CLOCK:\s+\[(.+?)\](--\[(.+?)\])?", line) + if match: + clock_in =3D parse_org_time(match.group(1)) + clock_out =3D match.group(3) # optional + if clock_out: + clock_out =3D parse_org_time(clock_out) + else: + #clock_out =3D time.localtime() + clock_out =3D None + if (not range_start or clock_in >=3D 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 =3D re.search(":BILLCODE:\s+(.+)", line) + if match: + billcode =3D match.group(1) + taskcode =3D None + + match =3D re.search(":TASKCODE:\s+(.+)", line) + if match: + taskcode =3D match.group(1) + +fd.close() +add_events() + +events.sort(key=3Dlambda x: time.mktime(x[0])) + +for event in events: + print("i %s %s" % (time.strftime(iso_date_fmt, event[0]), event[3]), + file=3Dargs.output) + if event[1]: + print("%s %s" % ('O' if event[2] =3D=3D 'DONE' else 'o', + time.strftime(iso_date_fmt, event[1])), + file=3Dargs.output) + +# org2tc ends here --=20 2.11.0