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 ///
82 /// # Errors
83 ///
84 /// Returns [`crate::error::ErrorKind::IoError`] if writing to `file` fails.
85 pub fn display_error(
86 &self,
87 file: &mut impl std::io::Write,
88 err: &error::Error,
89 ) -> Result<(), error::Error> {
90 use crate::extensions::ErrorFormatter as _;
91 let str = self.error_formatter.format_error(err, self);
92 write!(file, "{str}")?;
93
94 Ok(())
95 }
96}