conch_runtime_pshaw/env/
builtin.rs

1//! A module which defines interfaces for expressing shell builtin utilities,
2//! and provides a default implementations.
3
4use crate::env::{
5    ArgumentsEnvironment, AsyncIoEnvironment, ChangeWorkingDirectoryEnvironment,
6    FileDescEnvironment, RedirectEnvRestorer, ShiftArgumentsEnvironment, StringWrapper,
7    SubEnvironment, VarEnvRestorer, VariableEnvironment,
8};
9use crate::spawn::builtin;
10use crate::ExitStatus;
11use futures_core::future::BoxFuture;
12use std::borrow::Borrow;
13use std::fmt;
14use std::marker::PhantomData;
15
16/// An interface for builtin utilities which can be spawned with some arguments.
17///
18/// Builtin utilities are different than regular commands, and may wish to have
19/// different semantics when it comes to restoring local redirects or variables.
20/// Thus when a builtin is prepared for execution, it is provided any local
21/// redirection or variable restorers, and it becomes the builtin's responsibility
22/// to restore the redirects/variables (or not) based on its specific semantics.
23pub trait BuiltinUtility<'a, A, R, E>
24where
25    R: ?Sized,
26    E: 'a + ?Sized,
27{
28    /// Spawn the builtin utility using the provided arguments.
29    ///
30    /// Builtin utilities are different than regular commands, and may wish to have
31    /// different semantics when it comes to restoring local redirects or variables.
32    /// Thus when a builtin is prepared for execution, it is provided any local
33    /// redirection or variable restorers, and it becomes the builtin's responsibility
34    /// to restore the redirects/variables (or not) based on its specific semantics.
35    ///
36    /// For example, the `exec` utility appears like a regular command, but any
37    /// redirections that have been applied to it remain in effect for the rest
38    /// of the script.
39    fn spawn_builtin<'life0, 'life1, 'async_trait>(
40        &'life0 self,
41        args: A,
42        restorer: &'life1 mut R,
43    ) -> BoxFuture<'async_trait, BoxFuture<'static, ExitStatus>>
44    where
45        'life0: 'async_trait,
46        'life1: 'async_trait,
47        Self: 'async_trait,
48        A: 'async_trait;
49}
50
51impl<'a, A, R, E, T> BuiltinUtility<'a, A, R, E> for &'_ T
52where
53    R: ?Sized,
54    E: 'a + ?Sized,
55    T: BuiltinUtility<'a, A, R, E>,
56{
57    fn spawn_builtin<'life0, 'life1, 'async_trait>(
58        &'life0 self,
59        args: A,
60        restorer: &'life1 mut R,
61    ) -> BoxFuture<'async_trait, BoxFuture<'static, ExitStatus>>
62    where
63        'life0: 'async_trait,
64        'life1: 'async_trait,
65        Self: 'async_trait,
66        A: 'async_trait,
67    {
68        (**self).spawn_builtin(args, restorer)
69    }
70}
71
72/// An interface for getting shell builtin utilities.
73pub trait BuiltinEnvironment {
74    /// The name for looking up a builtin utility.
75    type BuiltinName;
76    /// The type of the builtin utility.
77    type Builtin;
78
79    /// Lookup and get a particular builtin by its name.
80    fn builtin(&self, name: &Self::BuiltinName) -> Option<Self::Builtin>;
81}
82
83impl<'a, T: ?Sized + BuiltinEnvironment> BuiltinEnvironment for &'a T {
84    type BuiltinName = T::BuiltinName;
85    type Builtin = T::Builtin;
86
87    fn builtin(&self, name: &Self::BuiltinName) -> Option<Self::Builtin> {
88        (**self).builtin(name)
89    }
90}
91
92#[derive(Debug, Clone, Copy, PartialEq, Eq)]
93enum BuiltinKind {
94    Cd,
95    Colon,
96    Echo,
97    False,
98    Pwd,
99    Shift,
100    True,
101}
102
103/// Represents a shell builtin utility managed by a `BuiltinEnv` instance.
104#[derive(Debug, Clone, Copy, PartialEq, Eq)]
105pub struct Builtin {
106    kind: BuiltinKind,
107}
108
109/// An environment module for getting shell builtin utilities.
110pub struct BuiltinEnv<T> {
111    phantom: PhantomData<fn(T)>,
112}
113
114impl<T> Eq for BuiltinEnv<T> {}
115impl<T> PartialEq<BuiltinEnv<T>> for BuiltinEnv<T> {
116    fn eq(&self, other: &BuiltinEnv<T>) -> bool {
117        self.phantom == other.phantom
118    }
119}
120
121impl<T> fmt::Debug for BuiltinEnv<T> {
122    fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
123        fmt.debug_struct("BuiltinEnv").finish()
124    }
125}
126
127impl<T> Copy for BuiltinEnv<T> {}
128impl<T> Clone for BuiltinEnv<T> {
129    fn clone(&self) -> Self {
130        *self
131    }
132}
133
134impl<T> Default for BuiltinEnv<T> {
135    fn default() -> Self {
136        Self::new()
137    }
138}
139
140impl<T> BuiltinEnv<T> {
141    /// Construct a new environment.
142    pub fn new() -> Self {
143        Self {
144            phantom: PhantomData,
145        }
146    }
147}
148
149impl<T> SubEnvironment for BuiltinEnv<T> {
150    fn sub_env(&self) -> Self {
151        *self
152    }
153}
154
155fn lookup_builtin(name: &str) -> Option<BuiltinKind> {
156    match name {
157        "cd" => Some(BuiltinKind::Cd),
158        ":" => Some(BuiltinKind::Colon),
159        "echo" => Some(BuiltinKind::Echo),
160        "false" => Some(BuiltinKind::False),
161        "pwd" => Some(BuiltinKind::Pwd),
162        "shift" => Some(BuiltinKind::Shift),
163        "true" => Some(BuiltinKind::True),
164
165        _ => None,
166    }
167}
168
169impl<T> BuiltinEnvironment for BuiltinEnv<T>
170where
171    T: StringWrapper,
172{
173    type BuiltinName = T;
174    type Builtin = Builtin;
175
176    fn builtin(&self, name: &Self::BuiltinName) -> Option<Self::Builtin> {
177        lookup_builtin(name.as_str()).map(|kind| Builtin { kind })
178    }
179}
180
181impl<'a, A, R, E> BuiltinUtility<'a, A, R, E> for Builtin
182where
183    A: Send + IntoIterator,
184    A::Item: Send + StringWrapper,
185    A::IntoIter: Send,
186    R: ?Sized + Send + RedirectEnvRestorer<'a, E> + VarEnvRestorer<'a, E>,
187    E: 'a
188        + ?Sized
189        + Send
190        + Sync
191        + AsyncIoEnvironment
192        + ArgumentsEnvironment
193        + ChangeWorkingDirectoryEnvironment
194        + FileDescEnvironment
195        + VariableEnvironment
196        + ShiftArgumentsEnvironment,
197    E::FileHandle: Clone,
198    E::IoHandle: Send + From<E::FileHandle>,
199    E::Var: Borrow<String> + From<String>,
200    E::VarName: Borrow<String> + From<String>,
201{
202    fn spawn_builtin<'life0, 'life1, 'async_trait>(
203        &'life0 self,
204        args: A,
205        restorer: &'life1 mut R,
206    ) -> BoxFuture<'async_trait, BoxFuture<'static, ExitStatus>>
207    where
208        'life0: 'async_trait,
209        'life1: 'async_trait,
210        Self: 'async_trait,
211        A: 'async_trait,
212    {
213        let kind = self.kind;
214
215        Box::pin(async move {
216            let env = restorer.get_mut();
217
218            let ret = match kind {
219                BuiltinKind::Cd => builtin::cd(args, env).await,
220                BuiltinKind::Echo => builtin::echo(args, env).await,
221                BuiltinKind::Pwd => builtin::pwd(args, env).await,
222                BuiltinKind::Shift => builtin::shift(args, env).await,
223
224                BuiltinKind::Colon => Box::pin(async { builtin::colon() }),
225                BuiltinKind::False => Box::pin(async { builtin::false_cmd() }),
226                BuiltinKind::True => Box::pin(async { builtin::true_cmd() }),
227            };
228
229            restorer.restore_vars();
230            restorer.restore_redirects();
231
232            ret
233        })
234    }
235}