emacs-orgmode@gnu.org archives
 help / color / mirror / code / Atom feed
* [PATCH] import org2tc scripts from John Wiegly into org-mode
@ 2017-01-20 18:18 Antoine Beaupré
  2017-01-22 13:13 ` Nicolas Goaziou
  0 siblings, 1 reply; 3+ messages in thread
From: Antoine Beaupré @ 2017-01-20 18:18 UTC (permalink / raw)
  To: emacs-orgmode; +Cc: Antoine Beaupré

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

^ permalink raw reply related	[flat|nested] 3+ messages in thread

* Re: [PATCH] import org2tc scripts from John Wiegly into org-mode
  2017-01-20 18:18 [PATCH] import org2tc scripts from John Wiegly into org-mode Antoine Beaupré
@ 2017-01-22 13:13 ` Nicolas Goaziou
  2017-01-22 19:30   ` Antoine Beaupré
  0 siblings, 1 reply; 3+ messages in thread
From: Nicolas Goaziou @ 2017-01-22 13:13 UTC (permalink / raw)
  To: Antoine Beaupré; +Cc: emacs-orgmode

Hello,

Antoine Beaupré <anarcat@debian.org> writes:

> 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.

Thank you.

However, I don't see any particular reason to distribute it within Org
(or within "timeclock.el", for that matter). It can happily live on
github (or somewhere else), can't it?

If it were written in Elisp, it could also go into GNU ELPA.

Regards,

-- 
Nicolas Goaziou

^ permalink raw reply	[flat|nested] 3+ messages in thread

* Re: [PATCH] import org2tc scripts from John Wiegly into org-mode
  2017-01-22 13:13 ` Nicolas Goaziou
@ 2017-01-22 19:30   ` Antoine Beaupré
  0 siblings, 0 replies; 3+ messages in thread
From: Antoine Beaupré @ 2017-01-22 19:30 UTC (permalink / raw)
  To: Nicolas Goaziou; +Cc: emacs-orgmode

On 2017-01-22 14:13:30, Nicolas Goaziou wrote:
> Hello,
>
> Antoine Beaupré <anarcat@debian.org> writes:
>
>> 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.
>
> Thank you.
>
> However, I don't see any particular reason to distribute it within Org
> (or within "timeclock.el", for that matter). It can happily live on
> github (or somewhere else), can't it?
>
> If it were written in Elisp, it could also go into GNU ELPA.

The main reason for this is that it's just this one tiny little
script. It seems silly to have a whole repo for it - and there are other
such scripts in contrib/scripts already...

I do not, unfortunately, have time to rewrite this in elisp just now. ;)

A.

-- 
People arbitrarily, or as a matter of taste, assigning numerical values
to non-numerical things. And then they pretend that they haven't just
made the numbers up, which they have. Economics is like astrology in
that sense, except that economics serves to justify the current power
structure, and so it has a lot of fervent believers among the powerful.
                        - Kim Stanley Robinson, Red Mars

^ permalink raw reply	[flat|nested] 3+ messages in thread

end of thread, other threads:[~2017-01-22 19:30 UTC | newest]

Thread overview: 3+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2017-01-20 18:18 [PATCH] import org2tc scripts from John Wiegly into org-mode Antoine Beaupré
2017-01-22 13:13 ` Nicolas Goaziou
2017-01-22 19:30   ` Antoine Beaupré

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).