%% This is part of the OpTeX project, see http://petr.olsak.net/optex \_codedecl \readkv {Key-value dictionaries <2023-11-24>} % preloaded in format \_doc ---------------------------- {\bf Implementation.}\nl The \`\readkv``` expands its parameter and does replace-strings in order to remove spaces around equal signs and commas. Then \`\_kvscan` reads the parameters list finished by `,\_fin` and saves values to `\_kv::` macros. The `\_kvx::` is processed (if it is defined) with parameter after it.\nl The \`\kvx``{}{}` defines the `\_kvx::#1` macro and \`\nokvx``{}` defines the `\_nokvx::` macro.\nl The \`\trykv``{}{}` returns unexpanded value of if declared, else it runs .\nl The \`\kv``{}` returns expanded value of if declared, else returns expanded value of `.notdef` key else expands \`\_kvunknown`.\nl The \`\iskv``{}\iftrue` (or `\iffalse`) is the test, if the `` is defined in current . \_cod ---------------------------- \_def\_readkv#1{\_ea\_def\_ea\_tmpb\_ea{#1,}% \_replstring\_tmpb{= }{=}\_replstring\_tmpb{ =}{=}\_replstring\_tmpb{ ,}{,}% \_ea \_nospaceafter \_ea\_kvscan\_tmpb\_fin} \_def\_kvscan#1,#2{\_ifx^#1^\_else \_kvsd #1==\_fin \_fi \_ifx\_fin#2\_empty \_ea\_ignoreit \_else\_ea\_useit \_fi {\_kvscan#2}} \_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##2{#1}} \_def\_trykv#1{\_ea\_trykvA \_begincsname\_kvcs#1\_endcsname \_ignoreit} \_def\_trykvA#1{\_ifx #1\_ignoreit \_ea\_useit\_else \_unexpanded\_ea{#1}\_fi} \_def\_kv#1{\_expanded{\_trykv{#1}{\_trykv{.\_csstring\notdef}{\_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 \trykv \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{}` macro. The \^`\kv{}` returns `???` if such a isn't declared but if `.notdef` key is declared then its value is returned in this case. The \^`\kv{}` macro fully expands the value of key. If you don't want to expand the value, use \^`\trykv``{}{}`. It returns unexpanded value of if declared else returns expanded . For example `\edef\macro{\trykv{key}{}}` defines `\macro` as the unexpanded value of the `key`. You can use keys without values in the parameters list too. Then you can ask if the key is declared by \^`\iskv``{}\iftrue` or the key is undeclared by \^`\iskv``{}\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 only `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``{}{}` to declare which is processed immediately when the `` 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` `{}{}` allows to use `#1` inside the code. It is replaced by the actual ``. Example: `\kvx{opt}{\message{opt is #1}}`, then `\readkv{opt=HELLO}` prints \"opt is HELLO". The \^`\nokvx` `{}` can declare a processed for all undeclared by \^`\kvx`. The `#1` and `#2` can be used in the , `#1` is , `#2` is . If `\nokvx` is unused then nothing is done for undeclared . Example: `\nokvx{\opwarning{Unknown option "#1"}}`. The default dictionary name (where key-value pairs are processed) is empty. You can use your specific dictionary by \^`\kvdict``={}`. Then `\redakv`, `\kv`, `\trykv`, `\iskv`, `\kvx` and `\nokvx` macros use this named dictionary of / pairs. Package options can be processed when `\kvdict={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 `={}`. A more extensive example can be found in \ulink[http://petr.olsak.net/optex/optex-tricks.html\#roundframe]{\OpTeX/ trick 0073}. \_endinput 2023-11-24 \trykv introduced. 2023-10-23 minor changes in \readkv, empty key isn't processed. 2023-03-11 \nokvx: \fi bug (due to \afterfi in \trycs) fixed. 2023-01-13 \kvx parameter added, \nokvx introduced. 2023-01-07 \kvdict, \kvx, \iskv added. 2020-12-21 Released