#![allow(dead_code)] use glob::glob; pub use std::path::Path; pub use std::path::PathBuf; use getset::Getters; use std::io::Read; use tempfile::{tempdir, TempDir}; pub trait DisplayPath { fn display_path(&self) -> String; } impl DisplayPath for PathBuf { fn display_path(&self) -> String { self.display().to_string() } } impl DisplayPath for str { fn display_path(&self) -> String { self.to_string() } } impl DisplayPath for &str { fn display_path(&self) -> String { self.to_string() } } impl DisplayPath for String { fn display_path(&self) -> String { self.clone() } } impl DisplayPath for &String { fn display_path(&self) -> String { self.to_string() } } impl DisplayPath for nu::AbsolutePath { fn display_path(&self) -> String { self.to_string() } } #[macro_export] macro_rules! nu { ($cwd:expr, $path:expr, $($part:expr),*) => {{ use $crate::helpers::DisplayPath; let path = format!($path, $( $part.display_path() ),*); nu!($cwd, &path) }}; ($cwd:expr, $path:expr) => {{ pub use std::error::Error; pub use std::io::prelude::*; pub use std::process::{Command, Stdio}; let commands = &*format!( " cd {} {} exit", $crate::helpers::in_directory($cwd), $crate::helpers::DisplayPath::display_path(&$path) ); let mut process = match Command::new(helpers::executable_path()) .stdin(Stdio::piped()) .stdout(Stdio::piped()) .spawn() { Ok(child) => child, Err(why) => panic!("Can't run test {}", why.description()), }; let stdin = process.stdin.as_mut().expect("couldn't open stdin"); stdin .write_all(commands.as_bytes()) .expect("couldn't write to stdin"); let output = process .wait_with_output() .expect("couldn't read from stdout"); let out = String::from_utf8_lossy(&output.stdout); let out = out.replace("\r\n", ""); let out = out.replace("\n", ""); out }}; } #[macro_export] macro_rules! nu_error { ($cwd:expr, $path:expr, $($part:expr),*) => {{ use $crate::helpers::DisplayPath; let path = format!($path, $( $part.display_path() ),*); nu_error!($cwd, &path) }}; ($cwd:expr, $commands:expr) => {{ use std::io::prelude::*; use std::process::{Command, Stdio}; let commands = &*format!( " cd {} {} exit", $crate::helpers::in_directory($cwd), $commands ); let mut process = Command::new(helpers::executable_path()) .stdin(Stdio::piped()) .stderr(Stdio::piped()) .spawn() .expect("couldn't run test"); let stdin = process.stdin.as_mut().expect("couldn't open stdin"); stdin .write_all(commands.as_bytes()) .expect("couldn't write to stdin"); let output = process .wait_with_output() .expect("couldn't read from stderr"); let out = String::from_utf8_lossy(&output.stderr); out.into_owned() }}; } pub enum Stub<'a> { FileWithContent(&'a str, &'a str), FileWithContentToBeTrimmed(&'a str, &'a str), EmptyFile(&'a str), } pub struct Playground { root: TempDir, tests: String, cwd: PathBuf, } #[derive(Getters)] #[get = "pub"] pub struct Dirs { pub root: PathBuf, pub test: PathBuf, pub fixtures: PathBuf, } impl Dirs { pub fn formats(&self) -> PathBuf { PathBuf::from(self.fixtures.join("formats")) } } impl Playground { pub fn root(&self) -> &Path { self.root.path() } pub fn test_dir_name(&self) -> String { self.tests.clone() } pub fn back_to_playground(&mut self) -> &mut Self { self.cwd = PathBuf::from(self.root()).join(self.tests.clone()); self } pub fn setup(topic: &str, block: impl FnOnce(Dirs, &mut Playground)) { let mut playground = Playground::setup_for(topic); let project_root = PathBuf::from(env!("CARGO_MANIFEST_DIR")); let playground_root = playground.root.path(); let fixtures = project_root.join(file!()); let fixtures = fixtures .parent() .expect("Couldn't find the fixtures directory") .parent() .expect("Couldn't find the fixtures directory") .join("fixtures"); let fixtures = dunce::canonicalize(fixtures.clone()).expect(&format!( "Couldn't canonicalize fixtures path {}", fixtures.display() )); let test = dunce::canonicalize(PathBuf::from(playground_root.join(topic))).expect(&format!( "Couldn't canonicalize test path {}", playground_root.join(topic).display() )); let dirs = Dirs { root: PathBuf::from(playground_root), test, fixtures, }; block(dirs, &mut playground); } pub fn setup_for(topic: &str) -> Playground { let root = tempdir().expect("Couldn't create a tempdir"); let nuplay_dir = root.path().join(topic); if PathBuf::from(&nuplay_dir).exists() { std::fs::remove_dir_all(PathBuf::from(&nuplay_dir)).expect("can not remove directory"); } std::fs::create_dir(PathBuf::from(&nuplay_dir)).expect("can not create directory"); Playground { root: root, tests: topic.to_string(), cwd: nuplay_dir, } } pub fn mkdir(&mut self, directory: &str) -> &mut Self { self.cwd.push(directory); std::fs::create_dir_all(&self.cwd).expect("can not create directory"); self.back_to_playground(); self } pub fn with_files(&mut self, files: Vec) -> &mut Self { let endl = line_ending(); files .iter() .map(|f| { let mut path = PathBuf::from(&self.cwd); let (file_name, contents) = match *f { Stub::EmptyFile(name) => (name, "fake data".to_string()), Stub::FileWithContent(name, content) => (name, content.to_string()), Stub::FileWithContentToBeTrimmed(name, content) => ( name, content .lines() .skip(1) .map(|line| line.trim()) .collect::>() .join(&endl), ), }; path.push(file_name); std::fs::write(PathBuf::from(path), contents.as_bytes()) .expect("can not create file"); }) .for_each(drop); self.back_to_playground(); self } pub fn within(&mut self, directory: &str) -> &mut Self { self.cwd.push(directory); std::fs::create_dir(&self.cwd).expect("can not create directory"); self } pub fn glob_vec(pattern: &str) -> Vec { let glob = glob(pattern); match glob { Ok(paths) => paths .map(|path| { if let Ok(path) = path { path } else { unreachable!() } }) .collect(), Err(_) => panic!("Invalid pattern."), } } } pub fn file_contents(full_path: &str) -> String { let mut file = std::fs::File::open(full_path).expect("can not open file"); let mut contents = String::new(); file.read_to_string(&mut contents) .expect("can not read file"); contents } pub fn line_ending() -> String { #[cfg(windows)] { String::from("\r\n") } #[cfg(not(windows))] { String::from("\n") } } pub fn normalize_string(input: &str) -> String { #[cfg(windows)] { input.to_string() } #[cfg(not(windows))] { format!("\"{}\"", input) } } pub fn create_file_at(full_path: impl AsRef) -> Result<(), std::io::Error> { let full_path = full_path.as_ref(); if let Some(parent) = full_path.parent() { panic!(format!("{:?} exists", parent.display())); } std::fs::write(full_path, "fake data".as_bytes()) } pub fn copy_file_to(source: &str, destination: &str) { std::fs::copy(source, destination).expect("can not copy file"); } pub fn files_exist_at(files: Vec<&Path>, path: impl AsRef) -> bool { files.iter().all(|f| { let mut loc = PathBuf::from(path.as_ref()); loc.push(f); loc.exists() }) } pub fn file_exists_at(path: impl AsRef) -> bool { path.as_ref().exists() } pub fn dir_exists_at(path: impl AsRef) -> bool { path.as_ref().exists() } pub fn delete_directory_at(full_path: &str) { std::fs::remove_dir_all(PathBuf::from(full_path)).expect("can not remove directory"); } pub fn executable_path() -> PathBuf { let mut buf = PathBuf::new(); buf.push("target"); buf.push("debug"); buf.push("nu"); buf } pub fn in_directory(str: impl AsRef) -> String { str.as_ref().display().to_string() }