use std::process::{
Command,
Stdio,
};
use crate::IO;
use super::LinesIter;
use crate::UILock;
use super::Result;
mod error;
pub use error::LocalIOError;
#[cfg(all(feature = "test_local_io", test))]
mod test;
fn spawn_transfer<'a, I, O>(
i: I,
mut o: O,
) -> std::thread::JoinHandle<usize> where
I: Iterator<Item = &'a str>,
O: std::io::Write + std::marker::Send + 'static,
{
let aggregated_input = i.fold(String::new(),|mut s, a| {s.push_str(a); s});
std::thread::spawn(move || {
let inputlen = aggregated_input.len();
o.write_all(aggregated_input.as_bytes()).expect("Pipe forwarding failed.");
inputlen
})
}
#[non_exhaustive]
pub struct LocalIO {
}
impl LocalIO {
pub fn new() -> Self {
Self{}
}
fn write_internal<'a>(
path: &str,
append: bool,
data: impl Iterator<Item = &'a str>,
) -> std::io::Result<usize> {
use std::io::Write;
let mut file = std::fs::OpenOptions::new()
.write(true)
.append(append)
.truncate(!append)
.create(true)
.open(path)
?;
let mut written = 0;
for line in data {
written += line.len();
file.write_all(line.as_bytes())?;
}
file.flush()?;
Ok(written)
}
}
impl Default for LocalIO {
fn default() -> Self {
Self::new()
}
}
impl IO for LocalIO {
fn run_command(&mut self,
_ui: &mut UILock,
command: String,
) -> Result<()> {
let shell = std::env::var("SHELL").unwrap_or("sh".to_owned());
let res = Command::new(shell)
.arg("-c")
.arg(command)
.spawn() .map_err(LocalIOError::ChildCreationFailed)?
.wait()
.map_err(LocalIOError::ChildFailedToStart)?
;
if !(res.success()) {
return Err(LocalIOError::child_return_res(res.code()).into());
}
Ok(())
}
fn run_read_command(&mut self,
_ui: &mut UILock,
command: String,
) -> Result<String> {
let shell = std::env::var("SHELL").unwrap_or("sh".to_owned());
let child = Command::new(shell)
.arg("-c")
.arg(command)
.stdout(Stdio::piped())
.spawn()
.map_err(LocalIOError::ChildCreationFailed)?
;
let res = child.wait_with_output()
.map_err(LocalIOError::ChildFailedToStart)
?;
if !(res.status.success()) {
return Err(LocalIOError::child_return_res(res.status.code()).into());
}
let output = String::from_utf8(res.stdout)
.map_err(LocalIOError::BadUtf8)?
;
Ok(output)
}
fn run_write_command(&mut self,
_ui: &mut UILock,
command: String,
input: LinesIter,
) -> Result<usize> {
let shell = std::env::var("SHELL").unwrap_or("sh".to_owned());
let mut child = Command::new(shell)
.arg("-c")
.arg(command)
.stdin(Stdio::piped())
.spawn()
.map_err(LocalIOError::ChildCreationFailed)?
;
let i = spawn_transfer(
input,
child.stdin.take().unwrap(),
);
let res = child.wait()
.map_err(LocalIOError::ChildFailedToStart)
;
let transfer_res = i.join().map_err(|_|LocalIOError::ChildPipingError)?;
let res = res?;
if !(res.success()) {
return Err(LocalIOError::child_return_res(res.code()).into());
}
Ok(transfer_res)
}
fn run_transform_command(&mut self,
_ui: &mut UILock,
command: String,
input: LinesIter,
) -> Result<String> {
let shell = std::env::var("SHELL").unwrap_or("sh".to_owned());
let mut child = Command::new(shell)
.arg("-c")
.arg(command)
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.spawn()
.map_err(LocalIOError::ChildCreationFailed)?
;
let i = spawn_transfer(
input,
child.stdin.take().unwrap(),
);
let res = child.wait_with_output()
.map_err(LocalIOError::ChildFailedToStart)
;
let _transfer_res = i.join().map_err(|_|LocalIOError::ChildPipingError)?;
let res = res?;
if !(res.status.success()) {
return Err(LocalIOError::child_return_res(res.status.code()).into());
}
let output = String::from_utf8_lossy(&res.stdout).into_owned();
Ok(output)
}
fn write_file(&mut self,
path: &str,
append: bool,
data: LinesIter,
) -> Result<usize> {
Self::write_internal(path, append, data)
.map_err(|e| LocalIOError::file_error(path, e).into())
}
fn read_file(&mut self,
path: &str,
must_exist: bool,
) -> Result<String> {
match std::fs::read_to_string(path)
.map_err(|e| LocalIOError::file_error(path, e))
{
Ok(data) => Ok(data),
Err(e) => match e {
LocalIOError::FileNotFound{..} => {
if must_exist { Err(e.into()) } else { Ok(String::new()) }
},
_ => Err(e.into()),
},
}
}
}