Skip to content

Commit

Permalink
Merge pull request #889 from pypa/fmtpass-alt
Browse files Browse the repository at this point in the history
Alternative string-based approach to format string passthrough
  • Loading branch information
joerick committed Nov 15, 2021
2 parents 53251c7 + c3e38ca commit 6d6793f
Show file tree
Hide file tree
Showing 3 changed files with 96 additions and 2 deletions.
46 changes: 44 additions & 2 deletions cibuildwheel/util.py
Expand Up @@ -48,14 +48,56 @@
)


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:
"""
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]:
Expand Down
4 changes: 4 additions & 0 deletions docs/options.md
Expand Up @@ -1259,6 +1259,10 @@ Platform-specific environment variables are also available:<br/>
芦 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>
.options-toc {
display: grid;
Expand Down
48 changes: 48 additions & 0 deletions unit_test/utils_test.py
@@ -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]}"
)

0 comments on commit 6d6793f

Please sign in to comment.