Merge pull request #237 from androbtech/wildcard_support

Wildcard support adventure starting with rm command.
This commit is contained in:
Jonathan Turner 2019-08-02 10:58:33 +12:00 committed by GitHub
commit 3da5fb93e3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 240 additions and 85 deletions

1
Cargo.lock generated
View file

@ -1821,6 +1821,7 @@ dependencies = [
"futures_codec 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)", "futures_codec 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)",
"getset 0.0.7 (registry+https://github.com/rust-lang/crates.io-index)", "getset 0.0.7 (registry+https://github.com/rust-lang/crates.io-index)",
"git2 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)", "git2 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)",
"glob 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
"heim 0.0.5 (registry+https://github.com/rust-lang/crates.io-index)", "heim 0.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
"image 0.22.0 (registry+https://github.com/rust-lang/crates.io-index)", "image 0.22.0 (registry+https://github.com/rust-lang/crates.io-index)",
"indexmap 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", "indexmap 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",

View file

@ -56,6 +56,7 @@ adhoc_derive = "0.1.2"
lazy_static = "1.3.0" lazy_static = "1.3.0"
git2 = "0.9.2" git2 = "0.9.2"
dirs = "2.0.2" dirs = "2.0.2"
glob = "0.3.0"
ctrlc = "3.1.3" ctrlc = "3.1.3"
ptree = "0.2" ptree = "0.2"
clipboard = "0.5" clipboard = "0.5"

View file

@ -2,6 +2,8 @@ use crate::errors::ShellError;
use crate::parser::hir::SyntaxType; use crate::parser::hir::SyntaxType;
use crate::parser::registry::{CommandConfig, NamedType, PositionalType}; use crate::parser::registry::{CommandConfig, NamedType, PositionalType};
use crate::prelude::*; use crate::prelude::*;
use glob::glob;
use indexmap::IndexMap; use indexmap::IndexMap;
pub struct Remove; pub struct Remove;
@ -43,7 +45,18 @@ pub fn rm(args: CommandArgs) -> Result<OutputStream, ShellError> {
file => full_path.push(file), file => full_path.push(file),
} }
if full_path.is_dir() { let entries = glob(&full_path.to_string_lossy());
if entries.is_err() {
return Err(ShellError::string("Invalid pattern."));
}
let entries = entries.unwrap();
for entry in entries {
match entry {
Ok(path) => {
if path.is_dir() {
if !args.has("recursive") { if !args.has("recursive") {
return Err(ShellError::labeled_error( return Err(ShellError::labeled_error(
"is a directory", "is a directory",
@ -51,9 +64,13 @@ pub fn rm(args: CommandArgs) -> Result<OutputStream, ShellError> {
args.call_info.name_span.unwrap(), args.call_info.name_span.unwrap(),
)); ));
} }
std::fs::remove_dir_all(&full_path).expect("can not remove directory"); std::fs::remove_dir_all(&path).expect("can not remove directory");
} else if full_path.is_file() { } else if path.is_file() {
std::fs::remove_file(&full_path).expect("can not remove file"); std::fs::remove_file(&path).expect("can not remove file");
}
}
Err(e) => return Err(ShellError::string(&format!("{:?}", e))),
}
} }
Ok(OutputStream::empty()) Ok(OutputStream::empty())

View file

@ -1,7 +1,8 @@
mod helpers; mod helpers;
use h::in_directory as cwd; use h::{in_directory as cwd, Playground, Stub::*};
use helpers as h; use helpers as h;
use std::path::PathBuf;
#[test] #[test]
fn lines() { fn lines() {
@ -83,17 +84,28 @@ fn open_error_if_file_not_found() {
#[test] #[test]
fn save_figures_out_intelligently_where_to_write_out_with_metadata() { fn save_figures_out_intelligently_where_to_write_out_with_metadata() {
let (playground_path, tests_dir) = h::setup_playground_for("save_smart_test"); let sandbox = Playground::setup_for("save_smart_test")
.with_files(vec![FileWithContent(
"cargo_sample.toml",
r#"
[package]
name = "nu"
version = "0.1.1"
authors = ["Yehuda Katz <wycats@gmail.com>"]
description = "A shell for the GitHub era"
license = "ISC"
edition = "2018"
"#,
)])
.test_dir_name();
let full_path = format!("{}/{}", playground_path, tests_dir); let full_path = format!("{}/{}", Playground::root(), sandbox);
let subject_file = format!("{}/{}", full_path, "cargo_sample.toml"); let subject_file = format!("{}/{}", full_path, "cargo_sample.toml");
h::copy_file_to("tests/fixtures/formats/cargo_sample.toml", &subject_file);
nu!( nu!(
_output, _output,
cwd("tests/fixtures"), cwd(&Playground::root()),
"open nuplayground/save_smart_test/cargo_sample.toml | inc package.version --minor | save" "open save_smart_test/cargo_sample.toml | inc package.version --minor | save"
); );
let actual = h::file_contents(&subject_file); let actual = h::file_contents(&subject_file);
@ -102,14 +114,14 @@ fn save_figures_out_intelligently_where_to_write_out_with_metadata() {
#[test] #[test]
fn save_can_write_out_csv() { fn save_can_write_out_csv() {
let (playground_path, tests_dir) = h::setup_playground_for("save_test"); let sandbox = Playground::setup_for("save_test").test_dir_name();
let full_path = format!("{}/{}", playground_path, tests_dir); let full_path = format!("{}/{}", Playground::root(), sandbox);
let expected_file = format!("{}/{}", full_path, "cargo_sample.csv"); let expected_file = format!("{}/{}", full_path, "cargo_sample.csv");
nu!( nu!(
_output, _output,
cwd(&playground_path), cwd(&Playground::root()),
"open ../formats/cargo_sample.toml | inc package.version --minor | get package | save save_test/cargo_sample.csv" "open ../formats/cargo_sample.toml | inc package.version --minor | get package | save save_test/cargo_sample.csv"
); );
@ -119,14 +131,14 @@ fn save_can_write_out_csv() {
#[test] #[test]
fn cp_can_copy_a_file() { fn cp_can_copy_a_file() {
let (playground_path, tests_dir) = h::setup_playground_for("cp_test"); let sandbox = Playground::setup_for("cp_test").test_dir_name();
let full_path = format!("{}/{}", playground_path, tests_dir); let full_path = format!("{}/{}", Playground::root(), sandbox);
let expected_file = format!("{}/{}", full_path, "sample.ini"); let expected_file = format!("{}/{}", full_path, "sample.ini");
nu!( nu!(
_output, _output,
cwd(&playground_path), cwd(&Playground::root()),
"cp ../formats/sample.ini cp_test/sample.ini" "cp ../formats/sample.ini cp_test/sample.ini"
); );
@ -135,14 +147,14 @@ fn cp_can_copy_a_file() {
#[test] #[test]
fn cp_copies_the_file_inside_directory_if_path_to_copy_is_directory() { fn cp_copies_the_file_inside_directory_if_path_to_copy_is_directory() {
let (playground_path, tests_dir) = h::setup_playground_for("cp_test_2"); let sandbox = Playground::setup_for("cp_test_2").test_dir_name();
let full_path = format!("{}/{}", playground_path, tests_dir); let full_path = format!("{}/{}", Playground::root(), sandbox);
let expected_file = format!("{}/{}", full_path, "sample.ini"); let expected_file = format!("{}/{}", full_path, "sample.ini");
nu!( nu!(
_output, _output,
cwd(&playground_path), cwd(&Playground::root()),
"cp ../formats/sample.ini cp_test_2" "cp ../formats/sample.ini cp_test_2"
); );
@ -151,86 +163,149 @@ fn cp_copies_the_file_inside_directory_if_path_to_copy_is_directory() {
#[test] #[test]
fn cp_error_if_attempting_to_copy_a_directory_to_another_directory() { fn cp_error_if_attempting_to_copy_a_directory_to_another_directory() {
let (playground_path, _) = h::setup_playground_for("cp_test_3"); Playground::setup_for("cp_test_3");
nu_error!( nu_error!(output, cwd(&Playground::root()), "cp ../formats cp_test_3");
output,
cwd(&playground_path),
"cp ../formats cp_test_3"
);
assert!(output.contains("../formats")); assert!(output.contains("../formats"));
assert!(output.contains("is a directory (not copied)")); assert!(output.contains("is a directory (not copied)"));
} }
#[test] #[test]
fn rm_can_remove_a_file() { fn rm_removes_a_file() {
let directory = "tests/fixtures/nuplayground"; let sandbox = Playground::setup_for("rm_test")
let file = format!("{}/rm_test.txt", directory); .with_files(vec![EmptyFile("i_will_be_deleted.txt")])
.test_dir_name();
h::create_file_at(&file);
nu!( nu!(
_output, _output,
cwd(directory), cwd(&Playground::root()),
"rm rm_test.txt" "rm rm_test/i_will_be_deleted.txt"
); );
assert!(!h::file_exists_at(&file)); assert!(!h::file_exists_at(&format!(
"{}/{}/{}",
Playground::root(),
sandbox,
"i_will_be_deleted.txt"
)));
} }
#[test] #[test]
fn rm_can_remove_directory_contents_with_recursive_flag() { fn rm_removes_files_with_wildcard() {
let (playground_path, tests_dir) = h::setup_playground_for("rm_test"); r#"
Given these files and directories
src
src/cli.rs
src/lib.rs
src/prelude.rs
src/parser
src/parser/parse.rs
src/parser/parser.rs
src/parser/parse
src/parser/hir
src/parser/parse/token_tree.rs
src/parser/hir/baseline_parse.rs
src/parser/hir/baseline_parse_tokens.rs
"#;
for f in ["yehuda.txt", "jonathan.txt", "andres.txt"].iter() { let sandbox = Playground::setup_for("rm_test_wildcard")
h::create_file_at(&format!("{}/{}/{}", playground_path, tests_dir, f)); .within("src")
.with_files(vec![
EmptyFile("cli.rs"),
EmptyFile("lib.rs"),
EmptyFile("prelude.rs"),
])
.within("src/parser")
.with_files(vec![EmptyFile("parse.rs"), EmptyFile("parser.rs")])
.within("src/parser/parse")
.with_files(vec![EmptyFile("token_tree.rs")])
.within("src/parser/hir")
.with_files(vec![
EmptyFile("baseline_parse.rs"),
EmptyFile("baseline_parse_tokens.rs"),
])
.test_dir_name();
let full_path = format!("{}/{}", Playground::root(), sandbox);
r#" The pattern
src/*/*/*.rs
matches
src/parser/parse/token_tree.rs
src/parser/hir/baseline_parse.rs
src/parser/hir/baseline_parse_tokens.rs
"#;
nu!(
_output,
cwd("tests/fixtures/nuplayground/rm_test_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_eq!(
Playground::glob_vec(&format!("{}/src/*/*/*.rs", full_path)),
Vec::<PathBuf>::new()
);
} }
#[test]
fn rm_removes_directory_contents_with_recursive_flag() {
let sandbox = Playground::setup_for("rm_test_recursive")
.with_files(vec![
EmptyFile("yehuda.txt"),
EmptyFile("jonathan.txt"),
EmptyFile("andres.txt"),
])
.test_dir_name();
nu!( nu!(
_output, _output,
cwd("tests/fixtures/nuplayground"), cwd("tests/fixtures/nuplayground"),
"rm rm_test --recursive" "rm rm_test_recursive --recursive"
); );
assert!(!h::file_exists_at(&format!("{}/{}", playground_path, tests_dir))); assert!(!h::file_exists_at(&format!(
"{}/{}",
Playground::root(),
sandbox
)));
} }
#[test] #[test]
fn rm_error_if_attempting_to_delete_a_directory_without_recursive_flag() { fn rm_errors_if_attempting_to_delete_a_directory_without_recursive_flag() {
let (playground_path, tests_dir) = h::setup_playground_for("rm_test_2"); let sandbox = Playground::setup_for("rm_test_2").test_dir_name();
let full_path = format!("{}/{}", playground_path, tests_dir); let full_path = format!("{}/{}", Playground::root(), sandbox);
nu_error!( nu_error!(output, cwd(&Playground::root()), "rm rm_test_2");
output,
cwd("tests/fixtures/nuplayground"),
"rm rm_test_2"
);
assert!(h::file_exists_at(&full_path)); assert!(h::file_exists_at(&full_path));
assert!(output.contains("is a directory")); assert!(output.contains("is a directory"));
} }
#[test] #[test]
fn rm_error_if_attempting_to_delete_single_dot_as_argument() { fn rm_errors_if_attempting_to_delete_single_dot_as_argument() {
nu_error!(output, cwd(&Playground::root()), "rm .");
nu_error!(
output,
cwd("tests/fixtures/nuplayground"),
"rm ."
);
assert!(output.contains("may not be removed")); assert!(output.contains("may not be removed"));
} }
#[test] #[test]
fn rm_error_if_attempting_to_delete_two_dot_as_argument() { fn rm_errors_if_attempting_to_delete_two_dot_as_argument() {
nu_error!(output, cwd(&Playground::root()), "rm ..");
nu_error!(
output,
cwd("tests/fixtures/nuplayground"),
"rm .."
);
assert!(output.contains("may not be removed")); assert!(output.contains("may not be removed"));
} }

View file

@ -1,5 +1,7 @@
#![allow(dead_code)] #![allow(dead_code)]
use glob::glob;
pub use std::path::Path;
pub use std::path::PathBuf; pub use std::path::PathBuf;
use std::io::Read; use std::io::Read;
@ -79,17 +81,80 @@ macro_rules! nu_error {
}; };
} }
pub fn setup_playground_for(topic: &str) -> (String, String) { pub enum Stub<'a> {
let home = "tests/fixtures/nuplayground"; FileWithContent(&'a str, &'a str),
let full_path = format!("{}/{}", home, topic); EmptyFile(&'a str),
if file_exists_at(&full_path) {
delete_directory_at(&full_path);
} }
create_directory_at(&full_path); pub struct Playground {
tests: String,
cwd: PathBuf,
}
(home.to_string(), topic.to_string()) impl Playground {
pub fn root() -> String {
String::from("tests/fixtures/nuplayground")
}
pub fn test_dir_name(&self) -> String {
self.tests.clone()
}
pub fn back_to_playground(&mut self) -> &mut Self {
self.cwd = PathBuf::from([Playground::root(), self.tests.clone()].join("/"));
self
}
pub fn setup_for(topic: &str) -> Playground {
let nuplay_dir = format!("{}/{}", Playground::root(), 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 {
tests: topic.to_string(),
cwd: PathBuf::from([Playground::root(), topic.to_string()].join("/")),
}
}
pub fn cd(&mut self, path: &str) -> &mut Self {
self.cwd.push(path);
self
}
pub fn with_files(&mut self, files: Vec<Stub>) -> &mut Self {
files
.iter()
.map(|f| {
let mut path = PathBuf::from(&self.cwd);
let (file_name, contents) = match *f {
Stub::EmptyFile(name) => (name, "fake data"),
Stub::FileWithContent(name, content) => (name, content),
};
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> {
glob(pattern).unwrap().map(|r| r.unwrap()).collect()
}
} }
pub fn file_contents(full_path: &str) -> String { pub fn file_contents(full_path: &str) -> String {
@ -116,10 +181,6 @@ pub fn delete_directory_at(full_path: &str) {
std::fs::remove_dir_all(PathBuf::from(full_path)).expect("can not remove directory"); std::fs::remove_dir_all(PathBuf::from(full_path)).expect("can not remove directory");
} }
pub fn create_directory_at(full_path: &str) {
std::fs::create_dir(PathBuf::from(full_path)).expect("can not create directory");
}
pub fn executable_path() -> PathBuf { pub fn executable_path() -> PathBuf {
let mut buf = PathBuf::new(); let mut buf = PathBuf::new();
buf.push("target"); buf.push("target");