Pygments - Styleンポーネント

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

【 目次 】

フォーマッタにスタイルクラスのインスタンスを文字列形式のstyleオプションを指定して渡すことができる。

>>> from pygments.formatters import HtmlFormatter
>>> HtmlFormatter(style='colorful').style
<class 'pygments.styles.colorful.ColorfulStyle'>

あるいは独自のスタイル(pygments.style.Styleのサブクラスでなければならない)をインポートし、それをフォーマッタに渡すこともできる。

>>> from yourapp.yourmodule import YourStyle
>>> from pygments.formatters import HtmlFormatter
>>> HtmlFormatter(style=YourStyle).style
<class 'yourapp.yourmodule.YourStyle'>

スタイルのAPI

pygments.stylesモジュールにはスタイルのAPIとして以下の関数が定義されている。

pygments.styles.get_style_by_name(name)
スタイルの名前(short name)を指定する事で、そのstyleクラスのインスタンスを返します。
組み込みスタイルの名前はpygments.styles.STYLE_MAPにリストされています。
その名前のスタイルが見つからない場合、pygments.util.ClassNotFoundを上げます。

styleクラスにはlexerクラスやformatterクラスのようにname属性は定義されていないようで、この場合のname引数はPygmentsに登録されているstyle名という事になるようだ。

pygments.styles.get_all_styles()
登録されたすべてのスタイルに対してiterableなスタイル名を返す

get_all_styles関数の例については利用可能なスタイルのリストを取得するを参照。

組み込みのスタイル

Pygmentsには、Pygmentsチームが管理するHTMLとLaTeXフォーマッタの両方で動作するいくつかの組み込みスタイルが付属している。
組み込みスタイルは、get_style_by_name関数で参照できる。

>>> from pygments.styles import get_style_by_name
>>> get_style_by_name('colorful')
<class 'pygments.styles.colorful.ColorfulStyle'>

利用可能なスタイルのリストを取得する

既知のスタイルのリストを取得するには、次のスニペットを使用する。

>>> from pygments.styles import STYLE_MAP
>>> STYLE_MAP.keys()
['default', 'emacs', 'friendly', 'colorful']

プラグインがスタイルを登録している可能性があるので、すべてのスタイルを反復処理する方法がある。

>>> from pygments.styles import get_all_styles
>>> styles = list(get_all_styles())

独自のスタイルを作成する

スタイルを作成は、スタイルをサブクラス化していくつかのスタイルを定義するだけ。

from pygments.style import Style
from pygments.token import Keyword, Name, Comment, String, Error, \
     Number, Operator, Generic

class YourStyle(Style):
    default_style = ""
    styles = {
        Comment:                'italic #888',
        Keyword:                'bold #005',
        Name:                   '#f00',
        Name.Function:          '#0f0',
        Name.Class:             'bold #0f0',
        String:                 'bg:#eee #111'
    }

たったこれだけ。
少しのルールが存在するのみ。
Name のスタイルを定義すると、スタイルは自動的にName.Functionなどにも影響する。
'bold'を定義し、サブトークンの太字を使用したくない場合は、'nobold'を使用する。

(pygmentsのポリーシー:スタイルはCSS構文で書かれていないので、さまざまなフォーマッタに使用できる。)

default_styleは、すべてのトークンタイプによって継承されたスタイルです。

Pygmentsでスタイルを使用できるようにするには、

  • プラグインとして登録する。(Register Plugins — Pygmentsを参照)
  • もしくは、スタイルごとにPygmentsディストリビューションの1つのスタイルクラスのスタイルサブパッケージに入れます。
    ファイル名はスタイル名で、クラス名は スタイル名Classです。
    たとえば、スタイルを"mondrian"と呼びたい場合 は、クラス名をMondrianStyleとし、ファイル mondrian.pyに入れ、このファイルをpygments.stylesサブパッケージディレクトリに入れます。

スタイル規則

