diff --git a/README.md b/README.md index dfdc89940..cb3902b81 100644 --- a/README.md +++ b/README.md @@ -129,6 +129,7 @@ Nu adheres closely to a set of goals that make up its design philosophy. As feat | cd path | Change to a new path | | cp source path | Copy files | | ls (path) | View the contents of the current or given path | +| mkdir path | Make directories, creates intermediary directories as required. | | date (--utc) | Get the current datetime | | ps | View current processes | | sys | View information about the current system | diff --git a/src/cli.rs b/src/cli.rs index f9223f66e..4fac51167 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -182,6 +182,7 @@ pub async fn cli() -> Result<(), Box> { Arc::new(Remove), Arc::new(Copycp), Arc::new(Open), + Arc::new(Mkdir), Arc::new(Date), Arc::new(Where), Arc::new(Config), diff --git a/src/commands.rs b/src/commands.rs index 6f551421f..e9ef8b244 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -23,6 +23,7 @@ crate mod get; crate mod lines; crate mod ls; crate mod next; +crate mod mkdir; crate mod open; crate mod pick; crate mod plugin; @@ -54,6 +55,7 @@ crate use cp::Copycp; crate use date::Date; crate use exit::Exit; crate use open::Open; +crate use mkdir::Mkdir; crate use rm::Remove; crate use save::Save; crate use skip_while::SkipWhile; diff --git a/src/commands/cp.rs b/src/commands/cp.rs index f1acfc506..de0f8129c 100644 --- a/src/commands/cp.rs +++ b/src/commands/cp.rs @@ -31,11 +31,79 @@ impl Command for Copycp { } } +#[derive(Debug, Eq, Ord, PartialEq, PartialOrd)] +pub struct Res { + pub loc: PathBuf, + pub at: usize, +} + +impl Res {} + +pub struct FileStructure { + root: PathBuf, + resources: Vec, +} + +impl FileStructure { + pub fn new() -> FileStructure { + FileStructure { + root: PathBuf::new(), + resources: Vec::::new(), + } + } + + pub fn set_root(&mut self, path: &Path) { + self.root = path.to_path_buf(); + } + + pub fn paths_applying_with(&mut self, to: F) -> Vec<(PathBuf, PathBuf)> + where + F: Fn((PathBuf, usize)) -> (PathBuf, PathBuf), + { + self.resources + .iter() + .map(|f| (PathBuf::from(&f.loc), f.at)) + .map(|f| to(f)) + .collect() + } + + pub fn walk_decorate(&mut self, start_path: &Path) { + self.set_root(&dunce::canonicalize(start_path).unwrap()); + self.resources = Vec::::new(); + self.build(start_path, 0); + self.resources.sort(); + } + + fn build(&mut self, src: &'a Path, lvl: usize) { + let source = dunce::canonicalize(src).unwrap(); + + if source.is_dir() { + for entry in std::fs::read_dir(&source).unwrap() { + let entry = entry.unwrap(); + let path = entry.path(); + + if path.is_dir() { + self.build(&path, lvl + 1); + } + + self.resources.push(Res { + loc: path.to_path_buf(), + at: lvl, + }); + } + } else { + self.resources.push(Res { + loc: source, + at: lvl, + }); + } + } +} + pub fn cp(args: CommandArgs) -> Result { let mut source = PathBuf::from(args.shell_manager.path()); let mut destination = PathBuf::from(args.shell_manager.path()); - - let mut dst = String::new(); + let name_span = args.call_info.name_span; match args .nth(0) @@ -55,26 +123,283 @@ pub fn cp(args: CommandArgs) -> Result { .as_str() { file => { - dst.push_str(file); destination.push(file); } } - if destination.is_dir() { - if source.is_file() { - let file_name = source.file_name().expect(""); - let file_name = file_name.to_str().expect(""); - destination.push(Path::new(file_name)); - } else if source.is_dir() { - return Err(ShellError::string(&format!( - "{:?} is a directory (not copied)", - source.to_string_lossy() - ))); + let sources = glob::glob(&source.to_string_lossy()); + + if sources.is_err() { + return Err(ShellError::labeled_error( + "Invalid pattern.", + "Invalid pattern.", + args.nth(0).unwrap().span(), + )); + } + + let sources: Vec<_> = sources.unwrap().collect(); + + if sources.len() == 1 { + if let Ok(entry) = &sources[0] { + if entry.is_dir() && !args.has("recursive") { + return Err(ShellError::labeled_error( + "is a directory (not copied). Try using \"--recursive\".", + "is a directory (not copied). Try using \"--recursive\".", + args.nth(0).unwrap().span(), + )); + } + + let mut sources: FileStructure = FileStructure::new(); + + sources.walk_decorate(&entry); + + if entry.is_file() { + let strategy = |(source_file, _depth_level)| { + if destination.exists() { + let mut new_dst = dunce::canonicalize(destination.clone()).unwrap(); + new_dst.push(entry.file_name().unwrap()); + (source_file, new_dst) + } else { + (source_file, destination.clone()) + } + }; + + for (ref src, ref dst) in sources.paths_applying_with(strategy) { + if src.is_file() { + match std::fs::copy(src, dst) { + Err(e) => { + return Err(ShellError::labeled_error( + e.to_string(), + e.to_string(), + name_span, + )); + } + Ok(o) => o, + }; + } + } + } + + if entry.is_dir() { + if !destination.exists() { + match std::fs::create_dir_all(&destination) { + Err(e) => { + return Err(ShellError::labeled_error( + e.to_string(), + e.to_string(), + name_span, + )); + } + Ok(o) => o, + }; + + let strategy = |(source_file, depth_level)| { + let mut new_dst = destination.clone(); + let path = dunce::canonicalize(&source_file).unwrap(); + + let mut comps: Vec<_> = path + .components() + .map(|fragment| fragment.as_os_str()) + .rev() + .take(1 + depth_level) + .collect(); + + comps.reverse(); + + for fragment in comps.iter() { + new_dst.push(fragment); + } + + (PathBuf::from(&source_file), PathBuf::from(new_dst)) + }; + + for (ref src, ref dst) in sources.paths_applying_with(strategy) { + if src.is_dir() { + if !dst.exists() { + match std::fs::create_dir_all(dst) { + Err(e) => { + return Err(ShellError::labeled_error( + e.to_string(), + e.to_string(), + name_span, + )); + } + Ok(o) => o, + }; + } + } + + if src.is_file() { + match std::fs::copy(src, dst) { + Err(e) => { + return Err(ShellError::labeled_error( + e.to_string(), + e.to_string(), + name_span, + )); + } + Ok(o) => o, + }; + } + } + } else { + destination.push(entry.file_name().unwrap()); + + match std::fs::create_dir_all(&destination) { + Err(e) => { + return Err(ShellError::labeled_error( + e.to_string(), + e.to_string(), + name_span, + )); + } + Ok(o) => o, + }; + + let strategy = |(source_file, depth_level)| { + let mut new_dst = dunce::canonicalize(&destination).unwrap(); + let path = dunce::canonicalize(&source_file).unwrap(); + + let mut comps: Vec<_> = path + .components() + .map(|fragment| fragment.as_os_str()) + .rev() + .take(1 + depth_level) + .collect(); + + comps.reverse(); + + for fragment in comps.iter() { + new_dst.push(fragment); + } + + (PathBuf::from(&source_file), PathBuf::from(new_dst)) + }; + + for (ref src, ref dst) in sources.paths_applying_with(strategy) { + if src.is_dir() { + if !dst.exists() { + match std::fs::create_dir_all(dst) { + Err(e) => { + return Err(ShellError::labeled_error( + e.to_string(), + e.to_string(), + name_span, + )); + } + Ok(o) => o, + }; + } + } + + if src.is_file() { + match std::fs::copy(src, dst) { + Err(e) => { + return Err(ShellError::labeled_error( + e.to_string(), + e.to_string(), + name_span, + )); + } + Ok(o) => o, + }; + } + } + } + } + } + } else { + if destination.exists() { + if !sources.iter().all(|x| (x.as_ref().unwrap()).is_file()) && !args.has("recursive") { + return Err(ShellError::labeled_error( + "Copy aborted (directories found). Try using \"--recursive\".", + "Copy aborted (directories found). Try using \"--recursive\".", + args.nth(0).unwrap().span(), + )); + } + + for entry in sources { + if let Ok(entry) = entry { + let mut to = PathBuf::from(&destination); + to.push(&entry.file_name().unwrap()); + + match std::fs::copy(&entry, &to) { + Err(e) => { + return Err(ShellError::labeled_error( + e.to_string(), + e.to_string(), + name_span, + )); + } + Ok(o) => o, + }; + } + } + } else { + return Err(ShellError::labeled_error( + format!( + "Copy aborted. (Does {:?} exist?)", + &destination.file_name().unwrap() + ), + format!( + "Copy aborted. (Does {:?} exist?)", + &destination.file_name().unwrap() + ), + args.nth(1).unwrap().span(), + )); } } - match std::fs::copy(source, destination) { - Err(_error) => Err(ShellError::string("can not copy file")), - Ok(_) => Ok(OutputStream::empty()), + Ok(OutputStream::empty()) +} + +#[cfg(test)] +mod tests { + + use super::{FileStructure, Res}; + use std::path::PathBuf; + + fn fixtures() -> PathBuf { + let mut sdx = PathBuf::new(); + sdx.push("tests"); + sdx.push("fixtures"); + sdx.push("formats"); + dunce::canonicalize(sdx).unwrap() + } + + #[test] + fn prepares_and_decorates_source_files_for_copying() { + let mut res = FileStructure::new(); + res.walk_decorate(fixtures().as_path()); + + assert_eq!( + res.resources, + vec![ + Res { + loc: fixtures().join("appveyor.yml"), + at: 0 + }, + Res { + loc: fixtures().join("caco3_plastics.csv"), + at: 0 + }, + Res { + loc: fixtures().join("cargo_sample.toml"), + at: 0 + }, + Res { + loc: fixtures().join("jonathan.xml"), + at: 0 + }, + Res { + loc: fixtures().join("sample.ini"), + at: 0 + }, + Res { + loc: fixtures().join("sgml_description.json"), + at: 0 + } + ] + ); } } diff --git a/src/commands/mkdir.rs b/src/commands/mkdir.rs new file mode 100644 index 000000000..5dccb2810 --- /dev/null +++ b/src/commands/mkdir.rs @@ -0,0 +1,49 @@ +use crate::errors::ShellError; +use crate::parser::hir::SyntaxType; +use crate::parser::registry::{CommandConfig, NamedType, PositionalType}; +use crate::prelude::*; +use indexmap::IndexMap; +use std::path::{Path, PathBuf}; + +pub struct Mkdir; + +impl Command for Mkdir { + fn run(&self, args: CommandArgs) -> Result { + mkdir(args) + } + + fn name(&self) -> &str { + "mkdir" + } + + fn config(&self) -> CommandConfig { + let named: IndexMap = IndexMap::new(); + + CommandConfig { + name: self.name().to_string(), + positional: vec![PositionalType::mandatory("file", SyntaxType::Path)], + rest_positional: false, + named, + is_sink: false, + is_filter: false, + } + } +} + +pub fn mkdir(args: CommandArgs) -> Result { + let mut full_path = PathBuf::from(args.shell_manager.path()); + + match &args.nth(0) { + Some(Tagged { item: value, .. }) => full_path.push(Path::new(&value.as_string()?)), + _ => {} + } + + match std::fs::create_dir_all(full_path) { + Err(reason) => Err(ShellError::labeled_error( + reason.to_string(), + reason.to_string(), + args.nth(0).unwrap().span(), + )), + Ok(_) => Ok(OutputStream::empty()), + } +} diff --git a/src/commands/rm.rs b/src/commands/rm.rs index aa0d10c43..c57446bcf 100644 --- a/src/commands/rm.rs +++ b/src/commands/rm.rs @@ -61,7 +61,7 @@ pub fn rm(args: CommandArgs) -> Result { if !args.has("recursive") { return Err(ShellError::labeled_error( "is a directory", - "", + "is a directory", args.call_info.name_span, )); } diff --git a/tests/command_cd_tests.rs b/tests/command_cd_tests.rs new file mode 100644 index 000000000..626a59f79 --- /dev/null +++ b/tests/command_cd_tests.rs @@ -0,0 +1,16 @@ +mod helpers; + +use helpers::in_directory as cwd; +use helpers::Playground; + +#[test] +fn cd_directory_not_found() { + let sandbox = Playground::setup_for("cd_directory_not_found_test").test_dir_name(); + + let full_path = format!("{}/{}", Playground::root(), sandbox); + + nu_error!(output, cwd(&full_path), "cd dir_that_does_not_exist"); + + assert!(output.contains("dir_that_does_not_exist")); + assert!(output.contains("directory not found")); +} \ No newline at end of file diff --git a/tests/command_cp_tests.rs b/tests/command_cp_tests.rs new file mode 100644 index 000000000..ef2a68c36 --- /dev/null +++ b/tests/command_cp_tests.rs @@ -0,0 +1,169 @@ +mod helpers; + +use h::{in_directory as cwd, Playground, Stub::*}; +use helpers as h; + +use std::path::{Path, PathBuf}; + +#[test] +fn copies_a_file() { + let sandbox = Playground::setup_for("cp_test").test_dir_name(); + + let full_path = format!("{}/{}", Playground::root(), sandbox); + let expected_file = format!("{}/{}", full_path, "sample.ini"); + + nu!( + _output, + cwd(&Playground::root()), + "cp ../formats/sample.ini cp_test/sample.ini" + ); + + assert!(h::file_exists_at(PathBuf::from(expected_file))); +} + +#[test] +fn copies_the_file_inside_directory_if_path_to_copy_is_directory() { + let sandbox = Playground::setup_for("cp_test_2").test_dir_name(); + + let full_path = format!("{}/{}", Playground::root(), sandbox); + let expected_file = format!("{}/{}", full_path, "sample.ini"); + + nu!( + _output, + cwd(&Playground::root()), + "cp ../formats/sample.ini cp_test_2" + ); + + assert!(h::file_exists_at(PathBuf::from(expected_file))); +} + +#[test] +fn error_if_attempting_to_copy_a_directory_to_another_directory() { + Playground::setup_for("cp_test_3"); + + nu_error!(output, cwd(&Playground::root()), "cp ../formats cp_test_3"); + + assert!(output.contains("../formats")); + assert!(output.contains("is a directory (not copied)")); +} + +#[test] +fn copies_the_directory_inside_directory_if_path_to_copy_is_directory_and_with_recursive_flag() { + let sandbox = Playground::setup_for("cp_test_4") + .within("originals") + .with_files(vec![ + EmptyFile("yehuda.txt"), + EmptyFile("jonathan.txt"), + EmptyFile("andres.txt"), + ]) + .within("copies_expected") + .test_dir_name(); + + let full_path = format!("{}/{}", Playground::root(), sandbox); + let expected_dir = format!("{}/{}", full_path, "copies_expected/originals"); + + nu!( + _output, + cwd(&full_path), + "cp originals copies_expected --recursive" + ); + + assert!(h::dir_exists_at(PathBuf::from(&expected_dir))); + assert!(h::files_exist_at( + vec![ + Path::new("yehuda.txt"), + Path::new("jonathan.txt"), + Path::new("andres.txt") + ], + PathBuf::from(&expected_dir) + )); +} + +#[test] +fn deep_copies_with_recursive_flag() { + r#" + Given these files and directories + originals + originals/manifest.txt + originals/contributors + originals/contributors/yehuda.txt + originals/contributors/jonathan.txt + originals/contributors/andres.txt + originals/contributors/jonathan + originals/contributors/jonathan/errors.txt + originals/contributors/jonathan/multishells.txt + originals/contributors/andres + originals/contributors/andres/coverage.txt + originals/contributors/andres/commands.txt + originals/contributors/yehuda + originals/contributors/yehuda/defer-evaluation.txt + "#; + + let sandbox = Playground::setup_for("cp_test_5") + .within("originals") + .with_files(vec![EmptyFile("manifest.txt")]) + .within("originals/contributors") + .with_files(vec![ + EmptyFile("yehuda.txt"), + EmptyFile("jonathan.txt"), + EmptyFile("andres.txt"), + ]) + .within("originals/contributors/jonathan") + .with_files(vec![EmptyFile("errors.txt"), EmptyFile("multishells.txt")]) + .within("originals/contributors/andres") + .with_files(vec![EmptyFile("coverage.txt"), EmptyFile("commands.txt")]) + .within("originals/contributors/yehuda") + .with_files(vec![EmptyFile("defer-evaluation.txt")]) + .within("copies_expected") + .test_dir_name(); + + let full_path = format!("{}/{}", Playground::root(), sandbox); + let expected_dir = format!("{}/{}", full_path, "copies_expected/originals"); + + let jonathans_expected_copied_dir = format!("{}/contributors/jonathan", expected_dir); + let andres_expected_copied_dir = format!("{}/contributors/andres", expected_dir); + let yehudas_expected_copied_dir = format!("{}/contributors/yehuda", expected_dir); + + nu!( + _output, + cwd(&full_path), + "cp originals copies_expected --recursive" + ); + + assert!(h::dir_exists_at(PathBuf::from(&expected_dir))); + assert!(h::files_exist_at( + vec![Path::new("errors.txt"), Path::new("multishells.txt")], + PathBuf::from(&jonathans_expected_copied_dir) + )); + assert!(h::files_exist_at( + vec![Path::new("coverage.txt"), Path::new("commands.txt")], + PathBuf::from(&andres_expected_copied_dir) + )); + assert!(h::files_exist_at( + vec![Path::new("defer-evaluation.txt")], + PathBuf::from(&yehudas_expected_copied_dir) + )); +} + +#[test] +fn copies_using_globs() { + let sandbox = Playground::setup_for("cp_test_6").test_dir_name(); + let expected_copies_path = format!("{}/{}", Playground::root(), sandbox); + + nu!( + _output, + cwd(&Playground::root()), + "cp ../formats/* cp_test_6" + ); + + assert!(h::files_exist_at( + vec![ + Path::new("caco3_plastics.csv"), + Path::new("cargo_sample.toml"), + Path::new("jonathan.xml"), + Path::new("sample.ini"), + Path::new("sgml_description.json") + ], + PathBuf::from(&expected_copies_path) + )); +} diff --git a/tests/command_mkdir_tests.rs b/tests/command_mkdir_tests.rs new file mode 100644 index 000000000..9cbb10755 --- /dev/null +++ b/tests/command_mkdir_tests.rs @@ -0,0 +1,37 @@ +mod helpers; + +use h::{in_directory as cwd, Playground}; +use helpers as h; +use std::path::PathBuf; + +#[test] +fn creates_directory() { + let sandbox = Playground::setup_for("mkdir_test").test_dir_name(); + + let full_path = format!("{}/{}", Playground::root(), sandbox); + + nu!(_output, cwd(&full_path), "mkdir my_new_directory"); + + let mut expected = PathBuf::from(full_path); + expected.push("my_new_directory"); + + assert!(h::dir_exists_at(expected)); +} + +#[test] +fn creates_intermediary_directories() { + let sandbox = Playground::setup_for("mkdir_test_2").test_dir_name(); + + let full_path = format!("{}/{}", Playground::root(), sandbox); + + nu!( + _output, + cwd(&full_path), + "mkdir some_folder/another/deeper_one" + ); + + let mut expected = PathBuf::from(full_path); + expected.push("some_folder/another/deeper_one"); + + assert!(h::dir_exists_at(expected)); +} diff --git a/tests/commands_test.rs b/tests/commands_test.rs index a1699a8d8..145793402 100644 --- a/tests/commands_test.rs +++ b/tests/commands_test.rs @@ -2,7 +2,7 @@ mod helpers; use h::{in_directory as cwd, Playground, Stub::*}; use helpers as h; -use std::path::PathBuf; +use std::path::{Path, PathBuf}; #[test] fn lines() { @@ -129,48 +129,6 @@ fn save_can_write_out_csv() { assert!(actual.contains("[list list],A shell for the GitHub era,2018,ISC,nu,0.2.0")); } -#[test] -fn cp_can_copy_a_file() { - let sandbox = Playground::setup_for("cp_test").test_dir_name(); - - let full_path = format!("{}/{}", Playground::root(), sandbox); - let expected_file = format!("{}/{}", full_path, "sample.ini"); - - nu!( - _output, - cwd(&Playground::root()), - "cp ../formats/sample.ini cp_test/sample.ini" - ); - - assert!(h::file_exists_at(&expected_file)); -} - -#[test] -fn cp_copies_the_file_inside_directory_if_path_to_copy_is_directory() { - let sandbox = Playground::setup_for("cp_test_2").test_dir_name(); - - let full_path = format!("{}/{}", Playground::root(), sandbox); - let expected_file = format!("{}/{}", full_path, "sample.ini"); - - nu!( - _output, - cwd(&Playground::root()), - "cp ../formats/sample.ini cp_test_2" - ); - - assert!(h::file_exists_at(&expected_file)); -} - -#[test] -fn cp_error_if_attempting_to_copy_a_directory_to_another_directory() { - Playground::setup_for("cp_test_3"); - - nu_error!(output, cwd(&Playground::root()), "cp ../formats cp_test_3"); - - assert!(output.contains("../formats")); - assert!(output.contains("is a directory (not copied)")); -} - #[test] fn rm_removes_a_file() { let sandbox = Playground::setup_for("rm_test") @@ -183,12 +141,14 @@ fn rm_removes_a_file() { "rm rm_test/i_will_be_deleted.txt" ); - assert!(!h::file_exists_at(&format!( + let path = &format!( "{}/{}/{}", Playground::root(), sandbox, "i_will_be_deleted.txt" - ))); + ); + + assert!(!h::file_exists_at(PathBuf::from(path))); } #[test] @@ -243,21 +203,13 @@ fn rm_removes_files_with_wildcard() { "rm \"src/*/*/*.rs\"" ); - assert!(!h::file_exists_at(&format!( - "{}/src/parser/parse/token_tree.rs", - full_path - ))); - assert!(!h::file_exists_at(&format!( - "{}/src/parser/hir/baseline_parse.rs", - full_path - ))); - assert!(!h::file_exists_at(&format!( - "{}/src/parser/hir/baseline_parse_tokens.rs", - full_path - ))); + assert!(!h::files_exist_at(vec![ + Path::new("src/parser/parse/token_tree.rs"), + Path::new("src/parser/hir/baseline_parse.rs"), + Path::new("src/parser/hir/baseline_parse_tokens.rs")], PathBuf::from(&full_path))); assert_eq!( - Playground::glob_vec(&format!("{}/src/*/*/*.rs", full_path)), + Playground::glob_vec(&format!("{}/src/*/*/*.rs", &full_path)), Vec::::new() ); } @@ -278,11 +230,13 @@ fn rm_removes_directory_contents_with_recursive_flag() { "rm rm_test_recursive --recursive" ); - assert!(!h::file_exists_at(&format!( + let expected = format!( "{}/{}", Playground::root(), sandbox - ))); + ); + + assert!(!h::file_exists_at(PathBuf::from(expected))); } #[test] @@ -292,7 +246,7 @@ fn rm_errors_if_attempting_to_delete_a_directory_without_recursive_flag() { nu_error!(output, cwd(&Playground::root()), "rm rm_test_2"); - assert!(h::file_exists_at(&full_path)); + assert!(h::file_exists_at(PathBuf::from(full_path))); assert!(output.contains("is a directory")); } diff --git a/tests/helpers/mod.rs b/tests/helpers/mod.rs index b441f8525..5520edc5a 100644 --- a/tests/helpers/mod.rs +++ b/tests/helpers/mod.rs @@ -21,7 +21,7 @@ macro_rules! nu { $cwd, $commands ); - let process = match Command::new(helpers::executable_path()) + let mut process = match Command::new(helpers::executable_path()) .stdin(Stdio::piped()) .stdout(Stdio::piped()) .spawn() @@ -30,22 +30,18 @@ macro_rules! nu { Err(why) => panic!("Can't run test {}", why.description()), }; - match process.stdin.unwrap().write_all(commands.as_bytes()) { - Err(why) => panic!("couldn't write to wc stdin: {}", why.description()), - Ok(_) => {} - } + let stdin = process.stdin.as_mut().expect("couldn't open stdin"); + stdin + .write_all(commands.as_bytes()) + .expect("couldn't write to stdin"); - let mut _s = String::new(); + let output = process + .wait_with_output() + .expect("couldn't read from stdout"); - match process.stdout.unwrap().read_to_string(&mut _s) { - Err(why) => panic!("couldn't read stdout: {}", why.description()), - Ok(_) => { - let _s = _s.replace("\r\n", "\n"); - } - } - - let _s = _s.replace("\r\n", ""); - let $out = _s.replace("\n", ""); + let $out = String::from_utf8_lossy(&output.stdout); + let $out = $out.replace("\r\n", ""); + let $out = $out.replace("\n", ""); }; } @@ -185,8 +181,20 @@ pub fn copy_file_to(source: &str, destination: &str) { std::fs::copy(source, destination).expect("can not copy file"); } -pub fn file_exists_at(full_path: &str) -> bool { - PathBuf::from(full_path).exists() +pub fn files_exist_at(files: Vec<&Path>, path: PathBuf) -> bool { + files.iter().all(|f| { + let mut loc = path.clone(); + loc.push(f); + loc.exists() + }) +} + +pub fn file_exists_at(path: PathBuf) -> bool { + path.exists() +} + +pub fn dir_exists_at(path: PathBuf) -> bool { + path.exists() } pub fn delete_directory_at(full_path: &str) {