프로그래밍 보조 도구

패키지나 프리앰블에서 조금만 복잡한 코드를 쓰기 시작하면, 순수 TeX로는 번거로운 것들이 곧 필요해집니다. 참/거짓 플래그, “이 명령이 정의되어 있는가?” 테스트, “이 문자열이 비어 있는가?” 테스트, 기존 명령에 내용 덧붙이기 같은 것들입니다. etoolbox 는 이런 도구를 LaTeX다운 감각으로 제공하고, pgfkeyskey=value 형식의 설정 인터페이스를 만드는 기반이 됩니다. 둘 다 많은 패키지 뒤에서 쓰이는 표준 도구입니다.

etoolbox란 무엇인가

etoolbox(Philipp Lehman 작성, 현재 Joseph Wright 유지보수)는 클래스와 패키지 작성자를 위한 프로그래밍 도구 상자입니다. e-TeX가 추가한 저수준 primitive 위에 LaTeX식 프런트엔드 를 씌우고, e-TeX와 직접 관련 없는 편리한 범용 도구도 함께 제공합니다. 요즘 TeX 엔진은 모두 e-TeX를 포함하므로 \usepackage{etoolbox}만 쓰면 사용할 수 있습니다.

expl3(LaTeX3 프로그래밍 계층)이 널리 퍼진 지금도 etoolbox가 자주 쓰이는 이유는 LaTeX2e 세계에 그대로 어울리기 때문입니다. #1을 자연스럽게 쓰는 인수, 익숙한 {참}{거짓} 두 갈래 분기, 그리고 기존 패키지 명령을 나중에 손볼 수 있는 \patchcmd(아래 설명)가 실제 프리앰블 작업에서 유용합니다.

참/거짓 플래그: toggle과 bool

“초안 모드인가?” 같은 참/거짓 상태 를 갖고 싶을 때 etoolbox에는 두 계열이 있습니다. 권장되는 것은 toggle 입니다. 독립된 이름공간을 가지므로 기존 명령과 충돌하지 않습니다. \newtoggle{flag}로 선언하고, \toggletrue{flag} / \togglefalse{flag} 또는 \settoggle{flag}{true}로 바꾸며, \iftoggle{flag}{⟨true code⟩}{⟨false code⟩} 로 분기합니다. 반대를 시험하는 \nottoggle{flag}{⟨if not true⟩}{⟨if not false⟩}도 있습니다.

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}{⟨true⟩}{⟨false⟩} 입니다. 내부적으로 LaTeX의 \newif와 같은 구조를 쓰므로 \ifflag라는 명령 이름 하나를 차지합니다. 대부분은 toggle로 충분하지만, \newif 기반 코드와 섞어야 할 때 bool이 도움이 됩니다.

명령의미비고
\newtoggle{f}toggle f를 선언(초기값은 거짓)독립된 이름공간
\settoggle{f}{v}f를 v(true/false)로 설정\toggletrue / \togglefalse도 가능
\iftoggle{f}{T}{F}참이면 T, 거짓이면 F인수 세 개
\ifbool{f}{T}{F}bool판 분기\newif와 같은 구조

정의 여부와 문자열 판정

패키지를 안전하게 쓰려면 “그 명령이 이미 정의되어 있는가?”, “인수가 비어 있는가?”를 확인하고 싶을 때가 많습니다. etoolbox의 판정은 모두 {⟨true⟩}{⟨false⟩} 두 갈래 분기로 통일되어 있습니다. 정의 여부는 \ifdef{\cmd}{⟨true⟩}{⟨false⟩}, 이름(문자열)으로 확인하려면 \ifcsdef{name}{⟨true⟩}{⟨false⟩} 를 씁니다. 반대 방향의 \ifundef / \ifcsundef도 있습니다.

주의할 점은 이름이 비슷한 \ifdefined 는 e-TeX primitive 이며, etoolbox가 제공하는 {true}{false} 형식 명령이 아니라는 것입니다. 두 갈래 분기가 필요할 때는 etoolbox\ifdef를 쓰고, 백슬래시가 붙은 실제 명령을 넘깁니다. 문자열 쪽에는 공백뿐인지 보는 \ifblank{⟨string⟩}{⟨true⟩}{⟨false⟩}, 그 반대 \notblank, 두 문자열의 일치를 보는 \ifstrequal{⟨string⟩}{⟨string⟩}{⟨true⟩}{⟨false⟩}, 비어 있는지 보는 \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{⟨env⟩}{⟨code⟩} / \AtEndEnvironment / \BeforeBeginEnvironment / \AfterEndEnvironment를 더합니다.

이런 훅이나 임의의 매크로에 나중에 내용을 더하는 것이 덧붙이기 계열 명령입니다. \appto{\cmd}{⟨code⟩} 는 끝에, \preto{\cmd}{⟨code⟩} 는 앞에 덧붙입니다(\gappto는 전역, \eappto는 먼저 전개한 뒤 덧붙임). 인수가 있는 매크로에 안전하게 덧붙이고 싶다면 성공/실패 분기를 가진 \apptocmd{\cmd}{⟨code⟩}{⟨success⟩}{⟨failure⟩} / \pretocmd 를 씁니다.

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

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

\patchcmd로 기존 명령 손보기

etoolbox에서 특히 유명한 도구가 \patchcmd 입니다. 이는 이미 정의된 명령의 본문 일부만 바꾸는 도구로, 다른 패키지나 커널 명령을 통째로 다시 정의하지 않고 조금 수정하고 싶을 때 씁니다. 형식은 다섯 인수입니다.

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

\cmd의 정의 안에서 ⟨search⟩ 와 일치하는 첫 위치를 찾고, 찾으면 ⟨replace⟩ 로 바꾼 뒤 ⟨success⟩ 를 실행합니다. 찾지 못하면 명령은 그대로 두고 ⟨failure⟩ 를 실행합니다. 바뀌는 것은 첫 번째 한 곳뿐입니다. ⟨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}{⟨item⟩}로 요소를 추가하고, \forlistloop{⟨handler⟩}{\mylist} 로 각 요소에 대해 한 인수 명령인 handler를 호출합니다. 손에 있는 쉼표 구분 문자열을 바로 돌리려면 \docsvlist{a,b,c}\forcsvlist{⟨handler⟩}{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) 로 이름공간이 정리되고, 각 키에는 “호출되었을 때 무엇을 할지”를 정하는 handler를 배정합니다.

키를 정의하는 handler 가 핵심입니다. 값을 그대로 매크로에 저장하려면 .store in=\macro 를 씁니다(내부적으로 \def\macro{value}). 값을 사용해 처리를 실행하려면 .code={... #1 ...} 로 하고, 코드 안에서는 전달된 값이 #1 로 들어옵니다. =value를 생략하고 호출했을 때의 기본값은 .default=value, 키의 초기값은 .initial=value 로 줍니다. 선택지를 열거하는 .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/를 앞에 붙이지 않아도 되도록 기본 경로 를 설정합니다. 이를 위해 \pgfqkeys{/mypkg}{⟨key list⟩}(q는 quick)가 관용적으로 쓰입니다. 이는 \pgfkeys{/mypkg/.cd, ⟨key list⟩}의 빠른 축약형입니다. 아래처럼 한 줄짜리 wrapper를 쓰면 사용자는 짧은 키 이름만으로 설정할 수 있습니다.

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를 선택하는 식으로 나누면 좋습니다.