Skip to main content

nu_command/network/url/
encode.rs

1use nu_cmd_base::input_handler::{CmdArgument, operate};
2use nu_engine::command_prelude::*;
3
4use percent_encoding::{AsciiSet, NON_ALPHANUMERIC, percent_encode, utf8_percent_encode};
5
6struct Arguments {
7    cell_paths: Option<Vec<CellPath>>,
8    ascii_set: &'static AsciiSet,
9}
10
11static ASCII_SET_ALL: &AsciiSet = NON_ALPHANUMERIC;
12static ASCII_SET_NOT_ALL: &AsciiSet = &NON_ALPHANUMERIC.remove(b'/').remove(b':').remove(b'.');
13
14impl CmdArgument for Arguments {
15    fn take_cell_paths(&mut self) -> Option<Vec<CellPath>> {
16        self.cell_paths.take()
17    }
18}
19
20#[derive(Clone)]
21pub struct UrlEncode;
22
23impl Command for UrlEncode {
24    fn name(&self) -> &str {
25        "url encode"
26    }
27
28    fn signature(&self) -> Signature {
29        Signature::build("url encode")
30            .input_output_types(vec![
31                (Type::String, Type::String),
32                (Type::Binary, Type::String),
33                (Type::List(Box::new(Type::one_of([Type::String, Type::Binary]))), Type::List(Box::new(Type::String))),
34                (Type::table(), Type::table()),
35                (Type::record(), Type::record()),
36            ])
37            .allow_variants_without_examples(true)
38            .switch(
39            "all",
40            "Encode all non-alphanumeric chars including `/`, `.`, `:`.",
41            Some('a'))
42            .rest(
43                "rest",
44                SyntaxShape::CellPath,
45                "For a data structure input, check strings at the given cell paths, and replace with result.",
46            )
47            .category(Category::Strings)
48    }
49
50    fn description(&self) -> &str {
51        "Converts a string to a percent encoded web safe string."
52    }
53
54    fn search_terms(&self) -> Vec<&str> {
55        vec!["string", "text", "convert"]
56    }
57
58    fn run(
59        &self,
60        engine_state: &EngineState,
61        stack: &mut Stack,
62        call: &Call,
63        input: PipelineData,
64    ) -> Result<PipelineData, ShellError> {
65        let cell_paths: Vec<CellPath> = call.rest(engine_state, stack, 0)?;
66        let cell_paths = Some(cell_paths).filter(|v| !v.is_empty());
67        let ascii_set = match call.has_flag(engine_state, stack, "all")? {
68            true => ASCII_SET_ALL,
69            false => ASCII_SET_NOT_ALL,
70        };
71        let args = Arguments {
72            cell_paths,
73            ascii_set,
74        };
75        operate(action, args, input, call.head, engine_state.signals())
76    }
77
78    fn examples(&self) -> Vec<Example<'_>> {
79        vec![
80            Example {
81                description: "Encode a URL with escape characters.",
82                example: "'https://example.com/foo bar' | url encode",
83                result: Some(Value::test_string("https://example.com/foo%20bar")),
84            },
85            Example {
86                description: "Encode multiple URLs with escape characters in list.",
87                example: "['https://example.com/foo bar' 'https://example.com/a>b' '中文字/eng/12 34'] | url encode",
88                result: Some(Value::list(
89                    vec![
90                        Value::test_string("https://example.com/foo%20bar"),
91                        Value::test_string("https://example.com/a%3Eb"),
92                        Value::test_string("%E4%B8%AD%E6%96%87%E5%AD%97/eng/12%2034"),
93                    ],
94                    Span::test_data(),
95                )),
96            },
97            Example {
98                description: "Encode all non alphanumeric chars with all flag.",
99                example: "'https://example.com/foo bar' | url encode --all",
100                result: Some(Value::test_string(
101                    "https%3A%2F%2Fexample%2Ecom%2Ffoo%20bar",
102                )),
103            },
104            Example {
105                description: "Encode a iso-8859-1 encoded string.",
106                example: "'£ rates' | encode iso-8859-1 | url encode",
107                result: Some(Value::test_string("%A3%20rates")),
108            },
109        ]
110    }
111}
112
113fn action(input: &Value, args: &Arguments, head: Span) -> Value {
114    match input {
115        Value::String { val, .. } => {
116            let utf8_percent_encode = utf8_percent_encode(val, args.ascii_set);
117            Value::string(utf8_percent_encode.to_string(), head)
118        }
119        Value::Binary { val, .. } => {
120            let utf8_percent_encode = percent_encode(val, args.ascii_set);
121            Value::string(utf8_percent_encode.to_string(), head)
122        }
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
140    #[test]
141    fn test_examples() -> nu_test_support::Result {
142        nu_test_support::test().examples(UrlEncode)
143    }
144}