Genshiのtag面白いね

TracでWikiMacroを作る場合はWikiMacroBaseを継承してexpand_macroを実装します。

本家にあるサンプルだとこうですね。

from datetime import datetime
# Note: since Trac 0.11, datetime objects are used internally

from genshi.builder import tag

from trac.util.datefmt import format_datetime, utc
from trac.wiki.macros import WikiMacroBase

class TimeStampMacro(WikiMacroBase):
    """Inserts the current time (in seconds) into the wiki page."""

    revision = "$Rev$"
    url = "$URL$"

    def expand_macro(self, formatter, name, text):
        t = datetime.now(utc)
        return tag.b(format_datetime(t, '%c'))

returnのところにあるようにGenshiのtagでHTML構文を作ります。インタラクティブシェルだと以下のようになります。

>>> print tag.b('hoge')
<b>hoge</b>

タグをネストさせたり属性を指定する場合はこんな感じですね。

>>> print tag.a(tag.b('hoge'), href='a.html')
<a href="a.html"><b>hoge</b></a>

キーワード引数は最後に指定しないといけません。

>>> print tag.a(href='a.html', tag.b('hoge'))
  File "<stdin>", line 1
SyntaxError: non-keyword arg after keyword arg

で、このtagはどうなっているのかgenshi/builder.pyを見てみるとトップレベルで以下のようになっています。ElementFactoryクラスのオブジェクトですね。

tag = ElementFactory()

tag.b('hoge')と書けますが、ElementFactoryクラスのオブジェクトであるtagにはbなんてアトリビュートはありません。
どうやっているかというと__getattr__でフックしてElementオブジェクトを返しています。つまりHTMLのタグをアトリビュートとして用意しないでダイナミックに作っているわけです。

Elementの__call__は下記のようになっています。

    def __call__(self, *args, **kwargs):
        """Append any positional arguments as child nodes, and keyword arguments
        as attributes.
        
        :return: the element itself so that calls can be chained
        :rtype: `Element`
        :see: `Fragment.append`
        """
        self.attrib |= _kwargs_to_attrs(kwargs)
        Fragment.__call__(self, *args)
        return self

__call__は特殊メソッドで

インスタンスが関数として ``呼ばれた'' 際に呼び出されます; このメソッドが定義されている場合、x(arg1, arg2, ...) は x.__call__(arg1, arg2, ...) を短く書いたものに なります。

http://www.python.jp/doc/nightly/ref/callable-types.html

なので、tag.b('hoge')とtag.b.__call__('hoge')は同じです。tag.a(tag.b('hoge'), href='a.html')とtag.a.__call__(tag.b.__call__('hoge'), href='a.html')も同じですね。

pythonでは*argsのようにすればキーワード指定しない引数をいくつでも受け付けることができます。いわゆる可変長引数。ポインタじゃないよ。**argsとやればキーワード指定された未定義の引数をとれます。ポインタのポインタじゃないよ。詳しくはみんpy p173-174参照

__call__の引数のargsでネストしたタグを受け取りkwargsでタグの属性を受け取っているわけですね。この辺の引数の仕組みは呪文のようではありますがAPI設計者にとっては強力でしょう。

いやあ上記のようなHTMLのタグや属性の扱い方はずっとJavaをやってきた僕からすると新鮮ですね。面白い。