Skip to main content

nu_parser/
deparse.rs

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