Pygments - Filterコンポーネント

初回公開:2018/10/14
最終更新:未

フィルタの機能によりキーワードの大文字変換等のソースコードを加工して出力する事ができる。

【 目次 】

Filters

レクサーからのトークンストリームをフィルタリングして、出力を改善または注釈付けすることができます。
たとえば、コメント内の特別な単語を強調表示したり、キーワードを大文字または小文字に変換してスタイルガイドなどを適用することができます。

フィルタを適用するには、レクサーのadd_filter()メソッドを使用します。

>>> from pygments.lexers import PythonLexer
>>> l = PythonLexer()
>>> # add a filter given by a string and options
>>> l.add_filter('codetagify', case='lower')
>>> l.filters
[<pygments.filters.CodeTagFilter object at 0xb785decc>]
>>> from pygments.filters import KeywordCaseFilter
>>> # or give an instance
>>> l.add_filter(KeywordCaseFilter(case='lower'))

add_filter()メソッドは、フィルタのコンストラクタへ転送されるキーワード引数を取ります。

登録されているすべてのフィルタのリストを名前で取得するには、既知のすべてのフィルタのイテレータを返すpygments.filtersモジュールのget_all_filters()関数を使用します。

独自のフィルターを作成したいなら、Write your own filterを参照。

組み込みのフィルター

pygmentsにはあらかじめ以下のFilterクラスが内臓されている

RaiseOnErrorTokenFilter

class RaiseOnErrorTokenFilter
Name: raiseonerror
レクサーがエラートークンを生成したときに例外を発生させます。

指定可能なオプション:

excclass : 例外クラス
発生させる例外クラスです。デフォルトはpygments.filters.ErrorTokenです。

VisibleWhitespaceFilter

class VisibleWhitespaceFilter
Name: whitespace
タブ、改行、スペースを可視文字に変換します。

指定可能なオプション:

spaces : 文字列またはbool値
これが1文字の文字列の場合、スペースはこの文字列で置き換えられます。
一方、bool値のTrueなら、スペースは(ユニコードMIDDLE DOT)置き換えられます。
Falseなら、スペースは置き換えられません。
デフォルトはFalseです。
tabs : 文字列またはbool値
スペースと同じですが、デフォルトの置換文字は`` (ユニコード RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK)です。
デフォルト値はFalseです。
注:これは、タブがすでに展開されているため、レクサーのtabsizeオプションが0以外の場合には機能しません。
tabsize : int
タブをこのフィルタで置き換える場合は(タブオプションを参照)、これはタブを展開する必要がある文字の総数です。デフォルトは8です。
newlines : 文字列またはbool値
(改行文字の置き換え)、スペースと同じですが、デフォルトの置換文字は (ユニコード PILCROW SIGN)です。
デフォルト値はFalseです。
wstokentype : bool値
trueの場合は、空白に特殊な空白トークンを指定します。
これにより、表示される空白のスタイルを異なるようにすることができます(例えば、グレー表示)が、背景色を混乱させる可能性があります。
デフォルトはTrueです。

サンプルコード

TokenMergeFilter

class TokenMergeFilter
Name: tokenmerge
レクサーの出力ストリームに同じトークンタイプの連続したトークンをマージします。

NameHighlightFilter

class NameHighlightFilter
Name: highlight
別のトークンタイプとして通常のName(およびName.*)トークンを強調表示します。

例:

filter = NameHighlightFilter(
    names=['foo', 'bar', 'baz'],
    tokentype=Name.Function,
)

これは関数として "foo"、 "bar"、 "baz"という名前を強調表示します。Name.Functionはデフォルトのトークンタイプです。

指定可能なオプション:

names : 文字列のリスト
異なるトークン・タイプを指定する必要がある名前のリスト。
デフォルトはありません。
tokentype : トークンタイプまたは文字列
名前の文字列を強調表示するために使用されるトークンタイプまたはトークンタイプ名を含む文字列。
デフォルトは Name.Functionです。

サンプルコード

GobbleFilter

class GobbleFilter
Name: gobble
Gobblesソースコード行(最初の文字を食べる)。

このフィルタは、コードの各行から最初のn文字を削除します。
これは、レクサーに供給されるソースコードが、出力に望ましくない固定量のスペースで字下げされている場合に役立ちます。

受け入れ可能なオプション:

n : int
削除する文字の数。

CodeTagFilter

