Skip to main content

shrub_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
9    /// configured 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
17    /// configured 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
25    /// not return a result or error to avoid risk that a caller treats an
26    /// error as fatal. Tracing failure should generally always be ignored
27    /// to avoid interfering with execution 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
53        // stderr, but if BASH_XTRACEFD is set and refers to a valid
54        // file descriptor, use that instead.
55        let trace_file = if let Some((_, xtracefd_var)) = self.env.get("BASH_XTRACEFD")
56            && let Ok(fd) = xtracefd_var
57                .value()
58                .to_cow_str(self)
59                .parse::<super::ShellFd>()
60            && let Some(file) = self.open_files.try_fd(fd)
61        {
62            Some(file.clone())
63        } else {
64            params.try_stderr(self)
65        };
66
67        // If we have a valid trace file, write to it.
68        if let Some(mut trace_file) = trace_file {
69            let _ = writeln!(trace_file, "{prefix}{}", command.as_ref());
70        }
71    }
72
73    /// Displays the given error to the user, using the shell's error display
74    /// mechanisms.
75    ///
76    /// # Arguments
77    ///
78    /// * `file_table` - The open file table to use for any file descriptor
79    ///   references.
80    /// * `err` - The error to display.
81    pub fn display_error(
82        &self,
83        file: &mut impl std::io::Write,
84        err: &error::Error,
85    ) -> Result<(), error::Error> {
86        use crate::extensions::ErrorFormatter as _;
87        let str = self.error_formatter.format_error(err, self);
88        write!(file, "{str}")?;
89
90        Ok(())
91    }
92}