Skip to main content

yash_cli/
startup.rs

1// This file is part of yash, an extended POSIX shell.
2// Copyright (C) 2023 WATANABE Yuki
3//
4// This program is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8//
9// This program is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12// GNU General Public License for more details.
13//
14// You should have received a copy of the GNU General Public License
15// along with this program.  If not, see <https://www.gnu.org/licenses/>.
16
17//! Shell startup
18
19use self::args::{Run, Source, Work};
20use std::str::FromStr as _;
21use yash_env::Env;
22use yash_env::input::IgnoreEofConfig;
23use yash_env::input::SuspendedJobsGuardConfig;
24use yash_env::io::Fd;
25use yash_env::option::Option::{Interactive, Monitor, Stdin};
26use yash_env::option::State::On;
27use yash_env::parser::IsKeyword;
28use yash_env::parser::IsName;
29use yash_env::prompt::GetPrompt;
30use yash_env::semantics::command::RunFunction;
31use yash_env::system::resource::GetRlimit;
32use yash_env::system::{Chdir, GetCwd, GetUid, Isatty, Sysconf, TcGetPgrp, Times, Umask, Write};
33use yash_env::trap::RunSignalTrapIfCaught;
34use yash_prompt::ExpandText;
35use yash_semantics::expansion::expand_text;
36use yash_semantics::{RunReadEvalLoop, Runtime};
37use yash_syntax::parser::lex::Lexer;
38
39pub mod args;
40pub mod init_file;
41pub mod input;
42
43/// Tests whether the shell should be implicitly interactive.
44///
45/// As per POSIX, "if the shell reads commands from the standard input and the
46/// shell's standard input and standard error are attached to a terminal, the
47/// shell is considered to be interactive." This function implements this rule.
48///
49/// This function returns `false` if the interactive option is explicitly
50/// specified in the command line arguments to honor the user's intent.
51pub fn auto_interactive<S: Isatty>(system: &S, run: &Run) -> bool {
52    if run.work.source != Source::Stdin {
53        return false;
54    }
55    if run.options.iter().any(|&(o, _)| o == Interactive) {
56        return false;
57    }
58    system.isatty(Fd::STDIN) && system.isatty(Fd::STDERR)
59}
60
61/// Get the environment ready for performing the work.
62///
63/// This function takes the parsed command-line arguments and applies them to
64/// the environment. It also sets up signal dispositions and prepares built-ins
65/// and variables. The function returns the work to be performed, which is
66/// extracted from the `run` argument.
67///
68/// This function is _pure_ in that all system calls are performed by the
69/// `System` trait object (`env.system`).
70pub async fn configure_environment<S>(env: &mut Env<S>, run: Run) -> Work
71where
72    S: Chdir
73        + Clone
74        + GetCwd
75        + GetRlimit
76        + GetUid
77        + Runtime
78        + Sysconf
79        + TcGetPgrp
80        + Times
81        + Umask
82        + Write
83        + 'static,
84{
85    // Apply the parsed options to the environment
86    if auto_interactive(&env.system, &run) {
87        env.options.set(Interactive, On);
88    }
89    if run.work.source == self::args::Source::Stdin {
90        env.options.set(Stdin, On);
91    }
92    for &(option, state) in &run.options {
93        env.options.set(option, state);
94    }
95    if env.options.get(Interactive) == On && !run.options.iter().any(|&(o, _)| o == Monitor) {
96        env.options.set(Monitor, On);
97    }
98
99    // Apply the parsed operands to the environment
100    env.arg0 = run.arg0;
101    env.variables.positional_params_mut().values = run.positional_params;
102
103    // Configure internal dispositions for signals
104    if env.options.get(Interactive) == On {
105        env.traps
106            .enable_internal_dispositions_for_terminators(&env.system)
107            .await
108            .ok();
109        if env.options.get(Monitor) == On {
110            env.traps
111                .enable_internal_dispositions_for_stoppers(&env.system)
112                .await
113                .ok();
114        }
115    }
116
117    // Make sure the shell is in the foreground if job control is enabled
118    if env.options.get(Monitor) == On {
119        // Ignore failures as we can still proceed even if we can't get into the foreground
120        env.ensure_foreground().await.ok();
121    }
122
123    // Prepare built-ins
124    env.builtins.extend(yash_builtin::iter());
125
126    // Prepare variables
127    env.init_variables();
128
129    // Inject dependencies
130    inject_dependencies(env);
131
132    run.work
133}
134
135/// Inject dependencies into the environment.
136fn inject_dependencies<S: Runtime + 'static>(env: &mut Env<S>) {
137    env.any
138        .insert(Box::new(SuspendedJobsGuardConfig::with_message(
139            "# There are stopped jobs. Type `exit -f` to exit anyway.\n",
140        )));
141    env.any.insert(Box::new(IgnoreEofConfig::with_message(
142        "# Type `exit` to leave the shell when the ignore-eof option is on.\n",
143    )));
144
145    env.any.insert(Box::new(IsKeyword::<S>(|_env, word| {
146        yash_syntax::parser::lex::Keyword::from_str(word).is_ok()
147    })));
148
149    env.any.insert(Box::new(IsName::<S>(|_env, name| {
150        yash_syntax::parser::lex::is_name(name)
151    })));
152
153    env.any.insert(Box::new(RunReadEvalLoop::<S>(|env, config| {
154        Box::pin(async move {
155            let mut lexer = Lexer::from(config);
156            yash_semantics::read_eval_loop(env, &mut lexer).await
157        })
158    })));
159
160    env.any.insert(Box::new(RunFunction::<S>(
161        |env, function, fields, env_prep_hook| {
162            Box::pin(async move {
163                yash_semantics::command::simple_command::execute_function_body(
164                    env,
165                    function,
166                    fields,
167                    env_prep_hook,
168                )
169                .await
170            })
171        },
172    )));
173
174    env.any
175        .insert(Box::new(RunSignalTrapIfCaught::<S>(|env, signal| {
176            Box::pin(async move { yash_semantics::trap::run_trap_if_caught(env, signal).await })
177        })));
178
179    env.any.insert(Box::new(ExpandText::<S>(|env, text| {
180        Box::pin(async move { expand_text(env, text).await.ok() })
181    })));
182
183    env.any.insert(Box::new(GetPrompt::<S>(|env, context| {
184        Box::pin(async move {
185            let prompt = yash_prompt::fetch_posix(&env.variables, context);
186            yash_prompt::expand_posix(env, &prompt, false).await
187        })
188    })));
189}