trac のレポジトリブラウザで Delphi のコードを syntax highlighting

trac のレポジトリブラウザでソースコードに色付けをするには,下記の2つのコンポーネントのうちいずれかをインストールする必要があります。

ただ,後者は別プロセスを立ち上げるので重そう。前者を使うことにします。

SilverCity は rpmforge 等でも rpm はなさそうなので,自らセットアップします。このへんの手順は lapis25 さんのtracでソースコードの色つけと権限設定 - LAPISLAZULI HILL#diaryが詳しいです。ので省略。

で,Delphi (Pascal) についてですが,SilverCity がバックエンドで利用している Scintilla のコード上ではサポートされているのですが,残念ながら SilverCity のレベルでサポートされていません。SilverCityAddLanguage – The Trac Project の手順に沿って Pascal 用のコードを自分で書く必要があります*1

上記ドキュメントによると SilverCity を展開したツリーの下に最新版の Scintilla のコードを展開せよ,とのことになっていますが,なぜかうまくコンパイルできませんでした。幸い SilverCity 同梱の Scintilla コードに Pascal のレキシカルアナライザが含まれているので最新版は使いませんでした。

ということで,上記ドキュメントと手順が異なるので要所要所を書いておくと,

  1. SilverCity を展開
  2. python setup.py build で一度ビルドしておく
  3. 同梱の Scintilla ライブラリを使うので ScintillaConstants.py の再生成は必要ない
  4. PySilverCity/SilverCity/Pascal.py というファイルを YAML.py あたりをベースにでっちあげる
  5. SCLEX_YAMLSCLEX_PASCAL に置き換えればいいんだけど,Pascal のアナライザはレキシカルステートとして C のものを利用しているので SCE_YAML という文字列リテラルはすべて SCE_C に置き換え
  6. Keywords.py に pascal_keywordspascal_classwords を追加する(後述)
  7. LanguageInfo.py の do_registrationimport Pascal を追加
  8. css まわりはなぜかスクリプトがみつからなかったので放置。先ほど上げたように,Pascal の場合 C と同じ css class を利用するので変更する必要はない
  9. setup.py の py_modules"SilverCity.Pascal" を追加
  10. 再度 python setup.py build を行う
  11. sudo python setup.py install でインストール

と,これで Pascal 対応の SilverCity がインストールされました。Perl, HTML 等デフォルトで対応しているコードについては自動的に色がつきますが,Pascal については trac.ini を書き換える必要があります。

重要な部分だけ抽出すると,こんな感じ。

[mimeviewer]
mime_map = text/x-pascal:pas
silvercity_modes = text/x-pascal:Pascal:5

mime_map で拡張子と対応する mime-type を関連付けます。silvercity_modes に記述しておくと,指定された mime-type のものについてはたとえ Enscript がインストールされていたとしても SilverCity で色付けしてくれます。「mime-type:SilverCityの対応モジュール:品質優先度」の順になります。品質優先度,のところは私もよくわからないので適当に 5 とかしてます。

はてさて,これでようやく無事 Delphi のコードに色がつく……はずなのです。ですが私の場合,各ファイルの svn:mime-type を「text/plain; charset=Shift_JIS」としていたため*2Pascal ファイルとして認識してくれませんでした。なので,「text/x-pascal; charset=Shift_JIS」のようにする必要があります。つまり上記の mime_map で設定した mime-type を指定する必要があります。

以下,私のでっちあげた Pascal.py とか。

import HTMLGenerator
import Keywords
import Lexer
from DispatchHandler import DispatchHandler
from _SilverCity import find_lexer_module_by_id, PropertySet, WordList
from ScintillaConstants import SCLEX_PASCAL
import LanguageInfo

class PascalLexer(Lexer.Lexer):
    def __init__(self, properties = PropertySet()):
        self._properties = properties
        self._lexer = find_lexer_module_by_id(SCLEX_PASCAL)
        self._keyword_lists = [
            WordList(Keywords.pascal_keywords),
            WordList(Keywords.pascal_classwords),
                               ]
            
class PascalHandler(DispatchHandler):
    def __init__(self):
        DispatchHandler.__init__(self, 'SCE_C')

class PascalHTMLGenerator(HTMLGenerator.SimpleHTMLGenerator, PascalHandler):
    name = 'pascal'
    description = 'Pascal'

    def __init__(self):
        PascalHandler.__init__(self)
        HTMLGenerator.SimpleHTMLGenerator.__init__(self, 'SCE_C')
            
    def generate_html(self, file, buffer, lexer = PascalLexer()):
        self._file = file
        
        lexer.tokenize_by_style(buffer, self.event_handler)


pascal_language_info = LanguageInfo.LanguageInfo(
                'Pascal',
                 ['pas', 'dpr'],
                 ['.*?pascal.*?'],
                 [PascalHTMLGenerator]
            ) 

LanguageInfo.register_language(pascal_language_info)

んで,Keywords に追加する pascal_keywords と pascal_classwords は以下の通り(といってもウェブで拾ったものですが)。

pascal_keywords = \
    "and array asm begin case cdecl class const constructor " \
    "default destructor div do downto else end end. except exit " \
    "exports external far file finalization finally for function " \
    "goto if implementation in index inherited initialization " \
    "inline interface label library message mod near nil not " \
    "object of on or out overload override packed pascal private " \
    "procedure program property protected public published raise " \
    "read record register repeat resourcestring safecall set shl " \
    "shr stdcall stored string then threadvar to try type unit " \
    "until uses var virtual while with write xor"

pascal_classwords = \
    "array boolean char integer file pointer real set string " \
    "text variant write read default public protected private " \
    "property published stored"

*1:ということで仮に rpm が存在したとしても手ビルドする必要があるのでした

*2:Windows のコードですからそうしたくなりますよね