emacs-orgmode@gnu.org archives
 help / color / mirror / code / Atom feed
blob 9ff6422d775f9b2aafa3f22427518975905cf6d1 5113 bytes (raw)
name: contrib/scripts/org2tc 	 # note: path name is non-authoritative(*)

  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

debug log:

solving 9ff6422d7 ...
found 9ff6422d7 in https://list.orgmode.org/orgmode/20170120181806.7332-1-anarcat@debian.org/

applying [1/1] https://list.orgmode.org/orgmode/20170120181806.7332-1-anarcat@debian.org/
diff --git a/contrib/scripts/org2tc b/contrib/scripts/org2tc
new file mode 100755
index 000000000..9ff6422d7

1:17: trailing whitespace.
© 2011-2016 John Wiegly 
Checking patch contrib/scripts/org2tc...
Applied patch contrib/scripts/org2tc cleanly.
warning: 1 line adds whitespace errors.

index at:
100755 9ff6422d775f9b2aafa3f22427518975905cf6d1	contrib/scripts/org2tc

(*) Git path names are given by the tree(s) the blob belongs to.
    Blobs themselves have no identifier aside from the hash of its contents.^

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