This mod is an addition to the TimeLog mod.

The TimelogAuditor automatically parses messages which are added to an issue. It creates and links a timelog-entry for each line that starts with a description and ends with a roundup Interval (see roundup.date). If you add a property title to the TimeLog class, the descriptions for each timelog will be saved.

Sample message:

    Re: Fix Flicker Bug

    Hello Jack
    I fixed the bug that bugged you.

    Bug-Fixing    2:30
    Testing       0:15
    Relaxing      2w 3d 2:20:05

detectors/timelogauditor.py:

    #
    # timelogAuditor scans messages for timelogs.
    # 
    # $Id$

    import re
    from roundup.date import Interval

    def timelogAuditor(db, cl, nodeid, newvalues):
        ''' Check if a new messages where added to the issue and scan them
            for timelogs in the following format

              [title] [interval] <endofline>

              Programming              2h
              Consulting            2h30m
              Implementing Bug-Fix    30m

            if newvalues has an attribute 'messages', then we either are dealing
            with a new issue (create-auditor) or the attribute 'messages' was 
            changed for an existing issue (set-auditor).
        '''

        # return quickly if there is no new message
        if not newvalues.has_key('messages'):
            return

        # parse the new messages for timelogs
        timelogs = []
        for msgid in determineNewMessages(cl, nodeid, newvalues):
            timelogs += parseMessage(db, msgid)

        # add links for timelogs to the current issue
        if timelogs:
            if newvalues.has_key('times'):
                times = newvalues['times']
            elif nodeid:
                times = db.issue.get(nodeid, 'times')
            else:
                times = []

            times += timelogs
            newvalues['times'] = times

    def parseMessage(db, msgid, time_re = re.compile(r'''
                \s*(?P<title>(.*?))                   # title
                \s*(?P<time>[-+]?(?:\d+\s*[ymwd]\s*)* # [+-] [#y] [#m] [#w] [#d]
                  [012]?\d:\d+(?:\:\d+)?)\s*$         # [[[H]H:MM]:SS]
            ''', re.IGNORECASE|re.VERBOSE)):
        ''' parse the messages content field and return an array of timelogs
        '''
        msg_content = db.msg.get(msgid, 'content')
        if not msg_content:
            raise KeyException("no content found for message: %s\n" %(msgid))

        timelogs = []
        for line in msg_content.split("\n"):
            m = time_re.match(line)
            if m:
                title = m.group("title")
                time_str = m.group("time")
                interval  = Interval(time_str)

                # if there is a property title=String() in timelog, then fill it
                if db.timelog.getprops().has_key("title"):
                    timelogs += db.timelog.create(period=interval, title=title),
                else:
                    timelogs += db.timelog.create(period=interval),

        return timelogs

    def determineNewMessages(cl, nodeid, newvalues):
        if not newvalues.has_key('messages'):
            return []

        # return all messages for a new node
        if nodeid is None:
            return newvalues['messages']

        # return only new messages for an existing node
        messages = []
        old_messages = cl.get(nodeid, 'messages')
        return [msgid for msgid in newvalues['messages'] if msgid not in old_messages]

    def init(db):
        ''' we need to audit issue-changes to catch new messages instead
            of attaching an auditor directly to the message class.
            this is the only way to have the issue ready when linking the
            new timelogs.
        '''
        db.issue.audit('set', timelogAuditor)
        db.issue.audit('create', timelogAuditor)

    # vim: set filetype=python ts=4 sw=4 et si


subtopics: