emacs-orgmode
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[O] [PATCH] import org2tc scripts from John Wiegly into org-mode


From: Antoine Beaupré
Subject: [O] [PATCH] import org2tc scripts from John Wiegly into org-mode
Date: Fri, 20 Jan 2017 13:18:06 -0500

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




reply via email to

[Prev in Thread] Current Thread [Next in Thread]