Skip to main content

brush_core/shell/
io.rs

1//! I/O support for shell instances.
2
3use std::io::Write;
4
5use crate::{error, extensions, ioutils};
6
7impl<SE: extensions::ShellExtensions> crate::Shell<SE> {
8    /// Returns a value that can be used to write to the shell's currently configured
9    /// standard output stream using `write!` et al.
10    pub fn stdout(&self) -> impl std::io::Write + 'static {
11        self.open_files.try_stdout().cloned().unwrap_or_else(|| {
12            ioutils::FailingReaderWriter::new("standard output not available").into()
13        })
14    }
15
16    /// Returns a value that can be used to write to the shell's currently configured
17    /// standard error stream using `write!` et al.
18    pub fn stderr(&self) -> impl std::io::Write + 'static {
19        self.open_files.try_stderr().cloned().unwrap_or_else(|| {
20            ioutils::FailingReaderWriter::new("standard error not available").into()
21        })
22    }
23
24    /// Outputs `set -x` style trace output for a command. Intentionally does not return
25    /// a result or error to avoid risk that a caller treats an error as fatal. Tracing
26    /// failure should generally always be ignored to avoid interfering with execution
27    /// flows.
28    ///
29    /// # Arguments
30    ///
31    /// * `command` - The command to trace.
32    pub(crate) async fn trace_command<S: AsRef<str>>(
33        &mut self,
34        params: &crate::interp::ExecutionParameters,
35        command: S,
36    ) {
37        // Expand the PS4 prompt variable to get our prefix.
38        let mut prefix = self
39            .as_mut()
40            .expand_prompt_var("PS4", "")
41            .await
42            .unwrap_or_default();
43
44        // Add additional depth-based prefixes using the first character of PS4.
45        let additional_depth = self.call_stack.script_source_depth() + self.depth;
46        if let Some(c) = prefix.chars().next() {
47            for _ in 0..additional_depth {
48                prefix.insert(0, c);
49            }
50        }
51
52        // Resolve which file descriptor to use for tracing. We default to stderr,
53        // but if BASH_XTRACEFD is set and refers to a valid file descriptor, use that instead.
54        let trace_file = if let Some((_, xtracefd_var)) = self.env.get("BASH_XTRACEFD")
55            && let Ok(fd) = xtracefd_var
56                .value()
57                .to_cow_str(self)
58                .parse::<super::ShellFd>()
59            && let Some(file) = self.open_files.try_fd(fd)
60        {
61            Some(file.clone())
62        } else {
63            params.try_stderr(self)
64        };
65
66        // If we have a valid trace file, write to it.
67        if let Some(trace_file) = trace_file
68            && let Ok(mut trace_file) = trace_file.try_clone()
69        {
70            let _ = writeln!(trace_file, "{prefix}{}", command.as_ref());
71        }
72    }
73
74    /// Displays the given error to the user, using the shell's error display mechanisms.
75    ///
76    /// # Arguments
77    ///
78    /// * `file_table` - The open file table to use for any file descriptor references.
79    /// * `err` - The error to display.
80    pub fn display_error(
81        &self,
82        file: &mut impl std::io::Write,
83        err: &error::Error,
84    ) -> Result<(), error::Error> {
85        use crate::extensions::ErrorFormatter as _;
86        let str = self.error_formatter.format_error(err, self);
87        write!(file, "{str}")?;
88
89        Ok(())
90    }
91}