nu_command/strings/encode_decode/
encode.rs

1use nu_engine::command_prelude::*;
2
3#[derive(Clone)]
4pub struct Encode;
5
6impl Command for Encode {
7    fn name(&self) -> &str {
8        "encode"
9    }
10
11    fn description(&self) -> &str {
12        // Note: "Encode a UTF-8 string into other forms" is semantically incorrect because
13        // Nushell strings, as abstract values, have no user-facing encoding.
14        // (Remember that "encoding" exclusively means "how the characters are
15        // observably represented by bytes").
16        "Encode a string into bytes."
17    }
18
19    fn search_terms(&self) -> Vec<&str> {
20        vec!["text", "encoding", "decoding"]
21    }
22
23    fn signature(&self) -> nu_protocol::Signature {
24        Signature::build("encode")
25            .input_output_types(vec![(Type::String, Type::Binary)])
26            .required("encoding", SyntaxShape::String, "The text encoding to use.")
27            .switch(
28                "ignore-errors",
29                "when a character isn't in the given encoding, replace with a HTML entity (like `&#127880;`)",
30                Some('i'),
31            )
32            .category(Category::Strings)
33    }
34
35    fn extra_description(&self) -> &str {
36        r#"Multiple encodings are supported; here are a few:
37big5, euc-jp, euc-kr, gbk, iso-8859-1, cp1252, latin5
38
39Note that since the Encoding Standard doesn't specify encoders for utf-16le and utf-16be, these are not yet supported.
40More information can be found here: https://docs.rs/encoding_rs/latest/encoding_rs/#utf-16le-utf-16be-and-unicode-encoding-schemes
41
42For a more complete list of encodings, please refer to the encoding_rs
43documentation link at https://docs.rs/encoding_rs/latest/encoding_rs/#statics"#
44    }
45
46    fn examples(&self) -> Vec<Example> {
47        vec![
48            Example {
49                description: "Encode an UTF-8 string into Shift-JIS",
50                example: r#""負けると知って戦うのが、遥かに美しいのだ" | encode shift-jis"#,
51                result: Some(Value::binary(
52                    vec![
53                        0x95, 0x89, 0x82, 0xaf, 0x82, 0xe9, 0x82, 0xc6, 0x92, 0x6d, 0x82, 0xc1,
54                        0x82, 0xc4, 0x90, 0xed, 0x82, 0xa4, 0x82, 0xcc, 0x82, 0xaa, 0x81, 0x41,
55                        0x97, 0x79, 0x82, 0xa9, 0x82, 0xc9, 0x94, 0xfc, 0x82, 0xb5, 0x82, 0xa2,
56                        0x82, 0xcc, 0x82, 0xbe,
57                    ],
58                    Span::test_data(),
59                )),
60            },
61            Example {
62                description: "Replace characters with HTML entities if they can't be encoded",
63                example: r#""🎈" | encode --ignore-errors shift-jis"#,
64                result: Some(Value::binary(
65                    vec![0x26, 0x23, 0x31, 0x32, 0x37, 0x38, 0x38, 0x30, 0x3b],
66                    Span::test_data(),
67                )),
68            },
69        ]
70    }
71
72    fn is_const(&self) -> bool {
73        true
74    }
75
76    fn run(
77        &self,
78        engine_state: &EngineState,
79        stack: &mut Stack,
80        call: &Call,
81        input: PipelineData,
82    ) -> Result<PipelineData, ShellError> {
83        let encoding: Spanned<String> = call.req(engine_state, stack, 0)?;
84        let ignore_errors = call.has_flag(engine_state, stack, "ignore-errors")?;
85        run(call, input, encoding, ignore_errors)
86    }
87
88    fn run_const(
89        &self,
90        working_set: &StateWorkingSet,
91        call: &Call,
92        input: PipelineData,
93    ) -> Result<PipelineData, ShellError> {
94        let encoding: Spanned<String> = call.req_const(working_set, 0)?;
95        let ignore_errors = call.has_flag_const(working_set, "ignore-errors")?;
96        run(call, input, encoding, ignore_errors)
97    }
98}
99
100fn run(
101    call: &Call,
102    input: PipelineData,
103    encoding: Spanned<String>,
104    ignore_errors: bool,
105) -> Result<PipelineData, ShellError> {
106    let head = call.head;
107
108    match input {
109        PipelineData::ByteStream(stream, ..) => {
110            let span = stream.span();
111            let s = stream.into_string()?;
112            super::encoding::encode(head, encoding, &s, span, ignore_errors)
113                .map(|val| val.into_pipeline_data())
114        }
115        PipelineData::Value(v, ..) => {
116            let span = v.span();
117            match v {
118                Value::String { val: s, .. } => {
119                    super::encoding::encode(head, encoding, &s, span, ignore_errors)
120                        .map(|val| val.into_pipeline_data())
121                }
122                Value::Error { error, .. } => Err(*error),
123                _ => Err(ShellError::OnlySupportsThisInputType {
124                    exp_input_type: "string".into(),
125                    wrong_type: v.get_type().to_string(),
126                    dst_span: head,
127                    src_span: v.span(),
128                }),
129            }
130        }
131        // This should be more precise, but due to difficulties in getting spans
132        // from PipelineData::ListStream, this is as it is.
133        _ => Err(ShellError::UnsupportedInput {
134            msg: "non-string input".into(),
135            input: "value originates from here".into(),
136            msg_span: head,
137            input_span: input.span().unwrap_or(head),
138        }),
139    }
140}
141
142#[cfg(test)]
143mod test {
144    use super::*;
145
146    #[test]
147    fn test_examples() {
148        crate::test_examples(Encode)
149    }
150}