forked from extern/nushell
ee8cd671cb
Previously, we would build a command that looked something like this: <ex_cmd> "$it" "&&" "<ex_cmd>" "$it" So that the "&&" and "<ex_cmd>" would also be arguments to the command, instead of a chained command. This commit builds up a command string that can be passed to an external shell.
450 lines
12 KiB
Rust
450 lines
12 KiB
Rust
#![allow(dead_code)]
|
|
|
|
use glob::glob;
|
|
pub use std::path::Path;
|
|
pub use std::path::PathBuf;
|
|
|
|
use app_dirs::{get_app_root, AppDataType};
|
|
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: $cwd:expr, $path:expr, $($part:expr),*) => {{
|
|
use $crate::helpers::DisplayPath;
|
|
|
|
let path = format!($path, $(
|
|
$part.display_path()
|
|
),*);
|
|
|
|
nu!($cwd, &path)
|
|
}};
|
|
|
|
(cwd: $cwd:expr, $path:expr) => {{
|
|
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: $cwd:expr, $path:expr, $($part:expr),*) => {{
|
|
use $crate::helpers::DisplayPath;
|
|
|
|
let path = format!($path, $(
|
|
$part.display_path()
|
|
),*);
|
|
|
|
nu_error!($cwd, &path)
|
|
}};
|
|
|
|
(cwd: $cwd:expr, $path:expr) => {{
|
|
nu_error!($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 = 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()
|
|
}};
|
|
}
|
|
|
|
#[macro_export]
|
|
macro_rules! nu_combined {
|
|
(cwd: $cwd:expr, $path:expr, $($part:expr),*) => {{
|
|
use $crate::helpers::DisplayPath;
|
|
|
|
let path = format!($path, $(
|
|
$part.display_path()
|
|
),*);
|
|
|
|
nu_combined!($cwd, &path)
|
|
}};
|
|
|
|
(cwd: $cwd:expr, $path:expr) => {{
|
|
nu_combined!($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 = Command::new(helpers::executable_path())
|
|
.stdin(Stdio::piped())
|
|
.stdout(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 stdout/stderr");
|
|
|
|
let err = String::from_utf8_lossy(&output.stderr).into_owned();
|
|
let out = String::from_utf8_lossy(&output.stdout).into_owned();
|
|
let out = out.replace("\r\n", "");
|
|
let out = out.replace("\n", "");
|
|
(out, err)
|
|
}};
|
|
}
|
|
|
|
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"))
|
|
}
|
|
|
|
pub fn config_path(&self) -> PathBuf {
|
|
get_app_root(AppDataType::UserConfig, &nu::APP_INFO).unwrap()
|
|
}
|
|
}
|
|
|
|
impl Playground {
|
|
pub fn root(&self) -> &Path {
|
|
self.root.path()
|
|
}
|
|
|
|
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 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");
|
|
|
|
let mut playground = Playground {
|
|
root: root,
|
|
tests: topic.to_string(),
|
|
cwd: nuplay_dir,
|
|
};
|
|
|
|
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 root = dunce::canonicalize(playground_root).expect(&format!(
|
|
"Couldn't canonicalize tests root path {}",
|
|
playground_root.display()
|
|
));
|
|
|
|
let dirs = Dirs {
|
|
root,
|
|
test,
|
|
fixtures,
|
|
};
|
|
|
|
block(dirs, &mut playground);
|
|
}
|
|
|
|
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<Stub>) -> &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::<Vec<&str>>()
|
|
.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<PathBuf> {
|
|
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: impl AsRef<Path>) -> String {
|
|
let mut file = std::fs::File::open(full_path.as_ref()).expect("can not open file");
|
|
let mut contents = String::new();
|
|
file.read_to_string(&mut contents)
|
|
.expect("can not read file");
|
|
contents
|
|
}
|
|
|
|
pub fn file_contents_binary(full_path: impl AsRef<Path>) -> Vec<u8> {
|
|
let mut file = std::fs::File::open(full_path.as_ref()).expect("can not open file");
|
|
let mut contents = Vec::new();
|
|
file.read_to_end(&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 delete_file_at(full_path: impl AsRef<Path>) {
|
|
let full_path = full_path.as_ref();
|
|
|
|
if full_path.exists() {
|
|
std::fs::remove_file(full_path).expect("can not delete file");
|
|
}
|
|
}
|
|
|
|
pub fn create_file_at(full_path: impl AsRef<Path>) -> 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<impl AsRef<Path>>, path: impl AsRef<Path>) -> bool {
|
|
files.iter().all(|f| {
|
|
let mut loc = PathBuf::from(path.as_ref());
|
|
loc.push(f);
|
|
loc.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<Path>) -> String {
|
|
str.as_ref().display().to_string()
|
|
}
|
|
|
|
pub fn pipeline(commands: &str) -> String {
|
|
commands
|
|
.lines()
|
|
.skip(1)
|
|
.map(|line| line.trim())
|
|
.collect::<Vec<&str>>()
|
|
.join(" ")
|
|
.trim_end()
|
|
.to_string()
|
|
}
|