use super::{Context, Input, Result};
use crate::Env;
use crate::io::Fd;
use crate::option::{IgnoreEof as IgnoreEofOption, Interactive, Off};
use crate::system::{Fcntl, Isatty, Write};
use std::cell::RefCell;
#[derive(Debug)]
pub struct IgnoreEof<'a, 'b, S, T> {
inner: T,
fd: Fd,
env: &'a RefCell<&'b mut Env<S>>,
message: String,
}
impl<'a, 'b, S, T> IgnoreEof<'a, 'b, S, T> {
pub fn new(inner: T, fd: Fd, env: &'a RefCell<&'b mut Env<S>>, message: String) -> Self {
Self {
inner,
fd,
env,
message,
}
}
}
impl<S, T: Clone> Clone for IgnoreEof<'_, '_, S, T> {
fn clone(&self) -> Self {
Self {
inner: self.inner.clone(),
fd: self.fd,
env: self.env,
message: self.message.clone(),
}
}
}
impl<S: Fcntl + Isatty + Write, T: Input> Input for IgnoreEof<'_, '_, S, T> {
#[allow(clippy::await_holding_refcell_ref)]
async fn next_line(&mut self, context: &Context) -> Result {
let mut remaining_tries = 50;
loop {
let line = self.inner.next_line(context).await?;
let env = self.env.borrow();
let should_break = !line.is_empty()
|| env.options.get(Interactive) == Off
|| env.options.get(IgnoreEofOption) == Off
|| remaining_tries == 0
|| !env.system.isatty(self.fd);
if should_break {
return Ok(line);
}
env.system.print_error(&self.message).await;
remaining_tries -= 1;
}
}
}
#[cfg(test)]
mod tests {
use super::super::Memory;
use super::*;
use crate::option::On;
use crate::system::Mode;
use crate::system::r#virtual::{FdBody, FileBody, Inode, OpenFileDescription, VirtualSystem};
use crate::test_helper::assert_stderr;
use enumset::EnumSet;
use futures_util::FutureExt as _;
use std::rc::Rc;
struct EofStub<T> {
inner: T,
count: usize,
}
impl<T> Input for EofStub<T>
where
T: Input,
{
async fn next_line(&mut self, context: &Context) -> Result {
if let Some(remaining) = self.count.checked_sub(1) {
self.count = remaining;
Ok("".to_string())
} else {
self.inner.next_line(context).await
}
}
}
fn set_stdin_to_tty(system: &mut VirtualSystem) {
system
.current_process_mut()
.set_fd(
Fd::STDIN,
FdBody {
open_file_description: Rc::new(RefCell::new(OpenFileDescription::new(
Rc::new(RefCell::new(Inode {
body: FileBody::Terminal { content: vec![] },
permissions: Mode::empty(),
})),
0,
true,
true,
false,
false,
))),
flags: EnumSet::empty(),
},
)
.unwrap();
}
fn set_stdin_to_regular_file(system: &mut VirtualSystem) {
system
.current_process_mut()
.set_fd(
Fd::STDIN,
FdBody {
open_file_description: Rc::new(RefCell::new(OpenFileDescription::new(
Rc::new(RefCell::new(Inode {
body: FileBody::Regular {
content: vec![],
is_native_executable: false,
},
permissions: Mode::empty(),
})),
0,
true,
true,
false,
false,
))),
flags: EnumSet::empty(),
},
)
.unwrap();
}
#[test]
fn decorator_reads_from_inner_input() {
let mut system = VirtualSystem::new();
set_stdin_to_tty(&mut system);
let mut env = Env::with_system(system);
env.options.set(Interactive, On);
env.options.set(IgnoreEofOption, On);
let ref_env = RefCell::new(&mut env);
let mut decorator = IgnoreEof::new(
Memory::new("echo foo\n"),
Fd::STDIN,
&ref_env,
"unused".to_string(),
);
let result = decorator
.next_line(&Context::default())
.now_or_never()
.unwrap();
assert_eq!(result.unwrap(), "echo foo\n");
}
#[test]
fn decorator_reads_input_again_on_eof() {
let mut system = VirtualSystem::new();
set_stdin_to_tty(&mut system);
let state = system.state.clone();
let mut env = Env::with_system(system);
env.options.set(Interactive, On);
env.options.set(IgnoreEofOption, On);
let ref_env = RefCell::new(&mut env);
let mut decorator = IgnoreEof::new(
EofStub {
inner: Memory::new("echo foo\n"),
count: 1,
},
Fd::STDIN,
&ref_env,
"EOF ignored\n".to_string(),
);
let result = decorator
.next_line(&Context::default())
.now_or_never()
.unwrap();
assert_eq!(result.unwrap(), "echo foo\n");
assert_stderr(&state, |stderr| assert_eq!(stderr, "EOF ignored\n"));
}
#[test]
fn decorator_reads_input_up_to_50_times() {
let mut system = VirtualSystem::new();
set_stdin_to_tty(&mut system);
let state = system.state.clone();
let mut env = Env::with_system(system);
env.options.set(Interactive, On);
env.options.set(IgnoreEofOption, On);
let ref_env = RefCell::new(&mut env);
let mut decorator = IgnoreEof::new(
EofStub {
inner: Memory::new("echo foo\n"),
count: 50,
},
Fd::STDIN,
&ref_env,
"EOF ignored\n".to_string(),
);
let result = decorator
.next_line(&Context::default())
.now_or_never()
.unwrap();
assert_eq!(result.unwrap(), "echo foo\n");
assert_stderr(&state, |stderr| {
assert_eq!(stderr, "EOF ignored\n".repeat(50))
});
}
#[test]
fn decorator_returns_empty_line_after_reading_51_times() {
let mut system = VirtualSystem::new();
set_stdin_to_tty(&mut system);
let state = system.state.clone();
let mut env = Env::with_system(system);
env.options.set(Interactive, On);
env.options.set(IgnoreEofOption, On);
let ref_env = RefCell::new(&mut env);
let mut decorator = IgnoreEof::new(
EofStub {
inner: Memory::new("echo foo\n"),
count: 51,
},
Fd::STDIN,
&ref_env,
"EOF ignored\n".to_string(),
);
let result = decorator
.next_line(&Context::default())
.now_or_never()
.unwrap();
assert_eq!(result.unwrap(), "");
assert_stderr(&state, |stderr| {
assert_eq!(stderr, "EOF ignored\n".repeat(50))
});
}
#[test]
fn decorator_returns_immediately_if_not_interactive() {
let mut system = VirtualSystem::new();
set_stdin_to_tty(&mut system);
let state = system.state.clone();
let mut env = Env::with_system(system);
env.options.set(Interactive, Off);
env.options.set(IgnoreEofOption, On);
let ref_env = RefCell::new(&mut env);
let mut decorator = IgnoreEof::new(
EofStub {
inner: Memory::new("echo foo\n"),
count: 1,
},
Fd::STDIN,
&ref_env,
"EOF ignored\n".to_string(),
);
let result = decorator
.next_line(&Context::default())
.now_or_never()
.unwrap();
assert_eq!(result.unwrap(), "");
assert_stderr(&state, |stderr| assert_eq!(stderr, ""));
}
#[test]
fn decorator_returns_immediately_if_not_ignore_eof() {
let mut system = VirtualSystem::new();
set_stdin_to_tty(&mut system);
let state = system.state.clone();
let mut env = Env::with_system(system);
env.options.set(Interactive, On);
env.options.set(IgnoreEofOption, Off);
let ref_env = RefCell::new(&mut env);
let mut decorator = IgnoreEof::new(
EofStub {
inner: Memory::new("echo foo\n"),
count: 1,
},
Fd::STDIN,
&ref_env,
"EOF ignored\n".to_string(),
);
let result = decorator
.next_line(&Context::default())
.now_or_never()
.unwrap();
assert_eq!(result.unwrap(), "");
assert_stderr(&state, |stderr| assert_eq!(stderr, ""));
}
#[test]
fn decorator_returns_immediately_if_not_terminal() {
let mut system = VirtualSystem::new();
set_stdin_to_regular_file(&mut system);
let state = system.state.clone();
let mut env = Env::with_system(system);
env.options.set(Interactive, On);
env.options.set(IgnoreEofOption, On);
let ref_env = RefCell::new(&mut env);
let mut decorator = IgnoreEof::new(
EofStub {
inner: Memory::new("echo foo\n"),
count: 1,
},
Fd::STDIN,
&ref_env,
"EOF ignored\n".to_string(),
);
let result = decorator
.next_line(&Context::default())
.now_or_never()
.unwrap();
assert_eq!(result.unwrap(), "");
assert_stderr(&state, |stderr| assert_eq!(stderr, ""));
}
}