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