use std::{
fs,
path::{Path, PathBuf},
};
use crate::LadduDataResult;
use super::{WritePlan, sink_error};
#[derive(Clone, Debug)]
pub struct OutputPath {
base: PathBuf,
mode: OutputMode,
}
#[derive(Clone, Copy, Debug, Default)]
pub enum OutputMode {
#[default]
Auto,
SingleFile,
PerRankFiles,
}
impl OutputPath {
pub fn new(path: impl Into<PathBuf>) -> Self {
Self {
base: path.into(),
mode: OutputMode::Auto,
}
}
pub fn with_mode(mut self, mode: OutputMode) -> Self {
self.mode = mode;
self
}
pub fn base(&self) -> &Path {
&self.base
}
pub fn mode(&self) -> OutputMode {
self.mode
}
pub fn resolve(&self, plan: WritePlan, default_extension: &str) -> LadduDataResult<PathBuf> {
let mode = match self.mode {
OutputMode::Auto if plan.is_distributed() => OutputMode::PerRankFiles,
OutputMode::Auto => OutputMode::SingleFile,
mode => mode,
};
match mode {
OutputMode::SingleFile => {
if plan.is_distributed() {
return Err(sink_error(
"resolve output path",
self.base.display(),
"single-file output is unsafe with multiple MPI ranks; use per-rank output",
));
}
Ok(self.base.clone())
}
OutputMode::PerRankFiles => Ok(per_rank_path(
&self.base,
plan.rank(),
plan.nranks(),
default_extension,
)),
OutputMode::Auto => unreachable!(),
}
}
pub fn create_parent_dirs(path: &Path) -> LadduDataResult<()> {
if let Some(parent) = path.parent()
&& !parent.as_os_str().is_empty()
{
fs::create_dir_all(parent)
.map_err(|e| sink_error("create output directory", parent.display(), e))?;
}
Ok(())
}
}
fn per_rank_path(base: &Path, rank: usize, nranks: usize, default_extension: &str) -> PathBuf {
if base.extension().is_none() {
let ext = default_extension.trim_start_matches('.');
return base.join(format!("part-rank{rank:05}-of{nranks:05}.{ext}"));
}
let parent = base.parent().unwrap_or_else(|| Path::new(""));
let stem = base.file_stem().unwrap_or_default().to_string_lossy();
let ext = base.extension().unwrap_or_default().to_string_lossy();
parent.join(format!("{stem}.rank{rank:05}-of{nranks:05}.{ext}"))
}