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