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 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225
//! Ephemeral creates a temporary project on your filesystem at any location of your choice
//! so that you can use it while testing anything that works on a rust project - mainly cargo
//! commands/binaries. It can be used to generate projects of other languages too.
//!
//! # INSTALLATION:
//!
//! To use this crate, add it to the dev-dependencies since it is used only during testing:
//!
//! ```toml
//! [dev-dependencies]
//! ephemeral = "0.2"
//! ```
//!
//! # USAGE:
//!
//! To create a project:
//!
//! ```rust
//! use ephemeral::{Project, Dir};
//!
//! fn main() {
//! let project = Project::new("tmp")
//! .add_dir(Dir::new("tmp/foo").add_file("bar", &vec![101u8]))
//! .build();
//!
//! project.clear();
//! }
//! ```
//!
//! This will create a new project in a dir called `tmp` which will contain a dir "foo" which will
//! contain a file `bar` with `e` (101u8) written to the file.
use std::fs::{create_dir_all, remove_dir_all, File as FsFile};
use std::{error::Error, io::Write, path::PathBuf};
/// Project represents a project created on the file system at any user-defined location defined by
/// the path parameter to the `new()` function.
///
/// This struct as a builder so directories and files can be added to it. Remember to call `build()`
/// at the end to create the project in the filesystem. The dirs vector will contain all the dirs &
/// subdirs in the project, which are added when the directory is added to the project.
#[derive(Clone, Debug)]
pub struct Project {
pub path: PathBuf,
dirs: Vec<Dir>,
}
impl Project {
/// Creates a new Project at the specified `path`. This will automatically add a "root" directory
/// to the `dirs` vector.
pub fn new<T>(path: T) -> Project
where
T: Into<PathBuf> + Clone,
{
let path = path.into();
Project {
dirs: vec![Dir::new(&path)],
path,
}
}
/// Creates the project in the filesystem. This will create all the directories & files that are
/// added by using `add_dir()`.
///
/// No function should be chained for this, except for `clear()`.
///
/// Function panics if the directory or file cannot be created or written to.
pub fn build(self) -> Self {
self.dirs.iter().for_each(|dir| {
dir.path.mkdir_p().expect("cannot create directory");
dir.files.iter().for_each(|file| {
let mut fs_file = FsFile::create(&file.path).expect("cannot create file");
fs_file
.write_all(&file.contents)
.expect("cannot write to the file");
})
});
self
}
/// Adds a directory to the chain which will be created when `build()` is called. This accepts
/// a Dir, with the files already attached to it.
///
/// To add a subdirectory, specify the path from
/// the project root.
///
/// To add files to the root of a directory, you need to call `add_dir()` and give a path which
/// matches the project path.
pub fn add_dir(mut self, directory: Dir) -> Self {
self.dirs.push(directory);
self
}
/// Deletes the project from the filesystem. This function can be used to clear the project
/// after running the tests.
///
/// This function panics if a directory cannot be deleted.
pub fn clear(self) {
remove_dir_all(&self.dirs[0].path).expect("can't delete directory")
}
}
/// Represents a dir in the filesystem. Accepts a path and contains a vector of files added.
///
/// To a Dir, you can attach files but not other dirs. To attach subdirectories, add them
/// directly to Project and specify the parent dir in the path.
#[derive(Clone, Debug)]
pub struct Dir {
pub path: PathBuf,
files: Vec<File>,
}
impl Dir {
pub fn new<T: Into<PathBuf>>(path: T) -> Dir {
Dir {
path: path.into(),
files: vec![],
}
}
/// Adds a file to the Dir. Accepts any type that can be converted to a PathBuf just like the
/// rest of the crate. Contents of the file should be specified as well (in bytes).
pub fn add_file<T: Into<PathBuf>>(mut self, path: T, contents: &[u8]) -> Self {
let path = path.into();
let full_path = if path.is_relative() {
self.path.join(path)
} else {
path
};
self.files.push(File::new(full_path, contents));
self
}
}
impl AsMut<Dir> for Dir {
fn as_mut(&mut self) -> &mut Dir {
self
}
}
/// Represents a file stored in the filesystem. Contains the path and the contents in bytes.
#[derive(Clone, Debug)]
pub struct File {
pub path: PathBuf,
contents: Vec<u8>,
}
impl File {
pub fn new<T: Into<PathBuf>>(path: T, contents: &[u8]) -> File {
File {
path: path.into(),
contents: contents.into(),
}
}
}
/// Adds common path-based function. This allows a path-based type to create directories. mkdir_p
/// will recursively create a directory and all of its parent components if they are missing while
/// mkdir will create a single directory.
trait FilePath {
fn mkdir_p(&self) -> Result<(), Box<dyn Error>>;
}
impl FilePath for PathBuf {
fn mkdir_p(&self) -> Result<(), Box<dyn Error>> {
create_dir_all(self).map_err(|err| err.into())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn project_empty_build_creates_dir() {
let path = PathBuf::from("tmp");
let project = Project::new(&path);
project.clone().build();
assert!(path.exists());
project.clear();
}
#[test]
fn project_with_dir_and_files_works() {
let path = PathBuf::from("tmp2");
let project = Project::new(&path)
.add_dir(Dir::new("tmp2/foo").add_file("bar", &vec![101u8]))
.build();
assert!(path.exists());
let path = path.join("foo");
assert!(path.exists());
let path = path.join("bar");
assert!(path.exists());
project.clear();
}
#[test]
fn project_with_1_file_in_root() {
let path = PathBuf::from("tmp3");
let project = Project::new(&path)
.add_dir(Dir::new("tmp3").add_file("bar", b"groot"))
.build();
assert!(path.exists());
let path = path.join("bar");
assert!(path.exists());
project.clear();
}
}