term_transcript/shell/
standard.rs1use std::{
4 ffi::OsStr,
5 io,
6 path::Path,
7 process::{Child, ChildStdin, Command},
8};
9
10use styled_str::StyledStr;
11
12use super::ShellOptions;
13use crate::{
14 ExitStatus,
15 traits::{ConfigureCommand, Echoing, SpawnShell, SpawnedShell},
16};
17
18#[derive(Debug, Clone, Copy)]
19enum StdShellType {
20 Sh,
22 Bash,
24 PowerShell,
26}
27
28#[derive(Debug)]
30pub struct StdShell {
31 shell_type: StdShellType,
32 command: Command,
33}
34
35impl ConfigureCommand for StdShell {
36 fn current_dir(&mut self, dir: &Path) {
37 self.command.current_dir(dir);
38 }
39
40 fn env(&mut self, name: &str, value: &OsStr) {
41 self.command.env(name, value);
42 }
43}
44
45#[cfg_attr(feature = "tracing", tracing::instrument(level = "debug", ret))]
46fn check_sh_exit_code(response: StyledStr<'_>) -> Option<ExitStatus> {
47 let response = response.text();
48 response.trim().parse().ok().map(ExitStatus)
49}
50
51#[cfg_attr(feature = "tracing", tracing::instrument(level = "debug", ret))]
52fn check_ps_exit_code(response: StyledStr<'_>) -> Option<ExitStatus> {
53 let response = response.text();
54 match response.trim() {
55 "True" => Some(ExitStatus(0)),
56 "False" => Some(ExitStatus(1)),
57 _ => None,
58 }
59}
60
61impl ShellOptions<StdShell> {
62 pub fn sh() -> Self {
64 let this = Self::new(StdShell {
65 shell_type: StdShellType::Sh,
66 command: Command::new("sh"),
67 });
68 this.with_status_check("echo $?", check_sh_exit_code)
69 }
70
71 pub fn bash() -> Self {
73 let this = Self::new(StdShell {
74 shell_type: StdShellType::Bash,
75 command: Command::new("bash"),
76 });
77 this.with_status_check("echo $?", check_sh_exit_code)
78 }
79
80 pub fn pwsh() -> Self {
82 let mut command = Command::new("pwsh");
83 command.arg("-NoLogo").arg("-NoExit");
84
85 let command = StdShell {
86 shell_type: StdShellType::PowerShell,
87 command,
88 };
89 Self::new(command)
90 .with_init_command("function prompt { }")
91 .with_status_check("echo $?", check_ps_exit_code)
92 }
93
94 #[must_use]
110 pub fn with_alias(self, name: &str, path_to_bin: &str) -> Self {
111 let alias_command = match self.command.shell_type {
112 StdShellType::Sh => {
113 format!("alias {name}=\"'{path_to_bin}'\"")
114 }
115 StdShellType::Bash => format!("{name}() {{ '{path_to_bin}' \"$@\"; }}"),
116 StdShellType::PowerShell => format!("function {name} {{ & '{path_to_bin}' @Args }}"),
117 };
118
119 self.with_init_command(alias_command)
120 }
121}
122
123impl SpawnShell for StdShell {
124 type ShellProcess = Echoing<Child>;
125 type Reader = os_pipe::PipeReader;
126 type Writer = ChildStdin;
127
128 #[cfg_attr(feature = "tracing", tracing::instrument(level = "debug", err))]
129 fn spawn_shell(&mut self) -> io::Result<SpawnedShell<Self>> {
130 let SpawnedShell {
131 shell,
132 reader,
133 writer,
134 } = self.command.spawn_shell()?;
135
136 let is_echoing = matches!(self.shell_type, StdShellType::PowerShell);
137 Ok(SpawnedShell {
138 shell: Echoing::new(shell, is_echoing),
139 reader,
140 writer,
141 })
142 }
143}