use super::args::Source;
use std::cell::RefCell;
use std::ffi::CString;
use thiserror::Error;
use yash_env::Env;
use yash_env::input::Echo;
use yash_env::input::FdReader2;
use yash_env::input::IgnoreEof;
use yash_env::input::Reporter;
use yash_env::io::Fd;
use yash_env::io::move_fd_internal;
use yash_env::option::Option::Interactive;
use yash_env::option::State::{Off, On};
use yash_env::parser::Config;
use yash_env::system::{
Close, Dup, Errno, Fcntl, Fstat, Isatty, Mode, OfdAccess, Open, OpenFlag, Read, Signals, Write,
};
use yash_prompt::Prompter;
use yash_syntax::input::InputObject;
use yash_syntax::input::Memory;
use yash_syntax::parser::lex::Lexer;
use yash_syntax::source::Source as SyntaxSource;
#[derive(Clone, Debug, Eq, Error, PartialEq)]
#[error("cannot open script file '{path}': {errno}")]
pub struct PrepareInputError<'a> {
pub errno: Errno,
pub path: &'a str,
}
pub async fn prepare_input<'s, 'i, 'e, S>(
env: &'i RefCell<&mut Env<S>>,
source: &'s Source,
) -> Result<Lexer<'i>, PrepareInputError<'e>>
where
's: 'i + 'e,
S: Close + Dup + Fcntl + Fstat + Isatty + Open + Read + Signals + Write + 'static,
{
fn lexer_with_input_and_source<'a>(
input: Box<dyn InputObject + 'a>,
source: SyntaxSource,
) -> Lexer<'a> {
let mut config = Config::with_input(input);
config.source = Some(source.into());
config.into()
}
match source {
Source::Stdin => {
let system = env.borrow().system.clone();
if system.isatty(Fd::STDIN) || system.fd_is_pipe(Fd::STDIN) {
_ = system.get_and_set_nonblocking(Fd::STDIN, false);
}
let input = prepare_fd_input(Fd::STDIN, env);
let source = SyntaxSource::Stdin;
Ok(lexer_with_input_and_source(input, source))
}
Source::File { path } => {
let system = env.borrow().system.clone();
let c_path = CString::new(path.as_str()).map_err(|_| PrepareInputError {
errno: Errno::EILSEQ,
path,
})?;
let fd = system
.open(
&c_path,
OfdAccess::ReadOnly,
OpenFlag::CloseOnExec.into(),
Mode::empty(),
)
.await
.and_then(|fd| move_fd_internal(&system, fd))
.map_err(|errno| PrepareInputError { errno, path })?;
let input = prepare_fd_input(fd, env);
let path = path.to_owned();
let source = SyntaxSource::CommandFile { path };
Ok(lexer_with_input_and_source(input, source))
}
Source::String(command) => {
let basic_input = Memory::new(command);
let is_interactive = env.borrow().options.get(Interactive) == On;
let input: Box<dyn InputObject> = if is_interactive {
Box::new(Reporter::new(basic_input, env))
} else {
Box::new(basic_input)
};
let source = SyntaxSource::CommandString;
Ok(lexer_with_input_and_source(input, source))
}
}
}
fn prepare_fd_input<'i, S>(fd: Fd, ref_env: &'i RefCell<&mut Env<S>>) -> Box<dyn InputObject + 'i>
where
S: Fcntl + Isatty + Read + Signals + Write + 'static,
{
let env = ref_env.borrow();
let system = env.system.clone();
let basic_input = Echo::new(FdReader2::new(fd, system), ref_env);
if env.options.get(Interactive) == Off {
Box::new(basic_input)
} else {
let prompter = Prompter::new(basic_input, ref_env);
let reporter = Reporter::new(prompter, ref_env);
let message =
"# Type `exit` to leave the shell when the ignore-eof option is on.\n".to_string();
Box::new(IgnoreEof::new(reporter, fd, ref_env, message))
}
}