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}