class CodeTagFilter
Name: codetagify
コメントとドキュメントストリングに特殊なコードタグを強調表示します。

受け入れ可能なオプション:

codetags : 文字列のリスト
コードタグとしてフラグが立てられた文字列のリスト。デフォルトでは、XXX、TODO、BUG、NOTEが強調表示されます。

サンプルコード

KeywordCaseFilter

class KeywordCaseFilter Name: keywordcase

キーワードを小文字(lower)または大文字(upper)に変換するか、先頭の文字を大文字(capitalize)に変換します。 capitalizeは、たとえばパスカルコードを強調表示し、コードをスタイルガイドに適合させたい場合に便利です。

受け入れ可能なオプション:

case : 文字列 キーワードをケーシングに変換するケーシング。'lower'、 'upper'、'capitalize' のいずれかでなければなりません。デフォルトは'lower'です。

サンプルコード

フィルタの操作

pygmentsにフィルタを適用するにはlexerクラスのadd_filterメソッドを使ってfilterを追加するのが一般的である。
add_filterメソッドの引数にはfilter名やfilterクラスのインスタンを指定する事ができる。
filter名を指定する場合は

from pygments.lexers import get_lexer_by_name
from pygments.token import Name

lexer = get_lexer_by_name("python")
lexer.add_filter('whitespace',spaces=True,tabs=True,newlines=True)
lexer.add_filter('highlight',names=['add_func'],tokentype=Name.Function)

上記のコードはfilterクラスのインスタンスを使って以下のように記述する事もできる。

from pygments.lexers import get_lexer_by_name
from pygments.filters import VisibleWhitespaceFilter,KeywordCaseFilter,NameHighlightFilter
from pygments.token import Name

lexer = get_lexer_by_name("python")
lexer.add_filter(VisibleWhitespaceFilter(spaces=True,tabs=True,newlines=True))
lexer.add_filter(NameHighlightFilter(names=['add_func'],tokentype=Name.Function))

lexerのベースクラスLexerはC:\Python27\Lib\site-packages\pygments\lexer.pyに記述されている。
Lexerクラスのソースコードをのぞいてadd_filterメソッドの部分を抜き出してみると

def add_filter(self, filter_, **options):
    """
    Add a new stream filter to this lexer.
    """
    if not isinstance(filter_, Filter):
        filter_ = get_filter_by_name(filter_, **options)
    self.filters.append(filter_)

上記コードの最後の行のfiltersは何を意味するかというと、
これはLexerクラスのコンストラクタのソースで確認できる。

def __init__(self, **options):
    === 途中略 ===
    self.filters = []
    for filter_ in get_list_opt(options, 'filters', ()):
        self.add_filter(filter_)

Lexerクラスのインスタンスにはfilters属性が定義されていてデフォルトでは空のリストが設定されている。
つまり、add_filterメソッドが何をしているかというとlexerオブジェクトのfilters属性にリストのappendメソッドを使って要素を追加している事になる。

また、公式ドキュメントAvailable lexers — Pygmentsには記述されていないが、Lexerクラスのコンストラクタのコードを読み解くとLexerクラスのコンストラクタにおいてもfilterを指定できる事がわかる。

例えば以下のadd_filterメソッドを使ったコードは

from pygments.lexers import get_lexer_by_name
from pygments.token import Name

lexer = get_lexer_by_name("python")
lexer.add_filter('whitespace',spaces=True,tabs=True,newlines=True)
lexer.add_filter('highlight',names=['add_func'],tokentype=Name.Function)

lexerクラスのコンストラクタにfilterオブジェクトを指定して、以下のコードに置き換えられるであろう。

from pygments.lexers.python import PythonLexer
from pygments.filters import VisibleWhitespaceFilter,KeywordCaseFilter,NameHighlightFilter
from pygments.token import Name

lexer = PythonLexer(filters=[
        VisibleWhitespaceFilter(spaces=True,tabs=True,newlines=True),
        NameHighlightFilter(names=['add_func'],tokentype=Name.Function)
    ])

Lexerクラスにはfilterを追加するadd_filterメソッドは定義されているが、filterを削除するメソッドは定義されていないようだ。
しかし、add_filterメソッドがlexerオブジェクトのメンバーfiltersリストを操作している事を考慮すると、リストの要素を削除する関数を操作する事で、lexerクラスに追加されているfilterを削除する事ができそうだ。

from pygments.lexers import get_lexer_by_name
from pygments.token import Name

