Skip to main content

uv_shell/
shlex.rs

1use crate::{Shell, Simplified};
2use std::path::Path;
3
4/// Quote a path, if necessary, for safe use in a POSIX-compatible shell command.
5pub fn shlex_posix(executable: impl AsRef<Path>) -> String {
6    // Convert to a display path.
7    let executable = executable.as_ref().portable_display().to_string();
8
9    // Like Python's `shlex.quote`:
10    // > Use single quotes, and put single quotes into double quotes
11    // > The string $'b is then quoted as '$'"'"'b'
12    if executable.contains(' ') {
13        format!("'{}'", escape_posix_for_single_quotes(&executable))
14    } else {
15        executable
16    }
17}
18
19/// Escape a string for being used in single quotes in a POSIX-compatible shell command.
20///
21/// We want our scripts to support any POSIX shell. There are two kinds of quotes in POSIX:
22/// Single and double quotes. In bash, single quotes must not contain another single
23/// quote, you can't even escape it (<https://linux.die.net/man/1/bash> under "QUOTING").
24/// Double quotes have escaping rules that differ from shell to shell, which we can't handle.
25/// Bash has `$'\''`, but that's not universal enough.
26///
27/// As a solution, use implicit string concatenations, by putting the single quote into double
28/// quotes.
29pub fn escape_posix_for_single_quotes(string: &str) -> String {
30    string.replace('\'', r#"'"'"'"#)
31}
32
33/// Quote a path, if necessary, for safe use in `PowerShell` and `cmd`.
34pub fn shlex_windows(executable: impl AsRef<Path>, shell: Shell) -> String {
35    // Convert to a display path.
36    let executable = executable.as_ref().user_display().to_string();
37
38    // Wrap the executable in quotes (and a `&` invocation on PowerShell), if it contains spaces.
39    if executable.contains(' ') {
40        if shell == Shell::Powershell {
41            // For PowerShell, wrap in a `&` invocation.
42            format!("& \"{executable}\"")
43        } else {
44            // Otherwise, assume `cmd`, which doesn't need the `&`.
45            format!("\"{executable}\"")
46        }
47    } else {
48        executable
49    }
50}