Skip to main content

yash_env/input/
echo.rs

1// This file is part of yash, an extended POSIX shell.
2// Copyright (C) 2024 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//! `Echo` definition
18
19use super::{Context, Input, Result};
20use crate::Env;
21use crate::option::Option::Verbose;
22use crate::option::State::On;
23use crate::system::{Fcntl, Write};
24use std::cell::RefCell;
25
26/// `Input` decorator that echoes the input.
27///
28/// This decorator adds the behavior of the [verbose](crate::option::Verbose)
29/// shell option to the input. If the option is enabled, the input is echoed to
30/// the standard error before it is returned to the caller. Otherwise, the input
31/// is returned as is.
32#[derive(Debug)]
33#[doc(alias = "Verbose")]
34#[must_use = "Echo does nothing unless used by a parser"]
35pub struct Echo<'a, 'b, S, T> {
36    inner: T,
37    env: &'a RefCell<&'b mut Env<S>>,
38}
39
40impl<'a, 'b, S, T> Echo<'a, 'b, S, T> {
41    /// Creates a new `Echo` decorator.
42    ///
43    /// The first argument is the inner `Input` that performs the actual input
44    /// operation. The second argument is the shell environment that contains
45    /// the shell option state and the system interface to print to the standard
46    /// error. It is wrapped in a `RefCell` so that it can be shared with other
47    /// decorators and the parser.
48    pub fn new(inner: T, env: &'a RefCell<&'b mut Env<S>>) -> Self {
49        Self { inner, env }
50    }
51}
52
53// Not derived automatically because S may not implement Clone.
54impl<S, T: Clone> Clone for Echo<'_, '_, S, T> {
55    fn clone(&self) -> Self {
56        Self {
57            inner: self.inner.clone(),
58            env: self.env,
59        }
60    }
61}
62
63impl<S: Fcntl + Write, T: Input> Input for Echo<'_, '_, S, T> {
64    // The RefCell should be local to the calling read-eval loop, so it is safe
65    // to keep the mutable borrow across await points.
66    #[allow(clippy::await_holding_refcell_ref)]
67    async fn next_line(&mut self, context: &Context) -> Result {
68        let line = self.inner.next_line(context).await?;
69
70        let env = &mut **self.env.borrow_mut();
71        if env.options.get(Verbose) == On {
72            env.system.print_error(&line).await;
73        }
74
75        Ok(line)
76    }
77}
78
79#[cfg(test)]
80mod tests {
81    use super::super::Memory;
82    use super::*;
83    use crate::system::r#virtual::VirtualSystem;
84    use crate::tests::assert_stderr;
85    use futures_util::FutureExt;
86    use std::rc::Rc;
87
88    #[test]
89    fn verbose_off() {
90        let system = VirtualSystem::new();
91        let state = Rc::clone(&system.state);
92        let mut env = Env::with_system(system);
93        let ref_env = RefCell::new(&mut env);
94        let memory = Memory::new("echo test\n");
95        let mut echo = Echo::new(memory, &ref_env);
96
97        let line = echo
98            .next_line(&Context::default())
99            .now_or_never()
100            .unwrap()
101            .unwrap();
102        assert_eq!(line, "echo test\n");
103        assert_stderr(&state, |stderr| assert_eq!(stderr, ""));
104    }
105
106    #[test]
107    fn verbose_on() {
108        let system = VirtualSystem::new();
109        let state = Rc::clone(&system.state);
110        let mut env = Env::with_system(system);
111        env.options.set(Verbose, On);
112        let ref_env = RefCell::new(&mut env);
113        let memory = Memory::new("echo test\nfoo");
114        let mut echo = Echo::new(memory, &ref_env);
115
116        let line = echo
117            .next_line(&Context::default())
118            .now_or_never()
119            .unwrap()
120            .unwrap();
121        assert_eq!(line, "echo test\n");
122        assert_stderr(&state, |stderr| assert_eq!(stderr, "echo test\n"));
123
124        let line = echo
125            .next_line(&Context::default())
126            .now_or_never()
127            .unwrap()
128            .unwrap();
129        assert_eq!(line, "foo");
130        assert_stderr(&state, |stderr| assert_eq!(stderr, "echo test\nfoo"));
131    }
132}