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