lexer = get_lexer_by_name("python")
lexer.add_filter('whitespace',spaces=True,tabs=True,newlines=True)
lexer.add_filter('highlight',names=['add_func'],tokentype=Name.Function)
print(lexer.filters)

del(lexer.filters[0])
print(lexer.filters)

実行結果

[<pygments.filters.VisibleWhitespaceFilter object at 0x03621750>, <pygments.filters.NameHighlightFilter object at 0x036C1B70>]
[<pygments.filters.NameHighlightFilter object at 0x036C1B70>]

前掲のadd_filterのソースをみるとget_filter_by_name関数を使ってfilterクラスののインスタンスを生成している。
公式ドキュメントThe full Pygments API — Pygmentsにはget_filter_by_name関数についての記述は無いがこの関数も使えそうである。

from pygments.filters import get_filter_by_name
from pygments.lexers import get_lexer_by_name
from pygments.token import Name

filter1=(get_filter_by_name('whitespace',spaces=True,tabs=True,newlines=True))
print(type(filter1))
filter2=(get_filter_by_name('highlight',names=['add_func'],tokentype=Name.Function))
print(type(filter2))
lexer = get_lexer_by_name("python",filters=[filter1,filter2])

print(lexer.filters)

実行結果

<class 'pygments.filters.VisibleWhitespaceFilter'>
<class 'pygments.filters.NameHighlightFilter'>
[<pygments.filters.VisibleWhitespaceFilter object at 0x03531930>, <pygments.filters.NameHighlightFilter object at 0x03531990>]

Write your own filter - 独自のフィルータを作成

独自のフィルタを書くことはとても簡単です。
Filterクラスをサブクラス化してフィルタメソッドをオーバーライドするだけです。
さらに、フィルタの動作を調整するために使用できるいくつかのキーワード引数を使用して、フィルタがインスタンス化されます。

フィルタのサブクラス化

例として、すべてのName.Functionトークンを通常のNameトークンに変換してより少ない色数の出力にするフィルタを作成します。

from pygments.util import get_bool_opt
from pygments.token import Name
from pygments.filter import Filter

class UncolorFilter(Filter):

    def __init__(self, **options):
        Filter.__init__(self, **options)
        self.class_too = get_bool_opt(options, 'classtoo')

    def filter(self, lexer, stream):
        for ttype, value in stream:
            if ttype is Name.Function or (self.class_too and
                                          ttype is Name.Class):
                ttype = Name
            yield ttype, value
lexer引数に関するいくつかの注釈:
それはレクサーのインスタンスである必要はないので、極めて混乱する可能性があります。
レクサーのadd_filter()関数を使用してフィルタが追加された場合、そのレクサーがフィルタに登録されます。
その場合、 lexerは、フィルタを登録しているレクサーを参照します。
これは、レクサーに渡されたオプションにアクセスするために使用できます。
Noneになる可能性があるため、もしアクセスする場合に常にチェックする必要があります。

上記コードのUncolorFilterクラスのfilterメソッドのコードを読み解くと、stream引数よりトークンのトークンタイプttypeとトークンの値valueのイテレータを得ることができる事がわかる。
その後のif文にてトークンタイプがName.Functionもしくは、class_tooオプションがTrueでありかつトークンタイプがName.Classの場合、トークンタイプをNameに変換している。
これにより、class_tooオプションの値に関わらずトークンタイプがName.Functionのトークン(関数名)はトークンタイプName(変数名)と同じ属性(色など)で表示される事になる。
また、class_tooオプションの値がTrueの場合にはさらにクラス名も変数名と同じ属性で表示される事になる。

UncolorFilterによるコードハイライトの例

デコレータを使う

pygments.filterモジュールのsimplefilterデコレータを使用することもできます。

from pygments.util import get_bool_opt
from pygments.token import Name
from pygments.filter import simplefilter

@simplefilter
def uncolor(self, lexer, stream, options):
    class_too = get_bool_opt(options, 'classtoo')
    for ttype, value in stream:
        if ttype is Name.Function or (class_too and
                                      ttype is Name.Class):
            ttype = Name
        yield ttype, value

デコレータは、内部フィルタクラスを自動的にサブクラス化し、装飾された関数をフィルタリングのためのメソッドとして使用します。(そのため、 おそらくこのメソッドでは使用することが無いself引数も含まれている。)

ページのトップへ戻る