Skip to main content

nu_command/system/
complete.rs

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