1use super::{Context, Input, Result};
20use crate::Env;
21use crate::io::Fd;
22use crate::option::{IgnoreEof as IgnoreEofOption, Interactive, Off};
23use crate::system::Isatty;
24use crate::system::concurrency::WriteAll;
25use std::cell::RefCell;
26
27#[derive(Debug)]
52pub struct IgnoreEof<'a, 'b, S, T> {
53 inner: T,
55 fd: Fd,
57 env: &'a RefCell<&'b mut Env<S>>,
59 message: String,
61}
62
63impl<'a, 'b, S, T> IgnoreEof<'a, 'b, S, T> {
64 pub fn new(inner: T, fd: Fd, env: &'a RefCell<&'b mut Env<S>>, message: String) -> Self {
79 Self {
80 inner,
81 fd,
82 env,
83 message,
84 }
85 }
86}
87
88impl<S, T: Clone> Clone for IgnoreEof<'_, '_, S, T> {
90 fn clone(&self) -> Self {
91 Self {
92 inner: self.inner.clone(),
93 fd: self.fd,
94 env: self.env,
95 message: self.message.clone(),
96 }
97 }
98}
99
100impl<S: Isatty + WriteAll, T: Input> Input for IgnoreEof<'_, '_, S, T> {
101 #[allow(
102 clippy::await_holding_refcell_ref,
103 reason = "other decorators, the parser, or the executor do not run concurrently with this method"
104 )]
105 async fn next_line(&mut self, context: &Context) -> Result {
106 let mut remaining_tries = 50;
107
108 loop {
109 let line = self.inner.next_line(context).await?;
110
111 let env = self.env.borrow();
112
113 let should_break = !line.is_empty()
114 || env.options.get(Interactive) == Off
115 || env.options.get(IgnoreEofOption) == Off
116 || remaining_tries == 0
117 || !env.system.isatty(self.fd);
118 if should_break {
119 return Ok(line);
120 }
121
122 env.system.print_error(&self.message).await;
123 remaining_tries -= 1;
124 }
125 }
126}
127
128#[cfg(test)]
129mod tests {
130 use super::super::Memory;
131 use super::*;
132 use crate::option::On;
133 use crate::system::r#virtual::{FdBody, FileBody, Inode, OpenFileDescription, VirtualSystem};
134 use crate::system::{Concurrent, Mode};
135 use crate::test_helper::assert_stderr;
136 use enumset::EnumSet;
137 use futures_util::FutureExt as _;
138 use std::rc::Rc;
139
140 struct EofStub<T> {
143 inner: T,
144 count: usize,
145 }
146
147 impl<T> Input for EofStub<T>
148 where
149 T: Input,
150 {
151 async fn next_line(&mut self, context: &Context) -> Result {
152 if let Some(remaining) = self.count.checked_sub(1) {
153 self.count = remaining;
154 Ok("".to_string())
155 } else {
156 self.inner.next_line(context).await
157 }
158 }
159 }
160
161 fn set_stdin_to_tty(system: &mut VirtualSystem) {
162 system
163 .current_process_mut()
164 .set_fd(
165 Fd::STDIN,
166 FdBody {
167 open_file_description: Rc::new(RefCell::new(OpenFileDescription::new(
168 Rc::new(RefCell::new(Inode {
169 body: FileBody::Terminal { content: vec![] },
170 permissions: Mode::empty(),
171 })),
172 0,
173 true,
174 true,
175 false,
176 false,
177 ))),
178 flags: EnumSet::empty(),
179 },
180 )
181 .unwrap();
182 }
183
184 fn set_stdin_to_regular_file(system: &mut VirtualSystem) {
185 system
186 .current_process_mut()
187 .set_fd(
188 Fd::STDIN,
189 FdBody {
190 open_file_description: Rc::new(RefCell::new(OpenFileDescription::new(
191 Rc::new(RefCell::new(Inode {
192 body: FileBody::Regular {
193 content: vec![],
194 is_native_executable: false,
195 },
196 permissions: Mode::empty(),
197 })),
198 0,
199 true,
200 true,
201 false,
202 false,
203 ))),
204 flags: EnumSet::empty(),
205 },
206 )
207 .unwrap();
208 }
209
210 #[test]
211 fn decorator_reads_from_inner_input() {
212 let mut system = VirtualSystem::new();
213 set_stdin_to_tty(&mut system);
214 let mut env = Env::with_system(Rc::new(Concurrent::new(system)));
215 env.options.set(Interactive, On);
216 env.options.set(IgnoreEofOption, On);
217 let ref_env = RefCell::new(&mut env);
218 let mut decorator = IgnoreEof::new(
219 Memory::new("echo foo\n"),
220 Fd::STDIN,
221 &ref_env,
222 "unused".to_string(),
223 );
224
225 let result = decorator
226 .next_line(&Context::default())
227 .now_or_never()
228 .unwrap();
229 assert_eq!(result.unwrap(), "echo foo\n");
230 }
231
232 #[test]
233 fn decorator_reads_input_again_on_eof() {
234 let mut system = VirtualSystem::new();
235 set_stdin_to_tty(&mut system);
236 let state = system.state.clone();
237 let mut env = Env::with_system(Rc::new(Concurrent::new(system)));
238 env.options.set(Interactive, On);
239 env.options.set(IgnoreEofOption, On);
240 let ref_env = RefCell::new(&mut env);
241 let mut decorator = IgnoreEof::new(
242 EofStub {
243 inner: Memory::new("echo foo\n"),
244 count: 1,
245 },
246 Fd::STDIN,
247 &ref_env,
248 "EOF ignored\n".to_string(),
249 );
250
251 let result = decorator
252 .next_line(&Context::default())
253 .now_or_never()
254 .unwrap();
255 assert_eq!(result.unwrap(), "echo foo\n");
256 assert_stderr(&state, |stderr| assert_eq!(stderr, "EOF ignored\n"));
257 }
258
259 #[test]
260 fn decorator_reads_input_up_to_50_times() {
261 let mut system = VirtualSystem::new();
262 set_stdin_to_tty(&mut system);
263 let state = system.state.clone();
264 let mut env = Env::with_system(Rc::new(Concurrent::new(system)));
265 env.options.set(Interactive, On);
266 env.options.set(IgnoreEofOption, On);
267 let ref_env = RefCell::new(&mut env);
268 let mut decorator = IgnoreEof::new(
269 EofStub {
270 inner: Memory::new("echo foo\n"),
271 count: 50,
272 },
273 Fd::STDIN,
274 &ref_env,
275 "EOF ignored\n".to_string(),
276 );
277
278 let result = decorator
279 .next_line(&Context::default())
280 .now_or_never()
281 .unwrap();
282 assert_eq!(result.unwrap(), "echo foo\n");
283 assert_stderr(&state, |stderr| {
284 assert_eq!(stderr, "EOF ignored\n".repeat(50))
285 });
286 }
287
288 #[test]
289 fn decorator_returns_empty_line_after_reading_51_times() {
290 let mut system = VirtualSystem::new();
291 set_stdin_to_tty(&mut system);
292 let state = system.state.clone();
293 let mut env = Env::with_system(Rc::new(Concurrent::new(system)));
294 env.options.set(Interactive, On);
295 env.options.set(IgnoreEofOption, On);
296 let ref_env = RefCell::new(&mut env);
297 let mut decorator = IgnoreEof::new(
298 EofStub {
299 inner: Memory::new("echo foo\n"),
300 count: 51,
301 },
302 Fd::STDIN,
303 &ref_env,
304 "EOF ignored\n".to_string(),
305 );
306
307 let result = decorator
308 .next_line(&Context::default())
309 .now_or_never()
310 .unwrap();
311 assert_eq!(result.unwrap(), "");
312 assert_stderr(&state, |stderr| {
313 assert_eq!(stderr, "EOF ignored\n".repeat(50))
314 });
315 }
316
317 #[test]
318 fn decorator_returns_immediately_if_not_interactive() {
319 let mut system = VirtualSystem::new();
320 set_stdin_to_tty(&mut system);
321 let state = system.state.clone();
322 let mut env = Env::with_system(Rc::new(Concurrent::new(system)));
323 env.options.set(Interactive, Off);
324 env.options.set(IgnoreEofOption, On);
325 let ref_env = RefCell::new(&mut env);
326 let mut decorator = IgnoreEof::new(
327 EofStub {
328 inner: Memory::new("echo foo\n"),
329 count: 1,
330 },
331 Fd::STDIN,
332 &ref_env,
333 "EOF ignored\n".to_string(),
334 );
335
336 let result = decorator
337 .next_line(&Context::default())
338 .now_or_never()
339 .unwrap();
340 assert_eq!(result.unwrap(), "");
341 assert_stderr(&state, |stderr| assert_eq!(stderr, ""));
342 }
343
344 #[test]
345 fn decorator_returns_immediately_if_not_ignore_eof() {
346 let mut system = VirtualSystem::new();
347 set_stdin_to_tty(&mut system);
348 let state = system.state.clone();
349 let mut env = Env::with_system(Rc::new(Concurrent::new(system)));
350 env.options.set(Interactive, On);
351 env.options.set(IgnoreEofOption, Off);
352 let ref_env = RefCell::new(&mut env);
353 let mut decorator = IgnoreEof::new(
354 EofStub {
355 inner: Memory::new("echo foo\n"),
356 count: 1,
357 },
358 Fd::STDIN,
359 &ref_env,
360 "EOF ignored\n".to_string(),
361 );
362
363 let result = decorator
364 .next_line(&Context::default())
365 .now_or_never()
366 .unwrap();
367 assert_eq!(result.unwrap(), "");
368 assert_stderr(&state, |stderr| assert_eq!(stderr, ""));
369 }
370
371 #[test]
372 fn decorator_returns_immediately_if_not_terminal() {
373 let mut system = VirtualSystem::new();
374 set_stdin_to_regular_file(&mut system);
375 let state = system.state.clone();
376 let mut env = Env::with_system(Rc::new(Concurrent::new(system)));
377 env.options.set(Interactive, On);
378 env.options.set(IgnoreEofOption, On);
379 let ref_env = RefCell::new(&mut env);
380 let mut decorator = IgnoreEof::new(
381 EofStub {
382 inner: Memory::new("echo foo\n"),
383 count: 1,
384 },
385 Fd::STDIN,
386 &ref_env,
387 "EOF ignored\n".to_string(),
388 );
389
390 let result = decorator
391 .next_line(&Context::default())
392 .now_or_never()
393 .unwrap();
394 assert_eq!(result.unwrap(), "");
395 assert_stderr(&state, |stderr| assert_eq!(stderr, ""));
396 }
397}