nu_parser/
deparse.rs

1use nu_utils::escape_quote_string;
2
3fn string_should_be_quoted(input: &str) -> bool {
4    input.starts_with('$')
5        || input
6            .chars()
7            .any(|c| c == ' ' || c == '(' || c == '\'' || c == '`' || c == '"' || c == '\\')
8}
9
10// Escape rules:
11// input argument is not a flag, does not start with $ and doesn't contain special characters, it is passed as it is (foo -> foo)
12// input argument is not a flag and either starts with $ or contains special characters, quotes are added, " and \ are escaped (two \words -> "two \\words")
13// input argument is a flag without =, it's passed as it is (--foo -> --foo)
14// input argument is a flag with =, the first two points apply to the value (--foo=bar -> --foo=bar; --foo=bar' -> --foo="bar'")
15//
16// special characters are white space, (, ', `, ",and \
17pub fn escape_for_script_arg(input: &str) -> String {
18    // handle for flag, maybe we need to escape the value.
19    if input.starts_with("--") {
20        if let Some((arg_name, arg_val)) = input.split_once('=') {
21            // only want to escape arg_val.
22            let arg_val = if string_should_be_quoted(arg_val) {
23                escape_quote_string(arg_val)
24            } else {
25                arg_val.into()
26            };
27
28            return format!("{arg_name}={arg_val}");
29        } else {
30            return input.into();
31        }
32    }
33    if string_should_be_quoted(input) {
34        escape_quote_string(input)
35    } else {
36        input.into()
37    }
38}
39
40#[cfg(test)]
41mod test {
42    use super::escape_for_script_arg;
43
44    #[test]
45    fn test_not_extra_quote() {
46        // check for input arg like this:
47        // nu b.nu word 8
48        assert_eq!(escape_for_script_arg("word"), "word".to_string());
49        assert_eq!(escape_for_script_arg("8"), "8".to_string());
50    }
51
52    #[test]
53    fn test_quote_special() {
54        // check for input arg like this:
55        // nu b.nu "two words" $nake "`123"
56        assert_eq!(
57            escape_for_script_arg("two words"),
58            r#""two words""#.to_string()
59        );
60        assert_eq!(escape_for_script_arg("$nake"), r#""$nake""#.to_string());
61        assert_eq!(escape_for_script_arg("`123"), r#""`123""#.to_string());
62    }
63
64    #[test]
65    fn test_arg_with_flag() {
66        // check for input arg like this:
67        // nu b.nu --linux --version=v5.2
68        assert_eq!(escape_for_script_arg("--linux"), "--linux".to_string());
69        assert_eq!(
70            escape_for_script_arg("--version=v5.2"),
71            "--version=v5.2".to_string()
72        );
73
74        // check for input arg like this:
75        // nu b.nu linux --version v5.2
76        assert_eq!(escape_for_script_arg("--version"), "--version".to_string());
77        assert_eq!(escape_for_script_arg("v5.2"), "v5.2".to_string());
78    }
79
80    #[test]
81    fn test_flag_arg_with_values_contains_special() {
82        // check for input arg like this:
83        // nu b.nu test_ver --version='xx yy' --separator="`"
84        assert_eq!(
85            escape_for_script_arg("--version='xx yy'"),
86            r#"--version="'xx yy'""#.to_string()
87        );
88        assert_eq!(
89            escape_for_script_arg("--separator=`"),
90            r#"--separator="`""#.to_string()
91        );
92    }
93
94    #[test]
95    fn test_escape() {
96        // check for input arg like this:
97        // nu b.nu \ --arg='"'
98        assert_eq!(escape_for_script_arg(r"\"), r#""\\""#.to_string());
99        assert_eq!(
100            escape_for_script_arg(r#"--arg=""#),
101            r#"--arg="\"""#.to_string()
102        );
103    }
104}