因為我們常要寫 wiki page 以便追蹤專案的進度、列出待辦事項等等,由於在 Trac 裡寫 #1 只會顯示 ticket number,往往我們必須要手動把 ticket summary 加在後面,當 ticket 有所變動時,wiki 與 ticket 兩邊就會不同步,維護起來非常麻煩。所以剛剛練習寫了一個 WikiMacro,取名為 TicketStatus.py,列出 ticket number、summary、status 與 owner,開會時一打開,所有事情一目了然。

WikiMacro 不難寫,不過因為我不太熟 python,所以比較麻煩的地方在於怎樣抓取資料。可能是由於 dynamic language 的特性,很難直接從原始碼追蹤,了解手上可以用的參數,其型別與屬性分別可以輾轉得到哪些資料。不過這對熟悉 python programming 的人,應該不是問題。因為多半有現成的工具,如 ipython 等,幫助我們直接取得、分析、追蹤整個物件結構。

TicketStatus.py 的原始碼如下:

"""
Display status and summaries of specified ticket numbers.  Useful for listing
TODOs.

Example:
{{{
[[TicketStatus(#1,#7,#31)]]               ... list of tickets
[[TicketStatus(1,7,31)]]                  ... '#' char can be omitted
}}}
"""

import re
import string

def uniq(x):
    y = []
    for i in x:
        if not y.count(i):
            y.append(i)
    return y

args_pat = [r"#?(?P<tktnum>\d+)",]

def execute(hdf, args, env):
    items = []
    args_re = re.compile("^(?:" + string.join(args_pat, "|") + ")___FCKpd___0quot;)
    for arg in [string.strip(s) for s in args.split(',')]:
        match = args_re.match(arg)
        if not match:
            env.log.debug('TicketStatus: unknown arg: %s' % arg)
            continue
        elif match.group('tktnum'):
            items.append(int(match.group('tktnum')))
    items = uniq(items)

    db = env.get_db_cnx()
    cursor = db.cursor()
    sql = "SELECT id, summary, status, resolution, owner FROM ticket WHERE id IN (%s)" % (string.join(['%d' % c for c in items], ', '))
    cursor.execute(sql)
    html = ''
    for (tkt_id, tkt_summary, tkt_status, tkt_resolution, tkt_owner) in cursor:
        html += '<tr><td width="1">'
        try:
            # for trac 0.9 or later
            from trac.wiki.formatter import wiki_to_oneliner
            html += wiki_to_oneliner(('#%d' % tkt_id), env, env.get_db_cnx())
        except:
            # for trac 0.8.x
            from trac.WikiFormatter import wiki_to_oneliner
            html = wiki_to_oneliner(('#%d' % tkt_id), hdf, env, env.get_db_cnx())
        html += '</td>'
        status = tkt_status
        if status == 'closed':
            status = tkt_resolution
        html += '<td width="1">%s</td><td width="1">%s</td><td>%s</td>' % (status, tkt_owner, tkt_summary)
        html += '</tr>'
    html = '<table class="ticketstatus wiki" width="90%%">\n<tbody>\n%s\n</tbody>\n</table>' % html
    return html

當一行太長時,我搞不清楚 python 縮排的條件是什麼,所以只好請大家將就點,往右捲來看。

用起來很簡單,把 ticket numbers 當參數傳進去即可,如:[[TicketStatus(#1,#2)]]。顯示效果如下:

使用說明如下:

理論上 trac 應該要有提供 API,讓我們可以靠 ticket number 就取得其他的屬性,不過因為前述的理由,所以最終我還是直接對 sqlite database 下 query。這其實是不太好的作法,不過黔驢技窮,那也沒辦法了。:-p

Updated (2007-08-23):

印出來的 html,應該要先把 &、<、>、"、' 等代換成對應的 html entities 才行。修改如下:

  1. 新增 htmlspecialchars 函式[1]

    def htmlspecialchars(s):
        s = re.sub(r'&',  r'&amp;',  s)
        s = re.sub(r'<',  r'&lt;',   s)
        s = re.sub(r'>',  r'&gt;',   s)
        s = re.sub(r'"',  r'&quot;', s)
        s = re.sub(r'\'', r'&apos;', s)
        return s;
  2. 在印出的時候,呼叫 htmlspecialchars 做轉換:

    html += '<td width="1">%s</td><td width="1">%s</td><td>%s</td>' % (htmlspecialchars(status), htmlspecialchars(tkt_owner), htmlspecialchars(tkt_summary))

    這一行實在是長到不行,不過沒辦法,python 就是這麼規定的[2]


  1. 就直接學 PHP 的命名吧。:-p
  2. 這大概是我無法對 python 產生熱情的原因之一吧。我已經是個很嚴格要求自己程式縮排規則的人了,python 的限制一加下去,反而正正得負,程式變難看了。