From: "Antoine Beaupré" <anarcat@debian.org>
To: emacs-orgmode@gnu.org
Cc: "Antoine Beaupré" <anarcat@debian.org>
Subject: [PATCH] import org2tc scripts from John Wiegly into org-mode
Date: Fri, 20 Jan 2017 13:18:06 -0500 [thread overview]
Message-ID: <20170120181806.7332-1-anarcat@debian.org> (raw)
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
+
+© 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 = "<None>"
+
+(billcode, taskcode) = ("<Unknown>", 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
--
2.11.0
next reply other threads:[~2017-01-20 18:18 UTC|newest]
Thread overview: 3+ messages / expand[flat|nested] mbox.gz Atom feed top
2017-01-20 18:18 Antoine Beaupré [this message]
2017-01-22 13:13 ` [PATCH] import org2tc scripts from John Wiegly into org-mode Nicolas Goaziou
2017-01-22 19:30 ` Antoine Beaupré
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
List information: https://www.orgmode.org/
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20170120181806.7332-1-anarcat@debian.org \
--to=anarcat@debian.org \
--cc=emacs-orgmode@gnu.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
Code repositories for project(s) associated with this public inbox
https://git.savannah.gnu.org/cgit/emacs/org-mode.git
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).