1use util::shell::get_system_shell;
2
3use crate::Shell;
4
5pub use util::shell::ShellKind;
6
7/// ShellBuilder is used to turn a user-requested task into a
8/// program that can be executed by the shell.
9pub struct ShellBuilder {
10 /// The shell to run
11 program: String,
12 args: Vec<String>,
13 interactive: bool,
14 /// Whether to redirect stdin to /dev/null for the spawned command as a subshell.
15 redirect_stdin: bool,
16 kind: ShellKind,
17}
18
19impl ShellBuilder {
20 /// Create a new ShellBuilder as configured.
21 pub fn new(remote_system_shell: Option<&str>, shell: &Shell) -> Self {
22 let (program, args) = match remote_system_shell {
23 Some(program) => (program.to_string(), Vec::new()),
24 None => match shell {
25 Shell::System => (get_system_shell(), Vec::new()),
26 Shell::Program(shell) => (shell.clone(), Vec::new()),
27 Shell::WithArguments { program, args, .. } => (program.clone(), args.clone()),
28 },
29 };
30
31 let kind = ShellKind::new(&program);
32 Self {
33 program,
34 args,
35 interactive: true,
36 kind,
37 redirect_stdin: false,
38 }
39 }
40 pub fn non_interactive(mut self) -> Self {
41 self.interactive = false;
42 self
43 }
44
45 /// Returns the label to show in the terminal tab
46 pub fn command_label(&self, command_to_use_in_label: &str) -> String {
47 if command_to_use_in_label.trim().is_empty() {
48 self.program.clone()
49 } else {
50 match self.kind {
51 ShellKind::PowerShell => {
52 format!("{} -C '{}'", self.program, command_to_use_in_label)
53 }
54 ShellKind::Cmd => {
55 format!("{} /C '{}'", self.program, command_to_use_in_label)
56 }
57 ShellKind::Posix
58 | ShellKind::Nushell
59 | ShellKind::Fish
60 | ShellKind::Csh
61 | ShellKind::Tcsh
62 | ShellKind::Rc => {
63 let interactivity = self.interactive.then_some("-i ").unwrap_or_default();
64 format!(
65 "{PROGRAM} {interactivity}-c '{command_to_use_in_label}'",
66 PROGRAM = self.program
67 )
68 }
69 }
70 }
71 }
72
73 pub fn redirect_stdin_to_dev_null(mut self) -> Self {
74 self.redirect_stdin = true;
75 self
76 }
77
78 /// Returns the program and arguments to run this task in a shell.
79 pub fn build(
80 mut self,
81 task_command: Option<String>,
82 task_args: &[String],
83 ) -> (String, Vec<String>) {
84 if let Some(task_command) = task_command {
85 let mut combined_command = task_args.iter().fold(task_command, |mut command, arg| {
86 command.push(' ');
87 command.push_str(&self.kind.to_shell_variable(arg));
88 command
89 });
90 if self.redirect_stdin {
91 match self.kind {
92 ShellKind::Posix
93 | ShellKind::Nushell
94 | ShellKind::Fish
95 | ShellKind::Csh
96 | ShellKind::Tcsh
97 | ShellKind::Rc => {
98 combined_command.insert(0, '(');
99 combined_command.push_str(") </dev/null");
100 }
101 ShellKind::PowerShell => {
102 combined_command.insert_str(0, "$null | & {");
103 combined_command.push_str("}");
104 }
105 ShellKind::Cmd => {
106 combined_command.push_str("< NUL");
107 }
108 }
109 }
110
111 self.args
112 .extend(self.kind.args_for_shell(self.interactive, combined_command));
113 }
114
115 (self.program, self.args)
116 }
117}
118
119#[cfg(test)]
120mod test {
121 use super::*;
122
123 #[test]
124 fn test_nu_shell_variable_substitution() {
125 let shell = Shell::Program("nu".to_owned());
126 let shell_builder = ShellBuilder::new(None, &shell);
127
128 let (program, args) = shell_builder.build(
129 Some("echo".into()),
130 &[
131 "${hello}".to_string(),
132 "$world".to_string(),
133 "nothing".to_string(),
134 "--$something".to_string(),
135 "$".to_string(),
136 "${test".to_string(),
137 ],
138 );
139
140 assert_eq!(program, "nu");
141 assert_eq!(
142 args,
143 vec![
144 "-i",
145 "-c",
146 "echo $env.hello $env.world nothing --($env.something) $ ${test"
147 ]
148 );
149 }
150
151 #[test]
152 fn redirect_stdin_to_dev_null_precedence() {
153 let shell = Shell::Program("nu".to_owned());
154 let shell_builder = ShellBuilder::new(None, &shell);
155
156 let (program, args) = shell_builder
157 .redirect_stdin_to_dev_null()
158 .build(Some("echo".into()), &["nothing".to_string()]);
159
160 assert_eq!(program, "nu");
161 assert_eq!(args, vec!["-i", "-c", "(echo nothing) </dev/null"]);
162 }
163}