Merge pull request #889 from pypa/fmtpass-alt
Alternative string-based approach to format string passthrough
This commit is contained in:
+44
-2
@@ -48,14 +48,56 @@ MUSLLINUX_ARCHS = (
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
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"""
|
||||||
|
(?<!\#) # don't match if preceded by a hash
|
||||||
|
{{ # literal open curly bracket
|
||||||
|
{re.escape(key)} # the field name
|
||||||
|
}} # literal close curly bracket
|
||||||
|
""",
|
||||||
|
re.VERBOSE,
|
||||||
|
)
|
||||||
|
|
||||||
|
# we use a lambda for repl to prevent re.sub interpreting backslashes
|
||||||
|
# in repl as escape sequences
|
||||||
|
result = re.sub(
|
||||||
|
pattern=find_pattern,
|
||||||
|
repl=lambda _: str(value),
|
||||||
|
string=result,
|
||||||
|
)
|
||||||
|
|
||||||
|
# transform escaped sequences into their literal equivalents
|
||||||
|
result = result.replace(f"#{{{key}}}", f"{{{key}}}")
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
def prepare_command(command: str, **kwargs: PathOrStr) -> str:
|
def prepare_command(command: str, **kwargs: PathOrStr) -> str:
|
||||||
"""
|
"""
|
||||||
Preprocesses a command by expanding variables like {python}.
|
Preprocesses a command by expanding variables like {python}.
|
||||||
|
|
||||||
For example, used in the test_command option to specify the path to the
|
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]:
|
def get_build_verbosity_extra_flags(level: int) -> List[str]:
|
||||||
|
|||||||
@@ -1259,6 +1259,10 @@ Platform-specific environment variables are also available:<br/>
|
|||||||
« subprocess_run("cibuildwheel", "--help") »
|
« subprocess_run("cibuildwheel", "--help") »
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Placeholders
|
||||||
|
|
||||||
|
Some options support placeholders, like `{project}`, `{package}` or `{wheel}`, that are substituted by cibuildwheel before they are used. If, for some reason, you need to write the literal name of a placeholder, e.g. literally `{project}` in a command that would ordinarily substitute `{project}`, prefix it with a hash character - `#{project}`. This is only necessary in commands where the specific string between the curly brackets would be substituted - otherwise, strings not modified.
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.options-toc {
|
.options-toc {
|
||||||
display: grid;
|
display: grid;
|
||||||
|
|||||||
@@ -0,0 +1,48 @@
|
|||||||
|
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 {} \\;"
|
||||||
|
)
|
||||||
|
|
||||||
|
assert format_safe("{param} {param}", param="1") == "1 1"
|
||||||
|
assert format_safe("# {param} {param}", param="1") == "# 1 1"
|
||||||
|
assert format_safe("#{not_a_param} {param}", param="1") == "#{not_a_param} 1"
|
||||||
|
|
||||||
|
|
||||||
|
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 backslashes in the replacement
|
||||||
|
assert (
|
||||||
|
prepare_command(
|
||||||
|
"command {wheel} \\Users\\Temp\\output_dir", wheel="\\Temporary Files\\cibw"
|
||||||
|
)
|
||||||
|
== "command \\Temporary Files\\cibw \\Users\\Temp\\output_dir"
|
||||||
|
)
|
||||||
|
|
||||||
|
# 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]}"
|
||||||
|
)
|
||||||
Reference in New Issue
Block a user