resymgen/
util.rs

1use std::error::Error;
2use std::fmt::{self, Display, Formatter};
3use std::fs;
4use std::path::Path;
5
6use tempfile::{NamedTempFile, PersistError};
7
8use super::data_formats::symgen_yml::{IntFormat, SymGen};
9
10/// Encapsulates a collection of similar errors for different files.
11#[derive(Debug)]
12pub struct MultiFileError {
13    pub base_msg: String,
14    pub errors: Vec<(String, Box<dyn Error>)>,
15}
16
17impl Error for MultiFileError {}
18
19impl Display for MultiFileError {
20    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
21        write!(f, "{} [{} file(s)]", self.base_msg, self.errors.len())?;
22        for (filename, err) in self.errors.iter() {
23            write!(f, "\n{}: {}", filename, err)?;
24        }
25        Ok(())
26    }
27}
28
29/// Persist the temporary file at the target path.
30///
31/// This wraps `NamedTempFile::persist()` with fallback to manual copying.
32/// On some systems, the normal `persist()` might not always work. For example, on Unix
33/// systems `persist()` ultimately relies on `std::fs::rename()`:
34/// https://github.com/Stebalien/tempfile/blob/66aa57f7d3a38234fcd393077366b61d36171e42/src/file/imp/unix.rs#L131
35/// which will fail if the tempfile and target path are not on the same filesystem:
36/// https://doc.rust-lang.org/std/fs/fn.rename.html. Since `/tmp` (the normal return value of
37/// `std::env::temp_dir()` on Linux) can sometimes be mounted on a separate filesystem, this is
38/// a fairly common case.
39///
40/// If the normal `persist()` method fails, try manually copying the tempfile to the destination.
41/// Unlike `std::fs::rename()`, `std::fs::copy()` works even across filesystems.
42pub fn persist_named_temp_file_safe<P: AsRef<Path>>(
43    file: NamedTempFile,
44    new_path: P,
45) -> Result<(), PersistError> {
46    if let Err(e) = file.persist(&new_path) {
47        let file = NamedTempFile::from(e); // Grab the file handle again
48        if let Err(io_err) = fs::copy(file.path(), new_path) {
49            return Err(PersistError {
50                error: io_err,
51                file,
52            });
53        }
54    }
55    Ok(())
56}
57
58/// Recursively write a [`SymGen`] and all its subregions to files, starting with the top-level
59/// file path specified by `top_path`, and using the given `int_format`.
60pub fn symgen_write_recursive<P: AsRef<Path>>(
61    symgen: &SymGen,
62    top_path: P,
63    int_format: IntFormat,
64) -> Result<(), Box<dyn Error>> {
65    for cursor in symgen.cursor(top_path.as_ref()).btraverse() {
66        // Write to a tempfile first, then replace the old one atomically.
67        let output_file = NamedTempFile::new()?;
68        cursor.symgen().write(&output_file, int_format)?;
69        persist_named_temp_file_safe(output_file, cursor.path())?;
70    }
71    Ok(())
72}