diff --git a/cibuildwheel/util.py b/cibuildwheel/util.py index 3474d3723..a4e3058fe 100644 --- a/cibuildwheel/util.py +++ b/cibuildwheel/util.py @@ -12,7 +12,7 @@ from enum import Enum from pathlib import Path from time import sleep -from typing import Dict, Iterator, List, Optional +from typing import Any, Dict, Iterator, List, Optional import bracex import certifi @@ -48,14 +48,50 @@ ) +def format_safe(template: str, **kwargs: Any) -> str: + """ + Works similarly to `template.format(**kwargs)`, except that unmatched + fields in `template` are passed through untouched. + + >>> format_safe('{a} {b}', a='123') + '123 {b}' + >>> format_safe('{a} {b[4]:3f}', a='123') + '123 {b[4]:3f}' + + To avoid variable expansion, precede with a single backslash e.g. + >>> format_safe('\\{a} {b}', a='123') + '{a} {b}' + """ + + result = template + + for key, value in kwargs.items(): + find_pattern = re.compile( + fr""" + (? str: """ Preprocesses a command by expanding variables like {python}. For example, used in the test_command option to specify the path to the - project's root. + project's root. Unmatched syntax will mostly be allowed through. """ - return command.format(python="python", pip="pip", **kwargs) + return format_safe(command, python="python", pip="pip", **kwargs) def get_build_verbosity_extra_flags(level: int) -> List[str]: diff --git a/unit_test/utils_test.py b/unit_test/utils_test.py new file mode 100644 index 000000000..cc5013493 --- /dev/null +++ b/unit_test/utils_test.py @@ -0,0 +1,36 @@ +from cibuildwheel.util import format_safe, prepare_command + + +def test_format_safe(): + assert format_safe("{wheel}", wheel="filename.whl") == "filename.whl" + assert format_safe("command \\{wheel}", wheel="filename.whl") == "command {wheel}" + assert format_safe("{command \\{wheel}}", wheel="filename.whl") == "{command {wheel}}" + + # check unmatched brackets + assert format_safe("{command {wheel}", wheel="filename.whl") == "{command filename.whl" + + # check positional-style arguments i.e. {} + assert ( + format_safe("find . -name * -exec ls -a {} \\;", project="/project") + == "find . -name * -exec ls -a {} \\;" + ) + + +def test_prepare_command(): + assert prepare_command("python -m {project}", project="project") == "python -m project" + assert prepare_command("python -m {something}", project="project") == "python -m {something}" + assert ( + prepare_command("python -m {something.abc}", project="project") + == "python -m {something.abc}" + ) + + assert ( + prepare_command("python -m {something.abc[4]:3f}", project="project") + == "python -m {something.abc[4]:3f}" + ) + + # test some unusual syntax that used to trip up the str.format approach + assert ( + prepare_command("{a}{a,b}{b:.2e}{c}{d%s}{e:3}{f[0]}", a="42", b="3.14159") + == "42{a,b}{b:.2e}{c}{d%s}{e:3}{f[0]}" + )