23
23
24
24
"""Variable type for List Variables.
25
25
26
- A list variable may given as 'all', 'none' or a list of names
27
- separated by comma. After the variable has been processed, the variable
28
- value holds either the named list elements, all list elements or no
29
- list elements at all.
26
+ A list variable allows selecting one or more from a supplied set of
27
+ allowable values, as well as from an optional mapping of alternate names
28
+ (such as aliases and abbreviations) and the special names ``'all'`` and
29
+ ``'none'``. Specified values are converted during processing into values
30
+ only from the allowable values set.
30
31
31
32
Usage example::
32
33
63
64
class _ListVariable (collections .UserList ):
64
65
"""Internal class holding the data for a List Variable.
65
66
66
- The initializer accepts two arguments, the list of actual values
67
- given, and the list of allowable values. Not normally instantiated
68
- by hand, but rather by the ListVariable converter function.
67
+ This is normally not directly instantiated, rather the ListVariable
68
+ converter callback "converts" string input (or the default value
69
+ if none) into an instance and stores it.
70
+
71
+ Args:
72
+ initlist: the list of actual values given.
73
+ allowedElems: the list of allowable values.
69
74
"""
70
75
71
- def __init__ (self , initlist = None , allowedElems = None ) -> None :
76
+ def __init__ (
77
+ self , initlist : Optional [list ] = None , allowedElems : Optional [list ] = None
78
+ ) -> None :
72
79
if initlist is None :
73
80
initlist = []
74
81
if allowedElems is None :
@@ -106,26 +113,60 @@ def prepare_to_store(self):
106
113
return str (self )
107
114
108
115
def _converter (val , allowedElems , mapdict ) -> _ListVariable :
109
- """Convert list variables."""
116
+ """Callback to convert list variables into a suitable form.
117
+
118
+ The arguments *allowedElems* and *mapdict* are non-standard
119
+ for a :class:`Variables` converter: the lambda in the
120
+ :func:`ListVariable` function arranges for us to be called correctly.
121
+ """
110
122
if val == 'none' :
111
123
val = []
112
124
elif val == 'all' :
113
125
val = allowedElems
114
126
else :
115
127
val = [_f for _f in val .split (',' ) if _f ]
116
128
val = [mapdict .get (v , v ) for v in val ]
117
- notAllowed = [v for v in val if v not in allowedElems ]
118
- if notAllowed :
119
- raise ValueError (
120
- f"Invalid value(s) for option: { ',' .join (notAllowed )} "
121
- )
122
129
return _ListVariable (val , allowedElems )
123
130
124
131
125
- # def _validator(key, val, env) -> None:
126
- # """ """
127
- # # TODO: write validator for list variable
128
- # pass
132
+ def _validator (key , val , env ) -> None :
133
+ """Callback to validate supplied value(s) for a ListVariable.
134
+
135
+ Validation means "is *val* in the allowed list"? *val* has
136
+ been subject to substitution before the validator is called. The
137
+ converter created a :class:`_ListVariable` container which is stored
138
+ in *env* after it runs; this includes the allowable elements list.
139
+ Substitution makes a string made out of the values (only),
140
+ so we need to fish the allowed elements list out of the environment
141
+ to complete the validation.
142
+
143
+ Note that since 18b45e456, whether or not ``subst`` has been
144
+ called is conditional on the value of the *subst* argument to
145
+ :meth:`~SCons.Variables.Variables.Add`, so we have to account for
146
+ possible different types of *val*.
147
+
148
+ Raises:
149
+ UserError: if validation failed.
150
+
151
+ .. versionadded:: 4.8.0
152
+ ``_validator`` split off from :func:`_converter` with an additional
153
+ check for whether *val* has been substituted before the call.
154
+ """
155
+ allowedElems = env [key ].allowedElems
156
+ if isinstance (val , _ListVariable ): # not substituted, use .data
157
+ notAllowed = [v for v in val .data if v not in allowedElems ]
158
+ else : # val will be a string
159
+ notAllowed = [v for v in val .split () if v not in allowedElems ]
160
+ if notAllowed :
161
+ # Converter only synthesized 'all' and 'none', they are never
162
+ # in the allowed list, so we need to add those to the error message
163
+ # (as is done for the help msg).
164
+ valid = ',' .join (allowedElems + ['all' , 'none' ])
165
+ msg = (
166
+ f"Invalid value(s) for variable { key !r} : { ',' .join (notAllowed )!r} . "
167
+ f"Valid values are: { valid } "
168
+ )
169
+ raise SCons .Errors .UserError (msg ) from None
129
170
130
171
131
172
# lint: W0622: Redefining built-in 'help' (redefined-builtin)
@@ -136,6 +177,7 @@ def ListVariable(
136
177
default : Union [str , List [str ]],
137
178
names : List [str ],
138
179
map : Optional [dict ] = None ,
180
+ validator : Optional [Callable ] = None ,
139
181
) -> Tuple [str , str , str , None , Callable ]:
140
182
"""Return a tuple describing a list variable.
141
183
@@ -149,25 +191,35 @@ def ListVariable(
149
191
the allowable values (not including any extra names from *map*).
150
192
default: the default value(s) for the list variable. Can be
151
193
given as string (possibly comma-separated), or as a list of strings.
152
- ``all`` or ``none`` are allowed as *default*.
194
+ ``all`` or ``none`` are allowed as *default*. You can also simulate
195
+ a must-specify ListVariable by giving a *default* that is not part
196
+ of *names*, it will fail validation if not supplied.
153
197
names: the allowable values. Must be a list of strings.
154
198
map: optional dictionary to map alternative names to the ones in
155
199
*names*, providing a form of alias. The converter will make
156
200
the replacement, names from *map* are not stored and will
157
201
not appear in the help message.
202
+ validator: optional callback to validate supplied values.
203
+ The default validator is used if not specified.
158
204
159
205
Returns:
160
206
A tuple including the correct converter and validator. The
161
207
result is usable as input to :meth:`~SCons.Variables.Variables.Add`.
208
+
209
+ .. versionchanged:: 4.8.0
210
+ The validation step was split from the converter to allow for
211
+ custom validators. The *validator* keyword argument was added.
162
212
"""
163
213
if map is None :
164
214
map = {}
215
+ if validator is None :
216
+ validator = _validator
165
217
names_str = f"allowed names: { ' ' .join (names )} "
166
218
if SCons .Util .is_List (default ):
167
219
default = ',' .join (default )
168
220
help = '\n ' .join (
169
221
(help , '(all|none|comma-separated list of names)' , names_str ))
170
- return key , help , default , None , lambda val : _converter (val , names , map )
222
+ return key , help , default , validator , lambda val : _converter (val , names , map )
171
223
172
224
# Local Variables:
173
225
# tab-width:4
0 commit comments