1use std::borrow::Cow;
2use std::collections::HashMap;
3use std::io::{Read, Write};
4use std::path::{Path, PathBuf};
5use std::sync::Arc;
6
7use normalize_path::NormalizePath;
8use tokio::sync::Mutex;
9
10use crate::arithmetic::Evaluatable;
11use crate::env::{EnvironmentLookup, EnvironmentScope, ShellEnvironment};
12use crate::interp::{self, Execute, ExecutionParameters};
13use crate::options::RuntimeOptions;
14use crate::results::ExecutionSpawnResult;
15use crate::sys::fs::PathExt;
16use crate::variables::{self, ShellVariable};
17use crate::{
18 ExecutionControlFlow, ExecutionExitCode, ExecutionResult, ProcessGroupPolicy, history,
19 interfaces, pathcache, pathsearch, scripts, trace_categories, wellknownvars,
20};
21use crate::{
22 builtins, commands, completion, env, error, expansion, functions, jobs, keywords, openfiles,
23 prompt, sys::users, traps,
24};
25
26pub type KeyBindingsHelper = Arc<Mutex<dyn interfaces::KeyBindings>>;
28
29pub type ErrorFormatterHelper = Arc<Mutex<dyn error::ErrorFormatter>>;
31
32pub type ShellFd = i32;
34
35pub struct Shell {
37 pub traps: traps::TrapHandlerConfig,
39
40 open_files: openfiles::OpenFiles,
42
43 working_dir: PathBuf,
45
46 pub env: ShellEnvironment,
48
49 funcs: functions::FunctionEnv,
51
52 pub options: RuntimeOptions,
54
55 pub jobs: jobs::JobManager,
57
58 pub aliases: HashMap<String, String>,
60
61 last_exit_status: u8,
63
64 pub last_pipeline_statuses: Vec<u8>,
66
67 depth: usize,
69
70 pub shell_name: Option<String>,
72
73 version: Option<String>,
75
76 pub positional_parameters: Vec<String>,
78
79 product_display_str: Option<String>,
81
82 script_call_stack: scripts::CallStack,
84
85 function_call_stack: functions::CallStack,
87
88 pub directory_stack: Vec<PathBuf>,
90
91 current_line_number: u32,
93
94 pub completion_config: completion::Config,
96
97 builtins: HashMap<String, builtins::Registration>,
99
100 pub program_location_cache: pathcache::PathCache,
102
103 last_stopwatch_time: std::time::SystemTime,
105
106 last_stopwatch_offset: u32,
108
109 key_bindings: Option<KeyBindingsHelper>,
111
112 history: Option<history::History>,
114
115 error_formatter: ErrorFormatterHelper,
117}
118
119impl Clone for Shell {
120 fn clone(&self) -> Self {
121 Self {
122 traps: self.traps.clone(),
123 open_files: self.open_files.clone(),
124 working_dir: self.working_dir.clone(),
125 env: self.env.clone(),
126 funcs: self.funcs.clone(),
127 options: self.options.clone(),
128 jobs: jobs::JobManager::new(),
129 aliases: self.aliases.clone(),
130 last_exit_status: self.last_exit_status,
131 last_pipeline_statuses: self.last_pipeline_statuses.clone(),
132 positional_parameters: self.positional_parameters.clone(),
133 shell_name: self.shell_name.clone(),
134 version: self.version.clone(),
135 product_display_str: self.product_display_str.clone(),
136 function_call_stack: self.function_call_stack.clone(),
137 script_call_stack: self.script_call_stack.clone(),
138 directory_stack: self.directory_stack.clone(),
139 current_line_number: self.current_line_number,
140 completion_config: self.completion_config.clone(),
141 builtins: self.builtins.clone(),
142 program_location_cache: self.program_location_cache.clone(),
143 last_stopwatch_time: self.last_stopwatch_time,
144 last_stopwatch_offset: self.last_stopwatch_offset,
145 key_bindings: self.key_bindings.clone(),
146 history: self.history.clone(),
147 error_formatter: self.error_formatter.clone(),
148 depth: self.depth + 1,
149 }
150 }
151}
152
153impl AsRef<Self> for Shell {
154 fn as_ref(&self) -> &Self {
155 self
156 }
157}
158
159impl AsMut<Self> for Shell {
160 fn as_mut(&mut self) -> &mut Self {
161 self
162 }
163}
164
165pub use shell_builder::State as ShellBuilderState;
166
167impl<S: shell_builder::IsComplete> ShellBuilder<S> {
168 pub async fn build(self) -> Result<Shell, error::Error> {
170 let options = self.build_settings();
171
172 Shell::new(options).await
173 }
174}
175
176impl<S: shell_builder::State> ShellBuilder<S> {
177 pub fn disable_option(mut self, option: impl Into<String>) -> Self {
179 self.disabled_options.push(option.into());
180 self
181 }
182
183 pub fn enable_option(mut self, option: impl Into<String>) -> Self {
185 self.enabled_options.push(option.into());
186 self
187 }
188
189 pub fn disable_options(mut self, options: impl IntoIterator<Item: Into<String>>) -> Self {
191 self.disabled_options
192 .extend(options.into_iter().map(Into::into));
193 self
194 }
195
196 pub fn enable_options(mut self, options: impl IntoIterator<Item: Into<String>>) -> Self {
198 self.enabled_options
199 .extend(options.into_iter().map(Into::into));
200 self
201 }
202
203 pub fn disable_shopt_option(mut self, option: impl Into<String>) -> Self {
205 self.disabled_shopt_options.push(option.into());
206 self
207 }
208
209 pub fn enable_shopt_option(mut self, option: impl Into<String>) -> Self {
211 self.enabled_shopt_options.push(option.into());
212 self
213 }
214
215 pub fn disable_shopt_options(mut self, options: impl IntoIterator<Item: Into<String>>) -> Self {
217 self.disabled_shopt_options
218 .extend(options.into_iter().map(Into::into));
219 self
220 }
221
222 pub fn enable_shopt_options(mut self, options: impl IntoIterator<Item: Into<String>>) -> Self {
224 self.enabled_shopt_options
225 .extend(options.into_iter().map(Into::into));
226 self
227 }
228
229 pub fn builtin(mut self, name: impl Into<String>, reg: builtins::Registration) -> Self {
231 self.builtins.insert(name.into(), reg);
232 self
233 }
234
235 pub fn builtins(
237 mut self,
238 builtins: impl IntoIterator<Item = (String, builtins::Registration)>,
239 ) -> Self {
240 self.builtins.extend(builtins);
241 self
242 }
243}
244
245#[derive(Default, bon::Builder)]
247#[builder(
248 builder_type(
249 name = ShellBuilder,
250 doc {
251 }),
253 finish_fn(
254 name = build_settings,
255 vis = "pub(self)",
256 ),
257 start_fn(
258 vis = "pub(self)"
259 )
260)]
261pub struct CreateOptions {
262 #[builder(field)]
264 pub disabled_options: Vec<String>,
265 #[builder(field)]
267 pub enabled_options: Vec<String>,
268 #[builder(field)]
270 pub disabled_shopt_options: Vec<String>,
271 #[builder(field)]
273 pub enabled_shopt_options: Vec<String>,
274 #[builder(field)]
276 pub builtins: HashMap<String, builtins::Registration>,
277 #[builder(default)]
279 pub disallow_overwriting_regular_files_via_output_redirection: bool,
280 #[builder(default)]
282 pub do_not_execute_commands: bool,
283 #[builder(default)]
285 pub exit_after_one_command: bool,
286 #[builder(default)]
288 pub interactive: bool,
289 #[builder(default)]
291 pub login: bool,
292 #[builder(default)]
294 pub no_editing: bool,
295 #[builder(default)]
297 pub no_profile: bool,
298 #[builder(default)]
300 pub no_rc: bool,
301 pub rc_file: Option<PathBuf>,
303 #[builder(default)]
305 pub do_not_inherit_env: bool,
306 pub fds: Option<HashMap<ShellFd, openfiles::OpenFile>>,
308 #[builder(default)]
310 pub posix: bool,
311 #[builder(default)]
313 pub print_commands_and_arguments: bool,
314 #[builder(default)]
316 pub read_commands_from_stdin: bool,
317 pub shell_name: Option<String>,
319 pub shell_product_display_str: Option<String>,
321 #[builder(default)]
323 pub sh_mode: bool,
324 #[builder(default)]
326 pub verbose: bool,
327 pub max_function_call_depth: Option<usize>,
329 pub key_bindings: Option<KeyBindingsHelper>,
331 pub error_formatter: Option<ErrorFormatterHelper>,
333 pub shell_version: Option<String>,
335}
336
337impl Shell {
338 pub fn builder() -> ShellBuilder<shell_builder::Empty> {
340 CreateOptions::builder()
341 }
342
343 pub async fn new(options: CreateOptions) -> Result<Self, error::Error> {
349 let mut shell = Self {
351 traps: traps::TrapHandlerConfig::default(),
352 open_files: openfiles::OpenFiles::new(),
353 working_dir: std::env::current_dir()?,
355 env: env::ShellEnvironment::new(),
356 funcs: functions::FunctionEnv::default(),
357 options: RuntimeOptions::defaults_from(&options),
358 jobs: jobs::JobManager::new(),
359 aliases: HashMap::default(),
360 last_exit_status: 0,
361 last_pipeline_statuses: vec![0],
362 positional_parameters: vec![],
363 shell_name: options.shell_name,
364 version: options.shell_version,
365 product_display_str: options.shell_product_display_str,
366 function_call_stack: functions::CallStack::new(),
367 script_call_stack: scripts::CallStack::new(),
368 directory_stack: vec![],
369 current_line_number: 0,
370 completion_config: completion::Config::default(),
371 builtins: options.builtins,
372 program_location_cache: pathcache::PathCache::default(),
373 last_stopwatch_time: std::time::SystemTime::now(),
374 last_stopwatch_offset: 0,
375 key_bindings: options.key_bindings,
376 history: None,
377 error_formatter: options
378 .error_formatter
379 .unwrap_or_else(|| Arc::new(Mutex::new(error::DefaultErrorFormatter::new()))),
380 depth: 0,
381 };
382
383 if let Some(fds) = options.fds {
385 shell.open_files.update_from(fds.into_iter());
386 }
387
388 shell.options.extended_globbing = true;
391
392 wellknownvars::initialize_vars(&mut shell, options.do_not_inherit_env)?;
394
395 if shell.options.enable_command_history {
397 if let Some(history_path) = shell.history_file_path() {
398 let mut options = std::fs::File::options();
399 options.read(true);
400
401 if let Ok(history_file) =
402 shell.open_file(&options, history_path, &shell.default_exec_params())
403 {
404 shell.history = Some(history::History::import(history_file)?);
405 }
406 }
407
408 if shell.history.is_none() {
409 shell.history = Some(history::History::default());
410 }
411 }
412
413 shell
415 .load_config(
416 options.no_profile,
417 options.no_rc,
418 options.rc_file.as_deref(),
419 )
420 .await?;
421
422 Ok(shell)
423 }
424
425 pub const fn current_line_number(&self) -> u32 {
427 self.current_line_number
428 }
429
430 pub const fn version(&self) -> &Option<String> {
432 &self.version
433 }
434
435 pub const fn last_result(&self) -> u8 {
437 self.last_exit_status
438 }
439
440 pub const fn function_call_stack(&self) -> &functions::CallStack {
442 &self.function_call_stack
443 }
444
445 pub const fn script_call_stack(&self) -> &scripts::CallStack {
447 &self.script_call_stack
448 }
449
450 pub const fn last_exit_status_mut(&mut self) -> &mut u8 {
452 &mut self.last_exit_status
453 }
454
455 pub const fn key_bindings(&self) -> &Option<KeyBindingsHelper> {
457 &self.key_bindings
458 }
459
460 pub const fn builtins(&self) -> &HashMap<String, builtins::Registration> {
462 &self.builtins
463 }
464
465 pub fn working_dir(&self) -> &Path {
467 &self.working_dir
468 }
469
470 pub(crate) const fn working_dir_mut(&mut self) -> &mut PathBuf {
473 &mut self.working_dir
474 }
475
476 pub const fn product_display_str(&self) -> &Option<String> {
478 &self.product_display_str
479 }
480
481 pub const fn funcs(&self) -> &functions::FunctionEnv {
483 &self.funcs
484 }
485
486 pub fn undefine_func(&mut self, name: &str) -> bool {
493 self.funcs.remove(name).is_some()
494 }
495
496 pub fn define_func(
504 &mut self,
505 name: impl Into<String>,
506 definition: brush_parser::ast::FunctionDefinition,
507 ) {
508 self.funcs.update(name.into(), definition.into());
509 }
510
511 pub fn func_mut(&mut self, name: &str) -> Option<&mut functions::Registration> {
518 self.funcs.get_mut(name)
519 }
520
521 pub fn define_func_from_str(
529 &mut self,
530 name: impl Into<String>,
531 body_text: &str,
532 ) -> Result<(), error::Error> {
533 let name = name.into();
534
535 let mut parser = create_parser(body_text.as_bytes(), &self.parser_options());
536 let func_body = parser.parse_function_parens_and_body().map_err(|e| {
537 error::Error::from(error::ErrorKind::FunctionParseError(name.clone(), e))
538 })?;
539
540 let def = brush_parser::ast::FunctionDefinition {
541 fname: name.clone().into(),
542 body: func_body,
543 source: String::new(),
544 };
545
546 self.define_func(name, def);
547
548 Ok(())
549 }
550
551 pub const fn last_stopwatch_time(&self) -> std::time::SystemTime {
553 self.last_stopwatch_time
554 }
555
556 pub const fn last_stopwatch_offset(&self) -> u32 {
558 self.last_stopwatch_offset
559 }
560
561 async fn load_config(
562 &mut self,
563 skip_profile: bool,
564 skip_rc: bool,
565 rc_file: Option<&Path>,
566 ) -> Result<(), error::Error> {
567 let mut params = self.default_exec_params();
568 params.process_group_policy = interp::ProcessGroupPolicy::SameProcessGroup;
569
570 if self.options.login_shell {
571 if skip_profile {
573 return Ok(());
574 }
575
576 self.source_if_exists(Path::new("/etc/profile"), ¶ms)
585 .await?;
586 if let Some(home_path) = self.home_dir() {
587 if self.options.sh_mode {
588 self.source_if_exists(home_path.join(".profile").as_path(), ¶ms)
589 .await?;
590 } else {
591 if !self
592 .source_if_exists(home_path.join(".bash_profile").as_path(), ¶ms)
593 .await?
594 {
595 if !self
596 .source_if_exists(home_path.join(".bash_login").as_path(), ¶ms)
597 .await?
598 {
599 self.source_if_exists(home_path.join(".profile").as_path(), ¶ms)
600 .await?;
601 }
602 }
603 }
604 }
605 } else {
606 if self.options.interactive {
607 if skip_rc || self.options.sh_mode {
609 return Ok(());
610 }
611
612 if let Some(rc_file) = rc_file {
614 self.source_if_exists(rc_file, ¶ms).await?;
616 } else {
617 self.source_if_exists(Path::new("/etc/bash.bashrc"), ¶ms)
624 .await?;
625 if let Some(home_path) = self.home_dir() {
626 self.source_if_exists(home_path.join(".bashrc").as_path(), ¶ms)
627 .await?;
628 self.source_if_exists(home_path.join(".brushrc").as_path(), ¶ms)
629 .await?;
630 }
631 }
632 } else {
633 let env_var_name = if self.options.sh_mode {
634 "ENV"
635 } else {
636 "BASH_ENV"
637 };
638
639 if self.env.is_set(env_var_name) {
640 return error::unimp(
644 "load config from $ENV/BASH_ENV for non-interactive, non-login shell",
645 );
646 }
647 }
648 }
649
650 Ok(())
651 }
652
653 async fn source_if_exists(
654 &mut self,
655 path: impl AsRef<Path>,
656 params: &ExecutionParameters,
657 ) -> Result<bool, error::Error> {
658 let path = path.as_ref();
659 if path.exists() {
660 self.source_script(path, std::iter::empty::<String>(), params)
661 .await?;
662 Ok(true)
663 } else {
664 tracing::debug!("skipping non-existent file: {}", path.display());
665 Ok(false)
666 }
667 }
668
669 pub async fn source_script<S: AsRef<str>, P: AsRef<Path>, I: Iterator<Item = S>>(
677 &mut self,
678 path: P,
679 args: I,
680 params: &ExecutionParameters,
681 ) -> Result<ExecutionResult, error::Error> {
682 self.parse_and_execute_script_file(path.as_ref(), args, params, scripts::CallType::Sourced)
683 .await
684 }
685
686 async fn parse_and_execute_script_file<S: AsRef<str>, P: AsRef<Path>, I: Iterator<Item = S>>(
695 &mut self,
696 path: P,
697 args: I,
698 params: &ExecutionParameters,
699 call_type: scripts::CallType,
700 ) -> Result<ExecutionResult, error::Error> {
701 let path = path.as_ref();
702 tracing::debug!("sourcing: {}", path.display());
703
704 let mut options = std::fs::File::options();
705 options.read(true);
706
707 let opened_file: openfiles::OpenFile = self
708 .open_file(&options, path, params)
709 .map_err(|e| error::ErrorKind::FailedSourcingFile(path.to_owned(), e))?;
710
711 if opened_file.is_dir() {
712 return Err(error::ErrorKind::FailedSourcingFile(
713 path.to_owned(),
714 std::io::Error::from(std::io::ErrorKind::IsADirectory),
715 )
716 .into());
717 }
718
719 let source_info = brush_parser::SourceInfo {
720 source: path.to_string_lossy().to_string(),
721 };
722
723 let mut result = self
724 .source_file(opened_file, &source_info, args, params, call_type)
725 .await?;
726
727 if matches!(
731 result.next_control_flow,
732 ExecutionControlFlow::ReturnFromFunctionOrScript
733 ) {
734 result.next_control_flow = ExecutionControlFlow::Normal;
735 }
736
737 Ok(result)
738 }
739
740 async fn source_file<F: Read, S: AsRef<str>, I: Iterator<Item = S>>(
750 &mut self,
751 file: F,
752 source_info: &brush_parser::SourceInfo,
753 args: I,
754 params: &ExecutionParameters,
755 call_type: scripts::CallType,
756 ) -> Result<ExecutionResult, error::Error> {
757 let mut reader = std::io::BufReader::new(file);
758 let mut parser =
759 brush_parser::Parser::new(&mut reader, &self.parser_options(), source_info);
760
761 tracing::debug!(target: trace_categories::PARSE, "Parsing sourced file: {}", source_info.source);
762 let parse_result = parser.parse_program();
763
764 let mut other_positional_parameters: Vec<_> = args.map(|s| s.as_ref().to_owned()).collect();
765 let mut other_shell_name = Some(source_info.source.clone());
766 let positional_params_given = !other_positional_parameters.is_empty();
767
768 std::mem::swap(&mut self.shell_name, &mut other_shell_name);
770
771 if positional_params_given {
774 std::mem::swap(
775 &mut self.positional_parameters,
776 &mut other_positional_parameters,
777 );
778 }
779
780 self.script_call_stack
781 .push(call_type, source_info.source.clone());
782
783 let result = self
784 .run_parsed_result(parse_result, source_info, params)
785 .await;
786
787 self.script_call_stack.pop();
788
789 std::mem::swap(&mut self.shell_name, &mut other_shell_name);
791
792 if positional_params_given {
794 std::mem::swap(
795 &mut self.positional_parameters,
796 &mut other_positional_parameters,
797 );
798 }
799
800 result
801 }
802
803 pub async fn invoke_function<N: AsRef<str>, I: IntoIterator<Item = A>, A: AsRef<str>>(
811 &mut self,
812 name: N,
813 args: I,
814 params: &ExecutionParameters,
815 ) -> Result<u8, error::Error> {
816 let name = name.as_ref();
817 let command_name = String::from(name);
818
819 let func_registration = self
820 .funcs
821 .get(name)
822 .ok_or_else(|| error::ErrorKind::FunctionNotFound(name.to_owned()))?;
823
824 let func = func_registration.definition.clone();
825
826 let context = commands::ExecutionContext {
827 shell: self,
828 command_name,
829 params: params.clone(),
830 };
831
832 let command_args = args
833 .into_iter()
834 .map(|s| commands::CommandArg::String(String::from(s.as_ref())))
835 .collect::<Vec<_>>();
836
837 match commands::invoke_shell_function(func, context, &command_args).await? {
838 ExecutionSpawnResult::StartedProcess(_) => {
839 error::unimp("child spawned from function invocation")
840 }
841 ExecutionSpawnResult::Completed(result) => Ok(result.exit_code.into()),
842 }
843 }
844
845 pub async fn run_string<S: Into<String>>(
852 &mut self,
853 command: S,
854 params: &ExecutionParameters,
855 ) -> Result<ExecutionResult, error::Error> {
856 self.current_line_number += 1;
860
861 let parse_result = self.parse_string(command.into());
862 let source_info = brush_parser::SourceInfo {
863 source: String::from("main"),
864 };
865 self.run_parsed_result(parse_result, &source_info, params)
866 .await
867 }
868
869 pub fn parse<R: Read>(
872 &self,
873 reader: R,
874 ) -> Result<brush_parser::ast::Program, brush_parser::ParseError> {
875 let mut parser = create_parser(reader, &self.parser_options());
876
877 tracing::debug!(target: trace_categories::PARSE, "Parsing reader as program...");
878 parser.parse_program()
879 }
880
881 pub fn parse_string<S: Into<String>>(
888 &self,
889 s: S,
890 ) -> Result<brush_parser::ast::Program, brush_parser::ParseError> {
891 parse_string_impl(s.into(), self.parser_options())
892 }
893
894 pub async fn basic_expand_string<S: AsRef<str>>(
900 &mut self,
901 params: &ExecutionParameters,
902 s: S,
903 ) -> Result<String, error::Error> {
904 let result = expansion::basic_expand_str(self, params, s.as_ref()).await?;
905 Ok(result)
906 }
907
908 pub async fn full_expand_and_split_string<S: AsRef<str>>(
915 &mut self,
916 params: &ExecutionParameters,
917 s: S,
918 ) -> Result<Vec<String>, error::Error> {
919 let result = expansion::full_expand_and_split_str(self, params, s.as_ref()).await?;
920 Ok(result)
921 }
922
923 pub fn default_exec_params(&self) -> ExecutionParameters {
925 ExecutionParameters::default()
926 }
927
928 pub async fn run_script<S: AsRef<str>, P: AsRef<Path>, I: Iterator<Item = S>>(
935 &mut self,
936 script_path: P,
937 args: I,
938 ) -> Result<ExecutionResult, error::Error> {
939 let params = self.default_exec_params();
940 let result = self
941 .parse_and_execute_script_file(
942 script_path.as_ref(),
943 args,
944 ¶ms,
945 scripts::CallType::Executed,
946 )
947 .await?;
948
949 let _ = self.on_exit().await;
950
951 Ok(result)
952 }
953
954 pub async fn on_exit(&mut self) -> Result<(), error::Error> {
956 self.invoke_exit_trap_handler_if_registered().await?;
957
958 Ok(())
959 }
960
961 async fn invoke_exit_trap_handler_if_registered(
962 &mut self,
963 ) -> Result<ExecutionResult, error::Error> {
964 let Some(handler) = self.traps.handlers.get(&traps::TrapSignal::Exit).cloned() else {
965 return Ok(ExecutionResult::success());
966 };
967
968 let mut params = self.default_exec_params();
970 params.process_group_policy = ProcessGroupPolicy::SameProcessGroup;
971
972 let orig_last_exit_status = self.last_exit_status;
973 self.traps.handler_depth += 1;
974
975 let result = self.run_string(handler, ¶ms).await;
976
977 self.traps.handler_depth -= 1;
978 self.last_exit_status = orig_last_exit_status;
979
980 result
981 }
982
983 pub(crate) async fn run_parsed_result(
984 &mut self,
985 parse_result: Result<brush_parser::ast::Program, brush_parser::ParseError>,
986 source_info: &brush_parser::SourceInfo,
987 params: &ExecutionParameters,
988 ) -> Result<ExecutionResult, error::Error> {
989 let result = match parse_result {
991 Ok(prog) => self.run_program(prog, params).await,
992 Err(parse_err) => Err(error::Error::from(error::ErrorKind::ParseError(
993 parse_err,
994 source_info.clone(),
995 ))),
996 };
997
998 match result {
1000 Ok(result) => Ok(result),
1001 Err(err) => {
1002 let _ = self.display_error(&mut params.stderr(self), &err).await;
1003 let exit_code = ExecutionExitCode::from(&err);
1004 *self.last_exit_status_mut() = exit_code.into();
1005 Ok(exit_code.into())
1006 }
1007 }
1008 }
1009
1010 pub async fn run_program(
1017 &mut self,
1018 program: brush_parser::ast::Program,
1019 params: &ExecutionParameters,
1020 ) -> Result<ExecutionResult, error::Error> {
1021 program.execute(self, params).await
1022 }
1023
1024 const fn default_prompt(&self) -> &'static str {
1025 if self.options.sh_mode {
1026 "$ "
1027 } else {
1028 "brush$ "
1029 }
1030 }
1031
1032 pub async fn compose_precmd_prompt(&mut self) -> Result<String, error::Error> {
1034 self.expand_prompt_var("PS0", "").await
1035 }
1036
1037 pub async fn compose_prompt(&mut self) -> Result<String, error::Error> {
1039 self.expand_prompt_var("PS1", self.default_prompt()).await
1040 }
1041
1042 pub async fn compose_alt_side_prompt(&mut self) -> Result<String, error::Error> {
1044 self.expand_prompt_var("BRUSH_PS_ALT", "").await
1046 }
1047
1048 pub async fn compose_continuation_prompt(&mut self) -> Result<String, error::Error> {
1050 self.expand_prompt_var("PS2", "> ").await
1051 }
1052
1053 async fn expand_prompt_var(
1054 &mut self,
1055 var_name: &str,
1056 default: &str,
1057 ) -> Result<String, error::Error> {
1058 let prompt_spec = self.parameter_or_default(var_name, default);
1065 if prompt_spec.is_empty() {
1066 return Ok(String::new());
1067 }
1068
1069 let params = self.default_exec_params();
1071 prompt::expand_prompt(self, ¶ms, prompt_spec.into_owned()).await
1072 }
1073
1074 fn parameter_or_default<'a>(&'a self, name: &str, default: &'a str) -> Cow<'a, str> {
1075 self.env_str(name).unwrap_or_else(|| default.into())
1076 }
1077
1078 pub const fn parser_options(&self) -> brush_parser::ParserOptions {
1081 brush_parser::ParserOptions {
1082 enable_extended_globbing: self.options.extended_globbing,
1083 posix_mode: self.options.posix_mode,
1084 sh_mode: self.options.sh_mode,
1085 tilde_expansion: true,
1086 }
1087 }
1088
1089 pub fn in_sourced_script(&self) -> bool {
1091 self.script_call_stack.in_sourced_script()
1092 }
1093
1094 pub fn in_function(&self) -> bool {
1096 !self.function_call_stack.is_empty()
1097 }
1098
1099 pub(crate) fn enter_function(
1107 &mut self,
1108 name: &str,
1109 function_def: &Arc<brush_parser::ast::FunctionDefinition>,
1110 ) -> Result<(), error::Error> {
1111 if let Some(max_call_depth) = self.options.max_function_call_depth {
1112 if self.function_call_stack.depth() >= max_call_depth {
1113 return Err(error::ErrorKind::MaxFunctionCallDepthExceeded.into());
1114 }
1115 }
1116
1117 if tracing::enabled!(target: trace_categories::FUNCTIONS, tracing::Level::DEBUG) {
1118 let depth = self.function_call_stack.depth();
1119 let prefix = repeated_char_str(' ', depth);
1120 tracing::debug!(target: trace_categories::FUNCTIONS, "Entering func [depth={depth}]: {prefix}{name}");
1121 }
1122
1123 self.function_call_stack.push(name, function_def);
1124 self.env.push_scope(env::EnvironmentScope::Local);
1125
1126 Ok(())
1127 }
1128
1129 pub(crate) fn leave_function(&mut self) -> Result<(), error::Error> {
1132 self.env.pop_scope(env::EnvironmentScope::Local)?;
1133
1134 if let Some(exited_call) = self.function_call_stack.pop() {
1135 if tracing::enabled!(target: trace_categories::FUNCTIONS, tracing::Level::DEBUG) {
1136 let depth = self.function_call_stack.depth();
1137 let prefix = repeated_char_str(' ', depth);
1138 tracing::debug!(target: trace_categories::FUNCTIONS, "Exiting func [depth={depth}]: {prefix}{}", exited_call.function_name);
1139 }
1140 }
1141
1142 Ok(())
1143 }
1144
1145 pub fn history_file_path(&self) -> Option<PathBuf> {
1147 self.env_str("HISTFILE")
1148 .map(|s| PathBuf::from(s.into_owned()))
1149 }
1150
1151 pub fn history_time_format(&self) -> Option<String> {
1153 self.env_str("HISTTIMEFORMAT").map(|s| s.into_owned())
1154 }
1155
1156 pub fn save_history(&mut self) -> Result<(), error::Error> {
1158 if let Some(history_file_path) = self.history_file_path() {
1159 if let Some(history) = &mut self.history {
1160 let write_timestamps = self.env.is_set("HISTTIMEFORMAT");
1162
1163 history.flush(
1165 history_file_path,
1166 true, true, write_timestamps,
1169 )?;
1170 }
1171 }
1172
1173 Ok(())
1174 }
1175
1176 pub fn add_to_history(&mut self, command: &str) -> Result<(), error::Error> {
1178 if let Some(history) = &mut self.history {
1179 let command = command.trim();
1181
1182 if command.is_empty() {
1184 return Ok(());
1185 }
1186
1187 history.add(history::Item {
1189 id: 0,
1190 command_line: command.to_owned(),
1191 timestamp: Some(chrono::Utc::now()),
1192 dirty: true,
1193 })?;
1194 }
1195
1196 Ok(())
1197 }
1198
1199 pub fn env_str(&self, name: &str) -> Option<Cow<'_, str>> {
1206 self.env.get_str(name, self)
1207 }
1208
1209 pub fn env_var(&self, name: &str) -> Option<&ShellVariable> {
1215 self.env.get(name).map(|(_, var)| var)
1216 }
1217
1218 pub fn set_env_global(&mut self, name: &str, var: ShellVariable) -> Result<(), error::Error> {
1225 self.env.set_global(name, var)
1226 }
1227
1228 pub fn register_builtin<S: Into<String>>(
1235 &mut self,
1236 name: S,
1237 registration: builtins::Registration,
1238 ) {
1239 self.builtins.insert(name.into(), registration);
1240 }
1241
1242 pub fn builtin_mut(&mut self, name: &str) -> Option<&mut builtins::Registration> {
1249 self.builtins.get_mut(name)
1250 }
1251
1252 pub fn ifs(&self) -> Cow<'_, str> {
1254 self.env_str("IFS").unwrap_or_else(|| " \t\n".into())
1255 }
1256
1257 pub(crate) fn get_ifs_first_char(&self) -> char {
1259 self.ifs().chars().next().unwrap_or(' ')
1260 }
1261
1262 pub async fn complete(
1269 &mut self,
1270 input: &str,
1271 position: usize,
1272 ) -> Result<completion::Completions, error::Error> {
1273 let completion_config = self.completion_config.clone();
1274 completion_config
1275 .get_completions(self, input, position)
1276 .await
1277 }
1278
1279 pub fn find_executables_in_path<'a>(
1285 &'a self,
1286 filename: &'a str,
1287 ) -> impl Iterator<Item = PathBuf> + 'a {
1288 let path_var = self.env.get_str("PATH", self).unwrap_or_default();
1289 let paths = path_var.split(':').map(|s| s.to_owned());
1290
1291 pathsearch::search_for_executable(paths.into_iter(), filename)
1292 }
1293
1294 pub fn find_executables_in_path_with_prefix(
1301 &self,
1302 filename_prefix: &str,
1303 case_insensitive: bool,
1304 ) -> impl Iterator<Item = PathBuf> {
1305 let path_var = self.env.get_str("PATH", self).unwrap_or_default();
1306 let paths = path_var.split(':').map(|s| s.to_owned());
1307
1308 pathsearch::search_for_executable_with_prefix(
1309 paths.into_iter(),
1310 filename_prefix,
1311 case_insensitive,
1312 )
1313 }
1314
1315 pub fn find_first_executable_in_path<S: AsRef<str>>(
1322 &self,
1323 candidate_name: S,
1324 ) -> Option<PathBuf> {
1325 for dir_str in self.env_str("PATH").unwrap_or_default().split(':') {
1326 let candidate_path = Path::new(dir_str).join(candidate_name.as_ref());
1327 if candidate_path.executable() {
1328 return Some(candidate_path);
1329 }
1330 }
1331 None
1332 }
1333
1334 pub fn find_first_executable_in_path_using_cache<S: AsRef<str>>(
1342 &mut self,
1343 candidate_name: S,
1344 ) -> Option<PathBuf> {
1345 if let Some(cached_path) = self.program_location_cache.get(&candidate_name) {
1346 Some(cached_path)
1347 } else if let Some(found_path) = self.find_first_executable_in_path(&candidate_name) {
1348 self.program_location_cache
1349 .set(&candidate_name, found_path.clone());
1350 Some(found_path)
1351 } else {
1352 None
1353 }
1354 }
1355
1356 pub fn absolute_path(&self, path: impl AsRef<Path>) -> PathBuf {
1362 let path = path.as_ref();
1363 if path.as_os_str().is_empty() || path.is_absolute() {
1364 path.to_owned()
1365 } else {
1366 self.working_dir().join(path)
1367 }
1368 }
1369
1370 pub(crate) fn open_file(
1378 &self,
1379 options: &std::fs::OpenOptions,
1380 path: impl AsRef<Path>,
1381 params: &ExecutionParameters,
1382 ) -> Result<openfiles::OpenFile, std::io::Error> {
1383 let path_to_open = self.absolute_path(path.as_ref());
1384
1385 if let Some(parent) = path_to_open.parent() {
1389 if parent == Path::new("/dev/fd") {
1390 if let Some(filename) = path_to_open.file_name() {
1391 if let Ok(fd_num) = filename.to_string_lossy().to_string().parse::<ShellFd>() {
1392 if let Some(open_file) = params.try_fd(self, fd_num) {
1393 return open_file.try_clone();
1394 }
1395 }
1396 }
1397 }
1398 }
1399
1400 Ok(options.open(path_to_open)?.into())
1401 }
1402
1403 pub fn set_working_dir(&mut self, target_dir: impl AsRef<Path>) -> Result<(), error::Error> {
1409 let abs_path = self.absolute_path(target_dir.as_ref());
1410
1411 match std::fs::metadata(&abs_path) {
1412 Ok(m) => {
1413 if !m.is_dir() {
1414 return Err(error::ErrorKind::NotADirectory(abs_path).into());
1415 }
1416 }
1417 Err(e) => {
1418 return Err(e.into());
1419 }
1420 }
1421
1422 let cleaned_path = abs_path.normalize();
1424
1425 let pwd = cleaned_path.to_string_lossy().to_string();
1426
1427 self.env.update_or_add(
1428 "PWD",
1429 variables::ShellValueLiteral::Scalar(pwd),
1430 |var| {
1431 var.export();
1432 Ok(())
1433 },
1434 EnvironmentLookup::Anywhere,
1435 EnvironmentScope::Global,
1436 )?;
1437 let oldpwd = std::mem::replace(self.working_dir_mut(), cleaned_path);
1438
1439 self.env.update_or_add(
1440 "OLDPWD",
1441 variables::ShellValueLiteral::Scalar(oldpwd.to_string_lossy().to_string()),
1442 |var| {
1443 var.export();
1444 Ok(())
1445 },
1446 EnvironmentLookup::Anywhere,
1447 EnvironmentScope::Global,
1448 )?;
1449
1450 Ok(())
1451 }
1452
1453 pub fn tilde_shorten(&self, s: String) -> String {
1459 if let Some(home_dir) = self.home_dir() {
1460 if let Some(stripped) = s.strip_prefix(home_dir.to_string_lossy().as_ref()) {
1461 return format!("~{stripped}");
1462 }
1463 }
1464 s
1465 }
1466
1467 pub(crate) fn home_dir(&self) -> Option<PathBuf> {
1469 if let Some(home) = self.env.get_str("HOME", self) {
1470 Some(PathBuf::from(home.to_string()))
1471 } else {
1472 users::get_current_user_home_dir()
1474 }
1475 }
1476
1477 pub fn replace_open_files(
1484 &mut self,
1485 open_fds: impl Iterator<Item = (ShellFd, openfiles::OpenFile)>,
1486 ) {
1487 self.open_files = openfiles::OpenFiles::from(open_fds);
1488 }
1489
1490 pub(crate) const fn persistent_open_files(&self) -> &openfiles::OpenFiles {
1491 &self.open_files
1492 }
1493
1494 pub fn stdout(&self) -> impl std::io::Write {
1497 self.open_files.try_stdout().cloned().unwrap()
1498 }
1499
1500 pub fn stderr(&self) -> impl std::io::Write {
1503 self.open_files.try_stderr().cloned().unwrap()
1504 }
1505
1506 pub(crate) async fn trace_command<S: AsRef<str>>(
1512 &mut self,
1513 params: &ExecutionParameters,
1514 command: S,
1515 ) -> Result<(), error::Error> {
1516 let ps4 = self.as_mut().expand_prompt_var("PS4", "").await?;
1518 let mut prefix = ps4;
1519
1520 let additional_depth = self.script_call_stack.depth() + self.depth;
1522 if let Some(c) = prefix.chars().next() {
1523 for _ in 0..additional_depth {
1524 prefix.insert(0, c);
1525 }
1526 }
1527
1528 let mut trace_file = params.try_stderr(self);
1530
1531 if let Some((_, xtracefd_var)) = self.env.get("BASH_XTRACEFD") {
1533 let xtracefd_value = xtracefd_var.value().to_cow_str(self);
1534 if let Ok(fd) = xtracefd_value.parse::<ShellFd>() {
1535 if let Some(file) = self.open_files.try_fd(fd) {
1536 trace_file = Some(file.clone());
1537 }
1538 }
1539 }
1540
1541 if let Some(trace_file) = trace_file {
1543 let mut trace_file = trace_file.try_clone()?;
1544 writeln!(trace_file, "{prefix}{}", command.as_ref())?;
1545 }
1546
1547 Ok(())
1548 }
1549
1550 pub(crate) fn get_keywords(&self) -> Vec<String> {
1552 if self.options.sh_mode {
1553 keywords::SH_MODE_KEYWORDS.iter().cloned().collect()
1554 } else {
1555 keywords::KEYWORDS.iter().cloned().collect()
1556 }
1557 }
1558
1559 pub fn is_keyword(&self, s: &str) -> bool {
1565 if self.options.sh_mode {
1566 keywords::SH_MODE_KEYWORDS.contains(s)
1567 } else {
1568 keywords::KEYWORDS.contains(s)
1569 }
1570 }
1571
1572 pub fn check_for_completed_jobs(&mut self) -> Result<(), error::Error> {
1574 let results = self.jobs.poll()?;
1575
1576 if self.options.enable_job_control {
1577 for (job, _result) in results {
1578 writeln!(self.stderr(), "{job}")?;
1579 }
1580 }
1581
1582 Ok(())
1583 }
1584
1585 pub fn eval_arithmetic(
1587 &mut self,
1588 expr: &brush_parser::ast::ArithmeticExpr,
1589 ) -> Result<i64, error::Error> {
1590 Ok(expr.eval(self)?)
1591 }
1592
1593 pub fn set_edit_buffer(&mut self, contents: String, cursor: usize) -> Result<(), error::Error> {
1600 self.env
1601 .set_global("READLINE_LINE", ShellVariable::new(contents))?;
1602
1603 self.env
1604 .set_global("READLINE_POINT", ShellVariable::new(cursor.to_string()))?;
1605
1606 Ok(())
1607 }
1608
1609 pub fn pop_edit_buffer(&mut self) -> Result<Option<(String, usize)>, error::Error> {
1612 let line = self
1613 .env
1614 .unset("READLINE_LINE")?
1615 .map(|line| line.value().to_cow_str(self).to_string());
1616
1617 let point = self
1618 .env
1619 .unset("READLINE_POINT")?
1620 .and_then(|point| point.value().to_cow_str(self).parse::<usize>().ok())
1621 .unwrap_or(0);
1622
1623 if let Some(line) = line {
1624 Ok(Some((line, point)))
1625 } else {
1626 Ok(None)
1627 }
1628 }
1629
1630 pub const fn history(&self) -> Option<&history::History> {
1632 self.history.as_ref()
1633 }
1634
1635 pub const fn history_mut(&mut self) -> Option<&mut history::History> {
1637 self.history.as_mut()
1638 }
1639
1640 pub const fn is_subshell(&self) -> bool {
1642 self.depth > 0
1643 }
1644
1645 pub const fn depth(&self) -> usize {
1647 self.depth
1648 }
1649
1650 pub async fn display_error(
1657 &self,
1658 file: &mut impl std::io::Write,
1659 err: &error::Error,
1660 ) -> Result<(), error::Error> {
1661 let str = self.error_formatter.lock().await.format_error(err, self);
1662 write!(file, "{str}")?;
1663
1664 Ok(())
1665 }
1666}
1667
1668#[cached::proc_macro::cached(size = 64, result = true)]
1669fn parse_string_impl(
1670 s: String,
1671 parser_options: brush_parser::ParserOptions,
1672) -> Result<brush_parser::ast::Program, brush_parser::ParseError> {
1673 let mut parser = create_parser(s.as_bytes(), &parser_options);
1674
1675 tracing::debug!(target: trace_categories::PARSE, "Parsing string as program...");
1676 parser.parse_program()
1677}
1678
1679fn create_parser<R: Read>(
1680 r: R,
1681 parser_options: &brush_parser::ParserOptions,
1682) -> brush_parser::Parser<std::io::BufReader<R>> {
1683 let reader = std::io::BufReader::new(r);
1684 let source_info = brush_parser::SourceInfo {
1685 source: String::from("main"),
1686 };
1687
1688 brush_parser::Parser::new(reader, parser_options, &source_info)
1689}
1690
1691fn repeated_char_str(c: char, count: usize) -> String {
1692 (0..count).map(|_| c).collect()
1693}