diff options
author | Mats Wichmann <mats@linux.com> | 2021-10-04 19:03:47 (GMT) |
---|---|---|
committer | Mats Wichmann <mats@linux.com> | 2021-10-07 22:56:15 (GMT) |
commit | 9fde9654191ccc21873710143357c3ca580fe72b (patch) | |
tree | 76408922ef5dd74f6660128af02ac725db60de0f | |
parent | 43f775a78f1d78515654e72c1280b074fd4c1899 (diff) | |
download | SCons-9fde9654191ccc21873710143357c3ca580fe72b.zip SCons-9fde9654191ccc21873710143357c3ca580fe72b.tar.gz SCons-9fde9654191ccc21873710143357c3ca580fe72b.tar.bz2 |
Tweak Variables incl. manpage, docstrings
* Added link anchors in variables-related funcs/methods,
and link to them - these methods are not part of the generated link setup.
* Clarified that vars set to defaults are not saved.
* Updated docstrings in the Variables source (for API docs).
* Added return-type annotations to Variables.
* Fix for converter function possibly failing if it tries to access an
environment. Fixes #2064.
* Fixed up the behavior of aliases to variables, and added docu. Fixes #3869.
* Fix PathIsDirCreate validator to catch permission problems. Fixes #2828
Signed-off-by: Mats Wichmann <mats@linux.com>
-rwxr-xr-x | CHANGES.txt | 8 | ||||
-rw-r--r-- | SCons/Variables/BoolVariable.py | 67 | ||||
-rw-r--r-- | SCons/Variables/EnumVariable.py | 51 | ||||
-rw-r--r-- | SCons/Variables/ListVariable.py | 74 | ||||
-rw-r--r-- | SCons/Variables/PackageVariable.py | 81 | ||||
-rw-r--r-- | SCons/Variables/PathVariable.py | 122 | ||||
-rw-r--r-- | SCons/Variables/PathVariableTests.py | 16 | ||||
-rw-r--r-- | SCons/Variables/VariablesTests.py | 96 | ||||
-rw-r--r-- | SCons/Variables/__init__.py | 163 | ||||
-rw-r--r-- | doc/man/scons.xml | 127 |
10 files changed, 453 insertions, 352 deletions
diff --git a/CHANGES.txt b/CHANGES.txt index 3e26ade..6a708e0 100755 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -53,6 +53,14 @@ RELEASE VERSION/DATE TO BE FILLED IN LATER Added new sconsign filenames to skip_entry_list in Scanner/Dir.py - Change SCons.Scanner.Base to ScannerBase. Old name kept as an alias but is now unused in SCons itself. + - Call Variables option converter consistently - the converter should + have access to the env if it needs to (issue #2064). + - Fixed the variables Add() method to accept a tuple for the variable + name the same way AddVariables() does (issue #3869). + - The premade validator PathIsDirCreate for for PathVariable now catches + the case where the directory could not be created due to permission + problems, allowing a more helpful error to be emitted (issue #2828) + RELEASE 4.2.0 - Sat, 31 Jul 2021 18:12:46 -0700 diff --git a/SCons/Variables/BoolVariable.py b/SCons/Variables/BoolVariable.py index 6d9ff72..b3c0dc4 100644 --- a/SCons/Variables/BoolVariable.py +++ b/SCons/Variables/BoolVariable.py @@ -21,7 +21,7 @@ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -"""Option type for true/false Variables. +"""Variable type for true/false Variables. Usage example:: @@ -32,51 +32,62 @@ Usage example:: ... """ -__all__ = ['BoolVariable',] +from typing import Tuple, Callable import SCons.Errors -__true_strings = ('y', 'yes', 'true', 't', '1', 'on' , 'all' ) -__false_strings = ('n', 'no', 'false', 'f', '0', 'off', 'none') +__all__ = ['BoolVariable',] +TRUE_STRINGS = ('y', 'yes', 'true', 't', '1', 'on' , 'all' ) +FALSE_STRINGS = ('n', 'no', 'false', 'f', '0', 'off', 'none') -def _text2bool(val): - """ - Converts strings to True/False depending on the 'truth' expressed by - the string. If the string can't be converted, the original value - will be returned. - See '__true_strings' and '__false_strings' for values considered - 'true' or 'false respectively. +def _text2bool(val) -> bool: + """Converts strings to True/False. - This is usable as 'converter' for SCons' Variables. + If *val* looks like it expresses a bool-like value, based on + the :data:`TRUE_STRINGS` and :data:`FALSE_STRINGS` tuples, + return the appropriate value. + + This is usable as a converter function for SCons Variables. + + Raises: + ValueError: if the string cannot be converted. """ + lval = val.lower() - if lval in __true_strings: return True - if lval in __false_strings: return False + if lval in TRUE_STRINGS: + return True + if lval in FALSE_STRINGS: + return False raise ValueError("Invalid value for boolean option: %s" % val) -def _validator(key, val, env): - """ - Validates the given value to be either '0' or '1'. - - This is usable as 'validator' for SCons' Variables. +def _validator(key, val, env) -> None: + """Validates the given value to be either true or false. + + This is usable as a validator function for SCons Variables. + + Raises: + KeyError: if key is not set in env + UserError: if key does not validate. """ if not env[key] in (True, False): raise SCons.Errors.UserError( - 'Invalid value for boolean option %s: %s' % (key, env[key])) + 'Invalid value for boolean option %s: %s' % (key, env[key]) + ) -def BoolVariable(key, help, default): - """ - The input parameters describe a boolean option, thus they are - returned with the correct converter and validator appended. The - 'help' text will by appended by '(yes|no) to show the valid - valued. The result is usable for input to opts.Add(). +def BoolVariable(key, help, default) -> Tuple[str, str, str, Callable, Callable]: + """Return a tuple describing a boolean SCons Variable. + + The input parameters describe a boolean option. Returns a tuple + including the correct converter and validator. + The *help* text will have ``(yes|no)`` automatically appended to show the + valid values. The result is usable as input to :meth:`Add`. """ - return (key, '%s (yes|no)' % help, default, - _validator, _text2bool) + help = '%s (yes|no)' % help + return (key, help, default, _validator, _text2bool) # Local Variables: # tab-width:4 diff --git a/SCons/Variables/EnumVariable.py b/SCons/Variables/EnumVariable.py index 6e0a8c5..e39eb02 100644 --- a/SCons/Variables/EnumVariable.py +++ b/SCons/Variables/EnumVariable.py @@ -21,10 +21,9 @@ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -"""Option type for enumeration Variables. +"""Variable type for enumeration Variables. -This file defines the option type for SCons allowing only specified -input-values. +Enumeration variables allow selection of one from a specified set of values. Usage example:: @@ -32,8 +31,8 @@ Usage example:: opts.Add( EnumVariable( 'debug', - 'debug output and symbols', - 'no', + help='debug output and symbols', + default='no', allowed_values=('yes', 'no', 'full'), map={}, ignorecase=2, @@ -44,48 +43,52 @@ Usage example:: ... """ +from typing import Tuple, Callable -__all__ = ['EnumVariable',] +import SCons.Errors +__all__ = ['EnumVariable',] -import SCons.Errors -def _validator(key, val, env, vals): +def _validator(key, val, env, vals) -> None: if val not in vals: raise SCons.Errors.UserError( 'Invalid value for option %s: %s. Valid values are: %s' % (key, val, vals)) -def EnumVariable(key, help, default, allowed_values, map={}, ignorecase=0): - """ +def EnumVariable(key, help, default, allowed_values, map={}, ignorecase=0) -> Tuple[str, str, str, Callable, Callable]: + """Return a tuple describing an enumaration SCons Variable. + The input parameters describe an option with only certain values - allowed. They are returned with an appropriate converter and - validator appended. The result is usable for input to - Variables.Add(). + allowed. Returns A tuple including an appropriate converter and + validator. The result is usable as input to :meth:`Add`. - 'key' and 'default' are the values to be passed on to Variables.Add(). + *key* and *default* are passed directly on to :meth:`Add`. - 'help' will be appended by the allowed values automatically + *help* is the descriptive part of the help text, + and will have the allowed values automatically appended. - 'allowed_values' is a list of strings, which are allowed as values + *allowed_values* is a list of strings, which are the allowed values for this option. - The 'map'-dictionary may be used for converting the input value + The *map*-dictionary may be used for converting the input value into canonical values (e.g. for aliases). - 'ignorecase' defines the behaviour of the validator: + The value of *ignorecase* defines the behaviour of the validator: - If ignorecase == 0, the validator/converter are case-sensitive. - If ignorecase == 1, the validator/converter are case-insensitive. - If ignorecase == 2, the validator/converter is case-insensitive and the converted value will always be lower-case. + * 0: the validator/converter are case-sensitive. + * 1: the validator/converter are case-insensitive. + * 2: the validator/converter is case-insensitive and the + converted value will always be lower-case. - The 'validator' tests whether the value is in the list of allowed values. The 'converter' converts input values - according to the given 'map'-dictionary (unmapped input values are returned unchanged). + The *validator* tests whether the value is in the list of allowed values. + The *converter* converts input values according to the given + *map*-dictionary (unmapped input values are returned unchanged). """ help = '%s (%s)' % (help, '|'.join(allowed_values)) # define validator - if ignorecase >= 1: + if ignorecase: validator = lambda key, val, env: \ _validator(key, val.lower(), env, allowed_values) else: diff --git a/SCons/Variables/ListVariable.py b/SCons/Variables/ListVariable.py index c615258..7bd6053 100644 --- a/SCons/Variables/ListVariable.py +++ b/SCons/Variables/ListVariable.py @@ -21,9 +21,7 @@ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -"""Option type for list Variables. - -This file defines the option type for SCons implementing 'lists'. +"""Variable type for list Variables. A 'list' option may either be 'all', 'none' or a list of names separated by comma. After the option has been processed, the option @@ -38,8 +36,8 @@ Usage example:: opts.Add( ListVariable( 'shared', - 'libraries to build as shared libraries', - 'all', + help='libraries to build as shared libraries', + default='all', elems=list_of_libs, ) ) @@ -54,44 +52,55 @@ Usage example:: # Known Bug: This should behave like a Set-Type, but does not really, # since elements can occur twice. -__all__ = ['ListVariable',] - import collections +from typing import Tuple, Callable import SCons.Util +__all__ = ['ListVariable',] + class _ListVariable(collections.UserList): - def __init__(self, initlist=[], allowedElems=[]): - collections.UserList.__init__(self, [_f for _f in initlist if _f]) + def __init__(self, initlist=None, allowedElems=None): + if initlist is None: + initlist = [] + if allowedElems is None: + allowedElems = [] + super().__init__([_f for _f in initlist if _f]) self.allowedElems = sorted(allowedElems) def __cmp__(self, other): raise NotImplementedError + def __eq__(self, other): raise NotImplementedError + def __ge__(self, other): raise NotImplementedError + def __gt__(self, other): raise NotImplementedError + def __le__(self, other): raise NotImplementedError + def __lt__(self, other): raise NotImplementedError + def __str__(self): - if len(self) == 0: + if not len(self): return 'none' self.data.sort() if self.data == self.allowedElems: return 'all' else: return ','.join(self) + def prepare_to_store(self): return self.__str__() -def _converter(val, allowedElems, mapdict): - """ - """ +def _converter(val, allowedElems, mapdict) -> _ListVariable: + """ """ if val == 'none': val = [] elif val == 'all': @@ -101,35 +110,40 @@ def _converter(val, allowedElems, mapdict): val = [mapdict.get(v, v) for v in val] notAllowed = [v for v in val if v not in allowedElems] if notAllowed: - raise ValueError("Invalid value(s) for option: %s" % - ','.join(notAllowed)) + raise ValueError( + "Invalid value(s) for option: %s" % ','.join(notAllowed) + ) return _ListVariable(val, allowedElems) -## def _validator(key, val, env): -## """ -## """ -## # todo: write validator for pgk list -## return 1 +# def _validator(key, val, env) -> None: +# """ """ +# # TODO: write validator for pgk list +# pass -def ListVariable(key, help, default, names, map={}): - """ - The input parameters describe a 'package list' option, thus they - are returned with the correct converter and validator appended. The - result is usable for input to opts.Add() . +def ListVariable(key, help, default, names, map={}) -> Tuple[str, str, str, None, Callable]: + """Return a tuple describing a list SCons Variable. + + The input parameters describe a 'list' option. Returns + a tuple including the correct converter and validator. + The result is usable for input to :meth:`Add`. + + *help* will have text appended indicating the legal values + (not including any extra names from *map*). + + *map* can be used to map alternative names to the ones in *names* - + that is, a form of alias. - A 'package list' option may either be 'all', 'none' or a list of - package names (separated by space). + A 'list' option may either be 'all', 'none' or a list of + names (separated by commas). """ names_str = 'allowed names: %s' % ' '.join(names) if SCons.Util.is_List(default): default = ','.join(default) help = '\n '.join( (help, '(all|none|comma-separated list of names)', names_str)) - return (key, help, default, - None, #_validator, - lambda val: _converter(val, names, map)) + return (key, help, default, None, lambda val: _converter(val, names, map)) # Local Variables: # tab-width:4 diff --git a/SCons/Variables/PackageVariable.py b/SCons/Variables/PackageVariable.py index d4dafd5..43ce99d 100644 --- a/SCons/Variables/PackageVariable.py +++ b/SCons/Variables/PackageVariable.py @@ -21,58 +21,58 @@ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -"""Option type for package Variables. - -This file defines the option type for SCons implementing 'package activation'. +"""Variable type for package Variables. To be used whenever a 'package' may be enabled/disabled and the package path may be specified. -Usage example: - - Examples: - x11=no (disables X11 support) - x11=yes (will search for the package installation dir) - x11=/usr/local/X11 (will check this path for existence) - - To replace autoconf's --with-xxx=yyy :: - - - opts = Variables() - opts.Add(PackageVariable('x11', - 'use X11 installed here (yes = search some places', - 'yes')) - ... - if env['x11'] == True: - dir = ... search X11 in some standard places ... - env['x11'] = dir - if env['x11']: - ... build with x11 ... +Given these options :: + + x11=no (disables X11 support) + x11=yes (will search for the package installation dir) + x11=/usr/local/X11 (will check this path for existence) + +Can be used as a replacement for autoconf's ``--with-xxx=yyy`` :: + + opts = Variables() + opts.Add( + PackageVariable( + key='x11', + help='use X11 installed here (yes = search some places)', + default='yes' + ) + ) + ... + if env['x11'] == True: + dir = ... # search X11 in some standard places ... + env['x11'] = dir + if env['x11']: + ... # build with x11 ... """ -__all__ = ['PackageVariable',] +from typing import Tuple, Callable import SCons.Errors +__all__ = ['PackageVariable',] + __enable_strings = ('1', 'yes', 'true', 'on', 'enable', 'search') __disable_strings = ('0', 'no', 'false', 'off', 'disable') -def _converter(val): - """ - """ +def _converter(val) -> bool: + """ """ lval = val.lower() if lval in __enable_strings: return True if lval in __disable_strings: return False - #raise ValueError("Invalid value for boolean option: %s" % val) return val -def _validator(key, val, env, searchfunc): +def _validator(key, val, env, searchfunc) -> None: + """ """ # NB: searchfunc is currently undocumented and unsupported - """ - """ # TODO write validator, check for path import os + if env[key] is True: if searchfunc: env[key] = searchfunc(key, val) @@ -81,20 +81,21 @@ def _validator(key, val, env, searchfunc): 'Path does not exist for option %s: %s' % (key, val)) -def PackageVariable(key, help, default, searchfunc=None): - # NB: searchfunc is currently undocumented and unsupported - """ - The input parameters describe a 'package list' option, thus they - are returned with the correct converter and validator appended. The - result is usable for input to opts.Add() . +def PackageVariable(key, help, default, searchfunc=None) -> Tuple[str, str, str, Callable, Callable]: + """Return a tuple describing a package list SCons Variable. - A 'package list' option may either be 'all', 'none' or a list of - package names (separated by space). + The input parameters describe a 'package list' option. Returns + a tuple including the correct converter and validator appended. + The result is usable as input to :meth:`Add` . + + A 'package list' option may either be 'all', 'none' or a pathname + string. This information is appended to *help*. """ + # NB: searchfunc is currently undocumented and unsupported help = '\n '.join( (help, '( yes | no | /path/to/%s )' % key)) return (key, help, default, - lambda k, v, e: _validator(k,v,e,searchfunc), + lambda k, v, e: _validator(k, v, e, searchfunc), _converter) # Local Variables: diff --git a/SCons/Variables/PathVariable.py b/SCons/Variables/PathVariable.py index 081872b..383c1f9 100644 --- a/SCons/Variables/PathVariable.py +++ b/SCons/Variables/PathVariable.py @@ -21,64 +21,74 @@ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -"""Option type for path Variables. +"""Variable type for path Variables. -This file defines an option type for SCons implementing path settings. - -To be used whenever a user-specified path override should be allowed. +To be used whenever a user-specified path override setting should be allowed. Arguments to PathVariable are: - option-name = name of this option on the command line (e.g. "prefix") - option-help = help string for option - option-dflt = default value for this option - validator = [optional] validator for option value. Predefined are: - PathAccept -- accepts any path setting; no validation - PathIsDir -- path must be an existing directory - PathIsDirCreate -- path must be a dir; will create - PathIsFile -- path must be a file - PathExists -- path must exist (any type) [default] - - The validator is a function that is called and which should return + * *key* - name of this option on the command line (e.g. "prefix") + * *help* - help string for option + * *dflt* - default value for this option + * *validator* - [optional] validator for option value. Predefined are: + + * *PathAccept* - accepts any path setting; no validation + * *PathIsDir* - path must be an existing directory + * *PathIsDirCreate* - path must be a dir; will create + * *PathIsFile* - path must be a file + * *PathExists* - path must exist (any type) [default] + + The *validator* is a function that is called and which should return True or False to indicate if the path is valid. The arguments - to the validator function are: (key, val, env). The key is the - name of the option, the val is the path specified for the option, - and the env is the env to which the Options have been added. + to the validator function are: (*key*, *val*, *env*). *key* is the + name of the option, *val* is the path specified for the option, + and *env* is the environment to which the Options have been added. Usage example:: - Examples: - prefix=/usr/local - - opts = Variables() - - opts = Variables() - opts.Add(PathVariable('qtdir', - 'where the root of Qt is installed', - qtdir, PathIsDir)) - opts.Add(PathVariable('qt_includes', - 'where the Qt includes are installed', - '$qtdir/includes', PathIsDirCreate)) - opts.Add(PathVariable('qt_libraries', - 'where the Qt library is installed', - '$qtdir/lib')) + opts = Variables() + opts.Add( + PathVariable( + 'qtdir', + help='where the root of Qt is installed', + default=qtdir, + validator=PathIsDir, + ) + ) + opts.Add( + PathVariable( + 'qt_includes', + help='where the Qt includes are installed', + default='$qtdir/includes', + validator=PathIsDirCreate, + ) + ) + opts.Add( + PathVariable( + 'qt_libraries', + help='where the Qt library is installed', + default='$qtdir/lib', + ) + ) """ -__all__ = ['PathVariable',] import os import os.path +from typing import Tuple, Callable import SCons.Errors +__all__ = ['PathVariable',] + class _PathVariableClass: @staticmethod - def PathAccept(key, val, env): + def PathAccept(key, val, env) -> None: """Accepts any path, no checking done.""" pass - + @staticmethod - def PathIsDir(key, val, env): + def PathIsDir(key, val, env) -> None: """Validator to check if Path is a directory.""" if not os.path.isdir(val): if os.path.isfile(val): @@ -88,17 +98,20 @@ class _PathVariableClass: raise SCons.Errors.UserError(m % (key, val)) @staticmethod - def PathIsDirCreate(key, val, env): + def PathIsDirCreate(key, val, env) -> None: """Validator to check if Path is a directory, creating it if it does not exist.""" - if os.path.isfile(val): + try: + os.makedirs(val, exist_ok=True) + except FileExistsError: m = 'Path for option %s is a file, not a directory: %s' raise SCons.Errors.UserError(m % (key, val)) - if not os.path.isdir(val): - os.makedirs(val) + except PermissionError: + m = 'Path for option %s could not be created: %s' + raise SCons.Errors.UserError(m % (key, val)) @staticmethod - def PathIsFile(key, val, env): + def PathIsFile(key, val, env) -> None: """Validator to check if Path is a file""" if not os.path.isfile(val): if os.path.isdir(val): @@ -108,32 +121,33 @@ class _PathVariableClass: raise SCons.Errors.UserError(m % (key, val)) @staticmethod - def PathExists(key, val, env): + def PathExists(key, val, env) -> None: """Validator to check if Path exists""" if not os.path.exists(val): m = 'Path for option %s does not exist: %s' raise SCons.Errors.UserError(m % (key, val)) - def __call__(self, key, help, default, validator=None): - """ - The input parameters describe a 'path list' option, thus they - are returned with the correct converter and validator appended. The - result is usable for input to opts.Add() . + def __call__(self, key, help, default, validator=None) -> Tuple[str, str, str, Callable, None]: + """Return a tuple describing a path list SCons Variable. - The 'default' option specifies the default path to use if the + The input parameters describe a 'path list' option. Returns + a tuple with the correct converter and validator appended. The + result is usable for input to :meth:`Add`. + + The *default* option specifies the default path to use if the user does not specify an override with this option. - validator is a validator, see this file for examples + *validator* is a validator, see this file for examples """ if validator is None: validator = self.PathExists if SCons.Util.is_List(key) or SCons.Util.is_Tuple(key): - return (key, '%s ( /path/to/%s )' % (help, key[0]), default, - validator, None) + helpmsg = '%s ( /path/to/%s )' % (help, key[0]) else: - return (key, '%s ( /path/to/%s )' % (help, key), default, - validator, None) + helpmsg = '%s ( /path/to/%s )' % (help, key) + return (key, helpmsg, default, validator, None) + PathVariable = _PathVariableClass() diff --git a/SCons/Variables/PathVariableTests.py b/SCons/Variables/PathVariableTests.py index 6e3a70b..a9aa8f0 100644 --- a/SCons/Variables/PathVariableTests.py +++ b/SCons/Variables/PathVariableTests.py @@ -96,8 +96,8 @@ class PathVariableTestCase(unittest.TestCase): o.validator('X', dne, {}) except SCons.Errors.UserError as e: assert str(e) == 'Directory path for option X does not exist: %s' % dne, e - except: - raise Exception("did not catch expected UserError") + except Exception as e: + raise Exception("did not catch expected UserError") from e def test_PathIsDirCreate(self): """Test the PathIsDirCreate validator""" @@ -121,8 +121,16 @@ class PathVariableTestCase(unittest.TestCase): o.validator('X', f, {}) except SCons.Errors.UserError as e: assert str(e) == 'Path for option X is a file, not a directory: %s' % f, e - except: - raise Exception("did not catch expected UserError") + except Exception as e: + raise Exception("did not catch expected UserError") from e + + f = '/yyy/zzz' # this not exists and should fail to create + try: + o.validator('X', f, {}) + except SCons.Errors.UserError as e: + assert str(e) == 'Path for option X could not be created: %s' % f, e + except Exception as e: + raise Exception("did not catch expected UserError") from e def test_PathIsFile(self): """Test the PathIsFile validator""" diff --git a/SCons/Variables/VariablesTests.py b/SCons/Variables/VariablesTests.py index 8def19e..4672cdd 100644 --- a/SCons/Variables/VariablesTests.py +++ b/SCons/Variables/VariablesTests.py @@ -46,7 +46,7 @@ class Environment: def check(key, value, env): assert int(value) == 6 * 9, "key %s = %s" % (key, repr(value)) - + # Check saved option file by executing and comparing against # the expected dictionary def checkSave(file, expected): @@ -138,7 +138,7 @@ class VariablesTestCase(unittest.TestCase): test = TestSCons.TestSCons() file = test.workpath('custom.py') opts = SCons.Variables.Variables(file) - + opts.Add('ANSWER', 'THE answer to THE question', "42", @@ -159,7 +159,7 @@ class VariablesTestCase(unittest.TestCase): file = test.workpath('custom.py') test.write('custom.py', 'ANSWER=54') opts = SCons.Variables.Variables(file) - + opts.Add('ANSWER', 'THE answer to THE question', "42", @@ -187,7 +187,7 @@ class VariablesTestCase(unittest.TestCase): file = test.workpath('custom.py') test.write('custom.py', 'ANSWER=42') opts = SCons.Variables.Variables(file) - + opts.Add('ANSWER', 'THE answer to THE question', "10", @@ -208,7 +208,7 @@ class VariablesTestCase(unittest.TestCase): file = test.workpath('custom.py') test.write('custom.py', 'ANSWER=10') opts = SCons.Variables.Variables(file) - + opts.Add('ANSWER', 'THE answer to THE question', "12", @@ -229,7 +229,7 @@ class VariablesTestCase(unittest.TestCase): file = test.workpath('custom.py') test.write('custom.py', 'ANSWER=10') opts = SCons.Variables.Variables(file) - + opts.Add('ANSWER', 'THE answer to THE question', "12", @@ -247,7 +247,7 @@ class VariablesTestCase(unittest.TestCase): test = TestSCons.TestSCons() file = test.workpath('custom.py') opts = SCons.Variables.Variables(file) - + opts.Add('ANSWER', help='THE answer to THE question', converter=str) @@ -282,9 +282,9 @@ class VariablesTestCase(unittest.TestCase): nopts = SCons.Variables.Variables() # Ensure that both attributes are initialized to - # an empty list and dict, respectively. + # an empty list and dict, respectively. assert len(nopts.files) == 0 - assert len(nopts.args) == 0 + assert len(nopts.args) == 0 def test_args(self): """Test updating an Environment with arguments overridden""" @@ -295,7 +295,7 @@ class VariablesTestCase(unittest.TestCase): file = test.workpath('custom.py') test.write('custom.py', 'ANSWER=42') opts = SCons.Variables.Variables(file, {'ANSWER':54}) - + opts.Add('ANSWER', 'THE answer to THE question', "42", @@ -315,7 +315,7 @@ class VariablesTestCase(unittest.TestCase): file = test.workpath('custom.py') test.write('custom.py', 'ANSWER=54') opts = SCons.Variables.Variables(file, {'ANSWER':42}) - + opts.Add('ANSWER', 'THE answer to THE question', "54", @@ -332,7 +332,7 @@ class VariablesTestCase(unittest.TestCase): file = test.workpath('custom.py') test.write('custom.py', 'ANSWER=54') opts = SCons.Variables.Variables(file, {'ANSWER':54}) - + opts.Add('ANSWER', 'THE answer to THE question', "54", @@ -354,7 +354,7 @@ class VariablesTestCase(unittest.TestCase): if val in [1, 'y']: val = 1 if val in [0, 'n']: val = 0 return val - + # test saving out empty file opts.Add('OPT_VAL', 'An option to test', @@ -400,11 +400,11 @@ class VariablesTestCase(unittest.TestCase): self.x = x def __str__(self): return self.x - + test = TestSCons.TestSCons() cache_file = test.workpath('cached.options') opts = SCons.Variables.Variables() - + opts.Add('THIS_USED_TO_BREAK', 'An option to test', "Default") @@ -412,11 +412,11 @@ class VariablesTestCase(unittest.TestCase): opts.Add('THIS_ALSO_BROKE', 'An option to test', "Default2") - + opts.Add('THIS_SHOULD_WORK', 'An option to test', Foo('bar')) - + env = Environment() opts.Update(env, { 'THIS_USED_TO_BREAK' : "Single'Quotes'In'String", 'THIS_ALSO_BROKE' : "\\Escape\nSequences\t", @@ -502,7 +502,10 @@ A: a - alpha test assert text == expectAlpha, text textBackwards = opts.GenerateHelpText(env, sort=lambda x, y: cmp(y, x)) - assert textBackwards == expectBackwards, "Expected:\n%s\nGot:\n%s\n"%(textBackwards, expectBackwards) + assert textBackwards == expectBackwards, "Expected:\n%s\nGot:\n%s\n" % ( + textBackwards, + expectBackwards, + ) def test_FormatVariableHelpText(self): """Test generating custom format help text""" @@ -550,7 +553,7 @@ B 42 54 b - alpha test ['B'] """ text = opts.GenerateHelpText(env, sort=cmp) assert text == expectAlpha, text - + def test_Aliases(self): """Test option aliases""" # test alias as a tuple @@ -560,17 +563,17 @@ B 42 54 b - alpha test ['B'] 'THE answer to THE question', "42"), ) - + env = Environment() opts.Update(env, {'ANSWER' : 'answer'}) - + assert 'ANSWER' in env - + env = Environment() opts.Update(env, {'ANSWERALIAS' : 'answer'}) - + assert 'ANSWER' in env and 'ANSWERALIAS' not in env - + # test alias as a list opts = SCons.Variables.Variables() opts.AddVariables( @@ -578,17 +581,16 @@ B 42 54 b - alpha test ['B'] 'THE answer to THE question', "42"), ) - + env = Environment() opts.Update(env, {'ANSWER' : 'answer'}) - + assert 'ANSWER' in env - + env = Environment() opts.Update(env, {'ANSWERALIAS' : 'answer'}) - - assert 'ANSWER' in env and 'ANSWERALIAS' not in env + assert 'ANSWER' in env and 'ANSWERALIAS' not in env class UnknownVariablesTestCase(unittest.TestCase): @@ -596,7 +598,7 @@ class UnknownVariablesTestCase(unittest.TestCase): def test_unknown(self): """Test the UnknownVariables() method""" opts = SCons.Variables.Variables() - + opts.Add('ANSWER', 'THE answer to THE question', "42") @@ -612,38 +614,38 @@ class UnknownVariablesTestCase(unittest.TestCase): r = opts.UnknownVariables() assert r == {'UNKNOWN' : 'unknown'}, r assert env['ANSWER'] == 'answer', env['ANSWER'] - + def test_AddOptionUpdatesUnknown(self): """Test updating of the 'unknown' dict""" opts = SCons.Variables.Variables() - + opts.Add('A', 'A test variable', "1") - + args = { 'A' : 'a', 'ADDEDLATER' : 'notaddedyet', } - + env = Environment() opts.Update(env,args) - + r = opts.UnknownVariables() assert r == {'ADDEDLATER' : 'notaddedyet'}, r assert env['A'] == 'a', env['A'] - + opts.Add('ADDEDLATER', 'An option not present initially', "1") - + args = { 'A' : 'a', 'ADDEDLATER' : 'added', } - + opts.Update(env, args) - + r = opts.UnknownVariables() assert len(r) == 0, r assert env['ADDEDLATER'] == 'added', env['ADDEDLATER'] @@ -651,33 +653,33 @@ class UnknownVariablesTestCase(unittest.TestCase): def test_AddOptionWithAliasUpdatesUnknown(self): """Test updating of the 'unknown' dict (with aliases)""" opts = SCons.Variables.Variables() - + opts.Add('A', 'A test variable', "1") - + args = { 'A' : 'a', 'ADDEDLATERALIAS' : 'notaddedyet', } - + env = Environment() opts.Update(env,args) - + r = opts.UnknownVariables() assert r == {'ADDEDLATERALIAS' : 'notaddedyet'}, r assert env['A'] == 'a', env['A'] - + opts.AddVariables( (('ADDEDLATER', 'ADDEDLATERALIAS'), 'An option not present initially', "1"), ) - + args['ADDEDLATERALIAS'] = 'added' - + opts.Update(env, args) - + r = opts.UnknownVariables() assert len(r) == 0, r assert env['ADDEDLATER'] == 'added', env['ADDEDLATER'] diff --git a/SCons/Variables/__init__.py b/SCons/Variables/__init__.py index 4a30a91..9c8bdca 100644 --- a/SCons/Variables/__init__.py +++ b/SCons/Variables/__init__.py @@ -44,7 +44,7 @@ class Variables: Holds all the options, updates the environment with the variables, and renders the help text. - If is_global is True, this is a singleton, create only once. + If *is_global* is true, this is a singleton, create only once. Args: files (optional): List of option configuration files to load @@ -64,7 +64,7 @@ class Variables: self.args = args if not SCons.Util.is_List(files): if files: - files = [ files ] + files = [files,] else: files = [] self.files = files @@ -77,20 +77,23 @@ class Variables: if not Variables.instance: Variables.instance=self - def _do_add(self, key, help="", default=None, validator=None, converter=None): + def _do_add(self, key, help="", default=None, validator=None, converter=None, **kwargs) -> None: class Variable: pass option = Variable() - # if we get a list or a tuple, we take the first element as the + # If we get a list or a tuple, we take the first element as the # option key and store the remaining in aliases. if SCons.Util.is_List(key) or SCons.Util.is_Tuple(key): - option.key = key[0] - option.aliases = key[1:] + option.key = key[0] + option.aliases = list(key[1:]) else: - option.key = key - option.aliases = [ key ] + option.key = key + # TODO: normalize to not include key in aliases. Currently breaks tests. + option.aliases = [key,] + if not SCons.Environment.is_valid_construction_var(option.key): + raise SCons.Errors.UserError("Illegal Variables key `%s'" % str(option.key)) option.help = help option.default = default option.validator = validator @@ -100,43 +103,42 @@ class Variables: # options might be added after the 'unknown' dict has been set up, # so we remove the key and all its aliases from that dict - for alias in list(option.aliases) + [ option.key ]: + for alias in option.aliases + [option.key,]: if alias in self.unknown: del self.unknown[alias] - def keys(self): - """ - Returns the keywords for the options - """ + def keys(self) -> list: + """Returns the keywords for the options.""" return [o.key for o in self.options] - def Add(self, key, help="", default=None, validator=None, converter=None, **kw): - r"""Add an option. + def Add(self, key, *args, **kwargs) -> None: + r""" Add an option. Args: - key: the name of the variable, or a list or tuple of arguments - help: optional help text for the options (Default value = "") - default: optional default value for option (Default value = None) - validator: optional function called to validate the option's value - (Default value = None) - converter: optional function to be called to convert the option's - value before putting it in the environment. (Default value = None) - \*\*kw: keyword args, unused. + key: the name of the variable, or a 5-tuple (or list). + If a tuple, and there are no additional arguments, + the tuple is unpacked into help, default, validator, converter. + If there are additional arguments, the first word of the tuple + is taken as the key, and the remainder as aliases. + \*args: optional positional arguments + help: optional help text for the options (Default value = "") + default: optional default value for option (Default value = None) + validator: optional function called to validate the option's value + (Default value = None) + converter: optional function to be called to convert the option's + value before putting it in the environment. (Default value = None) + \*\*kwargs: keyword args, can be the arguments from \*args or + arbitrary kwargs used by a variable itself """ - if SCons.Util.is_List(key) or isinstance(key, tuple): - self._do_add(*key) - return - - if not SCons.Util.is_String(key) or \ - not SCons.Environment.is_valid_construction_var(key): - raise SCons.Errors.UserError("Illegal Variables.Add() key `%s'" % str(key)) + if SCons.Util.is_List(key) or SCons.Util.is_Tuple(key): + if not (len(args) or len(kwargs)): + return self._do_add(*key) - self._do_add(key, help, default, validator, converter) + return self._do_add(key, *args, **kwargs) - def AddVariables(self, *optlist): - """ - Add a list of options. + def AddVariables(self, *optlist) -> None: + """ Add a list of options. Each list element is a tuple/list of arguments to be passed on to the underlying method for adding options. @@ -154,12 +156,13 @@ class Variables: for o in optlist: self._do_add(*o) + def Update(self, env, args=None) -> None: + """ Update an environment with the option variables. - def Update(self, env, args=None): - """ - Update an environment with the option variables. - - env - the environment to update. + Args: + env: the environment to update. + args: [optional] a dictionary of keys and values to update + in *env*. If omitted, uses the variables from the commandline. """ values = {} @@ -192,7 +195,7 @@ class Variables: for arg, value in args.items(): added = False for option in self.options: - if arg in list(option.aliases) + [ option.key ]: + if arg in option.aliases + [option.key,]: values[option.key] = value added = True if not added: @@ -206,7 +209,7 @@ class Variables: except KeyError: pass - # Call the convert functions: + # apply converters for option in self.options: if option.converter and option.key in values: value = env.subst('${%s}'%option.key) @@ -219,36 +222,39 @@ class Variables: raise SCons.Errors.UserError('Error converting option: %s\n%s'%(option.key, x)) - # Finally validate the values: + # apply validators for option in self.options: if option.validator and option.key in values: option.validator(option.key, env.subst('${%s}'%option.key), env) - def UnknownVariables(self): - """ - Returns any options in the specified arguments lists that - were not known, declared options in this object. + def UnknownVariables(self) -> dict: + """ Returns unknown variables. + + Identifies options that were not known, declared options in this object. """ return self.unknown - def Save(self, filename, env): - """ - Saves all the options in the given file. This file can - then be used to load the options next run. This can be used - to create an option cache file. + def Save(self, filename, env) -> None: + """ Save the options to a file. + + Saves all the options which have non-default settings + to the given file as Python expressions. This file can + then be used to load the options for a subsequent run. + This can be used to create an option cache file. - filename - Name of the file to save into - env - the environment get the option values from + Args: + filename: Name of the file to save into + env: the environment get the option values from """ # Create the file and write out the header try: - fh = open(filename, 'w') - - try: + with open(filename, 'w') as fh: # Make an assignment in the file for each option # within the environment that was assigned a value - # other than the default. + # other than the default. We don't want to save the + # ones set to default: in case the SConscript settings + # change you would then pick up old defaults. for option in self.options: try: value = env[option.key] @@ -268,55 +274,58 @@ class Variables: defaultVal = env.subst(SCons.Util.to_String(option.default)) if option.converter: - defaultVal = option.converter(defaultVal) + try: + defaultVal = option.converter(defaultVal) + except TypeError: + defaultVal = option.converter(defaultVal, env) if str(env.subst('${%s}' % option.key)) != str(defaultVal): fh.write('%s = %s\n' % (option.key, repr(value))) except KeyError: pass - finally: - fh.close() - except IOError as x: raise SCons.Errors.UserError('Error writing options to file: %s\n%s' % (filename, x)) - def GenerateHelpText(self, env, sort=None): - """ - Generate the help text for the options. + def GenerateHelpText(self, env, sort=None) -> str: + """ Generate the help text for the options. - env - an environment that is used to get the current values - of the options. - cmp - Either a function as follows: The specific sort function should take two arguments and return -1, 0 or 1 - or a boolean to indicate if it should be sorted. + Args: + env: an environment that is used to get the current values + of the options. + cmp: Either a comparison function used for sorting + (must take two arguments and return -1, 0 or 1) + or a boolean to indicate if it should be sorted. """ if callable(sort): - options = sorted(self.options, key=cmp_to_key(lambda x,y: sort(x.key,y.key))) + options = sorted(self.options, key=cmp_to_key(lambda x, y: sort(x.key, y.key))) elif sort is True: options = sorted(self.options, key=lambda x: x.key) else: options = self.options - def format(opt, self=self, env=env): + def format_opt(opt, self=self, env=env) -> str: if opt.key in env: actual = env.subst('${%s}' % opt.key) else: actual = None return self.FormatVariableHelpText(env, opt.key, opt.help, opt.default, actual, opt.aliases) - lines = [_f for _f in map(format, options) if _f] + lines = [_f for _f in map(format_opt, options) if _f] return ''.join(lines) - format = '\n%s: %s\n default: %s\n actual: %s\n' - format_ = '\n%s: %s\n default: %s\n actual: %s\n aliases: %s\n' + fmt = '\n%s: %s\n default: %s\n actual: %s\n' + aliasfmt = '\n%s: %s\n default: %s\n actual: %s\n aliases: %s\n' - def FormatVariableHelpText(self, env, key, help, default, actual, aliases=[]): + def FormatVariableHelpText(self, env, key, help, default, actual, aliases=None) -> str: + if aliases is None: + aliases = [] # Don't display the key name itself as an alias. aliases = [a for a in aliases if a != key] - if len(aliases)==0: - return self.format % (key, help, default, actual) + if aliases: + return self.aliasfmt % (key, help, default, actual, aliases) else: - return self.format_ % (key, help, default, actual, aliases) + return self.fmt % (key, help, default, actual) # Local Variables: # tab-width:4 diff --git a/doc/man/scons.xml b/doc/man/scons.xml index 214f38a..d38c6b7 100644 --- a/doc/man/scons.xml +++ b/doc/man/scons.xml @@ -4429,11 +4429,11 @@ from various sources, often from the command line:</para> call the &Variables; function:</para> <variablelist> - <varlistentry> + <varlistentry id="v-Variables"> <term><function>Variables</function>([<parameter>files, [args]]</parameter>)</term> <listitem> <para>If <parameter>files</parameter> is a file or -list of files, those are executed as Python scripts, +list of files, they are executed as Python scripts, and the values of (global) Python variables set in those files are added as &consvars; in the &DefEnv;. If no files are specified, @@ -4460,10 +4460,14 @@ CC = 'my_cc' is specified, it is a dictionary of values that will override anything read from <parameter>files</parameter>. -This is primarily intended to pass the +The primary use is to pass the &ARGUMENTS; dictionary that holds variables -specified on the command line. -Example:</para> +specified on the command line, +allowing you to indicate that if a setting appears +on both the command line and in the file(s), +the command line setting takes precedence. +However, any dictionary can be passed. +Examples:</para> <programlisting language="python"> vars = Variables('custom.py') @@ -4492,36 +4496,59 @@ pre-defined &consvars; that your project will make use of <para>Variables objects have the following methods:</para> <variablelist> - <varlistentry> + <varlistentry id="v-Add"> <term><replaceable>vars</replaceable>.<function>Add</function>(<parameter>key, [help, default, validator, converter]</parameter>)</term> <listitem> <para>Add a customizable &consvar; to the Variables object. <parameter>key</parameter> -is the name of the variable. +is either the name of the variable, +or a tuple (or list), in which case +the first item in the tuple is taken as the variable name, +and any remaining values are considered aliases for the variable. <parameter>help</parameter> -is the help text for the variable. +is the help text for the variable +(default empty string). <parameter>default</parameter> -is the default value of the variable; -if the default value is +is the default value of the variable +(default <constant>None</constant>). +If <parameter>default</parameter> is <constant>None</constant> -and there is no explicit value specified, +and a value is not specified, the &consvar; will not be added to the &consenv;. -If set, <parameter>validator</parameter> -is called to validate the value of the variable. -A function supplied as a validator shall accept -arguments: <parameter>key</parameter>, -<parameter>value</parameter>, and <parameter>env</parameter>. -The recommended way to handle an invalid value is -to raise an exception (see example below). -If set, <parameter>converter</parameter> -is called to convert the value before putting it in the environment, and -should take either a value, or the value and environment, as parameters. +</para> +<para> +As a special case, if <parameter>key</parameter> +is a tuple (or list) and is the <emphasis>only</emphasis> +argument, the tuple is unpacked into the five parameters +listed above left to right, with any missing members filled with +the respecitive default values. This form allows <function>Add</function> +to consume a tuple emitted by the convenience functions +<link linkend='v-BoolVariable'><function>BoolVariable</function></link>, +<link linkend='v-EnumVariable'><function>EnumVariable</function></link>, +<link linkend='v-ListVariable'><function>ListVariable</function></link>, +<link linkend='v-PackageVariable'><function>PackageVariable</function></link> +and +<link linkend='v-PathVariable'><function>PathVariable</function></link>. +</para> +<para> +If the optional <parameter>validator</parameter> is supplied, +it is called to validate the value of the variable. +A function supplied as a validator must accept +three arguments: <parameter>key</parameter>, +<parameter>value</parameter> and <parameter>env</parameter>, +and should raise an exception with a helpful error message +if <parameter>value</parameter> is invalid. +No return value is expected from the validator. +</para> +<para> +If the optional <parameter>converter</parameter> is supplied, +it is called to convert the value before putting it in the environment, +and should take either a value +or a value and environment as parameters. The converter function must return a value, -which will be converted into a string -before being validated by the -<parameter>validator</parameter> -(if any) +which will be converted into a string and be passed to the +<parameter>validator</parameter> (if any) and then added to the &consenv;.</para> <para>Examples:</para> @@ -4538,11 +4565,11 @@ vars.Add('COLOR', validator=valid_color) </listitem> </varlistentry> - <varlistentry> + <varlistentry id="v-AddVariables"> <term><replaceable>vars</replaceable>.<function>AddVariables</function>(<parameter>args</parameter>)</term> <listitem> <para>A convenience method that adds -multiple customizable &consvars; +one or more customizable &consvars; to a Variables object in one call; equivalent to calling &Add; multiple times. The <parameter>args</parameter> @@ -4551,10 +4578,10 @@ that contain the arguments for an individual call to the &Add; method. Since tuples are not Python mappings, the arguments cannot use the keyword form, -but rather are positional arguments as documented -for &Add;: a required name, the rest optional -but must be in the specified in order if used. - +but rather are positional arguments as documented for +<link linkend='v-Add'><function>Add</function></link>: +a required name, the other four optional, +but must be in the specified order if used. </para> <programlisting language="python"> @@ -4568,18 +4595,18 @@ opt.AddVariables( </listitem> </varlistentry> - <varlistentry> + <varlistentry id="v-Update"> <term><replaceable>vars</replaceable>.<function>Update</function>(<parameter>env, [args]</parameter>)</term> <listitem> <para>Update a &consenv; <parameter>env</parameter> -with the customized &consvars; . +with the customized &consvars;. Any specified variables that are not configured for the Variables object will be saved and may be retrieved using the -&UnknownVariables; -method, below.</para> +<link linkend='v-UnknownVariables'>&UnknownVariables;</link> +method.</para> <para>Normally this method is not called directly, but rather invoked indirectly by passing the Variables object to @@ -4592,7 +4619,7 @@ env = Environment(variables=vars) </listitem> </varlistentry> - <varlistentry> + <varlistentry id="v-UnknownVariables"> <term><replaceable>vars</replaceable>.<function>UnknownVariables</function>()</term> <listitem> <para>Returns a dictionary containing any @@ -4611,14 +4638,17 @@ for key, value in vars.UnknownVariables(): </listitem> </varlistentry> - <varlistentry> + <varlistentry id="v-Save"> <term><replaceable>vars</replaceable>.<function>Save</function>(<parameter>filename, env</parameter>)</term> <listitem> <para>Save the currently set variables into a script file named -by <parameter>filename</parameter> -that can be used on the next invocation to automatically load the current -settings. This method combined with the Variables method can be used to -support caching of variables between runs.</para> +by <parameter>filename</parameter>. Only variables that are +set to non-default values are saved. +You can load these saved settings on a subsequent run +by passing <parameter>filename</parameter> to the +<link linkend='v-Variables'>&Variables;</link> function, +providing a way to cache particular settings for reuse. +</para> <programlisting language="python"> env = Environment() @@ -4631,7 +4661,7 @@ vars.Save('variables.cache', env) </listitem> </varlistentry> - <varlistentry> + <varlistentry id="v-GenerateHelpText"> <term><replaceable>vars</replaceable>.<function>GenerateHelpText</function>(<parameter>env, [sort]</parameter>)</term> <listitem> <para>Generate help text documenting the customizable construction @@ -4663,7 +4693,7 @@ Help(vars.GenerateHelpText(env, sort=cmp)) </listitem> </varlistentry> - <varlistentry> + <varlistentry id="v-FormatVariableHelpText"> <term><replaceable>vars</replaceable>.<function>FormatVariableHelpText</function>(<parameter>env, opt, help, default, actual</parameter>)</term> <listitem> <para>Returns a formatted string @@ -4685,6 +4715,7 @@ string if you want the entries separated.</para> def my_format(env, opt, help, default, actual): fmt = "\n%s: default=%s actual=%s (%s)\n" return fmt % (opt, default, actual, help) + vars.FormatVariableHelpText = my_format </programlisting> </listitem> @@ -4701,7 +4732,7 @@ Each of these return a tuple ready to be passed to the &Add; or &AddVariables; method:</para> <variablelist> - <varlistentry> + <varlistentry id="v-BoolVariable"> <term><function>BoolVariable</function>(<parameter>key, help, default</parameter>)</term> <listitem> <para>Returns a tuple of arguments @@ -4736,7 +4767,7 @@ as false.</para> </listitem> </varlistentry> - <varlistentry> + <varlistentry id="v-EnumVariable"> <term><function>EnumVariable</function>(<parameter>key, help, default, allowed_values, [map, ignorecase]</parameter>)</term> <listitem> <para>Returns a tuple of arguments @@ -4785,7 +4816,7 @@ converted to lower case.</para> </listitem> </varlistentry> - <varlistentry> + <varlistentry id="v-ListVariable"> <term><function>ListVariable</function>(<parameter>key, help, default, names, [map]</parameter>)</term> <listitem> <para>Returns a tuple of arguments @@ -4824,7 +4855,7 @@ reflected in the generated help message). </para> </listitem> </varlistentry> - <varlistentry> + <varlistentry id="v-PackageVariable"> <term><function>PackageVariable</function>(<parameter>key, help, default</parameter>)</term> <listitem> <para>Returns a tuple of arguments @@ -4864,7 +4895,7 @@ to disable use of the specified option.</para> </listitem> </varlistentry> - <varlistentry> + <varlistentry id="v-PathVariable"> <term><function>PathVariable</function>(<parameter>key, help, default, [validator]</parameter>)</term> <listitem> <para>Returns a tuple of arguments |