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