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_builtin::BUILTINS;
22use yash_env::Env;
23use yash_env::System;
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::prompt::GetPrompt;
29use yash_env::semantics::command::RunFunction;
30use yash_env::trap::RunSignalTrapIfCaught;
31use yash_semantics::RunReadEvalLoop;
32
33pub mod args;
34pub mod init_file;
35pub mod input;
36
37/// Tests whether the shell should be implicitly interactive.
38///
39/// As per POSIX, "if the shell reads commands from the standard input and the
40/// shell's standard input and standard error are attached to a terminal, the
41/// shell is considered to be interactive." This function implements this rule.
42///
43/// This function returns `false` if the interactive option is explicitly
44/// specified in the command line arguments to honor the user's intent.
45pub fn auto_interactive<S: System>(system: &S, run: &Run) -> bool {
46    if run.work.source != Source::Stdin {
47        return false;
48    }
49    if run.options.iter().any(|&(o, _)| o == Interactive) {
50        return false;
51    }
52    system.isatty(Fd::STDIN) && system.isatty(Fd::STDERR)
53}
54
55/// Get the environment ready for performing the work.
56///
57/// This function takes the parsed command-line arguments and applies them to
58/// the environment. It also sets up signal dispositions and prepares built-ins
59/// and variables. The function returns the work to be performed, which is
60/// extracted from the `run` argument.
61///
62/// This function is _pure_ in that all system calls are performed by the
63/// `System` trait object (`env.system`).
64pub fn configure_environment(env: &mut Env, run: Run) -> Work {
65    // Apply the parsed options to the environment
66    if auto_interactive(&env.system, &run) {
67        env.options.set(Interactive, On);
68    }
69    if run.work.source == self::args::Source::Stdin {
70        env.options.set(Stdin, On);
71    }
72    for &(option, state) in &run.options {
73        env.options.set(option, state);
74    }
75    if env.options.get(Interactive) == On && !run.options.iter().any(|&(o, _)| o == Monitor) {
76        env.options.set(Monitor, On);
77    }
78
79    // Apply the parsed operands to the environment
80    env.arg0 = run.arg0;
81    env.variables.positional_params_mut().values = run.positional_params;
82
83    // Configure internal dispositions for signals
84    if env.options.get(Interactive) == On {
85        env.traps
86            .enable_internal_dispositions_for_terminators(&mut env.system)
87            .ok();
88        if env.options.get(Monitor) == On {
89            env.traps
90                .enable_internal_dispositions_for_stoppers(&mut env.system)
91                .ok();
92        }
93    }
94
95    // Make sure the shell is in the foreground if job control is enabled
96    if env.options.get(Monitor) == On {
97        // Ignore failures as we can still proceed even if we can't get into the foreground
98        env.ensure_foreground().ok();
99    }
100
101    // Prepare built-ins
102    env.builtins.extend(BUILTINS.iter().cloned());
103
104    // Prepare variables
105    env.init_variables();
106
107    // Inject dependencies
108    inject_dependencies(env);
109
110    run.work
111}
112
113/// Inject dependencies into the environment.
114fn inject_dependencies(env: &mut Env) {
115    env.any.insert(Box::new(IsKeyword(|_env, word| {
116        yash_syntax::parser::lex::Keyword::from_str(word).is_ok()
117    })));
118
119    env.any.insert(Box::new(RunReadEvalLoop(|env, config| {
120        Box::pin(async move {
121            let mut lexer = config.into_lexer();
122            yash_semantics::read_eval_loop(env, &mut lexer).await
123        })
124    })));
125
126    env.any.insert(Box::new(RunFunction(
127        |env, function, fields, env_prep_hook| {
128            Box::pin(async move {
129                yash_semantics::command::simple_command::execute_function_body(
130                    env,
131                    function,
132                    fields,
133                    env_prep_hook,
134                )
135                .await
136            })
137        },
138    )));
139
140    env.any
141        .insert(Box::new(RunSignalTrapIfCaught(|env, signal| {
142            Box::pin(async move { yash_semantics::trap::run_trap_if_caught(env, signal).await })
143        })));
144
145    env.any.insert(Box::new(GetPrompt(|env, context| {
146        Box::pin(async move {
147            let prompt = yash_prompt::fetch_posix(&env.variables, context);
148            yash_prompt::expand_posix(env, &prompt, false).await
149        })
150    })));
151}