接頭辞かどうか判定する

Data.ListモジュールのisPrefixOfを使います。

$ ghci
GHCi, version 6.12.2: http://www.haskell.org/ghc/  :? for help
Loading package ghc-prim ... linking ... done.
Loading package integer-gmp ... linking ... done.
Loading package base ... linking ... done.
Loading package ffi-1.0 ... linking ... done.
Prelude> :module Data.List
Prelude Data.List> "foo" `isPrefixOf` "foobarbazquux"
True

Yog 0.0.6リリース

Yog 0.0.6をリリースしました。

Yogは、PythonRubyの真ん中を目指した軽量スクリプト言語です。

主な変更点

libffi (http://sourceware.org/libffi/) により、YogからCのコードを呼び出せるようになりました。

特徴

必要なもの

CPU

バグ管理

バグなどは、http://neko-daisuki.ddo.jp/~SumiTomohiko/yog/issues/index.htmlで見れます。
オリジナルのデータは、ソースのissuesディレクトリにあります。

ダウンロード

以下からダウンロードできます。

ドキュメント

ドキュメントは、以下にあります。

ICUの正規化のサンプル

UnicodeのライブラリであるICU (http://site.icu-project.org/) の正規化を試してみました。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unicode/umachine.h>
#include <unicode/unorm2.h>
#include <unicode/ustring.h>
#include <unicode/utf.h>

#define array_sizeof(a) (sizeof(a) / sizeof(a[0]))

static void
utf8_to_hex(char* dest, const char* src)
{
    const char* p;
    char* q = dest;
    for (p = src; *p != '\0'; p++) {
        sprintf(q, "\\x%02x", 0xff & (*p));
        q += 4;
    }
}

static void
normalize(const char* name, UNormalization2Mode mode)
{
    UChar32 ch;
    for (ch = 0; ch < 0x110000; ch++) {
        if (!U_IS_UNICODE_CHAR(ch)) {
            continue;
        }
#define CHECK_ERROR(name) do { \
    if (U_FAILURE(e)) { \
        fprintf(stderr, "%s failed - %s\n", name, u_errorName(e)); \
        exit(1); \
    } \
} while (0)
        UChar32 utf32_src[2];
        utf32_src[0] = ch;
        utf32_src[1] = 0;
        UChar utf16_src[1024];
        UErrorCode e = U_ZERO_ERROR;
        u_strFromUTF32(utf16_src, array_sizeof(utf16_src), NULL, utf32_src, -1, &e);
        CHECK_ERROR("u_strFromUTF32");
        char utf8_src[1024];
        u_strToUTF8(utf8_src, array_sizeof(utf8_src), NULL, utf16_src, -1, &e);
        CHECK_ERROR("u_strToUTF8");
        const UNormalizer2* norm2 = unorm2_getInstance(NULL, name, mode, &e);
        CHECK_ERROR("unorm2_getInstance");
        UChar utf16_dest[1024];
        unorm2_normalize(norm2, utf16_src, -1, utf16_dest, array_sizeof(utf16_dest), &e);
        CHECK_ERROR("unorm2_normalize");
        char utf8_dest[1024];
        u_strToUTF8(utf8_dest, array_sizeof(utf8_dest), NULL, utf16_dest, -1, &e);
        CHECK_ERROR("u_strToUTF8");
        if (strcmp(utf8_src, utf8_dest) == 0) {
            continue;
        }
        char hex_src[1024];
        utf8_to_hex(hex_src, utf8_src);
        char hex_dest[1024];
        utf8_to_hex(hex_dest, utf8_dest);
        printf("%s (%s) -> %s (%s)\n", utf8_src, hex_src, utf8_dest, hex_dest);
#undef CHECK_ERROR
    }
}

int
main(int argc, const char* argv[])
{
    printf("--- nfc, UNORM2_COMPOSE ---\n");
    normalize("nfc", UNORM2_COMPOSE);
    printf("--- nfc, UNORM2_DECOMPOSE ---\n");
    normalize("nfc", UNORM2_DECOMPOSE);
    return 0;
}

/**
 * vim: tabstop=4 shiftwidth=4 expandtab softtabstop=4
 */

上のコードをmain.cとした場合、

$ gcc -o normalize main.c -licuuc

でコンパイルできます。これを実行すると、

$ ./normalize
 --- nfc, UNORM2_COMPOSE ---
̀ (\xcd\x80) -> ̀ (\xcc\x80)
́ (\xcd\x81) -> ́ (\xcc\x81)
̓ (\xcd\x83) -> ̓ (\xcc\x93)
̈́ (\xcd\x84) -> ̈́ (\xcc\x88\xcc\x81)
ʹ (\xcd\xb4) -> ʹ (\xca\xb9)
; (\xcd\xbe) -> ; (\x3b)
· (\xce\x87) -> · (\xc2\xb7)
क़ (\xe0\xa5\x98) -> क़ (\xe0\xa4\x95\xe0\xa4\xbc)
ख़ (\xe0\xa5\x99) -> ख़ (\xe0\xa4\x96\xe0\xa4\xbc)
(略)
 --- nfc, UNORM2_DECOMPOSE ---
À (\xc3\x80) -> À (\x41\xcc\x80)
Á (\xc3\x81) -> Á (\x41\xcc\x81)
 (\xc3\x82) -> Â (\x41\xcc\x82)
à (\xc3\x83) -> Ã (\x41\xcc\x83)
Ä (\xc3\x84) -> Ä (\x41\xcc\x88)
Å (\xc3\x85) -> Å (\x41\xcc\x8a)
Ç (\xc3\x87) -> Ç (\x43\xcc\xa7)
È (\xc3\x88) -> È (\x45\xcc\x80)
É (\xc3\x89) -> É (\x45\xcc\x81)
Ê (\xc3\x8a) -> Ê (\x45\xcc\x82)
Ë (\xc3\x8b) -> Ë (\x45\xcc\x88)
Ì (\xc3\x8c) -> Ì (\x49\xcc\x80)
Í (\xc3\x8d) -> Í (\x49\xcc\x81)
(以下略)

のように、正規結合と正規分解の結果が表示されます。

起動時に画面表示する方法

私のUbunutu Linux 8.04は、起動時に画面に何も表示しませんが、以下のように修正したら、起動時の挙動が表示されるようになりました。

/boot/grub/menu.lstの中の、kernel行にある、"quiet splash"を削除します。

Ubuntu Linux 8.04を暗号化したときの追加の手順

Ubuntu Linux 10.04でハードディスクを暗号化する方法はid:SumiTomohiko:20100510:1273484111に書きましたが、同じことをUbuntu Linux 8.04でやると以下の手順が追加で必要になります。

/etc/modulesにdm-cryptを追加する。

インストール後、インストールした環境を/targetにマウントし、/target/etc/modulesに

dm-crypt

を追加します。

Ubuntu Linux 8.04を再インストールしたときに遭遇したトラブル

私は、Ubuntu Linux 8.04を使っていたのですが、ハードディスクを暗号化するために一旦クリーンインストールし、以前使っていたファイルをホームディレクトリにコピーしたのですが、そこでいくつかのトラブルに見回れました。以下に記述します。

アンダースコアが入力できない。

アンダースコア"_"が入力できなくなりました。解決方法は、https://forums.ubuntulinux.jp/viewtopic.php?id=2044にありました。

ウィンドウが表示されない。

メニューなどからアプリケーションを選択しても、ウィンドウが表示されません。端末からアプリケーションを起動すると、"MIT-MAGIC-COOKIE-1"に関するエラーがでました。これは、インストールされた直後のホームディレクトリ以下にある.Xauthorityに置き換えたら直りました。.Xauthorityが何者かは、そのうち勉強します(再インストールしたら変えなきゃいけいないものなの?)。

ぼやき

最初2年ぶりにLTSが出たのでUbuntu Linuxを新しくしようと思ったのですが、10.04では期待通りに動かず、その後9.10も試したのですが、これもうまく動かないところがあり、結局もとの8.04に戻しました。バカみたいですが、一応、ハードディスクは暗号化がかかるようになったので、少しは前進だと思います。が、これに4日もかけています。2年に一回のことでも、これは嫌ですね。8.04のサポート期間は2011年4月で終了するのですが、そのときまでに期待通りのUbuntuが出てくればいいのですが。

Ubuntu Linux 10.04で日本語入力できなかった

私はUbuntu Linux 10.04を、

  1. CDからクリーンインストールし、
  2. 8.04のときのホームディレクトリを新しい環境にコピーした

という手順でインストールしたのですが、これで日本語が入力できないという状態になりました。すなわち、

  • 「Shift + スペース」を押しても日本語入力できない。
  • 「半角/全角 漢字」キーを押すと、端末といったフォーカスを持っているウィンドウが固まり、数秒後元に戻るが、日本語は入力できない。

という状態になりました。

いろいろ試したところ、「システム - システム管理 - 言語サポート」メニューを選択して「言語とテキスト」画面を開き、「キーボード入力に使うIMシステム」を"scim-immodule"にしてログインし直したら、日本語入力できるようになりました。

Ubuntu Linux 10.04を、暗号化されたLVMにインストールする。

はじめに

ハードディスクをcryptsetupで暗号化しつつ、LVMで分割し、ノートPCにUbuntuをインストールしたときの記録です。以下を対象にしています。

ハートディスクの容量は80Gbyteです。

方針

ハードディスクを、以下のように分割します。

デバイス 容量[byte] マウントポイント 暗号化
/dev/sda1 500M /boot しない(できない)
/dev/sda2 11G / しない
/dev/sda3 残りすべて   する

/dev/sda3は、LVMで以下のように分割します。

デバイス 容量[byte] マウントポイント
/dev/mapper/vg0-swap 1G swap
/dev/mapper/vg0-tmp 500M /tmp
/dev/mapper/vg0-var 4G /var
/dev/mapper/vg0-home 残りすべて /home

手順

fdisk

CDで起動したのち、fdiskで/dev/sdaをsda1, sda2, sda3に分割します。

$ sudo fdisk /dev/sda

fdiskを終えたら、

警告: パーティションテーブルの再読込みがエラー16で失敗しました: Device or resource busy. カーネルはまだ古いテーブルを使っています。次回リブート時か、partprobe(8)またはkpartx(8)を実行した後に使えるようになるでしょう。

と出ました。partprobeをしても同じ警告がでいたので、rebootしました。

ハードディスクにランダムなデータを書き込む
$ sudo dd if=/dev/urandom of=/dev/sda3 bs=`sudo blockdef --getss /dev/sda3` count=`sudo blockdev --getsize /dev/sda3`

76Gバイトを処理するのに、60866.7sec(17時間くらい!)かかりました (1.2MB/sec) 。

暗号化

cryptsetupで、/dev/sda3を暗号化します。

$ sudo cryptsetup --cipher aes-cbc-essiv:sha256 --key-size 256 luksFormat /dev/sda3

これで暗号化でき、

$ sudo cryptsetup luksOpen /dev/sda3 crypt-sda3

すると、/dev/mapper/crypt-sda3によって/dev/sda3にアクセスできます。

LVM

CDで起動した環境に、lvm2パッケージをインストールします。

$ sudo aptitude -y install lvm2

これでLVMの設定ができます。

$ sudo pvcreate /dev/mapper/crypt-sda3
$ sudo vgcreate vg0 /dev/mapper/crypt-sda3
$ sudo lvcreate --size 1G --name swap vg0
$ sudo lvcreate --size 500M --name tmp vg0
$ sudo lvcreate --size 4G --name var vg0
$ sudo lvcreate --extents 100%FREE --name home vg0

終わったら、/dev/vg0/homeなどができています。

フォーマットとインストール

この時点でインストールできてもよさそうですが、インストーラがLVMをうまく認識してくれないようなので、手作業でフォーマットします。

$ sudo mkfs.ext4 /dev/sda1
$ sudo mkfs.ext4 /dev/sda2
$ sudo mkfs.ext4 /dev/mapper/vg0-swap
$ sudo mkfs.ext4 /dev/mapper/vg0-tmp
$ sudo mkfs.ext4 /dev/mapper/vg0-var
$ sudo mkfs.ext4 /dev/mapper/vg0-home

swapはmkswapできてもよさそうですが、これもインストーラがうまく認識しなかったので、いったんext4でフォーマットします(その上で、インストーラでswapに変更します)。

こののち、インストーラを起動して、インストールしました。

足りないパッケージのインストール

インストールされた環境には、lvm2パッケージとcryptsetupパッケージが含まれていないので、これをインストールします(cryptsetupパッケージは、CDで起動した環境には入っているのですが)。

$ sudo mount /dev/sda2 /target
$ sudo mount /dev/sda1 /target/boot
$ sudo mount /dev/mapper/vg0-home /target/home
$ sudo mount /dev/mapper/vg0-tmp /target/tmp
$ sudo mount /dev/mapper/vg0/var /target/var
$ sudo chroot /target
# aptitude -y install lvm2
# aptitude -y install cryptsetup
/etc/fstab

/etc/fstabを確認して、/dev/mapper以下のディレクトリが記載されているか確認します。

/dev/mapper/vg0-home /home           ext4    defaults        0       2
/dev/mapper/vg0-tmp /tmp            ext4    defaults        0       2
/dev/mapper/vg0-var /var            ext4    defaults        0       2
/dev/mapper/vg0-swap none            swap    sw              0       0
/etc/crypttab

/etc/crypttabに以下を記述して、起動時に暗号化されたパーティションが分かるようにします。

crypt-sda3 /dev/sda3 none luks
完了

ここで再起動すれば、インストールしたUbuntuが起動する、はずです。

所要時間

まる2日かかった。

雑感

本当は、/(正確には/etc)も暗号化したかったです。私はローカルのPostfixからSMTPサーバにアクセスしてメールを送信しているのですが、そのときに必要なパスワードがかかれたファイルが/etc/postfixにあるのです。これは見られたくないと思って一度/も暗号化しようとしたのですが、暗号化されたパーティションを読むのに必要なファイルが/の中にあるというジレンマになり、諦めました。こういう場合は、ファイルの本体を暗号化されたパーティションに置いて、シンボリックリンクをはっておけばいいんでしょうか。

Ubuntu Linux 10.04 on Dell LATITUDE x300

Ubuntu Linux 10.04をDell LATITUDE x300にインストールしようとして、CD-ROMから起動したが、画面が真っ黒のまま止まってしまった。調べたら、以下の問題だった模様。

ブート時のオプションにxforcevesaをつけたら起動できました。

2.1 The Basicsメモ

この記事は、libffi (http://sourceware.org/libffi/) 3.0.9の中にあるdoc/libffi.infoの"2.1 The Basics"の日本語訳っぽい個人的なメモです。

ffi_status ffi_prep_cif(ffi_cif* CIF, ffi_abi ABI, unsigned int NARGS, ffi_type* RTYPE, ffi_type** ARGTYPES)

  • CIFを初期化する。
  • ABIは、FFI_DEFAULT_ABIにしておく。
  • NARGSはこの関数が受け入れる引数の数。可変長引数は扱えない。
  • RTYPEはこの関数の戻り値を記述するffi_type構造体へのポインタ。
  • ARGTYPESはffi_typeポインタの配列。ARGTYPESはNARGSだけ要素がなければならない。NARGSが0ならば、引数は無視される。
  • ffi_prep_cifは、libffi終了コードを返し、これの型はffi_statusである。すべてがうまくいったら、FFI_OKになる。ffi_typeオブジェクトのひとつが間違っていたら、FFI_BAD_TYPEDEFになる。ABIパラメータが不正なら、FFI_BAD_ABIとなる。

void ffi_call(ffi_cif* CIF, void* FN, void* RVALUE, void** AVALUES)

  • 初期化されたffi_cifを使って関数を呼び出すときは、ffi_call関数を使う。
  • この関数は、CIFで与えられる記述にしたがって関数FNを呼び出す。
  • RVALUEは関数呼び出しの結果を保持するメモリ領域へのポインタである。これは結果を受け取るのに充分な大きさがなければならず、適切にアラインメントされていなければならない。これを保証するのは呼出側の責任である。CIFが、関数がvoidを返すと宣言している(ffi_type_voidを使って)場合、RVALUEは無視される。
  • AVALUESは、呼び出しのための引数の値を保持するメモリ領域へのポインタの配列である。CIFが、この関数は引数を必要としない(すなわち、NARGSが0)と宣言しているのなら、AVALUESは無視される。

全文検索エンジンo 0.0.1公開

これは何ですか?

oは、全文検索エンジンです。N-gramを使っています。UTF-8の文書を扱えます。

名前について

oと書いて「しゃりん」と読みます。oが車輪の形をしているので「しゃりん」です。またoには「\(^o^)/人生オワタ」という意味も込められています。

Shalingという名前のメーラが既にありますが (http://www.unixuser.org/~euske/python/shaling/), 間違えることはないと思います。

動作環境

  • x86
  • Ubuntu Linux 8.04
  • libz.so.1
  • libbz2.so.1.0
  • librt.so.1
  • libpthread.so.0
  • libm.so.6
  • libc.so.6

ドキュメント

以下の場所にドキュメントがあります。

問題管理システム

バグなどは、ditz (http://ditz.rubyforge.org/) で管理しています。

sennacmd公開

Sennaを(MySQLにパッチを当てるのではなく)試してみたいと思い、sennacmdというコマンドラインインターフェースを作成しました。sennacmdを使って、

  1. インデックスの作成
  2. 文書の登録
  3. 検索

ができます。

使い方

インデックスの作成
$ sennacmd init path-to-index

上のコマンドにより、path-to-indexにインデックスが作成されます。

文書の登録
$ cat path-to-document | sennacmd put path-to-index key

上のコマンドにより、path-to-documentの内容が、インデックスpath-to-indexに、文書ID keyとして登録されます。文書の内容は、sennacmdには標準入力から与えます。

文書の検索
$ sennacmd get path-to-index word

上のコマンドにより、インデックスpath-to-indexをwordで検索します。検索結果は、

文書ID スコア

という形式で出力されます。

ダウンロード

ダウンロードは、http://github.com/SumiTomohiko/sennacmdからできます。

ドキュメント

ドキュメントは、http://SumiTomohiko.github.com/sennacmd/にあります。

ActionPythonとDagon

身辺の整理をしていたら、以前作ったものの中断しているプロジェクトがありました。もうこれ以上私が手を加えるつもりはないので、概要とソースの場所だけ説明しておきます。興味のある方はこれらのプログラムを自由にして構いません。

ActionPython

ActionPythonは、ActionScriptで実装したPython, になる予定でした。Flex上でPythonが動くことを目指して作り始めたような気がします(うろ覚えなので、正しい言い方ではないかもしれません)。

プログラムは、パーサの部分までできています。PythonのコードをASTに変換することができます(ただし、with文は除きます)。

ASTをAVMのバイトコードにコンパイルするところからはできていません(要するに肝心なところができていない)。

ソースは、http://bitbucket.org/SumiTomohiko/actionpython/overviewにあります。

Dagon

Dagonは、issues管理システムです。Mercurialリポジトリにこっそり専用のブランチを作成し、そこに置くファイルで管理します。こうすることで、issues管理も分散できるようになることを狙って作られました(Gitにも同じ考えのソフトウェアがあったはずで、Dagonはそれを真似しているのですが、名前は忘れました)。

MercurialPythonで書かれているので、DagonはMercurial内部のクラスを操作します。特に長いコードではないので、以下に掲載します。

#!/usr/bin/env python
# -*- coding: utf-8 -*-

"""
File Structure: File structure must be easy to merge or must avoid conflicts.

repository root (dagon branch)
 `+- dagon
      `+- ticket directory (SHA1 name)
       |   `+- main
       |    |   * title
       |    |   * author
       |    |   * date
       |    |   * state (new, assigned, resolved, closed, reopened, not a bug)
       |    +- SHA1 name
       |    |   * author
       |    |   * date
       |    |   * body (multiline)
       :    :
"""

from hashlib import sha1
from optparse import OptionParser
from os.path import basename, dirname, join
from sys import argv, exit

from mercurial.cmdutil import match
from mercurial.context import memctx, memfilectx
from mercurial.error import LookupError
from mercurial.hg import repository
from mercurial.ui import ui
from mercurial.util import datestr, parsedate

branch_name = "dagon"
dagon_root = "dagon"

def hash(s1, s2):
    m = sha1()
    m.update(s1)
    m.update(s2)
    return m.hexdigest()

class Ticket(object):

    def __init__(self):
        self.id = ""
        self.title = ""
        self.user = ""
        self.date = None
        self.desc = ""
        self.assigned = ""
        self.status = ""

    @classmethod
    def from_data(self, data):
        ticket = Ticket()
        desc = []
        in_desc = False
        for s in data.split("\n"):
            if in_desc:
                if s == ".":
                    ticket.desc = "\n".join(desc).strip()
                    in_desc = False
                else:
                    if s.startswith("."):
                        s = s[1:]
                    desc.append(s)
                continue

            n = s.find(":")
            if n < 0:
                continue
            key = s[:n].strip()
            value = s[n + 1:].strip()
            if key == "title":
                ticket.title = value
            elif key == "user":
                ticket.user = value
            elif key == "date":
                ticket.date = parsedate(value)
            elif key == "description":
                desc = [value]
                in_desc = True
            elif key == "assigned":
                ticket.assigned = value
            elif key == "status":
                ticket.status = value

        return ticket

    def create_key_value(self, key, value):
        return "%(key)s: %(value)s" % locals()

    def description2data(self, desc):
        lines = []
        for line in desc.split("\n"):
            if line.startswith("."):
                s = "." + line
            else:
                s = line
            lines.append(s)
        lines.append(".")
        return "\n".join(lines)

    def to_data(self):
        data = []
        data.append(self.create_key_value("title", self.title))
        data.append(self.create_key_value("user", self.user))
        data.append(self.create_key_value("date", datestr(self.date)))
        data.append(self.create_key_value("assigned", self.assigned))
        data.append(self.create_key_value("status", self.status))
        data.append(self.create_key_value("desc", self.description2data(self.desc)))
        return "\n".join(data)

def assign_ticket_directory(ticket, head):
    if ticket.id != "":
        return ticket.id
    if head is None:
        return hash("", ticket.title)
    return hash(head, ticket.title)

def save_ticket(path, ticket):
    repos = repository(ui(), path=path)
    heads = repos.branchheads(branch_name)
    try:
        head = heads[0]
    except IndexError:
        head = None
    dir = assign_ticket_directory(ticket, head)
    log = "saved ticket %s" % (dir, )
    path = join(dagon_root, dir, "main")

    def filectxfn(repo, memctx, path):
        if ticket.user is None:
            ticket.user = ctx.user()
        if ticket.date is None:
            ticket.date = ctx.date()
        return memfilectx(path, ticket.to_data(), False, False, None)

    ctx = memctx(repos, (head, None), log, [path], filectxfn, extra={ "branch": branch_name })
    repos.commitctx(ctx)

def find_ticket(path, id):
    repos = repository(ui(), path=path)
    heads = repos.branchheads(branch_name)
    try:
        head = heads[0]
    except IndexError:
        return None;
    changeset = repos[head]

    for file in changeset.walk(match(repos)):
        name = basename(file)
        if name != "main":
            continue
        node = basename(dirname(file))
        if not node.startswith(id):
            continue
        data = changeset.filectx(file).data()
        ticket = Ticket.from_data(data)
        ticket.id = node
        return ticket

    return None

def do_list(path, args):
    repos = repository(ui(), path=path)
    heads = repos.branchheads(branch_name)
    try:
        head = heads[0]
    except IndexError:
        return;
    changeset = repos[head]

    tickets = []
    for file in changeset.walk(match(repos)):
        name = basename(file)
        if name != "main":
            continue
        id = basename(dirname(file))
        data = changeset.filectx(file).data()
        ticket = Ticket.from_data(data)
        ticket.id = id
        tickets.append(ticket)
    tickets.sort(key=lambda ticket: - ticket.date[0])
    for ticket in tickets:
        l = "%(id)s %(title)-47s %(status)-8s %(assigned)s" % { "id": ticket.id[:6], "title": ticket.title, "status": ticket.status, "assigned": ticket.assigned }
        print l.strip()

def help_new():
    print "dagon new <title>"

def do_new(path, args):
    parser = OptionParser()
    parser.add_option("-d", "--desc", dest="desc", help="description")
    (options, args) = parser.parse_args(args)
    desc = options.desc or ""

    try:
        title = args[0]
    except IndexError:
        help_new()
        exit(-1)

    ticket = Ticket()
    ticket.title = title
    ticket.desc = desc
    ticket.assigned = ""
    ticket.status = "new"

    save_ticket(path, ticket)

def help_assign():
    print "dagon assign <id> <name>"

def do_assign(path, args):
    try:
        id = args[0]
        name = args[1]
    except IndexError:
        help_assign()
        exit(-1)
    ticket = find_ticket(path, id)
    if ticket is None:
        raise Exception("can't find ticket of %(id)s" % { "id": id })
    ticket.assigned = name
    if ticket.status == "new":
        ticket.status = "assigned"
    save_ticket(path, ticket)

def help_status():
    print "dagon status <id> <status>"

def do_status(path, args):
    try:
        id = args[0]
        status = args[1]
    except IndexError:
        help_status()
        exit(-1)
    ticket = find_ticket(path, id)
    if ticket is None:
        raise Exception("can't find ticket of %(id)s" % { "id": id })
    ticket.status = status
    save_ticket(path, ticket)

def main(args):
    parser = OptionParser()
    parser.add_option("-R", "--repository", dest="path", help="repository path")
    (options, args) = parser.parse_args(args)
    path = options.path or ""
    try:
        cmd = args[0]
    except IndexError:
        parser.print_help()
        exit(-1)

    commands = { "list": do_list, "new": do_new, "status": do_status, "assign": do_assign }
    commands[cmd](path, args[1:])

    """
    def filectxfn(repo, memctx, path):
        print filectxfn
        return memfilectx(path, data + "42", False, False, None)

    repos = repository(ui(), path=path)
    heads = repos.branchheads(branch_name)
    try:
        head = heads[0]
        from mercurial.node import short
        print short(head)
        recent_changeset = repos[head]
        print recent_changeset.files()
        print type(recent_changeset)
        path = "dagon.dat"

        from mercurial.cmdutil import match
        for s in recent_changeset.walk(match(repos)):
            print s

        try:
            data = recent_changeset.filectx(path).data()
        except LookupError:
            print "lookup error"
            data = ""
    except:
        head = None
        data = ""
    # TODO: do command
    ctx = memctx(repos, (head, None), "update", [path], filectxfn, extra={ "branch": branch_name })
    repos.commitctx(ctx)
    """

main(argv[1:])

# vim: tabstop=4 shiftwidth=4 expandtab softtabstop=4 filetype=python

以上を含めたファイルは、http://bitbucket.org/SumiTomohiko/dagonにあります。