Use Backbone.Paginator to create pagination for table layout HTML

我們教會人手非常不夠,也不見得有工程背景的人,所以很多東西都是簡單就好。
像是主日的錄音檔好了,我們的標準流程是用 Dreamweaver 修改表格,一個禮拜就會多上三個錄音檔,簡單算起來,一年就會有 156 個 entry 會放到頁面中。

例如說

<tr onMouseOver="mark(this,'#ECFBD4','#990000')" onMouseOut="mark(this,'#FFFFFF','#333333')" >
  <td align="center" valign="top">2013.01.20</td>
  <td align="center" valign="top">深思天國(一) 心決定一切</td>
  <td align="center" valign="top">徐坤靖牧師<br /></td>
  <td align="center" valign="top"><a href="http://files.ctfhc.org/sermon/130120.mp3"><img src="images/record.jpg" alt="錄音檔下載" width="120" height="28" border="0" /></a></td>
  <td align="center" valign="top"><a href="http://youtu.be/Y1iSbTN07Cc" target="_blank"><img src="images/video.jpg" alt="線上收看" width="120" height="28" border="0" /></a></td>
</tr>

長久下來之下,這個頁面就包含了 300 個 tr 標籤去顯示每個禮拜的影音檔。

剛好在 JSDC 結束之後,就決定想用 Backbone.js 來在前端用 JavaScript 動手腳,把 table 的每一個 tag 處理好當做 model,最後把 collection 做出來丟給 controller 跟 view。

中間也用了 Backbone.Paginator 幫忙做分頁,簡單的使用方式可以看一下他的範例

最後做出來就會像是

source code 放在 https://bitbucket.org/yhchan/ctfhc-media-pagination,其實東西並不難,只是一開始跟 JavaScript 不熟多花了很多時間而已。而且目前是用 JSONP 在後端把東西用 JSON 丟出來,其實應該可以在前端直接做掉的,但是我現在懶惰了…

比起這種方式,我可能還是希望有一天,我們能自己好好使用好的 CMS 去設計網站吧,2010 年想改用 Drupal,也因為人力的關係胎死腹中…

Using tox and jenkins to have python continuous integration environment

在看幾個 github 上的專案,像是 celery 都會發現他們的 repo 有一個 tox.ini,剛好手邊有個小小的 python 專案,就順便看一下 tox 是什麼。

節錄 tox 官網的介紹。

Tox as is a generic virtualenv management and test command line tool you can use for:

  • checking your package installs correctly with different Python versions and interpreters
  • running your tests in each of the environments, configuring your test tool of choice
  • acting as a frontend to Continuous Integration servers, greatly reducing boilerplate and merging CI and shell-based testing.

對我最實用的功能大概是拿來測試 python2.6 跟 python2.7 的差異,常常不小心就寫出 dictionary comprehension
(事實上如果你記得裝 syntastic 就會提醒你 …)

另外一個好處就是,他是用 virtualenv,確保每次測試的環境都是獨立的,dependency 沒有處理好就會叫一下,不會因為開發環境有裝,然後到 deployment 的時候又要再檢查。

我這邊裝 python2.6 跟 python2.7 是用 pythonbrew

pip install pythonbrew
pythonbrew_install
pythonbrew install 2.7.3 2.6.8

另外一個蠻不錯的,就是他跟 jenkins 結合也不是很困難,大概跟著官網的說明做就好了。

最後我寫的 tox.ini 就會像是

[tox]
envlist = py26, py27

[testenv]
commands = nosetests {posargs:--with-cov --cov-report=xml --with-xunit --cov package}
flake8 --exit-zero package

deps = nose
nose-cov
coverage
mock
flake8

[testenv:py26]
basepython={homedir}/.pythonbrew/pythons/Python-2.6.8/bin/python

[testenv:py27]
basepython={homedir}/.pythonbrew/pythons/Python-2.7.3/bin/python

他同時會幫你用 flake8 檢查程式碼,再配合 nose-cov 幫你產生 cobertura 的 coverage report,以及 xunit 的測試結果,跟 jenkins 接在一起就會像是

