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

2017-01-22 Thread Antoine Beaupré
On 2017-01-22 14:13:30, Nicolas Goaziou wrote:
> Hello,
>
> Antoine Beaupré  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



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

2017-01-22 Thread Nicolas Goaziou
Hello,

Antoine Beaupré  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



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

2017-01-21 Thread 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 0..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 = ""
+
+(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.app