用 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

從 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 時也必須留意更多細節,以免未來踩到更多地雷……。

以 boost::posix_time 處理時間字串

其實標題並不是那麼準確,這點我們之後再談。

其實起源很簡單,我們常常需要從各式各樣的時間字串轉成 time_t 或者 struct tm,要不然還可能是 FILETIME 跟 SYSTEMTIME 之類的結構。個人覺得 parsing 時間是一件難搞的事情,尤其是當你還要考慮 timezone 的影響,一切都變得不是那麼簡單了。

首先我必須要說,在 Windows 中沒有 strptime(3) 這樣還算好用的東西可以用,所以我又把腦袋動到 boost 上面去了。

我的目標是正確的處理「Sat, 28 Mar 2009 20:18:32 +0800」這樣的字串,然後把他轉成 time_t。我們會分兩個部份處理,首先我們先處理「Sat, 28 Mar 2009 20:18:32」這個部份,因為目前的 %q 只能處理輸出部份,請參考 Date Time Formatter/Parser Objects

    using namespace boost::posix_time;
    using namespace boost;
    using namespace std;

    std::string strDateTime = "Sat, 28 Mar 2009 20:18:32 +0800";
    std::string format = "%a, %d %b %Y %H:%M:%S";

    ptime pt(not_a_date_time);

    time_input_facet * input_facet = new time_input_facet(format);

    stringstream ss(strDateTime);
    ss.imbue(locale(ss.getloc(), input_facet ));

    ss >> pt;

這樣的確很簡單吧,只要按照文件寫的把 format 寫好,boost 就能幫你從 stringstream 的字串轉成 posix_time。

而 timezone 的部份就需要噁心一點的作法了。

    std::string strHour, strMinute;
    ss >> setw(3) >> strHour >> setw(2) >> strMinute;

    time_duration td(lexical_cast< int >(strHour), lexical_cast < int > (strMinute), 0);
    pt -= td;

我利用 setw 分別把 timezone 的小時與分抓出來,並轉成 time_duration,讓 posix_time 能直接減掉 timezone 的 offset 成為 GMT time,之後再做點手腳就可以變成 time_t 了。

嚴格來說,我想我是透過 time_input_facet 來處理字串的,希望這點小技巧對於需要 parsing 時間字串的人能有所幫助。

p.s: 其實這篇三個禮拜前就準備好了,只是一直拖到現在才把他丟出來 XD

初探 TR1 function object

最近的專案常常需要更新 INI 設定檔,而最麻煩的事情莫過於太多零散的設定,你必須要記住 INI 檔設定的 key,再把在某個 object 的 member function 把值抓出來,最後再丟給 WritePrivateProfileString 之類的 function 把值更新進檔案。

所以常常就會寫出一堆噁心的 code 像是。

    obj.iniKey = "INI_SERVER";
    obj.iniValue = info.GetServer();
    UpdateByObj(obj);

這樣的話,有幾個設定就要寫幾次,所有的 Key 跟 Value 的相關性都顯得零散,未來要加上新的設定,也非得要再加上類似的 code 才行……。

忽然我就靈機一動,也許可以用 tr1::function 跟 tr1::bind 來做這樣的事情。

    struct proxyConfig
    {
        std::string iniKey;
        std::tr1::function<std::string()> func;
    };

    const proxyConfig configArray [] =
    {
        { "INI_SERVER", std::tr1::bind(&proxyInfo::GetServer, &info) },
        { "INI_PORT",   std::tr1::bind(&proxyInfo::GetPort, &info) },
        { "INI_USER",   std::tr1::bind(&proxyInfo::GetUser, &info) },
        { "INI_PASS",   std::tr1::bind(&proxyInfo::GetPass, &info) },
    };

    for (size_t i = 0; i < sizeof(configArray)/sizeof(configArray[0]); ++i)
    {
        obj.iniKey = configArray[i].iniKey;
        obj.iniValue = configArray[i].func();
        UpdateByObj(obj);
    }

靠著把 INI 的 key 跟 member function 建表就顯得簡單一點,並把更新的程式碼簡化成一份,未來如果要新增不同的設定,也只要更動表中的 INI key 跟所綁定的 function 就好。

不過後來仔細想想,其實只要在建表的時候寫成 info.GetServer() 就可以滿足我的需求了。

而 tr1::function 與 tr1::bind 絕對不是只有這點小技倆而已,真正使用的巧妙應該是 C++ Function Objects in TR1 所提及那種彈性而優雅地將 function 包裹成物件,能有改變的彈性,也能應時應地做出適合的調整。

如果你的編譯器還不支援 tr1,除了換一個之外,你還可以考慮一下 boost,也許更瞭解一點,會讓你的生活變得更好歐。

C++ Format String using boost::format

如果你只想寫純粹的 C,那你可以忽略這篇的資訊了,因為 sprintf 之類的 function 大概就可以滿足你了。那為什麼還要提到 C++?我想 C++ 的 string 對於程式設計者是美好的,因為我們不用擔心 buffer 到底需要多長,我們可能是無腦的利用 operator += 去操作這個字串,並利用提供的 substring 等 function 快速的開發軟體。

但在我甚少的 C++ 開發經驗中,我總覺得透過 operator += 以及 stringstream 等東西,並沒有辦法像 sprintf 那樣直覺而優雅的將變數置換至字串當中。

就以 Windows 常見的 INI 格式來說,我們如果要輸出一個 AppName,像是 [MyApp],可能的作法是

    output += '[';
    output += strSection;
    output += ']';

這似乎比傳統的 sprintf 要顯得複雜的多,也顯得不好閱讀……

    sprintf(output, "[%s]", strSection);

現在你可以考慮 boost::format

    std::string output = boost::str(boost::format("[%s]") % strSection);

讓你寫的 C++ 能有 sprintf 的優雅性,又不必太過於擔心 buffer 的操作。

至於一些 manipulators,與詳細的 formatting 的用法,就請你自己去看一下官方的說明吧。此外,我覺得 python 的 formatting string 能吃 dictionary 也實在是很棒,而且很優雅的寫法。

    print "%(key)s %(key)s" % {'key': 'yoyo'}

在 boost::format 雖然只能這樣寫,但也聊勝於無了。

    std::cout < < boost::format("%1% %1%") % "yoyo";

某些情況下,你會考慮用 boost::lexical_cast,而捨棄 stringstream 等,不過也容許我提醒你一下,boost 會讓 object 變得肥上不少,至於怎麼取捨,就端看你的選擇了。

使用 sysexits.h 定義的 EX_ 變數

以前我們常常在程式裡面直接呼叫 exit, 並搭配一個自己「想要」的變數作為程式的 return code,像是

exit(-1)

這樣的壞處是,我們不知道 0 代表什麼,-1 又代表什麼,這樣一來可讀性也會顯得比較差了一點。今天在無意之間看到 FreeBSD 的 lockf,他使用了 sysexits(3),在這邊節錄一下 man page 的敘述。

According to style(9), it is not a good practice to call exit(3) with
arbitrary values to indicate a failure condition when ending a program.
Instead, the pre-defined exit codes from sysexits should be used, so the
caller of the process can get a rough estimation about the failure class
without looking up the source code.

現在你可以改呼叫

exit(EX_USAGE)

這樣是不是好讀的多呢?