Skip to main content

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, &params).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(), &params)
78                        .await?;
79                } else {
80                    if !self
81                        .source_if_exists(home_path.join(".bash_profile").as_path(), &params)
82                        .await?
83                    {
84                        if !self
85                            .source_if_exists(home_path.join(".bash_login").as_path(), &params)
86                            .await?
87                        {
88                            self.source_if_exists(home_path.join(".profile").as_path(), &params)
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, &params).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, &params).await?;
112                        }
113                        if let Some(home_path) = self.home_dir() {
114                            self.source_if_exists(home_path.join(".bashrc").as_path(), &params)
115                                .await?;
116                            self.source_if_exists(home_path.join(".brushrc").as_path(), &params)
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}