不過,如果你是放在 github 上,也許直接用 travis-ci 會簡單的多…

Filter is good

Java

import org.apache.commons.collections.*;
CollectionUtils.filter(list, new Predicate() {
  public boolean evaluate(Object o) {
    return !((String)o).isEmpty();
  }
});

PHP

$list = array_filter($list, function($s) {return !empty($s);});

Python

l = [x for x in l if x]

JavaScript

l = _.filter(l, function(s){return s.length;});

憑著直覺寫的,如果不能動的話再說 XD
這種寫法通常好讀又簡單,不過有些語言要寫的囉嗦一點…

用 Microsoft Translator API 翻譯 properties 檔案

因為另外一套要錢,所以來用 Microsoft Translator API

#!/usr/bin/env python

# coding: utf-8

import urllib
import sys
import xml.dom.minidom

# Get Bing AppID from https://ssl.bing.com/webmaster/developers/appids.aspx
BING_APPID = 'KERO~'

FILE_FROM = 'lang.properties'
FILE_TO = 'translated.properties'

LANG_FROM = 'en'
LANG_TO = 'zh-chs'

def translate(text, from_lang, to_lang):
    base_url = 'http://api.microsofttranslator.com/v2/Http.svc/Translate?'
    data = urllib.urlencode({'appId':BING_APPID,
                             'from': from_lang.encode('utf-8'),
                             'to': to_lang.encode('utf-8'),
                             'text': text.encode('utf-8')
                            })

    url = base_url + data
    response = urllib.urlopen(url).read()

    dom = xml.dom.minidom.parseString(response)
    result = dom.documentElement.childNodes[0].nodeValue

    return result.encode('utf-8')

def parse_properties(filename):
    langs = {}
    with open(filename, 'r') as f:
        lines = [ line.strip() for line in f.readlines() ]
        for line in lines:
            idx = line.find('=')
            if idx == -1:
                continue

            (key, val) = (line[:idx], line[idx+1:])
            if not key:
                continue

            langs[key] = val

    return langs

def main():
    langs = parse_properties(FILE_FROM)
    translated = { k: translate(v, LANG_FROM, LANG_TO) if v else ""
                   for k,v in langs.items() }

    with open(FILE_TO, 'w+') as f:
        for k,v in sorted(translated.items()):
            f.write("%s=%s\n" % (k,v))

if __name__ == '__main__':
    main()

隨手寫寫,一年沒寫文章,也許有點刻意,差不多該是改變的時候了。

Use VERSIONER_PYTHON_PREFER_32_BIT=yes in Snow Leopard

In python, you can use ctype (dl is deprecated now) to load dynamic link library. In 10.4 and 10.5 it may work fine, but it may occur errors in 10.6, such as

/Library/Frameworks/dummy.framework/dummy: no matching architecture in universal wrapper

Since the python in Snow Leopard is 64-bit in default, for those libraries which does not support 64-bit. You can test your library by

% file /Library/Frameworks/dummy.framework/Versions/Current/dummy

If you library is i386 only, you may need to handle it in this way.

% export VERSIONER_PYTHON_PREFER_32_BIT=yes
% python your_script.py

ericsk has also mentioned this for wxPython in his plurk

For more information, you can read the man page in your Snow Leopard.

Using minimock for Python unit testing

我也忘了什麼時候開始習慣作 unit testing 了,可能是前一個專案在 Xcode 上就開始學著寫一點測試了。

這次在閱讀 Writing Testable Code 對於 6. Static methods: (or living in a procedural world),格外有感覺。

尤其是修改別人的程式碼的時候,如果他總是直接呼叫 static method,不讓你用 mock object 把 implementation 換掉的話,這樣在測試起來就顯得麻煩的多。舉例來說,有一個 class method 會呼叫 SSHHelper.execute_ssh 這個指令去遠端執行一些東西,但這對 unit testing 而言就是一種外部的 dependency,你絕對不希望他真的在測試的時候跑出個 ssh 真的連過去執行些什麼吧。

剛好這次用 MiniMock 作我們的 mock library,因為他的因素,你就可以這樣作

minimock.mock('SSHHelper.execute_ssh', returns=ssh_output)

