1use std::borrow::Cow;
4use std::collections::HashMap;
5use std::path::{Path, PathBuf};
6use std::sync::Arc;
7
8use tokio::sync::Mutex;
9
10#[derive(Debug)]
12pub enum ExternalCommandAction {
13 Passthrough,
15 Replace(String, Vec<String>),
17 Block,
19}
20
21pub type ExternalCommandHook = Arc<dyn Fn(&str, &[String]) -> ExternalCommandAction + Send + Sync>;
24
25use crate::{
26 ExecutionControlFlow, ExecutionResult, builtins, env::ShellEnvironment, error, extensions,
27 functions, interfaces, jobs, keywords, openfiles, options::RuntimeOptions, pathcache,
28 wellknownvars,
29};
30
31pub type KeyBindingsHelper = Arc<Mutex<dyn interfaces::KeyBindings>>;
33
34pub type ShellFd = i32;
36
37mod builder;
44mod builtin_registry;
45mod callstack;
46mod completion;
47mod env;
48mod execution;
49mod expansion;
50mod fs;
51mod funcs;
52mod history;
53mod initscripts;
54mod io;
55mod job_control;
56mod parsing;
57mod prompts;
58mod readline;
59mod state;
60mod traps;
61
62pub use builder::{CreateOptions, ShellBuilder, ShellBuilderState};
63pub use initscripts::{ProfileLoadBehavior, RcLoadBehavior};
64pub use state::ShellState;
65
66#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
75pub struct Shell<SE: extensions::ShellExtensions = extensions::DefaultShellExtensions> {
76 #[cfg_attr(feature = "serde", serde(skip, default = "default_error_formatter"))]
78 error_formatter: SE::ErrorFormatter,
79
80 traps: crate::traps::TrapHandlerConfig,
82
83 open_files: openfiles::OpenFiles,
85
86 working_dir: PathBuf,
88
89 env: ShellEnvironment,
91
92 funcs: functions::FunctionEnv,
94
95 options: RuntimeOptions,
97
98 #[cfg_attr(feature = "serde", serde(skip))]
101 jobs: jobs::JobManager,
102
103 aliases: HashMap<String, String>,
105
106 last_exit_status: u8,
108
109 last_exit_status_change_count: usize,
111
112 last_pipeline_statuses: Vec<u8>,
114
115 depth: usize,
117
118 name: Option<String>,
120
121 args: Vec<String>,
123
124 version: Option<String>,
126
127 product_display_str: Option<String>,
129
130 call_stack: crate::callstack::CallStack,
132
133 directory_stack: Vec<PathBuf>,
135
136 completion_config: crate::completion::Config,
138
139 #[cfg_attr(feature = "serde", serde(skip))]
141 builtins: HashMap<String, builtins::Registration<SE>>,
142
143 program_location_cache: pathcache::PathCache,
145
146 last_stopwatch_time: std::time::SystemTime,
148
149 last_stopwatch_offset: u32,
151
152 #[cfg_attr(feature = "serde", serde(skip))]
154 parser_impl: crate::parser::ParserImpl,
155
156 #[cfg_attr(feature = "serde", serde(skip))]
158 key_bindings: Option<KeyBindingsHelper>,
159
160 history: Option<crate::history::History>,
162
163 #[cfg_attr(feature = "serde", serde(skip))]
165 external_command_hook: Option<ExternalCommandHook>,
166}
167
168impl<SE: extensions::ShellExtensions> Clone for Shell<SE> {
169 fn clone(&self) -> Self {
170 Self {
171 error_formatter: self.error_formatter.clone(),
172 traps: self.traps.clone(),
173 open_files: self.open_files.clone(),
174 working_dir: self.working_dir.clone(),
175 env: self.env.clone(),
176 funcs: self.funcs.clone(),
177 options: self.options.clone(),
178 jobs: jobs::JobManager::new(),
179 aliases: self.aliases.clone(),
180 last_exit_status: self.last_exit_status,
181 last_exit_status_change_count: self.last_exit_status_change_count,
182 last_pipeline_statuses: self.last_pipeline_statuses.clone(),
183 name: self.name.clone(),
184 args: self.args.clone(),
185 version: self.version.clone(),
186 product_display_str: self.product_display_str.clone(),
187 call_stack: self.call_stack.clone(),
188 directory_stack: self.directory_stack.clone(),
189 completion_config: self.completion_config.clone(),
190 builtins: self.builtins.clone(),
191 program_location_cache: self.program_location_cache.clone(),
192 last_stopwatch_time: self.last_stopwatch_time,
193 last_stopwatch_offset: self.last_stopwatch_offset,
194 parser_impl: self.parser_impl,
195 key_bindings: self.key_bindings.clone(),
196 history: self.history.clone(),
197 external_command_hook: self.external_command_hook.clone(),
198 depth: self.depth + 1,
199 }
200 }
201}
202
203impl<SE: extensions::ShellExtensions> AsRef<Self> for Shell<SE> {
204 fn as_ref(&self) -> &Self {
205 self
206 }
207}
208
209impl<SE: extensions::ShellExtensions> AsMut<Self> for Shell<SE> {
210 fn as_mut(&mut self) -> &mut Self {
211 self
212 }
213}
214
215impl<SE: extensions::ShellExtensions> Shell<SE> {
216 pub(crate) fn new(options: CreateOptions<SE>) -> Result<Self, error::Error> {
223 let runtime_options = RuntimeOptions::defaults_from(&options);
225
226 let mut shell = Self {
228 error_formatter: options.error_formatter,
229 open_files: openfiles::OpenFiles::new(),
230 options: runtime_options,
231 name: options.shell_name,
232 args: options.shell_args.unwrap_or_default(),
233 version: options.shell_version,
234 product_display_str: options.shell_product_display_str,
235 working_dir: options.working_dir.map_or_else(std::env::current_dir, Ok)?,
236 builtins: options.builtins,
237 parser_impl: options.parser,
238 key_bindings: options.key_bindings,
239 external_command_hook: options.external_command_hook,
240 ..Self::default()
241 };
242
243 shell.open_files.update_from(options.fds.into_iter());
245
246 shell.options.extended_globbing = true;
249
250 if !options.do_not_inherit_env {
252 wellknownvars::inherit_env_vars(&mut shell)?;
253 }
254
255 if !options.skip_well_known_vars {
257 wellknownvars::init_well_known_vars(&mut shell)?;
258 }
259
260 for (var_name, var_value) in options.vars {
262 shell.env.set_global(var_name, var_value)?;
263 }
264
265 if shell.options.enable_command_history {
267 shell.history = shell
268 .load_history()
269 .unwrap_or_default()
270 .or_else(|| Some(crate::history::History::default()));
271 }
272
273 Ok(shell)
274 }
275}
276
277impl<SE: extensions::ShellExtensions> Shell<SE> {
278 pub fn increment_interactive_line_offset(&mut self, delta: usize) {
285 self.call_stack.increment_current_line_offset(delta);
286 }
287
288 pub fn set_current_cmd(&mut self, cmd: &impl brush_parser::ast::Node) {
290 self.call_stack
291 .set_current_pos(cmd.location().map(|span| span.start));
292 }
293
294 pub const fn apply_errexit_if_enabled(&self, result: &mut ExecutionResult) {
301 if self.options.exit_on_nonzero_command_exit
302 && !result.is_success()
303 && result.is_normal_flow()
304 {
305 result.next_control_flow = ExecutionControlFlow::ExitShell;
306 }
307 }
308
309 pub(crate) fn get_keywords(&self) -> Vec<&str> {
311 if self.options.sh_mode {
312 keywords::SH_MODE_KEYWORDS.iter().copied().collect()
313 } else {
314 keywords::KEYWORDS.iter().copied().collect()
315 }
316 }
317
318 pub fn is_keyword(&self, s: &str) -> bool {
324 if self.options.sh_mode {
325 keywords::SH_MODE_KEYWORDS.contains(s)
326 } else {
327 keywords::KEYWORDS.contains(s)
328 }
329 }
330
331 pub(crate) const fn last_exit_status_change_count(&self) -> usize {
332 self.last_exit_status_change_count
333 }
334
335 pub fn external_command_hook(&self) -> Option<&ExternalCommandHook> {
337 self.external_command_hook.as_ref()
338 }
339}
340
341#[inherent::inherent]
342impl<SE: extensions::ShellExtensions> ShellState for Shell<SE> {
343 pub fn is_subshell(&self) -> bool {
345 self.depth > 0
346 }
347
348 pub fn last_stopwatch_time(&self) -> std::time::SystemTime {
350 self.last_stopwatch_time
351 }
352
353 pub fn last_stopwatch_offset(&self) -> u32 {
355 self.last_stopwatch_offset
356 }
357
358 pub fn env(&self) -> &ShellEnvironment {
360 &self.env
361 }
362
363 pub fn env_mut(&mut self) -> &mut ShellEnvironment {
365 &mut self.env
366 }
367
368 pub fn options(&self) -> &RuntimeOptions {
370 &self.options
371 }
372
373 pub fn options_mut(&mut self) -> &mut RuntimeOptions {
375 &mut self.options
376 }
377
378 pub fn aliases(&self) -> &HashMap<String, String> {
380 &self.aliases
381 }
382
383 pub fn aliases_mut(&mut self) -> &mut HashMap<String, String> {
385 &mut self.aliases
386 }
387
388 pub fn jobs(&self) -> &jobs::JobManager {
390 &self.jobs
391 }
392
393 pub fn jobs_mut(&mut self) -> &mut jobs::JobManager {
395 &mut self.jobs
396 }
397
398 pub fn traps(&self) -> &crate::traps::TrapHandlerConfig {
400 &self.traps
401 }
402
403 pub fn traps_mut(&mut self) -> &mut crate::traps::TrapHandlerConfig {
405 &mut self.traps
406 }
407
408 pub fn directory_stack(&self) -> &[PathBuf] {
410 &self.directory_stack
411 }
412
413 pub fn directory_stack_mut(&mut self) -> &mut Vec<PathBuf> {
415 &mut self.directory_stack
416 }
417
418 pub fn last_pipeline_statuses(&self) -> &[u8] {
420 &self.last_pipeline_statuses
421 }
422
423 pub fn last_pipeline_statuses_mut(&mut self) -> &mut Vec<u8> {
425 &mut self.last_pipeline_statuses
426 }
427
428 pub fn program_location_cache(&self) -> &pathcache::PathCache {
430 &self.program_location_cache
431 }
432
433 pub fn program_location_cache_mut(&mut self) -> &mut pathcache::PathCache {
435 &mut self.program_location_cache
436 }
437
438 pub fn completion_config(&self) -> &crate::completion::Config {
440 &self.completion_config
441 }
442
443 pub fn completion_config_mut(&mut self) -> &mut crate::completion::Config {
445 &mut self.completion_config
446 }
447
448 pub fn open_files(&self) -> &openfiles::OpenFiles {
450 &self.open_files
451 }
452
453 pub fn open_files_mut(&mut self) -> &mut openfiles::OpenFiles {
455 &mut self.open_files
456 }
457
458 pub fn current_shell_name(&self) -> Option<Cow<'_, str>> {
461 for frame in self.call_stack.iter() {
462 if frame.frame_type.is_run_script() {
464 return Some(frame.frame_type.name());
465 }
466 }
467
468 self.name.as_deref().map(|name| name.into())
469 }
470
471 pub fn depth(&self) -> usize {
473 self.depth
474 }
475
476 pub fn call_stack(&self) -> &crate::callstack::CallStack {
478 &self.call_stack
479 }
480
481 pub fn history(&self) -> Option<&crate::history::History> {
483 self.history.as_ref()
484 }
485
486 pub fn history_mut(&mut self) -> Option<&mut crate::history::History> {
488 self.history.as_mut()
489 }
490
491 pub fn version(&self) -> Option<&str> {
493 self.version.as_deref()
494 }
495
496 pub fn last_exit_status(&self) -> u8 {
498 self.last_exit_status
499 }
500
501 pub fn set_last_exit_status(&mut self, status: u8) {
503 self.last_exit_status = status;
504 self.last_exit_status_change_count += 1;
505 }
506
507 pub fn key_bindings(&self) -> Option<&KeyBindingsHelper> {
509 self.key_bindings.as_ref()
510 }
511
512 pub fn set_key_bindings(&mut self, key_bindings: Option<KeyBindingsHelper>) {
514 self.key_bindings = key_bindings;
515 }
516
517 pub fn working_dir(&self) -> &Path {
519 &self.working_dir
520 }
521
522 pub(crate) fn working_dir_mut(&mut self) -> &mut PathBuf {
525 &mut self.working_dir
526 }
527
528 pub fn product_display_str(&self) -> Option<&str> {
530 self.product_display_str.as_deref()
531 }
532}
533
534#[cfg(feature = "serde")]
535fn default_error_formatter<EF: extensions::ErrorFormatter>() -> EF {
536 EF::default()
537}