Skip to main content

nu_command/strings/str_/
escape_regex.rs

1use nu_engine::CallExt;
2use nu_protocol::{
3    Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Type, Value,
4    ast::CellPath,
5    engine::{Call, Command, EngineState},
6    record,
7};
8
9#[derive(Clone)]
10pub struct StrEscapeRegex;
11
12impl Command for StrEscapeRegex {
13    fn name(&self) -> &str {
14        "str escape-regex"
15    }
16
17    fn signature(&self) -> Signature {
18        Signature::build(self.name())
19            .input_output_types(vec![
20                (Type::String, Type::String),
21                (
22                    Type::List(Box::new(Type::String)),
23                    Type::List(Box::new(Type::String)),
24                ),
25                (Type::table(), Type::table()),
26                (Type::record(), Type::record()),
27            ])
28            .allow_variants_without_examples(true)
29            .rest(
30                "rest",
31                SyntaxShape::CellPath,
32                "For a data structure input, escape strings at the given cell paths.",
33            )
34            .category(Category::Strings)
35    }
36
37    fn description(&self) -> &str {
38        "Escapes special characters in the input string with '\\'."
39    }
40
41    fn is_const(&self) -> bool {
42        true
43    }
44
45    fn run(
46        &self,
47        engine_state: &nu_protocol::engine::EngineState,
48        stack: &mut nu_protocol::engine::Stack,
49        call: &nu_protocol::engine::Call,
50        input: PipelineData,
51    ) -> Result<PipelineData, ShellError> {
52        let column_paths: Vec<CellPath> = call.rest(engine_state, stack, 0)?;
53        operate(engine_state, call, input, column_paths)
54    }
55
56    fn run_const(
57        &self,
58        working_set: &nu_protocol::engine::StateWorkingSet,
59        call: &nu_protocol::engine::Call,
60        input: PipelineData,
61    ) -> Result<PipelineData, ShellError> {
62        let column_paths: Vec<CellPath> = call.rest_const(working_set, 0)?;
63        operate(working_set.permanent(), call, input, column_paths)
64    }
65
66    fn examples(&self) -> Vec<Example<'_>> {
67        vec![
68            Example {
69                description: "Escape dots in an IP address.",
70                example: "'192.168.1.1' | str escape-regex",
71                result: Some(Value::test_string("192\\.168\\.1\\.1")),
72            },
73            Example {
74                description: "Escape a list of strings containing special characters.",
75                example: "['(abc)', '1 + 1'] | str escape-regex",
76                result: Some(Value::test_list(vec![
77                    Value::test_string("\\(abc\\)"),
78                    Value::test_string("1 \\+ 1"),
79                ])),
80            },
81            Example {
82                description: "Escape characters in a specific column of a table.",
83                example: "[[pattern]; ['find.me'] ['(group)']] | str escape-regex pattern",
84                result: Some(Value::test_list(vec![
85                    Value::test_record(record! { "pattern" => Value::test_string("find\\.me") }),
86                    Value::test_record(record! { "pattern" => Value::test_string("\\(group\\)") }),
87                ])),
88            },
89        ]
90    }
91}
92
93fn operate(
94    engine_state: &EngineState,
95    call: &Call,
96    input: PipelineData,
97    column_paths: Vec<CellPath>,
98) -> Result<PipelineData, ShellError> {
99    let head = call.head;
100    input.map(
101        move |v| {
102            if column_paths.is_empty() {
103                action(&v, head)
104            } else {
105                let mut ret = v;
106                for path in &column_paths {
107                    let r =
108                        ret.update_cell_path(&path.members, Box::new(move |old| action(old, head)));
109                    if let Err(error) = r {
110                        return Value::error(error, head);
111                    }
112                }
113                ret
114            }
115        },
116        engine_state.signals(),
117    )
118}
119
120fn action(input: &Value, head: Span) -> Value {
121    match input {
122        Value::String { val, .. } => Value::string(fancy_regex::escape(val), head),
123        Value::Error { .. } => input.clone(),
124        _ => Value::error(
125            ShellError::OnlySupportsThisInputType {
126                exp_input_type: "string".into(),
127                wrong_type: input.get_type().to_string(),
128                dst_span: head,
129                src_span: input.span(),
130            },
131            head,
132        ),
133    }
134}
135
136#[cfg(test)]
137mod test {
138    use super::*;
139    #[test]
140    fn test_examples() -> nu_test_support::Result {
141        nu_test_support::test().examples(StrEscapeRegex)
142    }
143}