summaryrefslogtreecommitdiff
path: root/macros/generic/expkv-bundle/expkv-opt.dtx
diff options
context:
space:
mode:
Diffstat (limited to 'macros/generic/expkv-bundle/expkv-opt.dtx')
-rw-r--r--macros/generic/expkv-bundle/expkv-opt.dtx796
1 files changed, 796 insertions, 0 deletions
diff --git a/macros/generic/expkv-bundle/expkv-opt.dtx b/macros/generic/expkv-bundle/expkv-opt.dtx
new file mode 100644
index 0000000000..d2b51d0cb0
--- /dev/null
+++ b/macros/generic/expkv-bundle/expkv-opt.dtx
@@ -0,0 +1,796 @@
+% \iffalse meta-comment
+%
+% File: expkv-opt.dtx Copyright (C) 2020-2023 Jonathan P. Spratte
+%
+% This work may be distributed and/or modified under the conditions of the
+% LaTeX Project Public License (LPPL), either version 1.3c of this license or
+% (at your option) any later version. The latest version of this license is in
+% the file:
+%
+% http://www.latex-project.org/lppl.txt
+%
+% ------------------------------------------------------------------------------
+%
+%<*driver>^^A>>=
+\def\expkvDocNoGenerate{}
+\input expkv-bundle.ins
+\generate{\file{expkv-opt.sty}{\from{expkv-opt.dtx}{pkg}}}
+\endbatchfile
+%</driver>^^A=<<
+% \fi
+%
+% \section{\expkvo}
+%
+% \gobbledocstriptag
+%<*pkg>
+%
+% First we check whether the \LaTeXe\ kernel supports raw options. If it doesn't
+% we check whether a specific version was requested, and if that's not the case
+% we manually run |\pkgcls@parse@date@arg| with the last version that supported
+% non-raw options.
+% \begin{macrocode}
+\IfFormatAtLeastTF{2021/05/01}
+ {}
+ {%
+ \ifx\pkgcls@targetlabel\@empty
+ \ifnum\requestedLaTeXdate=\pkgcls@targetdate
+ \pkgcls@parse@date@arg{=v0.1}%
+ \fi
+ \fi
+ }
+% \end{macrocode}
+% Then we tell \LaTeXe\ where to find which release so that the package rollback
+% code of \LaTeXe\ can do its thing.
+% \begin{macrocode}
+\DeclareRelease{v0.1}{2020/10/10}{expkv-opt-2020-10-10.sty}
+\DeclareCurrentRelease{v0.2}{2021/04/04}
+% \end{macrocode}
+%
+% Start the package with the typical \LaTeX\ standards.
+%
+% \begin{macro}{\ekvoVersion,\ekvoDate}
+% Store the package's version and date in two macros.
+% \begin{macrocode}
+\newcommand*\ekvoVersion{1.0}
+\newcommand*\ekvoDate{2023-01-23}
+% \end{macrocode}
+% \end{macro}
+% And we report who we are and what we need.
+% \begin{macrocode}
+\ProvidesPackage{expkv-opt}
+ [%
+ \ekvoDate\space v\ekvoVersion\space
+ parse class and package options with expkv%
+ ]
+\RequirePackage{expkv}
+% \end{macrocode}
+%
+%
+% \subsection{Loop}
+%
+% \begin{macro}[internal]
+% {\ekvo@CurrentOption@loop,\ekvo@CurrentOption@loop@,\ekvo@end@loop}
+% We'll need some loop which can iterate over a comma separated list. The loop
+% is very basic and only works for commas of category~12. First we insert the
+% delimiters for the actual loop. The |\ekv@set@other| is necessary to get a
+% functional |\ekvmorekv| in this loop.
+% \begin{macrocode}
+\protected\long\def\ekvo@CurrentOption@loop#1#2%
+ {%
+ \ekvo@CurrentOption@loop@#2\ekv@set@other\ekv@mark#1,\ekv@stop,\ekvo@tail
+ }
+% \end{macrocode}
+% The actual loop checks whether the final element has been read and if so
+% ends the loop. Else blank elements are ignored, |\CurrentOption| is set and
+% the macro which parses the list elements called. Then call the next
+% iteration.
+% \begin{macrocode}
+\long\def\ekvo@CurrentOption@loop@#1\ekv@set@other#2,%
+ {%
+ \ekv@gobble@from@mark@to@stop#2\ekvo@end@loop\ekv@stop
+ \ekv@ifblank{#2}%
+ {}%
+ {%
+ \edef\CurrentOption{\unexpanded\expandafter{\ekv@gobble@mark#2}}%
+ #1{#2}%
+ }%
+ \ekvo@CurrentOption@loop@#1\ekv@set@other\ekv@mark
+ }
+\long\def\ekvo@end@loop#1\ekvo@tail{}
+% \end{macrocode}
+% \end{macro}
+%
+%
+% \subsection{Tests}
+%
+% \begin{macro}[internal]{\ekvo@ifx@TF,\ekvo@ifx@T,\ekvo@ifx@F}
+% We'll need branching |\ifx| tests so that user input containing unbalanced
+% \TeX\ ifs doesn't break (at least not because of us, everything else is the
+% fault of \LaTeXe).
+% \begin{macrocode}
+\def\ekvo@ifx@TF#1#2{\ifx#1#2\ekv@fi@firstoftwo\fi\@secondoftwo}
+\def\ekvo@ifx@T#1#2{\ifx#1#2\ekv@fi@firstofone\fi\@gobble}
+\def\ekvo@ifx@F#1#2{\ifx#1#2\ekv@fi@gobble\fi\@firstofone}
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}[internal]{\ekvo@do@with@set,\ekvo@name,\ekvo@setname}
+% This test checks whether the \set\ is defined. If it is we store it in
+% |\ekvo@setname| and set |\ekvo@name| to a short cut to get the \key's
+% callback name. Next we execute the code in |#2|, if the \set\ isn't defined
+% |#2| is gobbled.
+% \begin{macrocode}
+\protected\def\ekvo@do@with@set#1#2%
+ {%
+ \ekvifdefinedset{#1}%
+ {%
+ \expandafter
+ \let\expandafter\ekvo@name\csname\ekv@undefined@set{#1}\endcsname
+ \def\ekvo@setname{#1}%
+ #2%
+ }%
+ {\ekvo@err@undefined@set{#1}}%
+ }
+% \end{macrocode}
+% \end{macro}
+%
+%
+% \subsection{Key handlers}
+%
+% \expkvo\ uses handlers specifying what happens if a parsed \key\ is defined or
+% undefined.
+%
+% \begin{macro}[internal]
+% {\ekvo@handle@undefined@k@pkg,\ekvo@handle@undefined@kv@pkg}
+% The case for undefined keys in a local list of a package is easy, just throw
+% appropriate errors.
+% \begin{macrocode}
+\protected\long\def\ekvo@handle@undefined@k@pkg#1%
+ {%
+ \ekv@ifdefined{\ekvo@name{#1}}%
+ {\ekvo@err@value@required{#1}}%
+ {\ekvo@err@undefined@key{#1}}%
+ }
+\def\ekvo@handle@undefined@kv@pkg#1#2%
+ {%
+ \ekv@ifdefined{\ekvo@name{#1}N}%
+ {\ekvo@err@value@forbidden{#1}}%
+ {\ekvo@err@undefined@key{#1}}%
+ }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}[internal]
+% {
+% \ekvo@addto@unused@one,\ekvo@addto@unused@two,
+% \ekvo@rmfrom@unused@one,\ekvo@rmfrom@unused@two
+% }
+% These macros will add or remove the |\CurrentOption| to or from the list of
+% unused global options. Since |\ekvo@do@unusedoptionlist| will have some
+% overhead before calling the list changing macro in filtering the current
+% option, we use an optimization here in that we check whether the list is
+% empty before calling the |rmfrom| function.
+% \begin{macrocode}
+\long\def\ekvo@addto@unused@one#1{\ekvo@do@unusedoptionlist\ekvo@addnewto@list}
+\long\def\ekvo@addto@unused@two#1#2{\ekvo@do@unusedoptionlist\ekvo@addnewto@list}
+\long\def\ekvo@rmfrom@unused@one#1%
+ {%
+ \ekvo@ifx@F\@unusedoptionlist\@empty
+ {\ekvo@do@unusedoptionlist\ekvo@rmfrom@list}%
+ }
+\long\def\ekvo@rmfrom@unused@two#1#2%
+ {%
+ \ekvo@ifx@F\@unusedoptionlist\@empty
+ {\ekvo@do@unusedoptionlist\ekvo@rmfrom@list}%
+ }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}[internal]
+% {
+% \ekvo@do@unusedoptionlist,
+% \ekvo@prepare@unusedoption,
+% \ekvo@prepare@unusedoption@a,
+% \ekvo@prepare@unusedoption@b,
+% \ekvo@prepare@unusedoption@c
+% }
+% The way the new \LaTeXe\ kernel handles the unused option list changed. Now
+% not the entire |\CurrentOption| is listed, but just everything up to the
+% first equals sign, and spaces got zapped, doesn't matter whether the raw
+% option list gets used or not. So we have to zap spaces and remove everything
+% from the first equals sign onwards. The code used here will fail if the
+% current option contains an |\ekv@mark| or |\ekv@stop| before the first
+% equals sign (this seems rather unlikely).
+% \begin{macrocode}
+\protected\def\ekvo@do@unusedoptionlist#1%
+ {%
+ \let\ekvo@unpreparedCurrentOption\CurrentOption
+ \edef\CurrentOption
+ {\expandafter\ekvo@prepare@unusedoption\CurrentOption=\ekv@mark}%
+ #1\@unusedoptionlist
+ \let\CurrentOption\ekvo@unpreparedCurrentOption
+ }
+\def\ekvo@prepare@unusedoption{\ekvo@prepare@unusedoption@a\@empty}
+\def\ekvo@prepare@unusedoption@a#1%
+ {%
+ \long\def\ekvo@prepare@unusedoption@a##1=##2\ekv@mark
+ {%
+ \ekvo@prepare@unusedoption@b##1\ekv@stop
+ \ekv@mark\ekvo@prepare@unusedoption@b
+ #1\ekv@mark\ekvo@prepare@unusedoption@c
+ }%
+ }
+\ekvo@prepare@unusedoption@a{ }
+\long\def\ekvo@prepare@unusedoption@b#1 #2\ekv@mark#3{#3#1#2\ekv@mark#3}
+\long\def\ekvo@prepare@unusedoption@c
+ #1\ekv@stop
+ \ekv@mark\ekvo@prepare@unusedoption@b\ekv@mark\ekvo@prepare@unusedoption@c
+ {\unexpanded\expandafter{#1}}
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}[internal]
+% {
+% \ekvo@set@handlers@local,\ekvo@set@handlers@global,
+% \ekvo@set@handlers@list
+% }
+% These macros are boring. They just set up the handlers to respect the rules
+% documented earlier.
+% \begin{macrocode}
+\protected\def\ekvo@set@handlers@local
+ {%
+ \ekvo@if@need@handlers
+ {%
+ \ifx\@currext\@clsextension
+ \ifx\@classoptionslist\relax
+ \let\ekvo@handle@undefined@k\@gobble
+ \let\ekvo@handle@undefined@kv\@gobbletwo
+ \else
+ \expandafter
+ \ifx
+ \csname @raw@opt@\@currname.\@currext\endcsname
+ \@raw@classoptionslist
+ \let\ekvo@handle@undefined@k\ekvo@addto@unused@one
+ \let\ekvo@handle@undefined@kv\ekvo@addto@unused@two
+ \unless\ifx\@unusedoptionlist\@empty
+ \let\ekvo@handle@defined@k\ekvo@rmfrom@unused@one
+ \let\ekvo@handle@defined@kv\ekvo@rmfrom@unused@two
+ \fi
+ \else
+ \let\ekvo@handle@undefined@k\@gobble
+ \let\ekvo@handle@undefined@kv\@gobbletwo
+ \fi
+ \fi
+ \else
+ \let\ekvo@handle@undefined@k\ekvo@handle@undefined@k@pkg
+ \let\ekvo@handle@undefined@kv\ekvo@handle@undefined@kv@pkg
+ \fi
+ }%
+ }
+\protected\def\ekvo@set@handlers@global
+ {%
+ \unless\ifx\@unusedoptionlist\@empty
+ \let\ekvo@handle@defined@k\ekvo@rmfrom@unused@one
+ \let\ekvo@handle@defined@kv\ekvo@rmfrom@unused@two
+ \fi
+ \ekvo@if@need@handlers
+ {%
+ \let\ekvo@handle@undefined@k\@gobble
+ \let\ekvo@handle@undefined@kv\@gobbletwo
+ }%
+ }
+\protected\def\ekvo@set@handlers@list
+ {%
+ \ekvo@if@need@handlers
+ {%
+ \let\ekvo@handle@undefined@k\@gobble
+ \let\ekvo@handle@undefined@kv\@gobbletwo
+ }%
+ }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}[internal]{\ekvo@if@need@handlers,\ekvo@dont@need@handlers}
+% If the user specifies handlers this macro will be let to
+% |\ekvo@dont@need@handlers|, which will act like |\@gobble| and also let it
+% to |\@firstofone| afterwards.
+% \begin{macrocode}
+\let\ekvo@if@need@handlers\@firstofone
+\protected\long\def\ekvo@dont@need@handlers#1%
+ {%
+ \let\ekvo@if@need@handlers\@firstofone
+ }%
+% \end{macrocode}
+% \end{macro}
+%
+% We have to set the default for the handlers of defined keys, because they
+% don't necessarily get defined before a list is parsed.
+% \begin{macrocode}
+\let\ekvo@handle@defined@k\@gobble
+\let\ekvo@handle@defined@kv\@gobbletwo
+% \end{macrocode}
+%
+%
+% \subsection{Processing list elements}
+%
+% \begin{macro}[internal]{\ekvo@process@common}
+% All the key processing frontend macros use the same basic structure. |#1|
+% will be a simple test, deciding whether the list will really be parsed or
+% not, |#3| will be the \set, and |#2| will be the individual code of the
+% frontend macro which should be executed if both the test in |#1| is true and
+% the \set\ is defined.
+% \begin{macrocode}
+\protected\def\ekvo@process@common#1#2#3%
+ {%
+ #1{\ekvo@do@with@set{#3}{#2}}%
+ }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}[internal]{\ekvo@process@list}
+% This macro only expands the list holding macro and forwards it to the
+% loop macro.
+% \begin{macrocode}
+\protected\def\ekvo@process@list#1%
+ {%
+ \expandafter\ekvo@CurrentOption@loop\expandafter{#1}\ekvo@parse
+ }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}[internal]
+% {
+% \ekvo@parse,
+% \ekvo@parse@kv,\ekvo@parse@kv@,\ekvo@expansion@kv,\ekvo@expansion@kv@,
+% \ekvo@parse@k, \ekvo@parse@k@, \ekvo@expansion@k, \ekvo@expansion@k@
+% }
+% This macro calls internals of \expkv\ such that the input is split at an
+% equals sign of category other.
+% \begin{macrocode}
+\long\def\ekvo@parse#1%
+ {%
+ \ekv@eq@other#1\ekv@nil\ekv@mark\ekvo@parse@kv
+ =\ekv@mark\ekvo@parse@k
+ }
+% \end{macrocode}
+% If there was an equals sign, this will be called and remove the remainder of
+% the split. Afterwards the \expnotation\ is checked and possibly executed,
+% and the \kv\ pair is set. While reinsertion works via the |Rr|
+% \expansion-rule, it might affect the unused global option list.
+% \begin{macrocode}
+\long\def\ekvo@parse@kv#1\ekv@stop#2\ekv@nil=\ekv@mark\ekvo@parse@k
+ {\ekv@strip{#2}{\ekv@strip{#1}\ekvo@parse@kv@}}
+\long\def\ekvo@parse@kv@#1#2%
+ {%
+ \ekv@ifexp{#1}%
+ {\ekvo@expansion@kv{#2}}%
+ {\ekvo@set@kv{#1}{#2}}%
+ }
+% \end{macrocode}
+% Check for \expansion-rules was true, now we need to execute them, and use
+% the result in |\ekvo@set@kv|. Also we have to handle the key as if it was a
+% defined key if the |\r| \expansion-rule was found (which potentially removes
+% it from the list of global unused options).
+% \begin{macrocode}
+\long\def\ekvo@expansion@kv#1#2#3#4%
+ {%
+ \ekv@expansion@rule@{#1}#2\ekv@mark\ekv@stop{#3}{}%
+ {\ekvo@handle@defined@kv{#3}{#1}\ekvmorekv}%
+ \ekvo@expansion@kv@
+ }
+\long\def\ekvo@expansion@kv@#1#2{\ekvo@set@kv{#1}{#2}}
+% \end{macrocode}
+% And basically the same two biggish steps, but for the case that no equals
+% sign is found.
+% \begin{macrocode}
+\long\def\ekvo@parse@k#1\ekv@nil\ekv@mark\ekvo@parse@kv\ekv@stop\ekv@mark
+ {\ekv@strip{#1}\ekvo@parse@k@}
+\long\def\ekvo@parse@k@#1%
+ {%
+ \ekv@ifexp{#1}%
+ \ekvo@expansion@k
+ {\ekvo@set@k{#1}}%
+ }
+\long\def\ekvo@expansion@k#1#2#3%
+ {%
+ \ekv@expansion@rule@{#2}#1\ekv@mark\ekv@stop{}{}%
+ {\ekvo@handle@defined@k{#2}\ekvmorekv}%
+ \ekvo@expansion@k@
+ }
+\long\def\ekvo@expansion@k@#1#2{\ekvo@set@k{#2}}
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}[internal]{\ekvo@set@k,\ekvo@set@kv}
+% These two macros check whether the key is defined and if so call the handler
+% for defined keys and execute the key, else the handler for undefined keys is
+% called.
+% \begin{macrocode}
+\protected\def\ekvo@set@k#1%
+ {%
+ \ekv@ifdefined{\ekvo@name{#1}N}%
+ {%
+ \ekvo@handle@defined@k{#1}%
+ \csname\ekvo@name{#1}N\endcsname
+ }%
+ {\ekvo@handle@undefined@k{#1}}%
+ }
+\protected\def\ekvo@set@kv#1#2%
+ {%
+ \ekv@ifdefined{\ekvo@name{#1}}%
+ {%
+ \ekvo@handle@defined@kv{#1}{#2}%
+ \csname\ekvo@name{#1}\endcsname{#2}%
+ }%
+ {\ekvo@handle@undefined@kv{#1}{#2}}%
+ }
+% \end{macrocode}
+% \end{macro}
+%
+%
+% \subsection{List variable helpers}
+%
+% \begin{macro}[internal]{\ekvo@addto@list}
+% This macro is rather simple. If the list to which the |\CurrentOption|
+% should be added is empty we can just let the list to the |\CurrentOption|.
+% Else we have to expand the list once and the |\CurrentOption| once.
+% \begin{macrocode}
+\protected\def\ekvo@addto@list#1%
+ {%
+ \ekvo@ifx@TF#1\@empty
+ {\let#1\CurrentOption}%
+ {%
+ \edef#1%
+ {%
+ \unexpanded\expandafter{#1},%
+ \unexpanded\expandafter{\CurrentOption}%
+ }%
+ }%
+ }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}[internal]{\ekvo@addnewto@list}
+% This works just like |\ekvo@addto@list|, but it only adds elements which are
+% not yet part of the list, hence we check if the option is already in the
+% list.
+% \begin{macrocode}
+\protected\def\ekvo@addnewto@list#1%
+ {%
+ \ekvo@ifx@TF#1\@empty
+ {\let#1\CurrentOption}%
+ {%
+ \ekvo@if@in@list#1%
+ {}%
+ {%
+ \edef#1%
+ {%
+ \unexpanded\expandafter{#1},%
+ \unexpanded\expandafter{\CurrentOption}%
+ }%
+ }%
+ }%
+ }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}[internal]
+% {\ekvo@if@in@list,\ekvo@if@in@list@,\ekvo@if@in@list@result}
+% This is a slow but robust test whether some option is already contained in a
+% list. Using argument gobbling logic would be faster, but that way wouldn't
+% be robust for elements containing |{|, |}|, or |#|. Just loop over the code
+% and compare each element, end early if a match is found.
+% \begin{macrocode}
+\protected\def\ekvo@if@in@list#1%
+ {%
+ \ekvo@ifx@TF#1\@empty
+ \@secondoftwo
+ {%
+ \let\ekvo@curropt\CurrentOption
+ \let\ekvo@if@in@list@result\@secondoftwo
+ \expandafter\ekvo@CurrentOption@loop\expandafter{#1}\ekvo@if@in@list@
+ \let\CurrentOption\ekvo@curropt
+ \ekvo@if@in@list@result
+ }%
+ }
+\protected\long\def\ekvo@if@in@list@#1%
+ {%
+ \ekvo@ifx@T\ekvo@curropt\CurrentOption
+ {%
+ \let\ekvo@if@in@list@result\@firstoftwo
+ \ekvo@end@loop
+ }%
+ }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}[internal]{\ekvo@rmfrom@list,\ekvo@rmfrom@list@}
+% This works by looping over every list item and comparing it to
+% |\ekvo@curropt| which stores the real |\CurrentOption|. This is
+% comparatively slow, but works for items containing braces unlike what
+% \LaTeXe\ does. We could be faster for items not containing braces, though.
+% \begin{macrocode}
+\protected\def\ekvo@rmfrom@list#1%
+ {%
+ \ekvo@ifx@F#1\@empty
+ {%
+ \let\ekvo@tmp@list\@empty
+ \let\ekvo@curropt\CurrentOption
+ \expandafter\ekvo@CurrentOption@loop\expandafter{#1}\ekvo@rmfrom@list@
+ \let\CurrentOption\ekvo@curropt
+ \let#1\ekvo@tmp@list
+ }%
+ }
+\protected\long\def\ekvo@rmfrom@list@#1%
+ {%
+ \ekvo@ifx@F\CurrentOption\ekvo@curropt
+ {\ekvo@addto@list\ekvo@tmp@list}%
+ }
+% \end{macrocode}
+% \end{macro}
+%
+%
+% \subsection{Errors}
+%
+% \begin{macro}[internal]
+% {
+% \ekvo@err@undefined@key,\ekvo@err@value@required,
+% \ekvo@err@value@forbidden,\ekvo@err@undefined@set
+% }
+% Just some macros to throw errors in the few cases an error has to be thrown.
+% \begin{macrocode}
+\protected\def\ekvo@err@undefined@key#1%
+ {%
+ \ekvo@pkg@cls@error
+ {Undefined option `\detokenize{#1}'}%
+ {The used option was not defined. Perhaps you misspelled it?}%
+ }
+\protected\def\ekvo@err@value@required#1%
+ {%
+ \ekvo@pkg@cls@error
+ {Missing value for option `\detokenize{#1}'}%
+ {The used option requires a value or you misspelled its name.}%
+ }
+\protected\def\ekvo@err@value@forbidden#1%
+ {%
+ \ekvo@pkg@cls@error
+ {Unwanted value for option `\detokenize{#1}'}%
+ {The used option doesn't support a value or you misspelled its name.}%
+ }
+\protected\def\ekvo@err@undefined@set#1%
+ {%
+ \PackageError{expkv-opt}%
+ {Undefined set `#1'}%
+ {The set for which you try to parse options isn't defined in expkv.}%
+ }
+\protected\def\ekvo@pkg@cls@error
+ {%
+ \ekvo@ifx@TF\@currext\@clsextension
+ \ClassError
+ \PackageError
+ \@currname
+ }
+% \end{macrocode}
+% \end{macro}
+%
+%
+% \subsection{User Interface}
+%
+% The user interface macros just put together the bits and pieces.
+%
+% \begin{macro}{\ekvoProcessOptions}
+% First we check if user-defined handlers were used. If that's the case we
+% need to store them and restore them for each list processor. Else just call
+% them sequentially.
+% \begin{macrocode}
+\protected\def\ekvoProcessOptions#1%
+ {%
+ \ekvo@if@need@handlers
+ {%
+ \ekvoProcessGlobalOptions{#1}%
+ \ekvoProcessLocalOptions {#1}%
+ \ekvoProcessFutureOptions{#1}%
+ \@gobbletwo
+ }%
+ \@firstofone
+ {%
+ \let\ekvo@tmpa\ekvo@handle@undefined@k
+ \let\ekvo@tmpb\ekvo@handle@undefined@kv
+ \ekvoUseUnknownHandlers\ekvo@tmpa\ekvo@tmpb
+ \ekvoProcessGlobalOptions{#1}%
+ \ekvoUseUnknownHandlers\ekvo@tmpa\ekvo@tmpb
+ \ekvoProcessLocalOptions {#1}%
+ \ekvoUseUnknownHandlers\ekvo@tmpa\ekvo@tmpb
+ \ekvoProcessFutureOptions{#1}%
+ \let\ekvo@tmpa\ekvo@undefined
+ \let\ekvo@tmpb\ekvo@undefined
+ }%
+ }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\ekvoProcessLocalOptions}
+% \begin{macrocode}
+\protected\def\ekvoProcessLocalOptions
+ {%
+ \ekvo@process@common
+ {\ekv@ifdefined{@raw@opt@\@currname.\@currext}\@firstofone\@gobble}%
+ {%
+ \ekvo@set@handlers@local
+ \expandafter
+ \ekvo@process@list\csname @raw@opt@\@currname.\@currext\endcsname
+ \AtEndOfPackage{\let\@unprocessedoptions\relax}%
+ }%
+ }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\ekvoProcessGlobalOptions}
+% \begin{macrocode}
+\protected\def\ekvoProcessGlobalOptions
+ {%
+ \ekvo@process@common{\ekvo@ifx@F\@classoptionslist\relax}%
+ {%
+ \ekvo@set@handlers@global
+ \ekvo@process@list\@raw@classoptionslist
+ \let\ekvo@handle@defined@k\@gobble
+ \let\ekvo@handle@defined@kv\@gobbletwo
+ }%
+ }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\ekvoProcessUnusedGlobalOptions}
+% \begin{macrocode}
+\protected\def\ekvoProcessUnusedGlobalOptions#1%
+ {%
+ \PackageError{expkv-opt}%
+ {%
+ \noexpand
+ \ekvoProcessUnusedGlobalOptions no longer works because of changes in%
+ \MessageBreak
+ the LaTeX2e kernel%
+ }%
+ {}%
+ }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\ekvoProcessFutureOptions}
+% Parsing future options (without patching kernel internals) is only possible
+% with kernel versions after 2022-11-01.
+% The user macro needs to store the user-set handlers for later if
+% |\ekvoUseUnknownHandlers| was used. Then it stores the correct code to
+% process future options in the |opt@handler@| variable.
+% \begin{macrocode}
+\IfFormatAtLeastTF{2022/11/01}
+ {%
+ \protected\def\ekvoProcessFutureOptions#1%
+ {%
+ \ekvo@if@need@handlers\@gobbletwo
+ \@firstofone
+ {%
+ \expandafter\let
+ \csname ekvo@future@undefined@k@\@currname.\@currext\endcsname
+ \ekvo@handle@undefined@k
+ \expandafter\let
+ \csname ekvo@future@undefined@kv@\@currname.\@currext\endcsname
+ \ekvo@handle@undefined@kv
+ }%
+ \protected\expandafter
+ \xdef\csname opt@handler@\@currname.\@currext\endcsname
+ {\ekv@unexpanded{\ekvo@future{#1}}}%
+ }
+% \end{macrocode}
+% \begin{macro}[internal]{\ekvo@future,\ekvo@set@handlers@future}
+% Key parsing is pretty similar to |\ekvoProcessLocalOptions|, only the
+% handlers are defined differently, namely it is checked whether they are
+% user-specified, if not we reuse the undefined handlers for packages (which
+% will throw an error if an unknown key is found).
+% \begin{macrocode}
+ \protected\def\ekvo@future
+ {%
+ \ekvo@process@common
+ {\ekv@ifdefined{@raw@opt@\@currname.\@currext}\@firstofone\@gobble}%
+ {%
+ \ekvo@set@handlers@future
+ \expandafter\ekvo@process@list
+ \csname @raw@opt@\@currname.\@currext\endcsname
+ \AtEndOfPackage{\let\@unprocessedoptions\relax}%
+ }%
+ }
+ \protected\def\ekvo@set@handlers@future
+ {%
+ \expandafter\let\expandafter
+ \ekvo@handle@undefined@k
+ \csname
+ \ekv@ifdefined{ekvo@future@undefined@k@\@currname.\@currext}%
+ {ekvo@future@undefined@k@\@currname.\@currext}%
+ {ekvo@handle@undefined@k@pkg}%
+ \endcsname
+ \expandafter\let\expandafter
+ \ekvo@handle@undefined@kv
+ \csname
+ \ekv@ifdefined{ekvo@future@undefined@kv@\@currname.\@currext}%
+ {ekvo@future@undefined@kv@\@currname.\@currext}%
+ {ekvo@handle@undefined@kv@pkg}%
+ \endcsname
+ }
+% \end{macrocode}
+% \end{macro}
+% \begin{macrocode}
+ }
+ {%
+ \protected\def\ekvoProcessFutureOptions#1%
+ {%
+ \PackageWarning{expkv-opt}%
+ {LaTeX-kernel is too old to process future options.\@gobbletwo}%
+ \protected\gdef\ekvoProcessFutureOptions##1{}%
+ }%
+ }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\ekvoProcessOptionsList}
+% \begin{macrocode}
+\protected\def\ekvoProcessOptionsList#1%
+ {%
+ \ekvo@process@common{\ekvo@ifx@F#1\@empty}%
+ {%
+ \ekvo@set@handlers@list
+ \ekvo@process@list#1%
+ }%
+ }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\ekvoUseUnknownHandlers}
+% \begin{macro}[internal]{\ekvoUseUnknownHandlers@n,\ekvoUseUnknownHandlers@s}
+% \begin{macrocode}
+\protected\def\ekvoUseUnknownHandlers
+ {%
+ \let\ekvo@if@need@handlers\ekvo@dont@need@handlers
+ \@ifstar\ekvoUseUnknownHandlers@s\ekvoUseUnknownHandlers@n
+ }
+\protected\def\ekvoUseUnknownHandlers@s
+ {%
+ \long\def\ekvo@handle@undefined@k##1%
+ {%
+ \ekv@ifdefined{\ekvo@name{}uN}%
+ {%
+ \csname\ekvo@name{}uN\expandafter\endcsname\expandafter
+ {\detokenize{##1}}{##1}%
+ }%
+ {\@gobble}%
+ }%
+ \long\def\ekvo@handle@undefined@kv##1##2%
+ {%
+ \ekv@ifdefined{\ekvo@name{}u}%
+ {%
+ \csname\ekvo@name{}u\ekv@expanded{\endcsname
+ {\ekv@unexpanded{##2}}{\detokenize{##1}}}{##1}%
+ }%
+ {}%
+ }%
+ }
+\protected\def\ekvoUseUnknownHandlers@n#1#2%
+ {%
+ \let\ekvo@handle@undefined@k#1\relax
+ \let\ekvo@handle@undefined@kv#2\relax
+ }
+% \end{macrocode}
+% \end{macro}
+% \end{macro}
+%
+% All user interface macros should be only used in the preamble.
+% \begin{macrocode}
+\@onlypreamble\ekvoProcessOptions
+\@onlypreamble\ekvoProcessLocalOptions
+\@onlypreamble\ekvoProcessGlobalOptions
+\@onlypreamble\ekvoProcessFutureOptions
+\@onlypreamble\ekvoProcessOptionsList
+\@onlypreamble\ekvoUseUnknownHandlers
+% \end{macrocode}
+%
+% \gobbledocstriptag
+%</pkg>