use std::{
fs::File,
io::{self, Read, Write},
path::Path,
process::{Command, Stdio},
sync::{Arc, RwLock},
};
use crate::{
config::{self, Config},
dir_walker::walk_dir,
nu::{processing_failed, CompletionsProcessor},
};
use anyhow::{anyhow, Result};
use beau_collector::BeauCollector as _;
use log::{as_debug, debug, error, trace};
use tempfile::tempdir;
fn generate_patch(source: &Path, generated: &Path, destination: &Path) -> Result<()> {
trace!(
source = as_debug!(source), generated = as_debug!(generated),
destination = as_debug!(destination);
"checking for file differences"
);
let mut process = Command::new("diff") .arg(generated) .arg(source) .stdout(Stdio::piped())
.spawn()?;
let mut stdout = process.stdout.take().unwrap();
let mut buf = [0u8; 0x100];
let first_read = {
let n = stdout.read(&mut buf)?;
&buf[..n]
};
if !first_read.is_empty() {
trace!(first_read = first_read.len(); "read bytes from stdout, writing diff to file");
match File::create(destination) {
Ok(mut destination) => {
destination.write_all(first_read)?;
io::copy(&mut stdout, &mut destination)?;
let status = process.wait()?;
if let Some(code) = status.code() && code < 2 {
Ok(())
} else {
Ok(status.exit_ok()?)
}
}
Err(e) => {
error!(error = as_debug!(e); "error generating patch");
Err(if let Err(err_killing) = process.kill() {
error!(error = as_debug!(err_killing); "error stopping diff process");
anyhow!("while processing this error:\n\t{e}\nanother error occurred:\n\t{err_killing:?}")
} else {
e.into()
})
}
}
} else {
debug!(source = as_debug!(source); "source file did not differ");
Ok(())
}
}
pub(crate) fn generate_patches(opts: &config::PatchesGenerateOptions) -> Result<()> {
let freshly_generated_store = tempdir()?;
trace!(
sources = opts.sources.len(),
from = as_debug!(opts.from),
to = as_debug!(opts.to),
"temp dir" = as_debug!(freshly_generated_store.path());
"generating patches"
);
let processor = CompletionsProcessor::default();
let regeneration_errors: Arc<RwLock<Vec<Result<()>>>> = Default::default();
for source in opts.sources.iter() {
walk_dir(
source.as_ref(),
freshly_generated_store.path(),
|path, freshly_generated_store| {
trace!(path = as_debug!(path); "generating patch");
let result =
processor.process_file_given_output_dir(&path, freshly_generated_store);
let freshly_generated = match result {
Ok(freshly_generated) => freshly_generated,
Err(err) => {
if Config::fail_fast() {
return processing_failed(source, err).map(|_| unreachable!());
} else {
regeneration_errors
.write()
.expect("rwlock write access")
.push(Err(err));
return Ok(());
}
}
};
let Some(file_name) = path.file_name() else {
error!(path = as_debug!(path); "expected file to have filename");
return Err(anyhow!("expected {path:?} to have filename"));
};
let modified_source = opts.from.join(file_name).with_extension("nu");
generate_patch(
&modified_source,
&freshly_generated,
&opts.to.join(
freshly_generated
.with_extension("patch")
.file_name()
.ok_or_else(|| anyhow!("file had no name: {freshly_generated:?}"))?,
),
)
},
)?;
}
let mut regeneration_errors_2 = vec![];
regeneration_errors
.write()
.expect("rwlock write access")
.drain(..)
.for_each(|result| regeneration_errors_2.push(result));
regeneration_errors_2.into_iter().bcollect::<Vec<()>>()?;
Ok(())
}