如此一來,execute_ssh 這個 method 的結果就可以簡單的換成你預期的 ssh_output。

如果是本來就有比較好的設計的話,你也可以用 minimock.Mock (注意大小寫),去產生一個 mock object 出來。

mock_fs = minimock.Mock('MockFileSystem')
mock_fs.size.mock_returns = 1
mytools.fs_imp = mock_fs

如此一來 mock 就顯得簡單多了……

話說回來,之前想寫的 objective-c 跟 DTrace 的 topic 真的都忘的差不多了,算了,以後再說吧,也許之後把 python 的 Exception Chaining and Embedded Tracebacks 的想法整理一下再丟出來吧。

一個 boost::exception 的例子

因為覺得 dprintf 出現的次數實在太多了,我會希望在 exception 就可以包好 __FILE__ 跟 __LINE__,又可以把 std::wstring 包在 exception 一起丟出去,在 catch 的時候再印出來就好。

所以試著寫了這樣一個例子,結果寫的四不像,跟原本想的東西完全部不同了,而且我對於那個 BUFSIZ 相當不滿啊。其實原本希望可以用 ##__VA_ARGS__ 這種 macro 直接吃進來變成一個 boost::any[],然後再想辦法轉成 boost::format 可以用的格式,中間還可以透過 BOOST_FOREACH 來玩的,不過我徹底失敗了,所以最後的成果大家笑笑就好了。 Continue reading “一個 boost::exception 的例子”

從 inet_ntoa 看 thread safe 的 API

這周 refactor 一段 code 之後,被同事說:「為什麼 log 的來源 IP 跟目的地 IP 總是一樣呢?」

char *szDestIp = inet_ntoa(destAddr);
char *szSrcIp = inet_ntoa(srcAddr);

後來仔細想了一下 inet_ntoa 的 prototype,我就明白了!如果這個 function 不必特意去 free 回傳的 pointer,那可能在內部有一個 static buffer 去保存這個值。果不期然,FreeBSD 的原始碼是這樣實作的

/*const*/ char *
inet_ntoa(struct in_addr in) {
        static char ret[18];

        strcpy(ret, "[inet_ntoa error]");
        (void) inet_ntop(AF_INET, &in, ret, sizeof ret);
        return (ret);
}

char *
inet_ntoa_r(struct in_addr in, char *buf, socklen_t size)
{
        (void) inet_ntop(AF_INET, &in, buf, size);
        return (buf);
}

到這邊我們大概就知道,inet_ntoa 並不是一個 thread safe 的 function,你可以用下面這段程式測一下會發生什麼事情。

void* thread_func (void *param)
{
    char * addr= (char *)param;
    struct in_addr val;
    inet_aton(addr, &val);

    while (1)
        printf("%s => %s\n", addr, inet_ntoa(val));

    pthread_exit(NULL);
}

int main()
{
    pthread_t thr1, thr2;
    pthread_create(&thr1, NULL, thread_func, (void *)"127.0.0.1");
    pthread_create(&thr2, NULL, thread_func, (void *)"127.0.0.2");

    while(1) {
        sleep (1);
    }

    return 0;
}

輸出大概會像

127.0.0.2 => 127.0.0.1
127.0.0.1 => 127.0.0.2
127.0.0.1 => [inet_ntoa error]
127.0.0.2 => 127.0.0.2

所以,直接使用 inet_ntoa 在 reentrant 上可能會發生問題,如果你必須面對這些問題,最好的方式就是使用 inet_ntoa_r 這個 reentrant 的版本,或者直接使用 inet_ntop。

而這難道非得要踩到地雷,或者從 API 的細節才能窺知這一切嗎?其實並不完全是,當你發現回傳的是一個 char *,又沒有要求你特別 free 他,你是不是就該猜測他是回傳 function 內部的一個 static buffer 了呢?所以,舉凡 ctime, asctime 都不保證是 thread safe 的,這是我們 programmer 該留意的細節。

所以,也許我們以後需要更小心的去寫程式,在選擇 API 時也必須留意更多細節,以免未來踩到更多地雷……。