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