レポートを書こう

これは ISer Advent Calendar 2021 の18日目の記事です.

理学部情報科学科に所属しているのに紙のレポートを提出しているんですか?まさかね.

今回は私のレポート執筆環境の紹介です.全知の方向けに端的に言うと,「markdownを書き,pandocでfilterを適用しつつ.texに変換し,LuaLaTeXでコンパイルするフローを変更監視付きで行う」をしています.以下はそれぞれの技術についての詳細です.

Markdown

HTMLを簡単に書ける言語,もとい,文書の構造を少ない記号とインデントだけで表現できる言語です.装飾部分を意識せずに文書を書けるように,文法で制限していると考えるとmarkdownで書く意義が伝わると思います.

ここでは,たくさんあることで有名なMarkdownの方言のうち,markdown.plではなくpandocが認識するPandoc’s Markdownを扱います.レポートではこれを書きます.

LaTeX

出題されるレポートの内容上,数式が多く,また複雑になる上に,レイアウトにはそこまでこだわらなくてよいので,組版1自体はLaTeXで行うことにします.LaTeXは皆さんご存知2だと思うので説明は省きます.

レポートではこれを極力書きません.最悪処理系TeXに依存したコードを書きたくないので.

Pandoc

Pandocはマークアップ言語間で文書を変換するフリーウェアです.重要なことはすべてマニュアルに書いてある.

Pandoc is a Haskell library for converting from one markup format to another, and a command-line tool that uses this library.

pandoc.org

この"converting"は,それぞれの言語間に特殊な変換を施しているのではなく,pandoc独自の中間表現(Pandoc AST)を経由して実現されます.入力言語からASTへの変換をreader,ASTから出力言語への変換をwriterと呼びます.

Pandoc is structured as a set of readers, which translate various input formats into an abstract syntax tree (the Pandoc AST) representing a structured document, and a set of writers, which render this AST into various output formats. Pictorially:

[input format] ==reader==> [Pandoc AST] ==writer==> [output format]

This architecture allows pandoc to perform  M ×  N conversions with  M readers and  N writers.

https://pandoc.org/using-the-pandoc-api.html

この記事では入力言語としてmarkdownを選択し,出力言語としてlatexpdfを選択することを想定します. 他にもhtmldocxなどに対応しており,後述のfilterも活用することで様々な用途で活用できます.

https://pandoc.org/index.html

Readerやwriterを自分で作ることもできますが,この記事ではそこまで触れません.

Install

Pandoc - Installing pandocに書いてあります.最新版を入れるのがまあまあめんどくさいですし,後述のfilterで他のなんらかの環境を一緒に入れることになると思うので,Dockerを使いましょう.公式imageをFROMしたり,COPY --from=の対象にして/usr/local/bin/の関連ファイルをコピーしたりすれば動きます.

私と実験のグループが一緒になってしまった被害者さんたちにはもうDockerをinstallしてもらっていますし,堂々とDockerを登場させられますね.

Pandoc’s Markdown

Pandocは,markdownの様々な方言にわたってそれぞれの文法を一つずつ拡張機能と捉え,拡張機能の特定の集まりをデフォルト(pandoc’s markdown)とし,拡張機能単位で有効化/無効化することが可能です.

これも全部ここに書いてあります.優秀なドキュメント.

https://pandoc.org/MANUAL.html#pandocs-markdown

PDF Output

Pandocの出力言語にpdfを選択すると,pdf-engineで指定されたプログラムを用いてコンパイルを行います. それに渡されるオプションはpdf-engine-optで渡します.

Default File

コマンドラインオプションが与えられなかった場合のフォールバックを指定できます.いちいち長いコマンドを書かずに設定ファイル化できるので,使わない手はありません.

https://pandoc.org/MANUAL.html#default-files

実体はただのyamlです.ここで定義したオブジェクトのうち,variablesの中身がそのまま定義済み変数となり,template内で参照できます.

Template

-s--standalone)を引数に含む場合,そのwriterの言語の処理系で自己完結するファイルを生成するようにできます.これを指定しなかった場合との差異を埋めるのがtemplateです.

デフォルトのtemplateは

pandoc -D latex

によりstdoutに書かれます.

Templateは,プリアンブルに書かなければならないものにも使えます.例えば\usepackage{}とか\tcbuselibrary{most}みたいなものの有無や順序,引数などを適当に変数リストから拾ってtemplateで条件分岐することで実現できます.

私はLuaLaTeXのデフォルトの数式フォントが好きではないので,

\setmathfont{Latin Modern Math}
\setmathfont{TeX Gyre Pagella Math}[range={bb,bbit}, Scale=MatchUppercase]
\setmathfont[range=\setminus]{Xits Math}

こういうのも追記しています.3行目はLatin Modern Mathに無い\setminusを適当なところから借りるものです.

Filter

Pandocの変換過程でPandoc ASTを経由することにさっき触れました.Pandoc ASTをPandoc ASTに変換する関数があれば,文書構造だけを見て別の構造を変換できます.それがpandoc filterです.合成に関して単位的半群なのでいくらでも適用できます.

