Pygments - formatterコンポーネント
初回公開:2018/10/14
最終更新:未
【 目次 】
formatterクラス
formatterクラスは次のクラスから派生してる。
- class pygments.formatter.Formatter(**options)
- lexerクラスと同様に、このクラスのコンストラクタはオプションを処理してから、基本クラスの__init __()を呼び出さなければならない。
基本クラスFormatterクラスはstyle, full そして titleオプションを認識する。
その派生クラスのformatterクラスがこれらを使用するかどうかは、formatterによって異なる。
すべてのformatterは以下のオプションをサポートする。
- encoding
- "utf-8"のようなエンコーディング名でなければならない。
これは、トークン文字列(Unicode文字列)を出力のバイト文字列に変換するために使用されます(デフォルト:None)。
fullオプションが指定されている場合は、ドキュメントフォーマットに適したエンコーディング宣言(例えば、HTMLのメタ コンテンツタイプディレクティブまたは LaTeXのinputencパッケージの呼び出し)で記述されます。
これが""またはNoneの場合、Unicode文字列は出力ファイルに書き込まれます。
ほとんどのファイルライクなオブジェクトはサポートしません。
たとえば、pygments.highlight()は、outfile引数を指定しないで呼び出された場合Unicode文字列を返し、Unicode 引数をwrite()にサポートするStringIO.StringIOオブジェクトを使用するため、エンコーディング がNoneに設定されたformatterを返します。
通常のファイルオブジェクトを使用すると動作しません。 - outencoding
- コマンドラインからPygmentsを使用すると、指定されたエンコードオプションがlexerとformatterに渡されます。
たとえば、入力エンコーディングを"guess"(推測)に設定する場合など、これは望ましいことではありません。
したがって、outencodingが導入された場合、formatterのエンコーディングをオーバーライドします。
formatterクラスのメソッド
- get_style_defs(arg='')
- このメソッドは、後続のハイライトテキストを定義するための文や宣言(例えばHTMLFormatterにおけるCSSクラス)を返す必要がある。
オプション引数argは、世代を変更するために使用でき、フォーマッタに依存します(コマンドラインで与えることができるため、標準化されています)。
このメソッドは、コマンドラインオプション-Sによって呼び出される。
argは-aオプションによってあたえられ、HTMLではCSSのクラス属性を指定するために使われる。
- format(tokensource, outfile)
- このメソッドは、tokenソースのiterableからトークンをフォーマットしなければならない、そしてフォーマットされたバージョンをファイルオブジェクトoutfileに書き込む。
Formatterのオプションは、どのように正しくトークンを変換するかをコントロールできる。
formatterクラスの属性
フォーマッタには、組み込みのルックアップメカニズムで使用される次の属性が必要である。
- name
- 人が読める形式で書式のフルネーム。
- aliases
- get_formatter_by_name()を使用するなど、リストからフォーマッタを検索するために使用できる短い一意の識別子のリスト。
- filenames
- このフォーマッタが出力を生成できるファイル名と一致するfnmatchパターンのリスト。
このリストのパターンは、すべてのフォーマッタ間で一意である必要があります。
HtmlFormatterのこれらの属性を出力してみると
from pygments.formatters import HtmlFormatter print(HtmlFormatter.name) print(HtmlFormatter.aliases) print(HtmlFormatter.filenames)
実行結果
HTML ['html'] ['*.html', '*.htm']
HtmlFormatter
おそらくほとんどの場合、HtmlFormatter以外のフォーマッターを使用する事は無いと思われる。
そのためここでは、HtmlFormatterにしぼってみていく。
HtmlFormatterクラス
上記の公式ドキュメントにはHtmlFormatterは以下のように記述されている。
class HtmlFormatter Short names: html Filenames: *.html, *.htm
トークンはHTML4として<div>
タグで囲まれた(ラップされた)<pre>
タグ内の <span>タグにフォーマットされる。
<div>内のCSSクラスはCssClassオプションによって設定される。
linenosオプションを設定するとtableとして表示される。
以下の例のように'<TABLE>'タグの内側にさらに'<PRE>'タグにラップされ、片方は行番号を含み,もう一方はコードを含んだ、1 つの行と 2 つのセルで構成される。
<div class="highlight" > <table><tr> <td class="linenos" title="click to toggle" onclick="with (this.firstChild.style) { display = (display == '') ? 'none' : '' }"> <pre>1 2</pre> </td> <td class="code"> <pre><span class="Ke">def </span><span class="NaFu">foo</span>(bar): <span class="Ke">pass</span> </pre> </td> </tr></table></div>
(明瞭さを高めるために空白が追加されています)。
ラップは、nowrapオプションを使用して無効にすることができます。
hl_linesオプションを使用して行のリストを指定して、これらの行をハイライト表示することができます。
fullオプションはスタイル定義を<style>
タグの内部、あるいはcssfileオプションが指定されていれば分離されたファイルに含んだ、完全なHTML 4ドキュメントを出力する。
tagsfileがctagsのインデックスファイルのパスに設定されているなら、それは名前からそれらに定義されたハイパーリンクを生成するために使かわれる。
lineanchorsを有効にし、-nオプションを指定してctagsを実行して、これを機能させる必要があります。
この機能を使用するために、PyPIからpython-ctagsモジュールをインストールする必要がある。
そうでなければ、RuntimeErrorが発生してしまうだろう。
HtmlFormatterのget_style_defs(arg=’‘)メソッドは、フォーマッタによって使われるCSSクラスのCSS規則を含む文字列を返えす。
引数argは、クラスの先頭に追加されるCSSセレクタを指定するために使用できる。
formatter.get_style_defs( 'td .code')を呼び出すと、次のCSSクラスが生成される。
td .code .kw { font-weight: bold; color: #00FF00 } td .code .cm { color: #999999 } ...
リストまたはタプルをget_style_defs()メソッドに渡して、トークンのプレフィックスを複数要求することもできる。
div.syntax pre .kw, pre.syntax .kw { font-weight: bold; color: #00FF00 } div.syntax pre .cm, pre.syntax .cm { color: #999999 } ...
HtmlFormatterの受け入れ可能な追加オプション
HtmlFormatterには以下のような受け入れ可能な追加オプションが指定できる。
- nowrap
- Trueに設定すると、トークンをすべてwrapしない。
たとえ<pre>
タグ内でさえもwrapしなくなる。、
これにより他のほとんどのオプションは無効になります(デフォルト:False)。
wrapしないとはどういう意味なんだろう?
てっきりcssのnowrap属性なのかと思ったが。
cssのnowrapには2種類の機能があり。
- white-space-スタイルシートリファレンス
nowrap
ソース中の連続する半角スペース・タブ・改行を、1つの半角スペースとして表示します。ボックスの大きさが指定されている場合にも、自動的に改行されません。 - <th><td> nowrap セル内の改行を禁止する -HTMLタグ辞典-
<th>タグ、<td>タグにnowrap属性を記述すると、セル内の改行を禁止することができます。
そもそも英語のwrapの意味は?
- wrapの意味 - goo辞書 英和和英
1 〈体・体の一部を〉(衣類・外套がいとう・毛布で)包む,くるむ(enclose)((up,around,about/in,with ...))
実際に以下のコードでnowrap属性がTrueとFalseの場合の出力されるHTMLコードの違いを比較してみると。
from pygments import highlight from pygments.lexers import get_lexer_by_name from pygments.formatters import HtmlFormatter code = 'print("Hello World")' lexer = get_lexer_by_name("python") with open("wrap.html", 'w') as outfile: highlight(code, lexer, HtmlFormatter(nowrap=False),outfile) formatter = HtmlFormatter(nowrap=True) with open("nowrap.html", 'w') as outfile: highlight(code, lexer, HtmlFormatter(nowrap=True),outfile)
nowrap属性がTrueの場合、コードの前後の以下のdiv,pre,spanタグが取り除かれてしまうようだ。
<div class="highlight"><pre><span></span>...</pre></div>
でも、このnowrapオプションってどんな場合に必要なのだろう?
- full
- フォーマッタに "完全な"ドキュメント、つまり完全な自己完結型ドキュメントを出力するように指示します(デフォルト:False)。
これは、言い換えるとstyles指定などを外部のcssファイルを使わずにhtmlファイルの内部に記述して、htmlのヘッダーも含んだhtmlファイルだけで完結できる出力をおこなうという事になる。
- title
- fullオプションをTrueに指定した場合に出力されるhtmlのheadのtitleタグ内のページタイトルを指定(デフォルト:「」)。
- style
- 使用するスタイルは、文字列かStyleサブクラスです(デフォルト: 'default')。
このオプションは、cssfile およびnoclobber_cssfileオプションが指定され、cssfileで指定されたファイルが存在する場合は 無効です。
- noclasses
- trueに設定すると、トークン
<span>
タグはCSSクラスを使用せず、インラインスタイルを使用します。
コードサイズが大きい場合は、出力サイズがかなり大きくなるため、これはお勧めできません(デフォルト:False)。 - classprefix
- トークン型は比較的短いクラス名を使用するため、独自のクラス名と衝突する可能性があります。
この場合、classprefixオプションを使用して、すべてのPygmentsが生成したトークンタイプのCSSクラス名の前に文字列を付けることができます。
このオプションはget_style_defs()の出力にも影響することに注意してください。
- cssclass
- コードをラッピングしている
<div>
タグのCSSクラス(デフォルト:'highlight')を指定。
このオプションを設定すると、get_style_defs()のデフォルトのセレクタ がこのクラスになります。
table'行番号を選択すると、ラッピングテーブルにはこの文字列のCSSクラスと'table'が追加されます。
デフォルトはそれに応じて'highlighttable'になります。
- cssstyles
- ラップする
<div>
タグのインラインCSSスタイル(デフォルト:'') - prestyles
<pre>
タグのインラインCSSスタイル(デフォルト:'')。
- cssfile
- fullオプションがtrueで、このオプションが指定した場合、cssのstyleを出力する外部ファイルの名前を指定しなければなりません。
ファイル名に絶対パスが含まれていない場合、ファイルのパスは、メイン出力ファイルのパス(相対パスが見つかる可能性がある場合)との相対パスであるとみなされます。
スタイルシートはHTMLファイルの代わりにこのファイルに書き込まれます。
- noclobber_cssfile
- cssfileが与えられ、指定したファイルが存在している場合、CSSファイルは上書きされません。
これにより、ユーザ指定のCSSファイルと組み合わせてフルオプションを使用することができます。
デフォルトはFalseです。 - linenos
- 'table'に設定すると、行番号は2つのセルを持つテーブルとして出力され、1つは行番号を含み、もう1つはコード全体です。
これはコピー&ペーストに適していますが、一部のブラウザやフォントの位置合わせに問題が生じる可能性があります。
'inline'に設定されている場合、行番号はコードを含む<pre>
タグに統合されます。
デフォルト値はFalseです。これは、行番号が全くないことを意味します。
注意:デフォルトの( "テーブル")行番号の仕組みでは、囲む<pre>
タグに明示的な行高さの CSSプロパティを指定しない限り、行番号とコードはInternet Explorerで異なる行の高さを持つことができます:行の高さ 125% )。
- hl_lines
- ハイライトされる行のリストを指定します。
- linenostart
- 最初の行の行番号(デフォルトは1)。
- linenostep
- 数n> 1に設定すると、n番目の行番号だけが印刷されます。
- linenospecial
- n> 0の数に設定すると、n番目の行番号ごとにCSSクラスに"special"(デフォルト:0)が指定されます。
- nobackground
- Trueに設定されている場合、フォーマッタはラッピング要素の背景色を出力しません(wrapping要素がない場合は自動的にFalseに設定されます(例:get_syntax_defsメソッドの引数なし ))(デフォルト:False)。
get_syntax_defsメソッドとはどのようなメソッドなのかはググってみたが情報が無い。
nobackgroundオプションはcssファイルやnoclassesオプション指定時のstyle属性に影響を与えるようで、単にnobackgroundオプションだけを指定しても出力結果は変わらないようだ。
- lineseparator
- この文字列は、コード行の間に出力されます。デフォルトでは"\ n"になります。これは
<pre>
タグ内の行を分割するのには十分ですが、HTML行の区切りを得るには"<br>"に設定します。
- lineanchors
- 空でない文字列、例えばfooに設定されている場合、フォーマッタは各出力行をfoo-linenumberという名前のアンカータグにラップします。
これにより、特定の行に簡単にリンクすることができます。
- linespans
- 空でない文字列、たとえばfooに設定されている場合、フォーマッタは各出力行をidのfoo-linenumberを持つspanタグにラップします。
これにより、javascript経由で簡単に行にアクセスできます。
- anchorlinenos
- Trueに設定すると、<a>タグに行番号がラップされます。linenosおよびlineanchorsと組み合わせて使用されます。
- tagsfile
- tagsfileオプションにctagsファイルへのパスが設定すると、それらの定義へのリンクを持つアンカータグに名前をラップする。
lineanchorsを使用し、tagsファイルに行番号を指定する必要があります(ctagsの-nオプションを参照)。 - tagurlformat
- ctags定義へのリンクを生成するために使用される文字列フォーマットパターン。
利用可能な変数は%(path)s, %(fname)sそして%(fext)s。
デフォルトでは空文字列になり、#prefix-行番号のリンクになります。 - filename
- filenameオプションを指定した場合と指定しない場合の生成されるhtmlを比較してみるとfilenameオプションにxyzを指定した場合、
<pre>
ブロックの前に以下のタグが挿入されるようだ。
<span class="filename">xyz</span>
HtmlFormatterとctags
HtmlFormatterにはctagsに関係するオプションtagsfileとtagurlformatが用意されている。
そこでctagsについて調べてみた。
Ctagsとは
さまざまなプログラミング言語で定義されているオブジェクト - 変数,関数,クラス,クラスのメンバ等がインデックス化されエディタ等のタグジェンプに利用できるツールのようだ。
サクラエディタ とctags
サクラエディタでもctagsを使ってタグジェンプをする事が可能なもよう。
pythonのctagsパッケージ
Pygmentsでもctagsを利用してHtmlFormatterのtagsfileオプションやtagurlformatオプションを指定する事でオブジェクトの定義へのリンクを生成できそうなので試してみようと思ったのだが。
tagsfileオプションのエラー
tagsfileオプションを指定するといかのようなエラーがでてしまった。
raise RuntimeError('The "ctags" package must to be installed ' RuntimeError: The "ctags" package must to be installed to be able to use the "tagsfile" feature.
どうやらctagsパッケージをインストールする必要があるようだ。
pythonのctagsパッケージを検索してみると
C:\Python34\Scripts> python -m pip search ctags django-livinglots-generictags - A set of helpers for creating Django template tags for models with generic relations. z3c.recipe.tag - Generate ctags from eggs for development. ckanext-semantictags - Associates local tags with semantic resources python-ctags - Exuberant Ctags indexing python bindings cucutags - Generates ctags for BDD .feature/behave steps pyctags - Python ctags interface. TracTags - Tags plugin for Trac python-ctags3 - Ctags indexing python bindings
python-ctagsあたりをインストールすれば良いのかな?
ところが、Python2、7にpython-ctagsをインストールしようとすると
C:\Python27\Scripts> pip install python-ctags error: Microsoft Visual C++ 9.0 is required (Unable to find vcvarsall.bat). Get it from http://aka.ms/vcpython27
どうやら、http://aka.ms/vcpython27から「Microsoft Visual C++ Compiler for Python 2.7」をダウンロードしてインストールする必要があるようだ。
わざわざVisual C++ 9.0をインストールするのも嫌だったのでpython-ctagsのインストールは断念。
どうせ、ctagsの機能は使う事はあまり無いだろうと自分に言い聞かせた。
pygments.formattersモジュールに定義されているAPI関数
formatterクラスのインスタンスを取得する。
- pygments.formatters.get_formatter_by_name(alias, **options)
- alias引数にFormatterクラスの別名リスト(Available formattersに記述されているFormatterのShort namesの値 - HtmlFormatter
の場合html
のみ)にふくまれる別名に該当するFormatterのサブクラスのインスタンスを返す。
インスタンス化時にoptions引数によってFormatterのオプションを指定する。
エイリアス名に該当するFormatterクラスが見つからない場合はpygments.util.ClassNotFoundが発生する 。 - pygments.formatters.get_formatter_for_filename(fn, **options)
- fnに一致するFormatterのFilenamesのリスト(HtmlFormatter
の場合*.html
,*.htm
)に含まれるファイル名のパターンを持つFormatterサブクラスのインスタンスを返します。
インスタンス化時にoptions引数によってFormatterのオプションを指定する。
エイリアス名に該当するFormatterクラスが見つからない場合はpygments.util.ClassNotFoundが発生する。
以下に、formatterクラスのインスタンスを取得する複数の例を示す。
import pygments.formatters # 直接formatterのコンストラクタを使ってHtmlFormatterクラスのインスタンスを取得 html_formatter = pygments.formatters.HtmlFormatter(); print(type(html_formatter)) # get_formatter_by_name関数の例 html_formatter = pygments.formatters.get_formatter_by_name("html"); print(type(html_formatter)) # get_formatter_for_filename関数の例 html_formatter = pygments.formatters.get_formatter_for_filename("*.html"); print(type(html_formatter)) html_formatter = pygments.formatters.get_formatter_for_filename("*.htm"); print(type(html_formatter))
実行結果
<class 'pygments.formatters.html.HtmlFormatter'> <class 'pygments.formatters.html.HtmlFormatter'> <class 'pygments.formatters.html.HtmlFormatter'> <class 'pygments.formatters.html.HtmlFormatter'> <class 'MyFormatter'>
- pygments.formatters.load_formatter_from_file(filename, formattername="CustomFormatter", **options)
- 指定されたファイルからロードされたFormatterサブクラスのインスタンスを、現在のディレクトリを基準にして返します。
このファイルには、formattername(デフォルトではCustomFormatter)という名前のFormatterクラスが含まれている必要があります。
このメソッドは入力ファイルに対してevalを実行するのと同じですので、ユーザーは入力に非常に注意する必要があります。
インスタンス化時にoptions引数によってFormatterのオプションを指定する。
Formatterをロードする際にエラーが発生した場合は、 ClassNotFoundが発生します。
Lexerのload_lexer_from_fileに相当するAPIと思われる。
load_formatter_from_fileの例を示す。
Formatterクラスを継承した空のクラスMyFormatterを記述したmy_lexer_class.pyというpythonのソースファイルを用意する。
my_formatter_class.py
from pygments.formatter import Formatter class MyFormatter(Formatter): pass
load_formatter_from_file関数を実行
load_formatter_from_file関数を実行
from pygments.formatters import load_formatter_from_file html_formatter = load_formatter_from_file('my_formatter_class.py', formattername="MyFormatter") print(type(html_formatter))
実行結果
<class 'MyFormatter'>
HTMLフォーマッタのサブクラス化
HTMLフォーマッタは、簡単にサブクラス化できるように構築され、出力HTMLコードをカスタマイズするようになりました。
format()メソッドは(1, line)のタプルが得られるgeneratorを返すself._format_lines()を呼び出す。
タプルの一つ目の要素1は、二つ目の要素lineがフォーマット後のソースコードの行であることを示している。
NOWRAPのオプションが設定されるなら、generatorは反復処理されHTMLの結果が出力される。
それ以外の場合、format()はself.wrap ()を呼び出し、ジェネレータを他のジェネレータとラップします。
_format_lines()によって生成されたいくらかのHTMLコードに対して追加するかもしれない。
後者によって生成された行を修正して、(1, line)の行に対してもう一度処理をおこなう。
および,または(0, html)に対して他のHTMLコードの前後の行を生み出す。
ソース行と他のコードとの区別により、ジェネレータを複数回ラップすることができる。
デフォルトのwrap()実装では<、div>
タグと<pre>
タグが追加される。
カスタムHtmlFormatterサブクラスは次のようになります。
class CodeHtmlFormatter(HtmlFormatter): def wrap(self, source, outfile): return self._wrap_code(source) def _wrap_code(self, source): yield 0, '<code>' for i, t in source: if i == 1: # it's a line of formatted code t += '<br>' yield i, t yield 0, '</code>'
これにより、書式設定された行が<code>
タグでラップされて(包まれて)、ソース行が<br>
タグを使って区切られます。
wrap()を呼び出した後、 format()は、もし関係するオプションが設定されていれば「行番号」と(あるいは)「fullドキュメント」ラッパーを追加する。
その後、ラップされたジェネレータによって生成されたすべてのHTMLが出力される。
公式ドキュメントを読んでもよくわからなかったので実際に以下のコードを使って、HtmlFormatterによるいHTML出力とHtmlFormatterをサブクラス化したCodeHtmlFormatterのHTML出力を比較してみる。
最初の<div class="highlight"><pre><span></span>
が<code>
に、最後の</pre></div>
が</code>
に置き換わっているのが確認できる。
また、フォーマット後のソースコード行の各行の最後に<br>
が追加されている。
デバッガーでi, tの値をウォッチしてみるとiには1がtにはフォーマット後のソースコードの行が格納されている。
これをみると_wrap_codeメソッドの最初の行のyieldで0と最初に挿入したいHTMLコードを,最後のyield行で0と最後に挿入したいHTMLコードを,そしてソースコード行はfor文の中でtの変数の内容を加工処理すれば良い事がわかる。
フォーマッタを自作する
公式サイトにある以下のドキュメントが参考になりそう。