プログラミング補助(etoolbox / pgfkeys)

パッケージやプリアンブルでまとまった処理を書くと、生の TeX では面倒な「真偽フラグ」「命令が定義済みか」「文字列が空か」といった判定や、既存命令への追記がすぐ必要になります。**etoolbox はそうした道具を LaTeX らしい書き心地で提供し、pgfkeys** は key=value 形式の設定インタフェースを作る土台になります。この 2 つは多くのパッケージの裏側で使われている定番です。

etoolbox とは

etoolbox(作者 Philipp Lehman、現在のメンテナは Joseph Wright)は、クラス・パッケージ作成者向けのプログラミング道具箱です。e-TeX が追加した低水準のプリミティブに LaTeX らしいフロントエンド** をかぶせ、さらに e-TeX とは直接関係のない便利な汎用ツールも詰め合わせています。今どきの TeX エンジンは e-TeX を含むので、\usepackage{etoolbox} と書くだけで使えます。

expl3(LaTeX3 のプログラミング層)が広まった今でも etoolbox がよく使われるのは、LaTeX2e の世界にそのまま馴染む からです。#1 を素直に書ける引数、{真}{偽} の二分岐という見慣れた形、そして既存パッケージの命令を後から手当てできる **\patchcmd**(後述)が、現実のプリアンブル仕事で重宝します。

真偽フラグ:トグルとブール

「下書きモードかどうか」のような 真偽の状態 を持ちたいとき、etoolbox には 2 系統あります。おすすめは トグル(toggle) で、名前空間が独立しているため既存命令と衝突しません。\newtoggle{flag} で宣言し、\toggletrue{flag} / \togglefalse{flag}(または \settoggle{flag}{true})で切り替え、**\iftoggle{flag}{⟨真の処理⟩}{⟨偽の処理⟩}** で分岐します。逆を試す \nottoggle{flag}{⟨真でないとき⟩}{⟨偽でないとき⟩} もあります。

latex
\usepackage{etoolbox}

\newtoggle{draft}        % フラグを宣言 / declare the flag
\toggletrue{draft}       % 真にする / set it true

% 本文や命令の中で分岐 / branch on it
\iftoggle{draft}
  {\textbf{[DRAFT]}\ }    % 真のとき / when true
  {}                     % 偽のとき / when false

もう一系統が ブール(bool) で、\newbool{flag} / \setbool{flag}{true} / \booltrue{flag} / \boolfalse{flag}、判定は **\ifbool{flag}{⟨真⟩}{⟨偽⟩}** です。こちらは内部的に LaTeX の \newif と同じ仕組みを使うため、\ifflag という命令名を 1 つ占有します。多くの場合はトグルで十分ですが、\newif 由来のコードと混ぜたいときにブールが役立ちます。

命令意味備考
\newtoggle{f}トグル f を宣言(初期は偽)独立した名前空間
\settoggle{f}{v}f を v(true/false)にする\toggletrue/\togglefalse も可
\iftoggle{f}{T}{F}真なら T、偽なら F引数は 3 つ
\ifbool{f}{T}{F}ブール版の分岐\newif と同じ仕組み

定義の有無・文字列の判定

パッケージを安全に書くには「その命令はもう定義されているか」「引数は空か」を調べたくなります。etoolbox の判定はどれも **{⟨真⟩}{⟨偽⟩} の二分岐で統一されています。定義の有無は \ifdef{\cmd}{⟨真⟩}{⟨偽⟩}、名前(文字列)で調べるなら \ifcsdef{name}{⟨真⟩}{⟨偽⟩}**。逆向きの \ifundef / \ifcsundef もあります。

注意したいのは、よく似た名前の **\ifdefined は e-TeX の プリミティブ** であって、etoolbox が用意する {真}{偽} 形式の命令ではないことです。二分岐がほしいときは etoolbox\ifdef(先頭にバックスラッシュ付きの命令を渡す)を使います。文字列まわりは、空白だけかを見る **\ifblank{⟨文字列⟩}{⟨真⟩}{⟨偽⟩}、その逆の \notblank、二つの文字列の一致を見る \ifstrequal{⟨文字列⟩}{⟨文字列⟩}{⟨真⟩}{⟨偽⟩}**、空かどうかの \ifstrempty がそろっています。

