Skip to main content

nu_command/system/
complete.rs

1use nu_engine::command_prelude::*;
2use nu_protocol::OutDest;
3
4#[derive(Clone)]
5pub struct Complete;
6
7impl Command for Complete {
8    fn name(&self) -> &str {
9        "complete"
10    }
11
12    fn signature(&self) -> Signature {
13        Signature::build("complete")
14            .category(Category::System)
15            .input_output_types(vec![(Type::Any, Type::record())])
16    }
17
18    fn description(&self) -> &str {
19        "Capture the outputs and exit code from an external piped in command in a nushell table."
20    }
21
22    fn extra_description(&self) -> &str {
23        r#"In order to capture stdout, stderr, and exit_code, externally piped in commands need to be wrapped with `do`"#
24    }
25
26    fn run(
27        &self,
28        _engine_state: &EngineState,
29        _stack: &mut Stack,
30        call: &Call,
31        input: PipelineData,
32    ) -> Result<PipelineData, ShellError> {
33        let head = call.head;
34        match input {
35            PipelineData::ByteStream(stream, ..) => {
36                let Ok(mut child) = stream.into_child() else {
37                    return Err(ShellError::GenericError {
38                        error: "Complete only works with external commands".into(),
39                        msg: "complete only works on external commands".into(),
40                        span: Some(call.head),
41                        help: None,
42                        inner: vec![],
43                    });
44                };
45
46                // `complete` reports non-zero status via its `exit_code` field.
47                // Mark child status as handled so global `pipefail` does not raise
48                // `non_zero_exit_code` after this command has already captured it.
49                #[cfg(feature = "os")]
50                child.ignore_error(true);
51
52                let output = child.wait_with_output()?;
53                let exit_code = output.exit_status.code();
54                let mut record = Record::new();
55
56                if let Some(stdout) = output.stdout {
57                    record.push(
58                        "stdout",
59                        match String::from_utf8(stdout) {
60                            Ok(str) => Value::string(str, head),
61                            Err(err) => Value::binary(err.into_bytes(), head),
62                        },
63                    );
64                }
65
66                if let Some(stderr) = output.stderr {
67                    record.push(
68                        "stderr",
69                        match String::from_utf8(stderr) {
70                            Ok(str) => Value::string(str, head),
71                            Err(err) => Value::binary(err.into_bytes(), head),
72                        },
73                    );
74                }
75
76                record.push("exit_code", Value::int(exit_code.into(), head));
77
78                Ok(Value::record(record, call.head).into_pipeline_data())
79            }
80            // bubble up errors from the previous command
81            PipelineData::Value(Value::Error { error, .. }, _) => Err(*error),
82            _ => Err(ShellError::GenericError {
83                error: "Complete only works with external commands".into(),
84                msg: "complete only works on external commands".into(),
85                span: Some(head),
86                help: None,
87                inner: vec![],
88            }),
89        }
90    }
91
92    fn examples(&self) -> Vec<Example<'_>> {
93        vec![Example {
94            description: "Run the external command to completion, capturing stdout, stderr, and exit_code",
95            example: "^external arg1 | complete",
96            result: None,
97        }]
98    }
99
100    fn pipe_redirection(&self) -> (Option<OutDest>, Option<OutDest>) {
101        (Some(OutDest::PipeSeparate), Some(OutDest::PipeSeparate))
102    }
103}