1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
use std::{
fs::{self, File},
io::{self, Write},
path::{Path, PathBuf},
};
use rand::{self, distributions::Alphanumeric, Rng};
use crate::Error;
/// An output abstraction for associating a [`Write`] object with some metadata.
#[derive(Debug, Clone, Eq, PartialEq)]
pub enum OutputKind<'a> {
/// An output file.
File {
/// The path of the output file.
path: &'a Path,
/// The path to a temporary file in the same directory as the output file, which is used to
/// make the write operation transactional and ensures that the file at `path` is not
/// damaged if it pre-exists.
tmp_path: PathBuf,
/// If `overwrite_if_exists` is `true`, then the file at `path` will be overwritten.
overwrite_if_exists: bool,
},
/// Stdout.
Stdout,
}
impl<'a> OutputKind<'a> {
/// Returns a new `OutputKind::File`.
pub fn file(path: &'a Path, overwrite_if_exists: bool) -> Self {
let collision_resistant_string = rand::thread_rng()
.sample_iter(&Alphanumeric)
.take(5)
.map(char::from)
.collect::<String>();
let extension = format!(".{}.tmp", &collision_resistant_string);
let tmp_path = path.with_extension(extension);
OutputKind::File {
path,
tmp_path,
overwrite_if_exists,
}
}
/// Returns a `Result` containing a `Write` trait object.
pub(super) fn get(&self) -> Result<Box<dyn Write>, Error> {
match self {
OutputKind::File {
path,
tmp_path,
overwrite_if_exists,
} => {
if path.exists() && !*overwrite_if_exists {
return Err(Error::FileAlreadyExists(PathBuf::from(path)));
}
let file = File::create(tmp_path).map_err(|error| Error::IoError {
context: format!("failed to create {}", tmp_path.display()),
error,
})?;
let write: Box<dyn Write> = Box::new(file);
Ok(write)
}
OutputKind::Stdout if cfg!(test) => Ok(Box::new(io::sink())),
OutputKind::Stdout => Ok(Box::new(io::stdout())),
}
}
/// When `self` is `OutputKind::File`, causes the temp file to be renamed (moved) to its `path`.
/// When `self` is `OutputKind::Stdout` this is a no-op.
pub(super) fn commit(self) -> Result<(), Error> {
match self {
OutputKind::File { path, tmp_path, .. } => {
fs::rename(&tmp_path, path).map_err(|error| Error::IoError {
context: format!(
"could not move tmp file {} to destination {}",
tmp_path.display(),
path.display()
),
error,
})
}
OutputKind::Stdout => Ok(()),
}
}
}