use super::types::{Batch, Command, ReportStyle};
use std::path::{Path, PathBuf};
pub(super) struct Fan<'a> {
pub(super) command: &'static str,
pub(super) designs: Vec<PathBuf>,
pub(super) out: Option<(&'static str, PathBuf)>,
pub(super) out_dir: Option<PathBuf>,
pub(super) extension: &'a str,
pub(super) style: ReportStyle,
}
fn target_in(dir: &Path, design: &Path, extension: &str) -> PathBuf {
let stem = design.file_stem().map_or_else(
|| "design".to_string(),
|s| s.to_string_lossy().into_owned(),
);
dir.join(format!("{stem}.{extension}"))
}
fn name_key(target: Option<&PathBuf>) -> Option<String> {
target
.and_then(|path| path.file_name())
.map(|name| name.to_string_lossy().to_ascii_lowercase())
}
fn collision(command: &str, designs: &[PathBuf], targets: &[Option<PathBuf>]) -> Option<String> {
for (later, target) in targets.iter().enumerate() {
let Some(name) = name_key(target.as_ref()) else {
continue;
};
let Some(earlier) = targets
.iter()
.take(later)
.position(|other| name_key(other.as_ref()).as_deref() == Some(name.as_str()))
else {
continue;
};
return Some(format!(
"\"{command}\": {} and {} would both be written to {}; rename one of them, or run \
them separately",
designs.get(earlier)?.display(),
designs.get(later)?.display(),
target.as_ref()?.display(),
));
}
None
}
pub(super) fn fan_out(
fan: Fan<'_>,
build: impl Fn(PathBuf, Option<PathBuf>) -> Command,
) -> Result<Command, String> {
let Fan {
command,
designs,
out,
out_dir,
extension,
style,
} = fan;
if let (Some((flag, _)), Some(_)) = (&out, &out_dir) {
return Err(format!(
"give one of {flag} FILE and --out-dir DIR, not both"
));
}
if let [only] = designs.as_slice() {
let target = match (out, &out_dir) {
(Some((_, file)), _) => Some(file),
(None, Some(dir)) => Some(target_in(dir, only, extension)),
(None, None) => None,
};
return Ok(build(only.clone(), target));
}
if let Some((flag, _)) = &out {
return Err(format!(
"{flag} names one file, but {} design files were given: use --out-dir DIR to write \
one file for each",
designs.len()
));
}
let targets: Vec<Option<PathBuf>> = designs
.iter()
.map(|design| {
out_dir
.as_deref()
.map(|dir| target_in(dir, design, extension))
})
.collect();
if let Some(message) = collision(command, &designs, &targets) {
return Err(message);
}
let items = designs
.into_iter()
.zip(targets)
.map(|(design, target)| build(design, target))
.collect();
Ok(Command::Batch(Batch {
items,
out_dir,
style,
}))
}