diff options
author | Karl Berry <karl@freefriends.org> | 2015-12-06 23:51:36 +0000 |
---|---|---|
committer | Karl Berry <karl@freefriends.org> | 2015-12-06 23:51:36 +0000 |
commit | f68189f71d0a9a9a67cba3d61a6d3a1ce35ec832 (patch) | |
tree | 78bad79915ce0174ea790faf40d4b643d7684c07 /Master/texmf-dist/tex/latex/options | |
parent | 25bc28958d2f77d9fcaa69cc06575b6b1ff6ad0f (diff) |
options (6dec15)
git-svn-id: svn://tug.org/texlive/trunk@39030 c570f23f-e606-0410-a88d-b1316a301751
Diffstat (limited to 'Master/texmf-dist/tex/latex/options')
-rw-r--r-- | Master/texmf-dist/tex/latex/options/options.sty | 1387 |
1 files changed, 1387 insertions, 0 deletions
diff --git a/Master/texmf-dist/tex/latex/options/options.sty b/Master/texmf-dist/tex/latex/options/options.sty new file mode 100644 index 00000000000..ebd0311e27d --- /dev/null +++ b/Master/texmf-dist/tex/latex/options/options.sty @@ -0,0 +1,1387 @@ +%--------------------------------------------------------------------------- +% Copyright 2015 Daan Leijen, Microsoft Corporation. +% +% This work may be distributed and/or modified under the +% conditions of the LaTeX Project Public License, either version 1.3 +% of this license or (at your option) any later version. +% The latest version of this license is in +% http://www.latex-project.org/lppl.txt +% and version 1.3 or later is part of all distributions of LaTeX +% version 2005/12/01 or later. +%--------------------------------------------------------------------------- +\NeedsTeXFormat{LaTeX2e}[1995/12/01] + +\ProvidesPackage{options}[2015/03/01, Daan Leijen, Provides convenient path-value (or key-value) options] +\RequirePackage{etoolbox} +\RequirePackage{xcolor} + +\newif\ifopt@debug +\DeclareOption{debug}{\opt@debugtrue} +\ProcessOptions\relax + +% -------------------------------------------------------- +% make the package robust agains catcode changes of = and , +% -------------------------------------------------------- +\edef\opt@restore@catcodes{% + \catcode`\noexpand\=\the\catcode`\=\relax + \catcode`\noexpand\,\the\catcode`\,\relax + \let\noexpand\opt@catcodes\relax +} +\catcode`\=12\relax +\catcode`\,12\relax + + +% -------------------------------------------------------- +% Extensions to etoolbox +% -------------------------------------------------------- + +\providecommand*\@swaparg[2]{#2{#1}} +\providecommand*\expandnext[2]{\expandafter\@swaparg\expandafter{#2}{#1}} + +\providecommand*\@swaptwo[2]{#2#1} +\providecommand*\expandnextcmds[2]{\expandafter\@swaptwo\expandafter{#2}{#1}} + +% unsafe expand next if the command is a single token.. +\providecommand*\expandnextsingle[2]{\expandafter#1\expandafter{#2}} + +\providecommand*\eifstrequal[1]{\expandnextsingle\ifstrequal{#1}} +\providecommand*\eifblank[1]{\expandnextsingle\ifblank{#1}} + +\providecommand*\ontoggle[2]{\iftoggle{#1}{#2}{}} + +\providerobustcmd*\providelength[1]{\ifdef{#1}{}{\newlength#1}} +\providerobustcmd*\csnewlength[1]{\expandafter\newlength\csname #1\endcsname} +\providerobustcmd*\csprovidelength[1]{\ifcsdef{#1}{}{\expandafter\newlength\csname #1\endcsname}} +\providerobustcmd*\cssetlength[2]{\expandafter\setlength\csname #1\endcsname{#2}} + +% -------------------------------------------------------- +% Basic option commands +% \option{<path>} returns the value of a path +% \letoption{<path>}\cmd let's \cmd to the value of <path> +% -------------------------------------------------------- + +\newcommand*\optionname[1]{optk@#1} +\newcommand*\option[1]{\csname optk@#1\endcsname} +\newcommand*\letoption[2]{\expandafter\let\expandafter#2\csname optk@#1\endcsname} + +% \letoption@ will not be redefined for debugging and can be used to test if an option exists +\let\letoption@\letoption + +% -------------------------------------------------------- +% options, optionsalso +% main path setting routine +% -------------------------------------------------------- + +\newif\ifopt@allowsearch % allow searching along search-also handlers +\newif\ifopt@ignoreunknown % ignore unknown options? +\newif\ifopt@unknownwarnonly % issue just a warning on an unknown option? (instead of an error) + +% process options together with remaining ones from a previous run +\newrobustcmd*\optionswithremaining[1]{% + \letoptionlist{/options/remaining}\opt@savedremaining + \options{#1}% + %\typeout{now process: \meaning\opt@savedremaining, in default: \option@defaultpath}% + \expandnextsingle\optionsalso{\opt@savedremaining}% +} + +% process options +\newrobustcmd*\options{% + \def\option@defaultpath{}% + \opt@allowsearchtrue% + \opt@ignoreunknownfalse% + \opt@unknownwarnonlyfalse% + \optionsalso% +} + +% track nesting level of options (due to calls to options in an argument) +\newif\ifopt@nested +\newcount\opt@nesting + +% Process options in the context of an other options call +% For efficiency, only save state if nested +\newrobustcmd*\optionsalso[1]{% + \ifopt@nested + \optionsalso@save{#1}% save state in a nested context for chained handlers + \else + \opt@nestedtrue + \opt@parse#1,\opt@stop,% + \opt@nestedfalse + \fi +} + +% this saves and restores any nested state: for now only about chained handlers +\newrobustcmd*\optionsalso@save[1]{% + \opt@savestate{\the\opt@nesting}% + \advance\opt@nesting 1\relax + \opt@parse#1,\opt@stop,% + \advance\opt@nesting -1\relax + \opt@restorestate{\the\opt@nesting}% +} + +\newrobustcmd*\opt@savestate[1]{% + \cslet{option@handlernext#1}\option@handlernext% + \cslet{option@handlerpath#1}\option@handlerpath% + \cslet{optionvalue#1}\optionvalue% + \def\option@handlernext{}% + \def\option@handlerpath{}% +} + +\newrobustcmd*\opt@restorestate[1]{% + \letcs\option@handlernext{option@handlernext#1}% + \letcs\option@handlerpath{option@handlerpath#1}% + \letcs\optionvalue{optionvalue#1}% +} + +% Qualified options -- mostly for here for compatibility with pgfqkeys +\newrobustcmd*\qoptions[2]{% + \def\option@defaultpath{#1}% + \opt@allowsearchtrue% + \opt@ignoreunknownfalse% + \optionsalso{#2}% +} + +% Qualified optionsalso +\newrobustcmd*\qoptionsalso[2]{% + \def\option@defaultpath{#1}% + \optionsalso{#2}% +} + +%----------------------------------------------------------------------- +% The main option processing +% +% For an option <option> we may have these values: +% <option> : the value read by \option{<option>} +% /@code : the code invoked on assignment (use option@invoke to invoke explicitly), gets \optionvalue +% /@cmdN : command handlers invoked by code with arguments from \optionvalue +% /@type : the type of value (not set for pure code values) +% /@unknown : an unknown handler for this path +% /@code : taking 2 arguments: path and value +% /@searchalso: the other paths needed to be search for this path +% /@code +% +% Handlers start with a "." in their name; the handlers are found in: /handlers/<handlername> +% Special handlers are defined in: /handlers/special/<char> +% If a path starts with <char> it is invoked; just like first-char-syntax in pgfkeys. +%----------------------------------------------------------------------- + +% this is used if no argument is given +% define as relax, this makes it unique but also safe against infinite expansion +\def\optionnovalue{\relax} + +% opt@stop is used to stop argument processing in tex \def's with patterns +\let\opt@stop\relax + +% The main parsing of options +% This code is quite expanded to make it as efficient as possible. +% It is based on the original code by David Carlisle in the standard latex "keyval" package. +\def\opt@parse#1,{% + \ifx\opt@stop#1\@empty\else + \opt@parse@arg#1==\opt@stop + \expandafter\opt@parse\fi} + +\def\opt@parse@arg#1=#2=#3\opt@stop{% + \opt@trimdef\option@rawpath{#1}% + \ifx\@empty#3\@empty% + \let\optionvalue\optionnovalue + \else + \opt@trimdef\optionvalue{#2}% + \fi + \ifx\option@rawpath\@empty + \ifx\optionvalue\optionnovalue\else\optionerror@nopath{#2}\fi + \else + % try path directly + \expandafter\opt@setcodepath@\option@rawpath\opt@stop% + \ifx\opt@codepath\relax + \opt@handle{#1}{#2}% not directly found, try handlers, search etc. + \fi + % now \optionvalue is defined; invoke the /@code + \opt@codepath% + \ifx\option@handlernext\@empty\else\optionerror@handler\fi + \fi +} + +% Phase 2: look for a handler or search along the path +\newrobustcmd*\opt@handle[2]{% + % not directly found.. + \opt@setoptionpath{\option@rawpath}% create full option@path applying the default path + \option@gethandler\option@path% defines option@handlerpath & option@handler + \ifx\option@handler\@empty + % search path + \ifopt@allowsearch + \opt@searchalso{\option@path}% + \fi + \else + % handler + \letoption@{/handlers/\option@handler/@code}\opt@codepath + \fi + \ifx\opt@codepath\relax + \opt@handle@specials{#1}{#2}% + \fi +} + +% Set opt@codepath from a raw path taking the default path into account +\newcommand\opt@setcodepath[1]{\expandafter\opt@setcodepath@#1\opt@stop} +\def\opt@setcodepath@#1#2\opt@stop{\ifx#1/\relax\letoption@{#1#2/@code}\opt@codepath\else\letoption@{\option@defaultpath/#1#2/@code}\opt@codepath\fi} + +% Set option@path from a raw path taking the default path into account +\newcommand\opt@setoptionpath[1]{\expandafter\opt@setoptionpath@#1\opt@stop} +\def\opt@setoptionpath@#1#2\opt@stop{\ifx#1/\relax\edef\option@path{#1#2}\else\edef\option@path{\option@defaultpath/#1#2}\fi} + + +% Phase 3: look for a special syntax handler, or invoke the unknown handlers +\newrobustcmd*\opt@handle@specials[2]{% + % special handler? + \edef\opt@first{\expandafter\option@firstletterof@\option@rawpath\opt@stop}% + \letoption@{/handlers/special/\opt@first/@code}\opt@codepath + \ifx\opt@codepath\relax + % still not found: invoke unknown handler + \ifopt@ignoreunknown + \opt@collectunknown{#1}{#2}% expects \optionvalue to be defined + \else + \opt@unknown{\option@path}% + \fi + \else + % it is a special handler + \ifx\optionvalue\optionnovalue + \let\optionvalue\option@rawpath% + \else + \optionerror{\option@rawpath}{special options cannot take an argument ("\meaning\optionvalue")}% + \fi + \fi +} + +% Space gobbling; basically unchanged from the original definition in the keyval package by David Carlisle +% note: takes off one layer of braces +\newrobustcmd\opt@define@trimdef[1]{% + \long\def\opt@trimdef##1##2{% + \futurelet\opt@firsttoken\opt@trim@##2\opt@stop\opt@stop#1\opt@stop\relax##1}% + \def\opt@trim@{% + \ifx\opt@firsttoken\opt@sptoken% + \expandafter\opt@trim@left% + \else% + \expandafter\opt@trim@left\expandafter#1% add space token since sp@b removes one + \fi}% + \long\def\opt@trim@left#1##1 \opt@stop{\opt@trim@right##1}% +} +\opt@define@trimdef{ }% define trimdef with a space as argument +\long\def\opt@trim@right#1\opt@stop#2\relax#3{\edef#3{\unexpanded{#1}}} +{\def\:{\global\let\opt@sptoken= } \: } + + +% -------------------------------------------------------- +% Searching +% -------------------------------------------------------- + +% search a relative path: <default path><search path> +\newrobustcmd*\opt@searchforpath[2]{% + \option@ifisabsolute{#2}% + {\letoption@{#2/@code}\opt@codepath}% + {\letoption@{#1#2/@code}\opt@codepath% use default path + \ifx\opt@codepath\relax + \ifopt@allowsearch\else + \opt@searchalso{#1#2}% + \fi + \fi}% +} + +% search an absolute path +% this is called by search-also handlers. +\newrobustcmd*\opt@searchforabspath[1]{% + \letoption@{#1/@code}\opt@codepath + \ifx\opt@codepath\relax + \ifopt@allowsearch\else + \opt@searchalso{#1}% + \fi + \fi +} + +% \opt@searchalso: invoked to search for a path: set \opt@codepath if a match is found +% +% note: we can nest searches without grouping +% because they all use the same "\opt@do@search" (option@searchalsodo) and +% all need to break once the inner one breaks +\newrobustcmd*\opt@searchalso[1]{% + \option@foreachparent\opt@do@search{#1}% +} +\newrobustcmd*\opt@do@search[3]{% {<root>}{<subpath>}{<name>} + \letoption@{#1/@searchalso/@code}\opt@temp + \ifx\opt@temp\relax\else + \edef\option@searchpath{#2#3}% subpath + \opt@temp% invoke search routine + \ifx\opt@codepath\relax\else\opt@break\fi% on found: break loop + \fi +} + +% -------------------------------------------------------- +% Unknown handler search +% -------------------------------------------------------- + +% invoked on unknown path: set \opt@codepath to the unknown handler (with option@unknownpath) +\newrobustcmd*\opt@unknown[1]{% + %\typeout{found unknown: #1}% + \def\opt@do@unknown##1##2##3{% + \letoption@{##1/@unknown/@code}\opt@codepath + \ifx\opt@codepath\relax\else + \edef\option@unknownpath{#1}% full path + \edef\option@unknownsubpath{##2##3}% subpath + \expandnext{\opt@addarg{#1}}{\optionvalue}% set optionvalue to two arguments: the path and the original value + \opt@break% + \fi% + }% + \option@foreachparent\opt@do@unknown{#1}% +} +\newrobustcmd*\opt@addarg[2]{\edef\optionvalue{\unexpanded{{#1}{#2}}}}% + +% invoked when unknown option needs to be saved (and ignored) +\newrobustcmd*\opt@collectunknown[2]{% + \ifx\optionvalue\optionnovalue + \option@push{/options/remaining}{#1}% + \else + \option@push{/options/remaining}{#1={#2}}% + \fi +} + +% -------------------------------------------------------- +% Handlers' splitting and chaining +% -------------------------------------------------------- + +% \option@gethandler{path}: split off the part after the "/.", sets option@handlerpath and option@handler and option@handlernext +\def\option@gethandler#1{\expandafter\option@gethandler@#1/././.\opt@stop} +\def\option@gethandler@#1/.#2/.#3/.#4\opt@stop{% + \def\option@handler{#2}% + \ifx\option@handler\@empty\else + \def\option@handlerpath{#1}% + \def\option@handler{#2}% + \def\option@handlernext{#3}% + \fi +} +\def\option@handlerpath{} +\def\option@handlernext{} + +\newrobustcmd*\option@chainempty{\let\optionvalue\optionnovalue\option@chainonly} +\newrobustcmd*\option@chainonly{\ifx\option@handlernext\@empty\else\option@chain\fi} +\newrobustcmd*\option@chain{% + \ifx\option@handlernext\@empty + % we are done, invoke the option itself + \letoption@{\option@handlerpath/@code}\opt@codepath + \ifx\opt@codepath\relax + \opt@unknown{\option@handlerpath}% + \fi + \else + % invoke the next handler + %\typeout{call next handler: \option@handlerpath, \option@handlernext, \optionvalue}% + \edef\opt@temp{\option@handlerpath/.\option@handlernext}% + \option@gethandler{\opt@temp}% + \letoption@{/handlers/\option@handler/@code}\opt@codepath + \ifx\opt@codepath\relax + \opt@unknown{/handlers/\option@handler}% + \fi + \fi + \opt@codepath +} + +% -------------------------------------------------------- +% Path splitting +% -------------------------------------------------------- + +% \option@isabsolute{path}: check if path starts with / +\def\ifoptionisabsolute#1{\expandafter\ifoptionisabsolute@#1\opt@stop} +\def\ifoptionisabsolute@#1#2\opt@stop{\ifx#1/\relax\expandafter\@firstoftwo\else\expandafter\@secondoftwo\fi}% + +% \option@splitroot{path}: split off the first directory of a path into \option@root +% option@splitroot{/foo/bar} -> foo +% option@splitroot{/foo} -> foo +\newrobustcmd*\option@splitroot[1]{\expandafter\option@splitroot@#1//\opt@stop} +\def\option@splitroot@#1/#2/#3\opt@stop{% + \def\option@root{#1}% + \ifx\option@root\@empty + \def\option@root{#2}% + \fi +} + +% \option@splitpath{path}: \option@path and \option@name +% \option@splitpath{/foo/bar/baz} -> /foo/bar baz +\newrobustcmd*\option@splitpath[1]{% + \let\option@path\@empty + \ifoptionisabsolute{#1}{% + \def\opt@do@split##1##2##3{% + \edef\option@path{##1/}% + \edef\option@name{##3}% + \opt@break + }% + \option@foreachparent\opt@do@split{#1}% + }{\edef\option@name{#1}}% +} + + +% \option@foreachparent\<do>{<path>} basic routine for iterating over path components +% applies \<do> to each parent path. todo: can we optimize this routine more for the searchalso? +% /foo/bar/baz -> \<do>{/foo/bar}{}{baz} \<do>{/foo}{bar/}{baz} \<do>{}{foo/bar/}{baz} +\newrobustcmd*\option@foreachparent[2]{% + \let\opt@donext#1% + \expandafter\opt@foreachparent@#2/\opt@stop\opt@stop% + \opt@donext{}{\option@subpath}{\option@name}% +} + +\newrobustcmd*\opt@break{\def\opt@donext##1##2##3{}} + +\def\opt@foreachparent@/#1/#2\opt@stop#3\opt@stop{% #3 is the current root + \def\opt@temp{#2}% + \ifx\opt@temp\@empty% final path + \def\option@name{#1}% + \def\option@subpath{}% + \else + \opt@foreachparent@/#2\opt@stop#3/#1\opt@stop% recurse first + \opt@donext{#3/#1}{\option@subpath}{\option@name}% + \edef\option@subpath{#1/\option@subpath}% + \fi +} + + +% -------------------------------------------------------- +% Errors +% -------------------------------------------------------- +\newrobustcmd*\optionwarning[2]{% + \eifblank{\option@handler}{\def\opt@temp{}}{\def\opt@temp{(in ".\option@handler") }}% + \PackageWarning{options}{option "#1": \opt@temp #2}{}} + +\newrobustcmd*\optionerror[2]{% + \eifblank{\option@handler}{\def\opt@temp{}}{\def\opt@temp{(in ".\option@handler") }}% + \PackageError{options}{option "#1": \opt@temp #2}{}} + +\newrobustcmd*\optionerror@expect[2]{\optionerror{#1}{unexpected "\optionvalue" (expecting #2)}} +\newrobustcmd*\optionerror@toomany[2]{\optionerror{#1}{too many arguments (expecting #2)}} +\newrobustcmd*\optionerror@noarg[1]{\optionerror{#1}{does not take an argument}} +\newrobustcmd*\optionerror@needarg[1]{\optionerror{#1}{requires an argument}} + +\newrobustcmd*\optionerror@nopath[1]{\optionerror{?}{value without option path: "#1"}} +\newrobustcmd*\optionerror@handler[1]{\optionerror{\option@rawpath}{handler ".\option@handlernext" is not allowed to follow the previous handlers}}% + +\newrobustcmd*\option@ensurefree[1]{\ifoptiondefined{#1}{\optionerror{#1}{already defined}}{}} +\newrobustcmd*\option@ensuredefined[1]{\ifoptiondefined{#1}{}{\optionerror{#1}{unknown option}}} + + +% -------------------------------------------------------- +% Basic option setting +% -------------------------------------------------------- + +\newrobustcmd*\edefoption[2]{\protected@edef#2{\option{#1}}}% + +\newrobustcmd*\option@set[2]{\csdef{optk@#1}{#2}} +\newrobustcmd*\option@let[2]{\cslet{optk@#1}#2} + +\newrobustcmd*\option@def[1]{\csdef{optk@#1}} +\newrobustcmd*\option@undef[1]{\csundef{optk@#1}} + +\newrobustcmd*\option@eset[2]{\expandnext{\option@set{#1}}{#2}} +\newrobustcmd*\option@xset[2]{\protected@edef\opt@temp{#2}\option@let{#1}\opt@temp} + +\newrobustcmd*\option@appto[2]{\csappto{optk@#1}{#2}} +\newrobustcmd*\option@preto[2]{\cspreto{optk@#1}{#2}} + +\newcommand*\ifoptiondefined[1]{\ifcsdef{optk@#1}} +\newcommand*\ifoptionvoid[1]{\ifcsvoid{optk@#1}} +\newcommand*\ifoptioncmd[1]{\ifcsparam{optk@#1}} + +\newrobustcmd*\ifoptionequal[2]{% + \edefoption{#1}\opt@temp + \eifstrequal{\opt@temp}{#2}% +} + +\newrobustcmd*\ifoptionblank[2]{% + \edefoption{#1}\opt@temp + \eifblank{\opt@temp}{#2}% +} + +\newcommand*\ifoptiontype[2]{% + \letoptiontype{#1}\opt@type + \eifstrequal{\opt@type}{#2}% +} +\newcommand*\ifoptioniscode[1]{% + \let\opt@next\@secondoftwo% + \letoption@{#1/@code}\opt@code + \letoption@{#1}\opt@value + \ifx\opt@code\opt@value + \let\opt@next\@firstoftwo + \else + \letoptiontype{#1}\opt@type + \eifstrequal{\opt@type}{code}% + {\let\opt@next\@firstoftwo}{}% + \fi + \opt@next +} + +\newcommand*\letoptiontype[2]{% + \letoption@{#1/@type}#2% + \ifx#2\relax\def#2{code}\fi +}% + +\newcommand*\optionparamcount[1]{% + \ifoptiondefined{#1/@cmd1}{1}{% + \ifoptiondefined{#1/@cmd2}{2}{% + \ifoptiondefined{#1/@cmd3}{3}{% + \ifoptiondefined{#1/@cmd4}{4}{% + \ifoptiondefined{#1/@cmd5}{5}{% + \ifoptiondefined{#1/@cmdx}{x}{% + 0% + }}}}}}% +} + +\newcommand*\optionshowtype[1]{% + \letoptiontype{#1}\opt@type% + \eifstrequal{\opt@type}{code}% + {\option@font@special{cmd}$_\optionparamcount{#1}$}% + {\option@font@special{\opt@type}}% +} + +% Set the current color from a color options. +% Keeps color unchanged if the color name was blank. +\newcommand*\optioncolor[1]{\ifoptioncolortransparent{#1}{}{\color{#1}}} +\newcommand*\optiontextcolor[2]{\ifoptioncolortransparent{#1}{#2}{\textcolor{#1}{#2}}} +\newcommand*\optioncolorbox[2]{\ifoptioncolortransparent{#1}{#2}{\colorbox{#1}{#2}}} + +\newcommand*\ifoptioncolortransparent[3]{% + \ifoptionblank{#1}{#2}{% + \ifoptiontype{#1}{color expr}{% + \edefoption{#1}\opt@color + \expandnext{\opt@colorlet{#1}}{\opt@color}% + }{}% + #3 + }% +} + + +% convert dimension option into number in pt for use in picture +\newcommand*\opt@unit[1]{\strip@pt\dimexpr#1\relax} +\newcommand*\optionunit[1]{\opt@unit{\option{#1}}} + +% -------------------------------------------------------- +% Comma seperated values +% -------------------------------------------------------- + +\newcommand\option@headof[1]{\option@headof@#1,\opt@stop}% +\def\option@headof@#1,#2\opt@stop{#1}% + +\providerobustcmd*\option@ifanyof[2]{% + \let\opt@do@next\@secondoftwo + \edef\opt@temp{#1}% + \def\opt@do@anyof##1{\eifstrequal{\opt@temp}{##1}{\let\opt@do@next\@firstoftwo\listbreak}{}}% + \expandnext{\forcsvlist\opt@do@anyof}{#2}% + \opt@do@next +} + +\newrobustcmd*\ifoptionanyof[2]{\option@ifanyof{\option{#1}}{#2}} + +\newcount\opt@idx +\providerobustcmd*\option@findidx[3]{% + \opt@idx=0\relax + \def#3{-1}% + \edef\opt@temp{#1}% + \def\opt@do@findidx##1{\eifstrequal{\opt@temp}{##1}{\edef#3{\the\opt@idx}\listbreak}{\advance\opt@idx 1\relax}}% + \expandnext{\forcsvlist\opt@do@findidx}{#2}% +} + +% return the first letter of the argument or ' ' (space) if the input was empty. Does not expand the argument. +\providecommand*\option@firstletterof[1]{\eifblank{#1}{ }{\expandafter\option@firstletterof@#1\opt@stop}} +\def\option@firstletterof@#1#2\opt@stop{#1} + + +% -------------------------------------------------------- +% Lists +% -------------------------------------------------------- + +\newrobustcmd*\option@setnil[1]{\option@set{#1}{}} +\newrobustcmd*\option@push[2]{\listcsadd{optk@#1}{#2}} +\newrobustcmd*\option@epush[2]{\listcseadd{optk@#1}{#2}}% +\newrobustcmd\optionlistdo[2]{\def\opt@@do##1{#2}\forlistcsloop{\opt@@do}{optk@#1}} + +\newrobustcmd*\option@setlist[2]{\option@setnil{#1}\option@concat{#1}{#2}} +\newrobustcmd*\option@concat[2]{% + \def\opt@do@concat##1{\option@push{#1}{##1}}% + \expandnext{\forcsvlist\opt@do@concat}{#2}% +} +\newcommand*\letoptionlist[2]{% + \def#2{}% + \optionlistdo{#1}{% + \ifdefempty#2% + {\def#2{##1}}% + {\appto#2{,##1}}% + }% +} +\newcommand*\optionlist[1]{% + \let\opt@comma\@empty% + \optionlistdo{#1}{% + \opt@comma##1% + \ifx\opt@comma\@empty + \def\opt@comma{,}% + \fi + }% +} + +\let\ifoptionnil\ifoptionvoid +\newrobustcmd*\ifoptioncontains[2]{\xifinlistcs{#2}{optk@#1}} +\newrobustcmd*\option@pushifnew[2]{\ifoptioncontains{#1}{#2}{}{\option@push{#1}{#2}}} + + +% -------------------------------------------------------- +% Choices: these functions are used for the choice type +% -------------------------------------------------------- + +% find a choice: sets opt@choicename, opt@choicevalue, and opt@choiceord +\newcount\opt@choiceord +\newcommand*\opt@findchoice[2]{% {<name>}{<option>} + \opt@choiceord=0\relax + \def\opt@choicevalue{}% + \def\opt@choicename{}% + \def\opt@do@choice##1{\opt@do@choice@##1\opt@stop}% + \def\opt@do@choice@##1=##2\opt@stop{% + \ifstrequal{##1}{#1}{% + \def\opt@choicename{##1}% + \def\opt@choicevalue{##2}% + \listbreak + }% + {\advance\opt@choiceord 1\relax}% + }% + \forlistcsloop\opt@do@choice{\optionname{#2/@choices}}% + \ifx\opt@choicename\@empty% + \opt@choiceord=-1\relax + \fi +} + +% get choice list as comma separated name list +\newcommand\letoptionchoices[2]{% {<option>}{\<macro>} + \def#2{}% + \def\opt@do@choice##1{\opt@do@choice@##1\opt@stop}% + \def\opt@do@choice@##1=##2\opt@stop{% + \ifx#2\@empty + \def#2{##1}% + \else + \appto#2{,##1}% + \fi + }% + \forlistcsloop\opt@do@choice{\optionname{#1/@choices}}% +}% + +% parse choices into a list of elements "choice=value". Set \opt@choices and \opt@firstchoicename +\newcommand*\opt@choices@parse[2]{% {<option>}{<user list>} + \def\opt@choices{}% + \def\opt@firstchoicename{}% + \edef\opt@choicepath{#1}% + \opt@choice@foreach#2,\opt@stop,\relax + %\typeout{choice list: \opt@choices}% +} + +\def\opt@choice@foreach#1,{% + \ifx\opt@stop#1\@empty\else + \opt@choice@item#1==\opt@stop + \expandafter\opt@choice@foreach\fi} + +\def\opt@choice@item#1=#2=#3\opt@stop{% + \opt@trimdef\opt@choicename{#1}% + \ifx\@empty#3\@empty% + \let\opt@choicevalue\opt@choicename% default is the name itself + \else + \opt@trimdef\opt@choicevalue{#2}% + \fi + \expandnext{\expandnextsingle\opt@choice@push{\opt@choicename}}{\opt@choicevalue}% +} + +\newcommand*\opt@choice@push[2]{% + %\typeout{parse choice: #1={#2}}% + \listadd\opt@choices{#1={#2}}% + \ifx\opt@firstchoicename\@empty + \def\opt@firstchoicename{#1}% + \fi +} + + +% -------------------------------------------------------- +% Invoke +% -------------------------------------------------------- + +\newcommand*\option@xinvoke[2]{% + \protected@edef\opt@temp{#2}% + \expandnext{\option@invoke{#1}}{\opt@temp}% +}% +\newcommand*\option@einvoke[2]{% + \expandnext{\option@invoke{#1}}{#2}% +}% +\newcommand*\option@invoke[2]{% + \option@ensuredefined{#1}% + \def\optionvalue{#2}% + \option{#1/@code}% +}% +\newcommand*\option@invokedefault[1]{% + \option@ensuredefined{#1}% + \let\optionvalue\optionnovalue% + \option{#1/@code}% +}% + + +% -------------------------------------------------------- +% Record metadata +% -------------------------------------------------------- + +\newrobustcmd*\option@addpathinfo[1]{% + \def\opt@do@add##1##2##3{% + \ifoptiondefined{##1/@names}{}{% + \option@setnil{##1/@names}\option@setnil{##1/@paths}% + }% + \eifblank{##2}% + {\expandnext{\option@pushifnew{##1/@names}}{##3}% + %\typeout{path "##1/@names": add "##3": \option{##1/@names}}% + }% + {\option@splitroot{##2}% + \expandnext{\option@pushifnew{##1/@paths}}{\option@root}% + %\typeout{path "##1/@paths": add "\option@root"; \option{##1/@paths}}% + }% + }% + \option@foreachparent\opt@do@add{#1}% +} + +% -------------------------------------------------------- +% Basic definitions of options +% -------------------------------------------------------- +\def\option@patch@pre{} +\def\option@patch@post{} + +\newrobustcmd*\optionnewcode{\@ifstar{\optionnewcode@{true}}{\optionnewcode@{false}}} +\newrobustcmd*\optionnewcode@[3]{%*[] + %\typeout{define cmd: #2}% + \option@ensurefree{#2}% + \option@splitpath{#2}% sets option@path, and option@name + \eifblank{\option@path}{% + \optionerror{#2}{no valid option name specified. Option definitions must start with a forward slash (i.e. "/path/<name>")}% + }{}% + \option@addpathinfo{#2}% + \ifbool{#1}% noarg? expand out for efficiency + {\csdef{optk@#2/@code}{% + \ifx\optionvalue\optionnovalue\else\optionerror@noarg{#2}\fi + \option@patch@pre% hook to patch in extra checking code + #3% + \option@patch@post% hook for post code.. generally unsafe due to chained handlers + }}% + {\csdef{optk@#2/@code}{% + \ifx\optionvalue\optionnovalue + \letoption@{#2/@def}\optionvalue% + \ifx\optionvalue\relax\optionerror@needarg{#2}\fi + \fi + \option@patch@pre + #3% + \option@patch@post + }}% + \csletcs{optk@#2}{optk@#2/@code}% point at first to the cmd +} + +% define handler code; such code takes the expanded option@handlerpath and optionvalue as arguments +\newrobustcmd*\optionnewhandler{\@ifstar{\optionnewhandler@{true}}{\optionnewhandler@{false}}} +\newrobustcmd*\optionnewhandler@[3]{% + \option@ensurefree{#2}% + \option@def{#2/@cmd2}##1##2{#3}% + \optionnewcode@{#1}{#2}{\expandnext{\expandnext{\option{#2/@cmd2}}{\option@handlerpath}}{\optionvalue}}\relax% +} + + +% -------------------------------------------------------- +% Add pre/post processing to an option +% note: patchcmd leaves some spaces behind.. can we switch to appto? +% -------------------------------------------------------- + +\newrobustcmd*\optionprependcode[2]{% + \option@ensuredefined{#1}% + \expandafter\patchcmd\expandafter{\csname optk@#1/@code\endcsname}{\option@patch@pre}{#2\option@patch@pre}{}{\optionerror{#1}{unable to prepend code}}% +} + +\newrobustcmd*\optionappendcode[2]{% + \option@ensuredefined{#1}% + \expandafter\patchcmd\expandafter{\csname optk@#1/@code\endcsname}{\option@patch@post}{\option@patch@post #2}{}{\optionerror{#2}{unable to append code}}% +} + + +% -------------------------------------------------------- +% Bootstrap and use our options themselves to define everything else +% -------------------------------------------------------- + +% start bootstrap with the 'new handler' handler +\optionnewhandler{/handlers/new handler}{\optionnewhandler{#1}{#2\option@chainempty}\option@chainempty} + +\options{ + % + /handlers/new transformer/.new handler =\optionnewhandler{#1}{#2\option@chain}, + /handlers/new operation/.new handler =\optionnewhandler{#1}{#2\option@chainonly}, + /handlers/new handler*/.new handler =\optionnewhandler*{#1}{#2\option@chainempty}, + /handlers/new operation*/.new handler =\optionnewhandler*{#1}{#2\option@chainonly}, + % + /handlers/new cmd transformer/.new handler=\optionsalso{% + #1/.new operation* = {\expandnextsingle\optionprependcode{##1}{#2}}% prepend code to the option it is handling (##1) + }, + % command transformers + /handlers/expands/.new cmd transformer ={\protected@edef\optionvalue{\optionvalue}}, + /handlers/defaults/.new cmd transformer ={\option@default@transform}, + % the "new handler" can be provided with defaults. + /handlers/new handler/.defaults, + % value transformers + /handlers/expand once/.new transformer =\edef\optionvalue{\expandafter\expandafter\expandafter\noexpand\optionvalue}, + /handlers/expand twice/.new transformer =\edef\optionvalue{\expandafter\expandafter\expandafter\noexpand\optionvalue}% + \edef\optionvalue{\expandafter\expandafter\expandafter\noexpand\optionvalue}, + /handlers/expanded/.new transformer =\protected@edef\optionvalue{\optionvalue}, +} + +% For values of the form "[default]value" we assign "default" as the default, and continue with "value" +\newrobustcmd\option@default@transform{\expandafter\option@default@transform@\optionvalue\opt@stop} +\def\option@default@transform@{\@ifnextchar[{\option@default@transform@opt}{\option@default@transform@end}} +\def\option@default@transform@end#1\opt@stop{} +\def\option@default@transform@opt[#1]#2\opt@stop{% + \option@set{\option@handlerpath/@def}{#1}% + \edef\optionvalue{\unexpanded{#2}}% preserve # arguments in optionvalue +} + + +% -------------------------------------------------------- +% command actions take arguments +% -------------------------------------------------------- + +% provide command pattern; defined separately so we can expand the option@handler and pass as an argument +\newrobustcmd*\optionnewcmdx[4]{% + \option@def{#1/@cmdx}#2\opt@stop{#4}% + \optionnewcode{#1}{\expandnextcmds{\option{#1/@cmdx}}{\optionvalue#3\opt@stop}\relax}% +} + +\options{ + /handlers/new code/.new handler/.defaults = \optionnewcode{#1}{#2}\option@chainempty, + /handlers/new cmd*/.new handler = \optionnewcode*{#1}{#2}\option@chainempty, + /options/exec/.new code =\optionvalue, + % commands + /handlers/new cmd/.new handler/.defaults=\optionsalso{% + /options/exec={\option@def{#1/@cmd1}##1{#2}},% + #1/.new code={\expandnext{\option{#1/@cmd1}}{\optionvalue}},% + },% + /handlers/new cmd 2/.new handler/.defaults = \optionsalso{ + /options/exec = {\option@def{#1/@cmd2}##1##2##3{\ifblank{##3}{#2}{\optionerror@toomany{#1}{2 (but got: ##1,##2,##3)}}}}, + #1/.new code = {\expandnextcmds{\option{#1/@cmd2}}{\optionvalue}{}{}{}\relax}, + }, + /handlers/new cmd 3/.new handler/.defaults = \optionsalso{ + /options/exec = {\option@def{#1/@cmd3}##1##2##3##4{\ifblank{##4}{#2}{\option@toomany{#1}{3 (but got: ##1,##2,##3,##4)}}}}, + #1/.new code = {\expandnextcmds{\option{#1/@cmd3}}{\optionvalue}{}{}{}{}\relax}, + }, + /handlers/new cmd 4/.new handler/.defaults = \optionsalso{ + /options/exec = \option@def{#1/@cmd4}##1##2##3##4##5{\ifblank{##5}{#2}{\optionerror@toomany{#1}{4}}}, + #1/.new code = \expandnextcmds{\option{#1/@cmd4}}{\optionvalue}{}{}{}{}{}\relax, + }, + % tuples and triples take exactly 2 or 3 arguments + /handlers/new cmd tuple*/.new handler/.defaults = \optionsalso{ + #1/.new cmdx = {##1,##2,##3}{,,}% + {\ifstrequal{##3}{,}{#2}{\optionerror{#1}{expecting a 2 argument tuple \{_,_\}, but got "\optionvalue"}}}, + #1/.type=cmd tuple*, + }, + /handlers/new cmd tuple/.new handler/.defaults = \optionsalso{ + /options/exec = {\option@def{#1/@cmd2}##1##2{#2}}, + #1/.new cmdx = {##1,##2}{,}% + {\ifblank{##2}{\option{#1/@cmd2}{##1}{##1}}{\option{#1/@cmd2}{##1}{##2}}}, + #1/.type=cmd tuple, + }, + /handlers/new cmd triple/.new handler/.defaults = \optionsalso{ + #1/.new cmdx = {##1,##2,##3,##4}{,,,}% + {\ifstrequal{##4}{,,}{#2}{\optionerror{#1}{expecting a 3 argument triple \{_,_,_\}, but got "\optionvalue"}}}, + #1/.type=cmd triple, + }, + % aliases + /handlers/new cmdx/.new cmd 3/.defaults = \expandnextsingle\optionnewcmdx{\option@handlerpath}{#1}{#2}{#3}, + /handlers/new code alias/.new handler = \option@set{#1/@code}{\option{#2/@code}}, + /handlers/new cmd 0/.new code alias = /handlers/new cmd*, + /handlers/new cmd 1/.new code alias = /handlers/new cmd, +} + + +% -------------------------------------------------------- +% Provide unknown here so we get a proper error for unknown options +% -------------------------------------------------------- + +\options{ + /@unknown/.new cmd 2 = {% + \ifopt@unknownwarnonly + \optionwarning{#1}{unknown option}% + \else + \optionerror{#1}{unknown option}% + \fi% + }% +} + + +% -------------------------------------------------------- +% Convenience handlers +% -------------------------------------------------------- + +% debugging +\options{ + /handlers/show/.new handler* = {\option@font@name{#1}=\optionshow{#1}}, + /handlers/typeout/.new handler* = {\optiontypeout{#1}}, +} + +% family options +\options{ + /handlers/new style/.new handler/.defaults = \optionsalso{ + #1/.new cmd = \optionsalso{#2}, + #1/.type = style, + }, + /handlers/new style*/.new handler = \optionsalso{ + #1/.new cmd* = \optionsalso{#2}, + #1/.type = style, + }, + /handlers/new alias/.new handler ={% + \option@ensuredefined{#2}% + \optionsalso{% + #1/.new cmd = \optionsalso{#2={##1}}, + #1/.type = alias, + }% + \option@xset{#1}{\option{#2}}% + %\option@xset{#1/@ini}{\option{#2/@ini}}% + }, + /handlers/new alias*/.new handler = \optionsalso{ + \option@ensuredefined{#2}% + \optionsalso{ + #1/.new cmd = \optionsalso{#2}, + #1/.type = alias, + }% + \option@xset{#1}{\option{#2}}% + %\option@xset{#1/@ini}{\option{#2/@ini}}% + }, + /handlers/show/style/.new cmd = {\option@font@special{style}}, + % + /handlers/search also/.new handler =\optionaddsearch{#1}{#2}, + /handlers/cd/.new handler* =\edef\option@defaultpath{#1}, + /handlers/new family/.new handler =[]{\optionsalso{ + #1/.new cmd* = {\edef\option@defaultpath{#1}}, + #1/.search also = {#2}, + #1/.type = family, + #1/.cd + }}, + /handlers/show/family/.new cmd = {\option@font@special{family}, searches: \option@showvalue{\optionlist{#1/@searchalso}}}, +} + +% compatibility +\options{ + /handlers/code/.new handler = \optionsalso{#1/.new cmd = {#2}}, + /handlers/style/.new handler = \optionsalso{#1/.new style= {#2}}, + /handlers/is family/.new handler* = \optionsalso{#1/.new family}, +} + +% defining new option types +\options{% + /handlers/undef/.new handler = {% + \ifoptiondefined{#1}{% + \option@undef{#1}% + \option@undef{#1/@type}% + \option@undef{#1/@code}% + \option@undef{#1/@def}% + \option@undef{#1/@ini}% + % may leave other values but the above ones are surely required to be removed + }% + }, + /handlers/type/.new handler = \option@ensuredefined{#1}\option@set{#1/@type}{#2}, + /handlers/default/.new handler = \option@ensuredefined{#1}\option@set{#1/@def}{#2}, + /handlers/defaulted/.new handler= {% + \option@ensuredefined{#1}% + \option@default@transform% changes optionvalue and sets /@def if provided + }, + /handlers/initial/.new handler = {% + \option@ensuredefined{#1}% + \option@default@transform% changes optionvalue and sets /@def if provided + \option@eset{#1/@ini}{\optionvalue}% + \option@einvoke{#1}{\optionvalue}% + }, + /handlers/reset/.new operation* = {% + \letoption@{#1/@ini}\opt@temp + \ifx\opt@temp\relax + \optionerror{#1}{cannot reset an option that has no initial value}% + \else + \option@einvoke{#1}{\opt@temp}% + \fi + }, +} + +% \optionaddsearch{<path>}{<search paths>} +% add new search paths to a path. +% for efficiency we expand out the search code inline instead of iterating +\newrobustcmd*\optionaddsearch[2]{% + \ifoptiondefined{#1/@searchalso/@code}% + {\option@concat{#1/@searchalso}{#2}}% + {\option@setlist{#1/@searchalso}{#2}% + \option@set{#1/@searchalso/@type}{searchalso}% + \option@def{#1/@searchalso/@code}{}}% + \def\opt@do@addsearch##1{% + \edef\opt@temp{% + \noexpand\ifx\noexpand\opt@codepath\noexpand\relax + \noexpand\opt@searchforabspath{##1/\noexpand\option@searchpath}% + \noexpand\fi + }% + \expandnext{\option@appto{#1/@searchalso/@code}}{\opt@temp}% + }% + \forcsvlist\opt@do@addsearch{#2}% +} + + +% -------------------------------------------------------- +% Basic data type handlers +% -------------------------------------------------------- + +\options{ + /handlers/new value/.new handler = []\optionsalso{% + #1/.new code = \option@let{#1}\optionvalue, + #1/.type = value, + #1/.initial = {#2}, + }, + % + /handlers/new num/.new handler = [0]\optionsalso{% + #1/.new code = \option@eset{#1}{\the\numexpr\optionvalue\relax}, + #1/.type = num, + #1/.initial = {#2}, + }, + % + /handlers/new glue/.new handler = [0pt]\optionsalso{% + #1/.new code = \option@eset{#1}{\the\glueexpr\optionvalue\relax}, + #1/.type = glue, + #1/.initial = {#2}, + }, + % + /handlers/new length/.new handler = [0pt]\optionsalso{% + #1/.new code = \cssetlength{#1}{\dimexpr\optionvalue\relax}, + #1/.type = length, + /options/exec = \csnewlength{#1}\option@eset{#1}{\csname #1\endcsname}, + #1/.initial = {#2}, + }, + /handlers/show/length/.new cmd = {\option@showvalue{\the\option{#1}}}, + % + /handlers/new dim/.new handler = [0pt]\optionsalso{% + #1/.new code = {\option@eset{#1/@dimexpr}{\optionvalue}}, + #1/.type = dim, + /options/exec = {\option@eset{#1}{\dimexpr\option{#1/@dimexpr}\relax}},% read as dimexpr + #1/.initial = {#2}, + }, + /handlers/show/dim/.new cmd ={\option@showvalue{\the\option{#1}}\ (=\optionshowliteral{#1/@dimexpr})}, + % + /handlers/new toggle/.new handler = [false]\optionsalso{% + #1/.new code = {[true]{% + \ifcsdef{toggle\optionvalue}% + {\csuse{toggle\optionvalue}{#1}}% + {\eifstrequal\optionvalue{True}% + {\toggletrue{#1}}% + {\eifstrequal\optionvalue{False}% + {\togglefalse{#1}}% + {\optionerror@expect{#1}{true or false}}}}% + }}, + #1/.type = toggle, + /options/exec = {\newtoggle{#1}\option@set{#1}{\iftoggle{#1}{true}{false}}}, + #1/.initial = {#2}, + }, + /handlers/flip/.new operation = \iftoggle{#1}{\togglefalse{#1}}{\toggletrue{#1}}, + % + /handlers/new list/.new handler = []\optionsalso{% + #1/.new code = \expandnext{\option@setlist{#1}}{\optionvalue}, + #1/.type = list, + #1/.initial = {#2}, + }, + /handlers/show/list/.new cmd = \letoptionlist{#1}\opt@list\option@showvalue{\opt@list}, + /handlers/push/.new operation = \option@push{#1}{#2}, + /handlers/concat/.new operation = \option@concat{#1}{#2}, + % + /handlers/new choice/.new handler={% + \opt@choices@parse{#1}{#2}% sets \opt@choices & opt@firstchoicename + \option@let{#1/@choices}{\opt@choices}% + \optionsalso{% + #1/.new cmd/.expands = {% + \opt@findchoice{##1}{#1}% + \ifnum\opt@choiceord<0% + \letoptionchoices{#1}\opt@choices + \optionerror@expect{#1}{one of "\opt@choices"}% + \else + \option@eset{#1}{\opt@choicevalue}% + \option@eset{#1/@ord}{\the\opt@choiceord}% + \option@eset{#1/@name}{\opt@choicename}% + \fi + }, + #1/.type = choice, + #1/.expanded/.initial = \opt@firstchoicename,% + }% + }, + /handlers/show/choice/.new cmd = {% + \option@showvalue{\option{#1}} % + (\option@font@name{@ord}=\option{#1/@ord}), % + \letoptionchoices{#1}\opt@choices + choices=\option@showvalue{\opt@choices}% + }, + % + /handlers/new color/.new handler=[black]{% + %\opt@ensurexcolor{#1}% + \optionsalso{% + #1/.new cmd/.expands = {% + \option@set{#1}{##1}% + \eifblank{##1}{}{\opt@colorlet{#1}{##1}}% + }, + #1/.type = color, + #1/.initial = {#2}, + }% + }, + /handlers/new color expr/.new handler=[black]{% + %\opt@ensurexcolor{#1}% + \optionsalso{% + #1/.new value, + #1/.type = color expr, + #1/.initial = {#2}, + }% + }, +} + +\providecommand\colorlet[2]{\optionerror{#1}{color options require package "xcolor"; please include this package}}% +\providecommand\definecolor[3]{\optionerror{#1}{color options require package "xcolor"; please include this package}}% + +% opt@colorlet{<color name>}{<color>} lets color be a color name or \#XXXXXX HTML color +\def\opt@colorlet@html#1\opt@stop#2\opt@stop{\definecolor{#2}{HTML}{#1}} +\def\opt@colorlet@name#1\opt@stop#2\opt@stop{\colorlet{#2}{#1}} +\def\opt@colorlet@{\@ifnextchar \#{\@firstoftwo{\opt@colorlet@html}}{\opt@colorlet@name}} +\newrobustcmd*\opt@colorlet[2]{% + \ifdef{\colorlet}% + {\opt@colorlet@#2\opt@stop#1\opt@stop}% + {\definecolor{#1}{named}{#2}}% if no xcolor, only support named colors +} + + + + +% -------------------------------------------------------- +% Hook into existing definitions +% -------------------------------------------------------- + +\options{ + /handlers/is if/.new handler = \optionsalso{% + /options/exec = {\ifcsdef{if#2}{}{\optionerror{#1}{no `if` with name "#2" is defined}}}, + #1/.new code = {[true]\ifcsdef{#2\optionvalue}{\csuse{#2\optionvalue}}{\optionerror@expect{#1}{true or false}}}, + #1/.type = if, + /options/exec = {\option@set{#1}{#2}},% read name of the if + }, + /handlers/show/if/.new cmd = {\option@showvalue{\option{#1}} (=\option@showvalue{\ifbool{\option{#1}}{true}{false})}}, + % + /handlers/is def/.new handler = \optionsalso{% + /options/exec = \ifdef{#2}{}{\optionerror{#1}{no definition "\detokenize{#2}" is found}}, + #1/.new code = \expandafter\def\expandafter#2\expandafter{\optionvalue}, + #1/.type = def, + /options/exec =\option@set{#1}{#2},% read returns definition + %#1/.initial = {#2}, + }, + % + /handlers/is edef/.new handler = \optionsalso{% + /options/exec = \ifdef{#2}{}{\optionerror{#1}{no definition "\detokenize{#2}" is found}}, + #1/.new code = \protected@edef#2{\optionvalue}, + #1/.type = edef, + /options/exec =\option@set{#1}{#2},% read returns definition + %#1/.initial = {#2}, + }, + % + /handlers/is counter/.new handler = \optionsalso{% + /options/exec = \ifltxcounter{#2}{}{\optionerror{#1}{no definition "\detokenize{#2}" is found}}, + #1/.new code = \setcounter{#2}{\optionvalue}, + #1/.type = counter, + /options/exec =\option@set{#1}{#2},% read returns definition + %#1/.initial = {#2}, + }, + /handlers/show/counter/.new cmd = {\option@showvalue{\option{#1}} (=\option@font@value{\arabic{\option{#1}}})}, + /handlers/inc/.new operation = [1]{% + \ifoptiontype{#1}{counter}% check if this is a counter or a number + {\addtocounter{\option{#1}}{#2}}% + {\option@xinvoke{#1}{\option{#1} + #2}}% + }, + /handlers/step/.new operation* = {% + \ifoptiontype{#1}{counter}% check if this is a counter + {\stepcounter{\option{#1}}}% + {\optionerror{#1}{cannot "step" a non-counter}}% + }, + /handlers/refstep/.new operation* = {% + \ifoptiontype{#1}{counter}% check if this is a counter + {\refstepcounter{\option{#1}}}% + {\optionerror{#1}{cannot "refstep" a non-counter}}% + }, +} + + +% -------------------------------------------------------- +% Initial options for our library +% -------------------------------------------------------- +\options{ + /options/ignoreunknown/.is if = opt@ignoreunknown, + /options/unknownwarnonly/.is if = opt@unknownwarnonly, + /options/allowsearch/.is if = opt@allowsearch, + /options/collectunknown/.new style* = {/options/ignoreunknown,/options/remaining={}}, + /options/showbuiltin/.new toggle, +} + +\csundef{optk@/options/remaining}% tricksy, but we want to define it officially here :-) +\optionsalso{ + /options/remaining/.new list, +} + + +% -------------------------------------------------------- +% Show definitions +% -------------------------------------------------------- + +\newrobustcmd*\optionshowall[1][false]{\options{/options/showbuiltin=#1}\optionshowpath{}} + +\newcommand*\option@font@name[1]{\textsf{#1}} +\newcommand*\option@font@path[1]{\textsf{#1}} +\newcommand*\option@font@value[1]{\textsf{#1}} +\newcommand*\option@font@special[1]{\textit{#1}} + +\newcommand*\option@showvalue[1]{$\langle$\option@font@value{#1}$\rangle$} + +\newrobustcmd*\optionshowpath[2][]{\option@showpath{#1#2}{#2}} +\newrobustcmd*\option@showpath[2]{% + \noindent\option@font@path{#2/}% + \option@showitems{/@names}{\option@showname}{#1}% + \option@showitems{/@paths}{\option@showpath}{#1}% +} + +\newrobustcmd*\option@showitems[3]{% + \ifoptionvoid{#3#1}{}{% + \begin{opt@marginlr}{1em}{0em}% + \optionlistdo{#3#1}{% + \iftoggle{/options/showbuiltin}{}% + {\option@ifanyof{#3/##1}{/handlers,/options}{\@gobble}% + {\if\option@firstletterof{##1}.\relax\expandafter\@gobble\fi}}% + {% + \noindent\hspace*{-0.5em}#2{#3/##1}{##1}% + \par% + }% + }% + \end{opt@marginlr}% + }% +} + +\newrobustcmd*\option@showname[2]{% + \option@font@name{#2}% + =\optionshow{#1}% + \ifoptiondefined{#1/@ini}{% + , initial=\optionshowliteral{#1/@ini}% + }{}% + \ifoptiondefined{#1/@def}{% + , default=\optionshowliteral{#1/@def}% + }{}% +} + +\newrobustcmd*\optionshowliteral[1]{\letoption{#1}\opt@temp\option@showvalue{\texttt{\expandnextsingle\detokenize{\opt@temp}}}} + +\newcommand*\optiontypeout[1]{% + \def\option@font@special##1{##1}% + \def\option@font@name##1{##1}% + \def\option@font@path##1{##1}% + \def\option@font@value##1{##1}% + \def\option@showvalue##1{##1}% + \typeout{#1=\optionshow{#1}}% +} + +\newcommand*\optionshow[1]{% + \ifoptiondefined{#1/@type}% + {\ifoptiondefined{/handlers/show/\option{#1/@type}}{\@firstoftwo}{\@secondoftwo}}% + {\@secondoftwo}% + {\option@invoke{/handlers/show/\option{#1/@type}}{#1}}% + {\ifoptiondefined{#1}% + {\ifoptioniscode{#1}% + {\optionshowtype{#1}}% + {\option@showvalue{\option{#1}}}}% + {\option@font@special{undefined}}% + }% +} + +\newenvironment{opt@marginlr}[2]{% + \topsep\z@ + \partopsep\z@ + \trivlist + \rightmargin=\dimexpr#2\relax% + \leftmargin=\dimexpr#1\relax% + \advance\linewidth -\rightmargin + \advance\linewidth -\leftmargin + \advance\@totalleftmargin \leftmargin + \parshape \@ne \@totalleftmargin \linewidth + \item[]% +}% +{\endtrivlist} + + +% -------------------------------------------------------- +% Handling Package and Class options +% -------------------------------------------------------- + +% Process the 'global' class options in a package file +% Just take care of known options and adjust the unusedoptionslist accordingly +\newrobustcmd*\option@ProcessGlobalOptions[1]{% + \ifdefvoid{\@classoptionslist}{}{% + % process known options, and collect the rest in the remaining list + \options{/options/collectunknown,#1}\expandnext\optionsalso{\@classoptionslist}% + % remove processed options from the latex @unusedoptionlist + \ifx\@unusedoptionlist\@empty\relax\else + \def\opt@do##1{% + \ifoptioncontains{/options/remaining}{##1}{}{% + \expandnext{\@removeelement{##1}}{\@unusedoptionlist}{\@unusedoptionlist}% + }% + }% + \expandnext{\forcsvlist\opt@do}{\@classoptionslist}% + \fi + }% +} + +% Process the local option in a .sty file, raise an error on unknown flags +\newrobustcmd*\option@ProcessPackageOptions[1]{% + \letcs\opt@localoptions{opt@\@currname.\@currext}% + \ifdefvoid{\opt@localoptions}{}{% + \options{#1}\expandnext\optionsalso{\opt@localoptions}% + }% + \let\CurrentOption\@empty + \AtEndOfPackage{\let\@unprocessedoptions\relax}% +} + +% Process the local options in a .cls file +\newrobustcmd*\option@ProcessClassOptions[1]{% + \letcs\opt@localoptions{opt@\@currname.\@currext}% + \ifdefvoid{\opt@localoptions}{}{% + \options{/options/collectunknown,#1}\expandnext\optionsalso{\opt@localoptions}% + \letoptionlist{/options/remaining}\@unusedoptionlist% set the unused option list + }% + \let\CurrentOption\@empty + \AtEndOfPackage{\let\@unprocessedoptions\relax}% +} + +% Process class- or package options. +% A package will take global class options into account +\newrobustcmd*\options@ProcessOptions[1]{% + \ifx\@currext\@clsextension + \option@ProcessClassOptions{#1}% + \else + \option@ProcessGlobalOptions{#1}% + \option@ProcessPackageOptions{#1}% + \fi +} +\@onlypreamble\options@ProcessOptions +\@onlypreamble\options@ProcessPackageOptions +\@onlypreamble\options@ProcessClassOptions +\@onlypreamble\options@ProcessGlobalOptions + + +% -------------------------------------------------------- +% Enable debugging +% -------------------------------------------------------- +\ifopt@debug + \let\option@fast\option + \renewcommand*\option[1]{% + \ifcsdef{optk@#1}{}{\z@\optionerror{#1}{debug option: undefined option}}% + \option@fast{#1}% + } + \let\letoption@fast\letoption + \renewcommand*\letoption[2]{% + \ifcsdef{optk@#1}{}{\z@\optionerror{#1}{debug letoption: undefined option}}% + \letoption@fast{#1}#2% + } +\fi + +% -------------------------------------------------------- +% restore cat codes +% -------------------------------------------------------- +\opt@restore@catcodes |