すべて許可されているスタイルの小さな概要

bold
太字のテキストにレンダリングします。
nobold
(太字強調されているsubtokensを防ぐために)太字としてテキストをレンダリングしません。
italic
斜体のテキストにレンダリングします。
noitalic
斜体のテキストにレンダリングしません。
underline
下線のテキストにレンダリングします。
nounderline
下線のテキストにレンダリングしません。
bg:
透明な背景
bg: #000000
背景色 (黒)
border:
枠なし
border:#ffffff
罫線の色 (白)
#ff0000
テキストの色 (赤)
noinherit
supertoken からスタイルを継承しません。
(公式ドキュメントに記述のあるsupertokenって何?,styleクラスの継承元クラスのトークン?)

スタイル定義の文字列がホワイトスペースで分割されないように、bg:とカラー値の間にスペースがない可能性がある事に注意。
サポートされているカラー名はフォーマッタによって異なるので名前付けされた色を使う事も許されない。
さらに、すべてのlexersがすべてのスタイルをサポートするわけではありません。

とここまでは、公式ドキュメントを訳してみたけど、独自のスタイルをどやって作成するのかピンとこなかってので、実際のコードがどうなっているのかと組み込みスタイルDefaultStyleクラスやEmacsStyleクラスのソースを覗いてみた。(C:\Python27\Lib\site-packages\pygments\stylesデレクトリのdefault.pyおよびemacs.py)

default.pyとemacs.pyの比較

default.pyとemacs.pyの比較

WinMergeでソースの比較をおこなってみると、default.pyとemacs.pyの内容はほとんど同じで辞書stylesの文字色や字体などの定義だけが多少異なっている事がわかる。

スタイルクラスとtokenタイプ

試しに以下のコードを使って、新しいstyleクラスを定義して、stylesのKeywordName.FunctionをDefaultStyleクラスと異なる値に変更してみる。

#coding: UTF-8

from pygments.style import Style
from pygments.token import Keyword, Name, Comment, String, Error, \
     Number, Operator, Generic, Whitespace

class MyStyle(Style):
    background_color = "#f8f8f8"
    default_style = ""

    styles = {
        Whitespace:                "#bbbbbb",
        Comment:                   "italic #408080",
        Comment.Preproc:           "noitalic #BC7A00",

        #Keyword:                   "bold #AA22FF",
        Keyword:                   "bold #FF0000",  # "#008000"を変更
        Keyword.Pseudo:            "nobold",
        Keyword.Type:              "nobold #B00040",

        Operator:                  "#666666",
        Operator.Word:             "bold #AA22FF",

        Name.Builtin:              "#008000",
        Name.Function:             "#00FF00",   # "#0000FF"を変更
        Name.Class:                "bold #0000FF",
        Name.Namespace:            "bold #0000FF",
        Name.Exception:            "bold #D2413A",
        Name.Variable:             "#19177C",
        Name.Constant:             "#880000",
        Name.Label:                "#A0A000",
        Name.Entity:               "bold #999999",
        Name.Attribute:            "#7D9029",
        Name.Tag:                  "bold #008000",
        Name.Decorator:            "#AA22FF",

        String:                    "#BA2121",
        String.Doc:                "italic",
        String.Interpol:           "bold #BB6688",
        String.Escape:             "bold #BB6622",
        String.Regex:              "#BB6688",
        #String.Symbol:             "#B8860B",
        String.Symbol:             "#19177C",
        String.Other:              "#008000",
        Number:                    "#666666",

        Generic.Heading:           "bold #000080",
        Generic.Subheading:        "bold #800080",
        Generic.Deleted:           "#A00000",
        Generic.Inserted:          "#00A000",
        Generic.Error:             "#FF0000",
        Generic.Emph:              "italic",
        Generic.Strong:            "bold",
        Generic.Prompt:            "bold #000080",
        Generic.Output:            "#888",
        Generic.Traceback:         "#04D",

        Error:                     "border:#FF0000"
    }


