summaryrefslogtreecommitdiff
path: root/macros/optex/base/keyval.opm
blob: 68f3fea5369c490e819e379eb96bf6965183a2f5 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
%% This is part of the OpTeX project, see http://petr.olsak.net/optex

\_codedecl \readkv {Key-value dictionaries <2023-10-23>} % 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 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 \_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\_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 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``{<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{\opwarning{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>}`.

A more extensive example can be found in
\ulink[http://petr.olsak.net/optex/optex-tricks.html\#roundframe]{\OpTeX/ trick 0073}.

\_endinput

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