Unify message(), format() and fstring formatting
Share a common function to convert objects to display strings for consistency. While at it, also add support for formatting user options.
This commit is contained in:
parent
465ad6d261
commit
cec3edc08a
|
@ -0,0 +1,10 @@
|
|||
## Unified message(), str.format() and f-string formatting
|
||||
|
||||
They now all support the same set of values: strings, integers, bools, options,
|
||||
dictionaries and lists thereof.
|
||||
|
||||
- Feature options (i.e. enabled, disabled, auto) were not previously supported
|
||||
by any of those functions.
|
||||
- Lists and dictionaries were not previously supported by f-string.
|
||||
- str.format() allowed any type and often resulted in printing the internal
|
||||
representation which is now deprecated.
|
|
@ -14,6 +14,10 @@ methods:
|
|||
|
||||
See [the Meson syntax entry](Syntax.md#string-formatting) for more
|
||||
information.
|
||||
|
||||
*Since 1.3.0* values other than strings, integers, bools, options,
|
||||
dictionaries and lists thereof are deprecated. They were previously printing
|
||||
the internal representation of the raw Python object.
|
||||
example: |
|
||||
```meson
|
||||
template = 'string: @0@, number: @1@, bool: @2@'
|
||||
|
|
|
@ -35,6 +35,7 @@ from ..interpreterbase import InterpreterException, InvalidArguments, InvalidCod
|
|||
from ..interpreterbase import Disabler, disablerIfNotFound
|
||||
from ..interpreterbase import FeatureNew, FeatureDeprecated, FeatureBroken, FeatureNewKwargs
|
||||
from ..interpreterbase import ObjectHolder, ContextManagerObject
|
||||
from ..interpreterbase import stringifyUserArguments
|
||||
from ..modules import ExtensionModule, ModuleObject, MutableModuleObject, NewExtensionModule, NotFoundExtensionModule
|
||||
|
||||
from . import interpreterobjects as OBJ
|
||||
|
@ -139,20 +140,6 @@ def _project_version_validator(value: T.Union[T.List, str, mesonlib.File, None])
|
|||
return 'when passed as array must contain a File'
|
||||
return None
|
||||
|
||||
|
||||
def stringifyUserArguments(args: T.List[T.Any], quote: bool = False) -> str:
|
||||
if isinstance(args, list):
|
||||
return '[%s]' % ', '.join([stringifyUserArguments(x, True) for x in args])
|
||||
elif isinstance(args, dict):
|
||||
return '{%s}' % ', '.join(['{} : {}'.format(stringifyUserArguments(k, True), stringifyUserArguments(v, True)) for k, v in args.items()])
|
||||
elif isinstance(args, bool):
|
||||
return 'true' if args else 'false'
|
||||
elif isinstance(args, int):
|
||||
return str(args)
|
||||
elif isinstance(args, str):
|
||||
return f"'{args}'" if quote else args
|
||||
raise InvalidArguments('Function accepts only strings, integers, bools, lists, dictionaries and lists thereof.')
|
||||
|
||||
class Summary:
|
||||
def __init__(self, project_name: str, project_version: str):
|
||||
self.project_name = project_name
|
||||
|
@ -1343,12 +1330,18 @@ class Interpreter(InterpreterBase, HoldableObject):
|
|||
success &= self.add_languages(langs, required, MachineChoice.HOST)
|
||||
return success
|
||||
|
||||
def _stringify_user_arguments(self, args: T.List[TYPE_var], func_name: str) -> T.List[str]:
|
||||
try:
|
||||
return [stringifyUserArguments(i, self.subproject) for i in args]
|
||||
except InvalidArguments as e:
|
||||
raise InvalidArguments(f'{func_name}(): {str(e)}')
|
||||
|
||||
@noArgsFlattening
|
||||
@noKwargs
|
||||
def func_message(self, node: mparser.BaseNode, args, kwargs):
|
||||
if len(args) > 1:
|
||||
FeatureNew.single_use('message with more than one argument', '0.54.0', self.subproject, location=node)
|
||||
args_str = [stringifyUserArguments(i) for i in args]
|
||||
args_str = self._stringify_user_arguments(args, 'message')
|
||||
self.message_impl(args_str)
|
||||
|
||||
def message_impl(self, args):
|
||||
|
@ -1427,7 +1420,7 @@ class Interpreter(InterpreterBase, HoldableObject):
|
|||
def func_warning(self, node, args, kwargs):
|
||||
if len(args) > 1:
|
||||
FeatureNew.single_use('warning with more than one argument', '0.54.0', self.subproject, location=node)
|
||||
args_str = [stringifyUserArguments(i) for i in args]
|
||||
args_str = self._stringify_user_arguments(args, 'warning')
|
||||
mlog.warning(*args_str, location=node)
|
||||
|
||||
@noArgsFlattening
|
||||
|
@ -1435,14 +1428,14 @@ class Interpreter(InterpreterBase, HoldableObject):
|
|||
def func_error(self, node, args, kwargs):
|
||||
if len(args) > 1:
|
||||
FeatureNew.single_use('error with more than one argument', '0.58.0', self.subproject, location=node)
|
||||
args_str = [stringifyUserArguments(i) for i in args]
|
||||
args_str = self._stringify_user_arguments(args, 'error')
|
||||
raise InterpreterException('Problem encountered: ' + ' '.join(args_str))
|
||||
|
||||
@noArgsFlattening
|
||||
@FeatureNew('debug', '0.63.0')
|
||||
@noKwargs
|
||||
def func_debug(self, node, args, kwargs):
|
||||
args_str = [stringifyUserArguments(i) for i in args]
|
||||
args_str = self._stringify_user_arguments(args, 'debug')
|
||||
mlog.debug('Debug:', *args_str)
|
||||
|
||||
@noKwargs
|
||||
|
|
|
@ -17,8 +17,9 @@ from ...interpreterbase import (
|
|||
noKwargs,
|
||||
noPosargs,
|
||||
typed_pos_args,
|
||||
|
||||
InvalidArguments,
|
||||
FeatureBroken,
|
||||
stringifyUserArguments,
|
||||
)
|
||||
|
||||
|
||||
|
@ -90,12 +91,14 @@ class StringHolder(ObjectHolder[str]):
|
|||
@noArgsFlattening
|
||||
@noKwargs
|
||||
@typed_pos_args('str.format', varargs=object)
|
||||
def format_method(self, args: T.Tuple[T.List[object]], kwargs: TYPE_kwargs) -> str:
|
||||
def format_method(self, args: T.Tuple[T.List[TYPE_var]], kwargs: TYPE_kwargs) -> str:
|
||||
arg_strings: T.List[str] = []
|
||||
for arg in args[0]:
|
||||
if isinstance(arg, bool): # Python boolean is upper case.
|
||||
arg = str(arg).lower()
|
||||
arg_strings.append(str(arg))
|
||||
try:
|
||||
arg_strings.append(stringifyUserArguments(arg, self.subproject))
|
||||
except InvalidArguments as e:
|
||||
FeatureBroken.single_use(f'str.format: {str(e)}', '1.3.0', self.subproject, location=self.current_node)
|
||||
arg_strings.append(str(arg))
|
||||
|
||||
def arg_replace(match: T.Match[str]) -> str:
|
||||
idx = int(match.group(1))
|
||||
|
|
|
@ -35,6 +35,7 @@ __all__ = [
|
|||
'default_resolve_key',
|
||||
'flatten',
|
||||
'resolve_second_level_holders',
|
||||
'stringifyUserArguments',
|
||||
|
||||
'noPosargs',
|
||||
'noKwargs',
|
||||
|
@ -134,6 +135,11 @@ from .exceptions import (
|
|||
)
|
||||
|
||||
from .disabler import Disabler, is_disabled
|
||||
from .helpers import default_resolve_key, flatten, resolve_second_level_holders
|
||||
from .helpers import (
|
||||
default_resolve_key,
|
||||
flatten,
|
||||
resolve_second_level_holders,
|
||||
stringifyUserArguments,
|
||||
)
|
||||
from .interpreterbase import InterpreterBase
|
||||
from .operator import MesonOperator
|
||||
|
|
|
@ -14,13 +14,15 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from .. import mesonlib, mparser
|
||||
from .exceptions import InterpreterException
|
||||
from .exceptions import InterpreterException, InvalidArguments
|
||||
from ..coredata import UserOption
|
||||
|
||||
|
||||
import collections.abc
|
||||
import typing as T
|
||||
|
||||
if T.TYPE_CHECKING:
|
||||
from .baseobjects import TYPE_var, TYPE_kwargs
|
||||
from .baseobjects import TYPE_var, TYPE_kwargs, SubProject
|
||||
|
||||
def flatten(args: T.Union['TYPE_var', T.List['TYPE_var']]) -> T.List['TYPE_var']:
|
||||
if isinstance(args, mparser.StringNode):
|
||||
|
@ -54,3 +56,22 @@ def default_resolve_key(key: mparser.BaseNode) -> str:
|
|||
if not isinstance(key, mparser.IdNode):
|
||||
raise InterpreterException('Invalid kwargs format.')
|
||||
return key.value
|
||||
|
||||
def stringifyUserArguments(args: TYPE_var, subproject: SubProject, quote: bool = False) -> str:
|
||||
if isinstance(args, str):
|
||||
return f"'{args}'" if quote else args
|
||||
elif isinstance(args, bool):
|
||||
return 'true' if args else 'false'
|
||||
elif isinstance(args, int):
|
||||
return str(args)
|
||||
elif isinstance(args, list):
|
||||
return '[%s]' % ', '.join([stringifyUserArguments(x, subproject, True) for x in args])
|
||||
elif isinstance(args, dict):
|
||||
l = ['{} : {}'.format(stringifyUserArguments(k, subproject, True),
|
||||
stringifyUserArguments(v, subproject, True)) for k, v in args.items()]
|
||||
return '{%s}' % ', '.join(l)
|
||||
elif isinstance(args, UserOption):
|
||||
from .decorators import FeatureNew
|
||||
FeatureNew.single_use('User option in string format', '1.3.0', subproject)
|
||||
return stringifyUserArguments(args.printable_value(), subproject)
|
||||
raise InvalidArguments('Value other than strings, integers, bools, options, dictionaries and lists thereof.')
|
||||
|
|
|
@ -40,7 +40,7 @@ from .exceptions import (
|
|||
|
||||
from .decorators import FeatureNew
|
||||
from .disabler import Disabler, is_disabled
|
||||
from .helpers import default_resolve_key, flatten, resolve_second_level_holders
|
||||
from .helpers import default_resolve_key, flatten, resolve_second_level_holders, stringifyUserArguments
|
||||
from .operator import MesonOperator
|
||||
from ._unholder import _unholder
|
||||
|
||||
|
@ -433,11 +433,12 @@ class InterpreterBase:
|
|||
var = str(match.group(1))
|
||||
try:
|
||||
val = _unholder(self.variables[var])
|
||||
if not isinstance(val, (str, int, float, bool)):
|
||||
raise InvalidCode(f'Identifier "{var}" does not name a formattable variable ' +
|
||||
'(has to be an integer, a string, a floating point number or a boolean).')
|
||||
|
||||
return str(val)
|
||||
if isinstance(val, (list, dict)):
|
||||
FeatureNew.single_use('List or dictionary in f-string', '1.3.0', self.subproject, location=self.current_node)
|
||||
try:
|
||||
return stringifyUserArguments(val, self.subproject)
|
||||
except InvalidArguments as e:
|
||||
raise InvalidArguments(f'f-string: {str(e)}')
|
||||
except KeyError:
|
||||
raise InvalidCode(f'Identifier "{var}" does not name a variable.')
|
||||
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
project('test format string')
|
||||
|
||||
# Test all supported types in message(), format(), f-string.
|
||||
foreach t : [get_option('opt'), 42, true, false, 'str', ['list'], {'dict': 'value'}]
|
||||
message(t, '@0@'.format(t), f'@t@', [t], {'key': t})
|
||||
endforeach
|
||||
|
||||
# Deprecated but should work with str.format().
|
||||
env = environment()
|
||||
message('@0@'.format(env))
|
||||
|
||||
# Should fail with f-string and message()
|
||||
error_msg = 'Value other than strings, integers, bools, options, dictionaries and lists thereof.'
|
||||
testcase expect_error('message(): ' + error_msg)
|
||||
message(env)
|
||||
endtestcase
|
||||
|
||||
testcase expect_error('f-string: ' + error_msg)
|
||||
message(f'@env@')
|
||||
endtestcase
|
|
@ -0,0 +1 @@
|
|||
option('opt', type: 'feature')
|
|
@ -0,0 +1,28 @@
|
|||
{
|
||||
"stdout": [
|
||||
{
|
||||
"line": "Message: auto auto auto [auto] {'key' : auto}"
|
||||
},
|
||||
{
|
||||
"line": "Message: 42 42 42 [42] {'key' : 42}"
|
||||
},
|
||||
{
|
||||
"line": "Message: true true true [true] {'key' : true}"
|
||||
},
|
||||
{
|
||||
"line": "Message: false false false [false] {'key' : false}"
|
||||
},
|
||||
{
|
||||
"line": "Message: str str str ['str'] {'key' : 'str'}"
|
||||
},
|
||||
{
|
||||
"line": "Message: ['list'] ['list'] ['list'] [['list']] {'key' : ['list']}"
|
||||
},
|
||||
{
|
||||
"line": "Message: {'dict' : 'value'} {'dict' : 'value'} {'dict' : 'value'} [{'dict' : 'value'}] {'key' : {'dict' : 'value'}}"
|
||||
},
|
||||
{
|
||||
"line": "Message: <EnvironmentVariables: []>"
|
||||
}
|
||||
]
|
||||
}
|
Loading…
Reference in New Issue