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