1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
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
|