latex
% 命令が未定義のときだけ用意する / provide a command only if missing
\ifdef{\highlight}
  {}                              % 既にあれば何もしない / leave it alone
  {\newcommand{\highlight}[1]{\textbf{#1}}}

% 引数が空かどうかで出し分け / vary on an empty argument
\newcommand{\field}[1]{\ifblank{#1}{(none)}{#1}}

フックと既存命令への追記

etoolboxフック(特定のタイミングで実行されるコード置き場) を扱う命令も提供します。文書開始時・終了時の \AtBeginDocument / \AtEndDocument は LaTeX カーネルの機能ですが、etoolbox はこれを補完して、プリアンブル末尾の **\AtEndPreamble**、文書本当の最後の \AfterEndDocument、そして特定環境の前後に差し込む **\AtBeginEnvironment{⟨環境⟩}{⟨コード⟩} / \AtEndEnvironment** / \BeforeBeginEnvironment / \AfterEndEnvironment を加えます。

こうしたフックや任意のマクロに 後から中身を足す のが追記系の命令です。**\appto{\cmd}{⟨コード⟩} は末尾へ、\preto{\cmd}{⟨コード⟩}** は先頭へ追記します(\gappto はグローバル、\eappto は展開してから追記)。引数つきマクロを安全に追記したいときは、成功・失敗の分岐を持つ **\apptocmd{\cmd}{⟨コード⟩}{⟨成功⟩}{⟨失敗⟩} / \pretocmd** を使います。

latex
% すべての itemize の冒頭に行間設定を差し込む
\AtBeginEnvironment{itemize}{\setlength{\itemsep}{2pt}}

% 文書開始時に走るフックへ追記 / append to the begin-document hook
\appto{\@begindocumenthook}{\typeout{Hello from etoolbox}}

既存命令を手当てする \patchcmd

etoolbox でとりわけ有名なのが **\patchcmd です。これは すでに定義されている命令の中身を、一部だけ置き換える** ための道具で、他パッケージやカーネルの命令を「再定義し直さずに」ちょっと直したいときに使います。書式は次の 5 引数です。

latex
\patchcmd{\cmd}{⟨search⟩}{⟨replace⟩}{⟨success⟩}{⟨failure⟩}

\cmd の定義の中から **⟨search⟩ に一致する最初の箇所を探し、見つかれば ⟨replace⟩ に置き換えて ⟨success⟩ を実行、見つからなければ命令はそのままで ⟨failure⟩** を実行します。置き換わるのは最初の 1 か所だけです。⟨success⟩ / ⟨failure⟩ を空にもできますが、失敗に気づけるよう警告などを入れておくのが安全です。

実務上のコツが二つあります。第一に、対象命令の名前や ⟨search⟩ に **@** が含まれることがほとんど(内部命令だから)なので、\makeatletter\makeatother で囲むか、\patchcmd は内部で @ のカテゴリコードを一時的に文字扱いにしてくれることを踏まえます。第二に、うまく当たらないときは **\tracingpatches** をプリアンブルに置くと、ログに「未定義/パターン不一致」などの診断が出ます。なお引数指定や複数箇所の置換まで踏み込むなら、etoolbox を拡張した **xpatch** が便利です。

document.tex
\usepackage{etoolbox}

\makeatletter
% 例:ある内部命令 \@foo の定義中の \small を \footnotesize に差し替える
\patchcmd{\@foo}
  {\small}            % 探す / search
  {\footnotesize}     % 置き換える / replace
  {}                  % 成功時 / on success
  {\PackageWarning{mypkg}{Patch to \protect\@foo\space failed}} % 失敗時
\makeatother

リストとループ

etoolbox には軽量な 内部リスト とその反復処理もあります。\listadd{\mylist}{⟨項目⟩} で要素を足し、**\forlistloop{⟨ハンドラ⟩}{\mylist} で各要素に対しハンドラ(1 引数の命令)を呼びます。手元のカンマ区切り文字列をそのまま回すなら \docsvlist{a,b,c}\forcsvlist{⟨ハンドラ⟩}{a,b,c}** が手軽です。独自の区切り文字でパーサを作る \DeclareListParser もあります。

latex
% カンマ区切りの各要素を箇条書きにする / each CSV item becomes a bullet
\newcommand{\asitem}[1]{\item #1}
\begin{itemize}
  \forcsvlist{\asitem}{apples, pears, plums}
\end{itemize}

pgfkeys:key=value インタフェースの土台

pgfkeys は PGF/TikZ に含まれる強力な キー=値エンジン** で、TikZ のあの [draw, thick, fill=blue] という記法も、数多くのパッケージの \…setup{...} 風インタフェースも、その多くがこの仕組みで実装されています。中心となるのが **\pgfkeys{/my/key=value}**。キーは / 区切りの パス(family) で名前空間が整理され、各キーには「呼ばれたときに何をするか」をハンドラで割り当てます。

キーの 定義に使うハンドラ が肝です。値をそのままマクロに格納するなら **.store in=\macro**(内部的に \def\macro{値})。値を使って処理を走らせるなら **.code={... #1 ...} とし、コード内では渡された値を #1** で受け取ります。=値 を省いて呼ばれたときの既定値は **.default=値、キーの初期値は .initial=値 で与えます。選択肢を列挙する .is choice** もあります。

latex
\usepackage{pgfkeys}

% キーを定義する / define keys under /book
\pgfkeys{
  /book/title/.store in = \bookTitle,        % 値をマクロに格納
  /book/edition/.code   = {Edition #1},       % #1 は渡された値
  /book/edition/.default = 1,                  % =値 省略時の既定
  /book/draft/.initial   = false,              % 初期値
}

% キーを設定する / set them
\pgfkeys{/book/title=TeX by Topic, /book/edition=3}

パッケージが \mypkgsetup{...} のような窓口を作るときは、毎回 /mypkg/ を前置せずに済むよう 既定パス を設定します。これには \pgfkeys{/mypkg/.cd, ⟨キー列⟩} の短縮形である **\pgfqkeys{/mypkg}{⟨キー列⟩}**(q は quick)が定番です。下のように 1 行のラッパを書けば、利用者は短いキー名だけで設定できます。

latex
% パッケージ側:設定窓口を一行で / the package: a one-line entry point
\newcommand{\mypkgsetup}[1]{\pgfqkeys{/mypkg}{#1}}

% 利用者側:短いキー名で設定 / the user: short key names
\mypkgsetup{title = My Report, edition = 2}

なお LaTeX3 側には同種の機能として **l3keys**(expl3 のキー=値モジュール)があり、\keys_define:nn などで同様のインタフェースを宣言できます。新規に expl3 でパッケージを書くなら l3keys、TikZ/pgf 由来のコードや既存資産に合わせるなら pgfkeys、と棲み分けるとよいでしょう。