https://pandoc.org/filters.html

Filterは任意の言語で書くことができます.AST上の値がadjacently taggedなjsonになってstdoutに流されるので,それを歩けば良いです.HaskellLuaなど一部の言語では,AST上の値に対する関数を書くだけでfilterの機能をするようにサポートされています.

公式の例(残念ながらpython)が割と網羅的で良い感じです.pythonを使うならpanfluteを使うとpandocfiltersライブラリよりも意味が取りやすいコードが書けるらしいです.

github.com

上のページやリポジトリからもわかるように,filterの用途は多岐にわたります.他のプログラムの入力をfenced_code_blocksに書き,出力を埋め込むという使い方が特に有効で,当環境でも頻繁に用いています.

Crossref

数式を文書内で参照するような場合,crossrefの機能でラベル付けしたものを参照すると,自動で番号が振られます.これもfilterです.ただ,これは独自のAST上の型を持っているわけではなく,直にstringを書き換えるものなので,filterを適用するとラベルの情報は消えます.

引用が生じるほど構成がごちゃごちゃしたレポートを書きたくないので,最近の私は使っていません.

Citeproc

論文を引用したくなることがあります.citeprocオプションを使うと適切な形式で引用ができます. citeprocはbuilt-inなfilterとして実装されているので,他のfilterとの順を扱う場合はciteprocをオプションではなくfilterとして渡します.

cite-methodオプションはlatex出力にのみ有効であり,これにbiblatexを指定すると,hyperref\autorefにより引用し,末尾に\printbibliographyをするようになります.cleverefを使いたかったらfilterを書きましょう.

biblatexでコンパイルする場合,texファイルを複数回コンパイルする必要があるのでした.

to: pdf
pdf-engine: lualatex

は1度しかコンパイルを発火しないので,このような場合にはこれは使えず,texファイルをlatexmkの類でコンパイルすることになります.このような事情もあって,pdf-engineコンパイルを任せきっていません.

VSCode

definition_listsのような,よくあるmarkdownに無いような文法を含んでおり,VS Codeに標準で備わっているsyntax highlightの不足を補う必要3が出てきます.重要なことはここに.

code.visualstudio.com

Syntax highlightを提供するには,addonのcontributeにTextMate文法の定義ファイルを与えます.

その実体(yamlなど)は機械的に生成できるため,MSが開発しているmarkdownのsyntax highlightのリポジトリと同じように,ある程度出力を自動化すると楽です.

TextMate文法は公式のreferenceがあり,他のsyntax highlightを提供するコードからも学ぶことができます.

Environment

これらを用いた最終的な環境について簡単に書きます.

拡張機能も作ったのでエディタはVS Codeです.Syntax highlightだけならAtomとかでも使えるんでしょうけど.

LuaLaTeXとpandocとluaの処理系を共存させたいので,適当なdocker containerを建てており,書いたレポートはdockerのマウントによりコンテナと入出力しています.

変更の監視は,手癖でnode.jschokidarインスタンス.on('change',pandocコマンドを走らせるexec()を置いています.

pandocの起動引数にはデフォルトファイルを渡しており,その中に大勢の変数をくっつけています.文書のメタデータからは改変したlatex用のtemplateや,bibliographyに値を渡しています.

他のメタデータとして,LaTeXの対応マクロに変換されるtitleauthorを与えています. メタデータ以外のmarkdownの部分は,普通のmarkdownの文法に加え,pipe_tablesdefinition_listsfootnotesinline_notesfenced_code_blocksなどを用いて書きます.数式はLaTeXに変換される自作の言語のparserとfilterを使ってtex_math_dollarsに,命題や証明の枠はdivをLaTeXの定理環境に変換するfilterを使ってfenced_divsに,オートマトンとか構文木を書くときはgraphvizをfilter化したものを使って,クラス名を与えたfenced_code_blocksに,構造式もopenbabelをfilter化したものを使い,引用はciteprocを使うので,Zoteroに文献を追加し.bibファイルのパスをbibliographyに渡します.こう見るとほとんどfilterでできているような気がしてきますね. どうしても生のLaTeXを書く必要があるときはraw_attributeで書きます.

おわり

Pandocとpandoc filterで生活を豊かにする記事でした.明日の記事誰か書いてください.


  1. (ほとんど)同じソースから,CSS組版するvivliostyle用のhtmlを吐かせることもできます.

  2. LaTeXでレポートを書いている,という人はそこそこいるものの,モダンな仕様や便利なpackageを追えている人はそう多くないですよね.まだplatexコンパイルしていたり,\newcommand{\bf }eqnarrayを使っていたり,偏微分をいちいち\partialを使って書いていたりしてませんよね.bxjs系記事,unicode-mathなどをLuaLaTeX/XeLaTeX上で使えてますよね.

  3. 数式にハイライトを入れたくて拡張機能を作った数日後にJune 2021 (version 1.58)が出て,時間がだいぶ無駄になったどころかファイルの関連付けの上書きまでされたという経験があります.あとvscodeがあんまりエラーを吐いてくれなくてつらい.