brush_core/shell/initscripts.rs
1//! Init script support for shells.
2
3use std::path::PathBuf;
4
5use crate::{Shell, error, extensions, interp};
6
7/// Behavior for loading profile files.
8#[derive(Default)]
9pub enum ProfileLoadBehavior {
10 /// Load the default profile files.
11 #[default]
12 LoadDefault,
13 /// Skip loading profile files.
14 Skip,
15}
16
17impl ProfileLoadBehavior {
18 /// Returns whether profile loading should be skipped.
19 pub const fn skip(&self) -> bool {
20 matches!(self, Self::Skip)
21 }
22}
23
24/// Behavior for loading rc files.
25#[derive(Default)]
26pub enum RcLoadBehavior {
27 /// Load the default rc files.
28 #[default]
29 LoadDefault,
30 /// Load a custom rc file; do not load defaults.
31 LoadCustom(PathBuf),
32 /// Skip loading rc files.
33 Skip,
34}
35
36impl RcLoadBehavior {
37 /// Returns whether rc loading should be skipped.
38 pub const fn skip(&self) -> bool {
39 matches!(self, Self::Skip)
40 }
41}
42
43impl<SE: extensions::ShellExtensions> Shell<SE> {
44 /// Loads and executes standard shell configuration files (i.e., rc and profile).
45 ///
46 /// # Arguments
47 ///
48 /// * `profile_behavior` - Behavior for loading profile files.
49 /// * `rc_behavior` - Behavior for loading rc files.
50 pub async fn load_config(
51 &mut self,
52 profile_behavior: &ProfileLoadBehavior,
53 rc_behavior: &RcLoadBehavior,
54 ) -> Result<(), error::Error> {
55 let mut params = self.default_exec_params();
56 params.process_group_policy = interp::ProcessGroupPolicy::SameProcessGroup;
57
58 if self.options.login_shell {
59 // --noprofile means skip this.
60 if matches!(profile_behavior, ProfileLoadBehavior::Skip) {
61 return Ok(());
62 }
63
64 //
65 // Source the system profile if it exists.
66 //
67 // Next source the first of these that exists and is readable (if any):
68 // * ~/.bash_profile
69 // * ~/.bash_login
70 // * ~/.profile
71 //
72 if let Some(system_profile) = crate::sys::fs::get_system_profile_path() {
73 self.source_if_exists(system_profile, ¶ms).await?;
74 }
75 if let Some(home_path) = self.home_dir() {
76 if self.options.sh_mode {
77 self.source_if_exists(home_path.join(".profile").as_path(), ¶ms)
78 .await?;
79 } else {
80 if !self
81 .source_if_exists(home_path.join(".bash_profile").as_path(), ¶ms)
82 .await?
83 {
84 if !self
85 .source_if_exists(home_path.join(".bash_login").as_path(), ¶ms)
86 .await?
87 {
88 self.source_if_exists(home_path.join(".profile").as_path(), ¶ms)
89 .await?;
90 }
91 }
92 }
93 }
94 } else {
95 if self.options.interactive {
96 match rc_behavior {
97 _ if self.options.sh_mode => (),
98 RcLoadBehavior::Skip => (),
99 RcLoadBehavior::LoadCustom(rc_file) => {
100 // If an explicit rc file is provided, source it.
101 self.source_if_exists(rc_file, ¶ms).await?;
102 }
103 RcLoadBehavior::LoadDefault => {
104 //
105 // Otherwise, for non-login interactive shells, load in this order:
106 //
107 // system rc file (e.g. /etc/bash.bashrc on Unix)
108 // ~/.bashrc
109 //
110 if let Some(system_rc) = crate::sys::fs::get_system_rc_path() {
111 self.source_if_exists(system_rc, ¶ms).await?;
112 }
113 if let Some(home_path) = self.home_dir() {
114 self.source_if_exists(home_path.join(".bashrc").as_path(), ¶ms)
115 .await?;
116 self.source_if_exists(home_path.join(".brushrc").as_path(), ¶ms)
117 .await?;
118 }
119 }
120 }
121 } else {
122 let env_var_name = if self.options.sh_mode {
123 "ENV"
124 } else {
125 "BASH_ENV"
126 };
127
128 if self.env.is_set(env_var_name) {
129 //
130 // TODO(well-known-vars): look at $ENV/BASH_ENV; source its expansion if that
131 // file exists
132 //
133 return error::unimp(
134 "load config from $ENV/BASH_ENV for non-interactive, non-login shell",
135 );
136 }
137 }
138 }
139
140 Ok(())
141 }
142}