%% This is part of the OpTeX project, see http://petr.olsak.net/optex

\_codedecl \readkv {Key-value dictionaries <2023-01-14>} % preloaded in format

   \_doc ----------------------------
   {\bf Implementation.}\nl
   The \`\readkv``<list>` expands its parameter and does replace-strings in order to
   remove spaces around equal signs and after commas.
   Then \`\_kvscan` reads the parameters list finished by `,\_fin,`
   and saves values to `\_kv:<dict>:<key>` macros.
   The `\_kvx:<dict>:<key>` is processed (if it is defined) with parameter
   <value> after it.\nl
   The \`\kvx``{<key>}{<code>}` defines the `\_kvx:<dict>:<key>#1` macro
   and \`\nokvx``{<code>}` defines the `\_nokvx:<dict>:<key>` macro.\nl
   The \`\kv``{<key>}` expands the `\_kv:<dict>:<key>` macro. If this macro isn't
   defined then \`\_kvunknown` is processed. You can re-define it if you want.\nl
   The \`\iskv``{<key>}\iftrue` (or `\iffalse`) is the test,
   if the `<key>` is defined in current <dict>.
   \_cod ----------------------------

\_def\_readkv#1{\_ea\_def\_ea\_tmpb\_ea{#1}%
   \_replstring\_tmpb{= }{=}\_replstring\_tmpb{ =}{=}\_replstring\_tmpb{, }{,}%
   \_ea \_kvscan\_tmpb,\_fin,}
\_def\_kvscan#1,{\_ifx\_fin#1\_empty\_ea\_ignoreit \_else\_ea\_useit \_fi
   {\_kvsd #1==\_fin \_kvscan}}
\_def\_kvsd#1=#2=#3\_fin{\_sdef{\_kvcs#1}{#2}%
   \_trycs{_kvx:\_the\_kvdict:#1}%
          {\_trycs{_nokvx:\_the\_kvdict}{\_ea\_ignoreit}{#1}\_ea\_ignoreit}{#2}}
\_def\_kvx#1#2{\_sdef{_kvx:\_the\_kvdict:#1}##1{#2}}
\_def\_nokvx#1{\_sdef{_nokvx:\_the\_kvdict}##1\_ea\_ignoreit\_fi##2{\_fi#1}}
\_def\_kv#1{\_trycs{\_kvcs#1}{\_kvunknown}}
\_def\_iskv#1#2{#2\_else\_ea\_unless\_fi \_ifcsname\_kvcs#1\_endcsname}
\_def\_kvcs{_kv:\_the\_kvdict:}
\_def\_kvunknown{???}

\public \readkv \kvx \nokvx \kv \iskv ;

\_endcode

Users or macro programmers can define macros with options in key=value format.
It means a comma-separated list of equations key=value. First, we give an
example.

Suppose that you want to define a macro `\myframe` with options: color of
rules, color of text inside the frame, rule-width, space between text and
rules. You want to use this macro as:

\begtt
\myframe [margins=5pt,rule-width=2pt,frame-color=\Red,text-color=\Blue] {text1}
or
\myframe [frame-color=\Blue] {text2} % other parameters are default
\endtt
%
or simply `\myframe {text3}`. You can define `\myframe` as follows:

\begtt
\def\myframedefaults{%    defaults:
   frame-color=\Black, % color of frame rules
   text-color=\Black,  % color of text inside the frame
   rule-width=0.4pt,   % width of rules used in the frame
   margins=2pt,        % space between text inside and rules.
}
\optdef\myframe [] #1{\bgroup
   \readkv\myframedefaults \readkv{\the\opt}%
   \rulewidth=\kv{rule-width}
   \hhkern=\kv{margins}\vvkern=\kv{margins}\relax
   \kv{frame-color}\frame{\kv{text-color}\strut #1}%
   \egroup
}
\endtt
%
We recommend using \^`\optdef` for defining macros with optional parameters
written in `[]`. Then the optional parameters are saved in the \^`\opt` tokens
register. First: we read default parameters by \^`\readkv\myframedefaults`
and secondly the actual parameters are read by \^`\readkv{\the\opt}`.
The last setting wins.
Third: the values can be used by the expandable \^`\kv{<key>}` macro.
The \^`\kv{<key>}` returns `???` if such key is not declared.

You can use keys without values in the parameters list too.
Then you can ask if the key is declared by \^`\iskv``{<key>}\iftrue`
or the key is undeclared by \^`\iskv``{<key>}\iffalse`.
For example, you write to your documentation of your code that
user can set the `draft` option without the value. Then you can do
\begtt
\optdef\myframe [] #1{...
   \readkv\myframedefaults \readkv{\the\opt}%
   \iskv{draft}\iftrue ...draft mode... \else ...final mode... \fi
   ...}
\endtt
Maybe, you want to allow not ony `draft` option but `final` option (which is
opposite to `draft`) too and you want to apply the result from the last given
option. Then `\iskv` doesn't work because you can only check if both options
are declared but you don't know what one is given as last. But you can
use \^`\kvx``{<key>}{<code>}` to declare <code> which is processed
immediately when the `<key>` is processed by `\readkv`. For example
\begtt
\newcount\mydraftmode
\kvx{draft}{\mydraftmode=1 }
\kvx{final}{\mydraftmode=0 }
\optdef\myframe [] #1{...
   \readkv\myframedefaults \readkv{\the\opt}%
   \ifnum\mydraftmode=1 ...draft mode... \else ...final mode... \fi
   ...}
\endtt
The syntax of \^`\kvx` `{<key>}{<code>}` allows to use `#1` inside the code.
It is replaced by the actual `<value>`. Example: `\kvx{opt}{\message{opt is #1}}`,
then `\readkv{opt=HELLO}` prints \"opt is HELLO".

The \^`\nokvx` `{<code>}` can declare a <code> processed for all <keys>
undeclared by \^`\kvx`. The `#1` and `#2` can be used in the <code>,
`#1` is <key>, `#2` is <value>. If `\nokvx` is unused then nothing is done
for undeclared <key>. Example: `\nokvx{\opwarnig{Unknown option "#1"}}`.

The default dictionary name (where key-value pairs are processed) is
empty. You can use your specific dictionary by
\^`\kvdict``={<name>}`. Then `\redakv`, `\kv`, `\iskv`, `\kvx` and `\nokvx`
macros use this named dictionary of <key>/<value> pairs.
Package options can be processed when
`\kvdict={pkg:<pkg>}`, example is the `\mathset` macro in
\ulink[https://petr.olsak.net/ftp/olsak/optex/math-doc.pdf]{{\tt math.opm} package}.

Recommendation: If the value of the key-value pair includes `=` or `,` or
`]`, then use the syntax `<key>={<value>}`.

\_endinput

2023-01-13 \kvx parameter added, \nokvx introduced.
2023-01-07 \kvdict, \kvx, \iskv added.
2020-12-21 Released
