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}