code=u"""
def add_func(a,b):
    return a+b

print add_func(2,3)
"""

from pygments import highlight
from pygments.lexers import get_lexer_by_name
from pygments.formatters import HtmlFormatter

lexer = get_lexer_by_name("python")
formatter = HtmlFormatter(full=True,style="default",encoding="utf-8")

with open('default.html', 'wb') as outfile:
    highlight(code, lexer, formatter,outfile)

formatter = HtmlFormatter(full=True,style=MyStyle,encoding="utf-8")
with open('my_style.html', 'wb') as outfile:
    highlight(code, lexer, formatter,outfile)

これをみると、python言語のキーワードや関数定義の関数名の表示を変更するにはKeywordName.Functionを変更すれば良いことがわかる。
KeywordName.Functionはpygments.tokenモジュールに定義されている。
pygments.tokenモジュールには以下のようなSTANDARD_TYPESという辞書が定義されている。

pygments.tokenモジュールより抜粋

STANDARD_TYPES = {
    Token:                         '',

    Text:                          '',
    Whitespace:                    'w',
    Escape:                        'esc',
    Error:                         'err',
    Other:                         'x',

    Keyword:                       'k',
    Keyword.Constant:              'kc',
    Keyword.Declaration:           'kd',
    Keyword.Namespace:             'kn',
    Keyword.Pseudo:                'kp',
    Keyword.Reserved:              'kr',
    Keyword.Type:                  'kt',

    Name:                          'n',
    Name.Attribute:                'na',
    Name.Builtin:                  'nb',
    Name.Builtin.Pseudo:           'bp',
    Name.Class:                    'nc',
    Name.Constant:                 'no',
    Name.Decorator:                'nd',
    Name.Entity:                   'ni',
    Name.Exception:                'ne',
    Name.Function:                 'nf',
    Name.Function.Magic:           'fm',
    Name.Property:                 'py',
    Name.Label:                    'nl',
    Name.Namespace:                'nn',
    Name.Other:                    'nx',
    Name.Tag:                      'nt',
    Name.Variable:                 'nv',
    Name.Variable.Class:           'vc',
    Name.Variable.Global:          'vg',
    Name.Variable.Instance:        'vi',
    Name.Variable.Magic:           'vm',

    Literal:                       'l',
    Literal.Date:                  'ld',

    String:                        's',
    String.Affix:                  'sa',
    String.Backtick:               'sb',
    String.Char:                   'sc',
    String.Delimiter:              'dl',
    String.Doc:                    'sd',
    String.Double:                 's2',
    String.Escape:                 'se',
    String.Heredoc:                'sh',
    String.Interpol:               'si',
    String.Other:                  'sx',
    String.Regex:                  'sr',
    String.Single:                 's1',
    String.Symbol:                 'ss',

    Number:                        'm',
    Number.Bin:                    'mb',
    Number.Float:                  'mf',
    Number.Hex:                    'mh',
    Number.Integer:                'mi',
    Number.Integer.Long:           'il',
    Number.Oct:                    'mo',

    Operator:                      'o',
    Operator.Word:                 'ow',

    Punctuation:                   'p',

    Comment:                       'c',
    Comment.Hashbang:              'ch',
    Comment.Multiline:             'cm',
    Comment.Preproc:               'cp',
    Comment.PreprocFile:           'cpf',
    Comment.Single:                'c1',
    Comment.Special:               'cs',

    Generic:                       'g',
    Generic.Deleted:               'gd',
    Generic.Emph:                  'ge',
    Generic.Error:                 'gr',
    Generic.Heading:               'gh',
    Generic.Inserted:              'gi',
    Generic.Output:                'go',
    Generic.Prompt:                'gp',
    Generic.Strong:                'gs',
    Generic.Subheading:            'gu',
    Generic.Traceback:             'gt',
}

どうやらcssのスタイル名とトークンとの関係はここに定義されているようだ。

