パッケージやプリアンブルでまとまった処理を書くと、生の 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}{⟨真でないとき⟩}{⟨偽でないとき⟩} もあります。
\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 がそろっています。
% 命令が未定義のときだけ用意する / 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** を使います。
% すべての itemize の冒頭に行間設定を差し込む
\AtBeginEnvironment{itemize}{\setlength{\itemsep}{2pt}}
% 文書開始時に走るフックへ追記 / append to the begin-document hook
\appto{\@begindocumenthook}{\typeout{Hello from etoolbox}}既存命令を手当てする \patchcmd
etoolbox でとりわけ有名なのが **\patchcmd です。これは すでに定義されている命令の中身を、一部だけ置き換える** ための道具で、他パッケージやカーネルの命令を「再定義し直さずに」ちょっと直したいときに使います。書式は次の 5 引数です。
\patchcmd{\cmd}{⟨search⟩}{⟨replace⟩}{⟨success⟩}{⟨failure⟩}\cmd の定義の中から **⟨search⟩ に一致する最初の箇所を探し、見つかれば ⟨replace⟩ に置き換えて ⟨success⟩ を実行、見つからなければ命令はそのままで ⟨failure⟩** を実行します。置き換わるのは最初の 1 か所だけです。⟨success⟩ / ⟨failure⟩ を空にもできますが、失敗に気づけるよう警告などを入れておくのが安全です。
実務上のコツが二つあります。第一に、対象命令の名前や ⟨search⟩ に **@** が含まれることがほとんど(内部命令だから)なので、\makeatletter … \makeatother で囲むか、\patchcmd は内部で @ のカテゴリコードを一時的に文字扱いにしてくれることを踏まえます。第二に、うまく当たらないときは **\tracingpatches** をプリアンブルに置くと、ログに「未定義/パターン不一致」などの診断が出ます。なお引数指定や複数箇所の置換まで踏み込むなら、etoolbox を拡張した **xpatch** が便利です。
\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 もあります。
% カンマ区切りの各要素を箇条書きにする / 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** もあります。
\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 行のラッパを書けば、利用者は短いキー名だけで設定できます。
% パッケージ側:設定窓口を一行で / 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、と棲み分けるとよいでしょう。