nu_command/network/url/
encode.rs

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