スタイルクラスを簡潔に記述する

DefaultStyleクラスのスタイルの一部だけを変更する場合に、前項のMyStyleのようにわざわざStyleクラスのメンバーである辞書stylesの要素をすべて定義し直すのはコードが冗長すぎる気がする。
そこで、MyStyleクラスをもっと簡潔に記述する事を試みてみた。

from pygments.styles.default import DefaultStyle
from pygments.token import Keyword, Name

class SimpleMyStyle(DefaultStyle):
    styles = DefaultStyle.styles.copy()
    styles[Keyword]="bold #FF0000"  # "#008000"を変更
    styles[Name.Function]="#00FF00" # "#0000FF"を変更

このコードのキモは、変更したいスタイルクラスを継承して、基底クラスのstyles辞書をコピーして新しいstyles辞書を定義、変更したい辞書の要素だけを定義し直すことにある。
これにより、劇的にコードの量を少なくすることができる。(多少パフォーマンス等の問題はあるかも知れないが)

styleのcssファイルへの出力

スタイルをcssとして取り出すにはHtmlFormatterのget_style_defsメソッドを使う事は以前にも述べた。 ここでは、定義済みのスタイルemacsをcssファイルとして保存する例を示す。

from pygments.formatters import HtmlFormatter
css_text = HtmlFormatter(style="emacs").get_style_defs('.highlight')

print(css_text)

import io
with io.open('emacs.css', 'w',encoding='utf-8') as file:
    file.write(css_text)

ターミナルスタイル

使うことは無いと思うが、ついでなのでターミナルスタイルについても公式ページのドキュメントを訳してみる。

256色のターミナルフォーマッタで使用されるカスタムスタイルでは、デフォルトのANSIカラーを使用するように色をマッピングすることもできます。
これを行うには、# ansigreen、#ansiredまたはpygments.style.ansicolorsで定義されている他の色を使用します。
フォアグラウンドのANSIカラーは、対応するエスケープコード30〜37にマッピングされ、したがって、多くの端末エミュレータによって提供されるカスタムカラーマッピングおよびテーマを尊重します。
ライトバリアントはフォアグラウンドカラーとして扱われ、太字のフラグが追加されます。
また、bg:#ansi <color>も尊重されますが、ライトのバリアントはダークバリアントと同じシェードになります。

文字列"hello world"の色が、拡張されたフォアグラウンドおよびバックグラウンドカラーではなく、エスケープシーケンス\ x1b [34; 01m(Ansi Blue、Bold、41は赤い背景です)によって支配される。
以下の例を参照してください。

>>> from pygments import highlight
>>> from pygments.style import Style
>>> from pygments.token import Token
>>> from pygments.lexers import Python3Lexer
>>> from pygments.formatters import Terminal256Formatter

>>> class MyStyle(Style):
        styles = {
            Token.String:     '#ansiblue bg:#ansired',
        }

>>> code = 'print("Hello World")'
>>> result = highlight(code, Python3Lexer(), Terminal256Formatter(style=MyStyle))
>>> print(result.encode())
b'\x1b[34;41;01m"\x1b[39;49;00m\x1b[34;41;01mHello World\x1b[39;49;00m\x1b[34;41;01m"\x1b[39;49;00m'

#ansi *で指定された色は、ターミナル256フォーマッタ以外のフォーマッタで使用すると、デフォルトのRGBカラーセットに変換されます。
ANSIの定義により、以下の色は“light” colorsと見なされ、ほとんどの端末によって太字で表示されます。

  • darkgray”, “red”, “green”, “yellow”, “blue”, “fuchsia”, “turquoise”, “white”

以下は“dark” colorsとみなされ、太字で表示されません。

  • “black”, “darkred”, “darkgreen”, “brown”, “darkblue”, “purple”, “teal”, “lightgray”

正確な動作は、使用している端末エミュレータとその設定によって異なります。

ページのトップへ戻る