diff --git a/.gitignore b/.gitignore index 2714c13c06..5a7ebb7172 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,3 @@ /target **/*.rs.bk -history.txt \ No newline at end of file +history.txt diff --git a/README.md b/README.md index 30965534f4..01d5083c7c 100644 --- a/README.md +++ b/README.md @@ -124,6 +124,7 @@ Nu adheres closely to a set of goals that make up its design philosophy. As feat | ps | View current processes | | sysinfo | View information about the current system | | open {filename or url} | Load a file into a cell, convert to table if possible (avoid by appending '--raw') | +| rm {file or directory} | Remove a file, (for removing directory append '--recursive') | | exit | Exit the shell | ## Filters on tables (structured data) diff --git a/src/cli.rs b/src/cli.rs index fecd87c7d9..a36e28e075 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -175,7 +175,7 @@ pub async fn cli() -> Result<(), Box> { command("to-toml", Box::new(to_toml::to_toml)), command("to-yaml", Box::new(to_yaml::to_yaml)), command("sort-by", Box::new(sort_by::sort_by)), - command("sort-by", Box::new(sort_by::sort_by)), + Arc::new(Remove), Arc::new(Open), Arc::new(Where), Arc::new(Config), diff --git a/src/commands.rs b/src/commands.rs index c0608f35e7..206fd1d698 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -4,6 +4,7 @@ crate mod macros; crate mod args; crate mod autoview; crate mod cd; +crate mod rm; crate mod classified; crate mod clip; crate mod command; @@ -41,6 +42,7 @@ crate mod where_; crate use command::command; crate use config::Config; +crate use rm::Remove; crate use open::Open; crate use skip_while::SkipWhile; crate use where_::Where; diff --git a/src/commands/rm.rs b/src/commands/rm.rs new file mode 100644 index 0000000000..776d27d179 --- /dev/null +++ b/src/commands/rm.rs @@ -0,0 +1,63 @@ +use crate::errors::ShellError; +use crate::prelude::*; +use crate::parser::registry::{CommandConfig, NamedType, PositionalType}; +use crate::parser::hir::SyntaxType; +use indexmap::IndexMap; + +pub struct Remove; + +impl Command for Remove { + fn run(&self, args: CommandArgs) -> Result { + rm(args) + } + + fn name(&self) -> &str { + "rm" + } + + fn config(&self) -> CommandConfig { + let mut named: IndexMap = IndexMap::new(); + named.insert("recursive".to_string(), NamedType::Switch); + + CommandConfig { + name: self.name().to_string(), + positional: vec![PositionalType::mandatory("file", SyntaxType::Path)], + rest_positional: false, + named, + is_sink: true, + is_filter: false, + } + } +} + +pub fn rm(args: CommandArgs) -> Result { + let mut full_path = args.env + .lock() + .unwrap() + .path() + .to_path_buf(); + + + match args.nth(0) + .ok_or_else(|| ShellError::string(&format!("No file or directory specified")))? + .as_string()? + .as_str() { + "." | ".." => return Err(ShellError::string("\".\" and \"..\" may not be removed")), + file => full_path.push(file), + } + + + if full_path.is_dir() { + if !args.has("recursive") { + return Err(ShellError::labeled_error( + "is a directory", + "", + args.name_span.unwrap())); + } + std::fs::remove_dir_all(&full_path).expect("can not remove directory"); + } else if full_path.is_file() { + std::fs::remove_file(&full_path).expect("can not remove file"); + } + + Ok(OutputStream::empty()) +} diff --git a/tests/commands_test.rs b/tests/commands_test.rs index 219ec88e07..c4f65e6cec 100644 --- a/tests/commands_test.rs +++ b/tests/commands_test.rs @@ -1,6 +1,7 @@ mod helpers; -use helpers::in_directory as cwd; +use helpers as h; +use h::in_directory as cwd; #[test] fn lines() { @@ -56,3 +57,69 @@ fn open_error_if_file_not_found() { assert!(output.contains("File cound not be opened")); } + +#[test] +fn rm() { + let directory = "tests/fixtures/nuplayground"; + let file = format!("{}/rm_test.txt", directory); + + h::create_file_at(&file); + + nu!(_output, + cwd(directory), + "rm rm_test.txt"); + + assert!(!h::file_exists_at(&file)); +} + +#[test] +fn can_remove_directory_contents_with_recursive_flag() { + let path = "tests/fixtures/nuplayground/rm_test"; + + if h::file_exists_at(&path) { h::delete_directory_at(path) } + h::create_directory_at(path); + + for f in ["yehuda.txt", "jonathan.txt", "andres.txt"].iter() { + h::create_file_at(&format!("{}/{}", path, f)); + }; + + nu!(_output, + cwd("tests/fixtures/nuplayground"), + "rm rm_test --recursive"); + + assert!(!h::file_exists_at(&path)); +} + +#[test] +fn rm_error_if_attempting_to_delete_a_directory_without_recursive_flag() { + let path = "tests/fixtures/nuplayground/rm_test"; + + if h::file_exists_at(&path) { h::delete_directory_at(path) } + h::create_directory_at(path); + + nu_error!(output, + cwd("tests/fixtures/nuplayground"), + "rm rm_test"); + + assert!(h::file_exists_at(&path)); + assert!(output.contains("is a directory")); + h::delete_directory_at(path); +} + +#[test] +fn rm_error_if_attempting_to_delete_single_dot_as_argument() { + nu_error!(output, + cwd("tests/fixtures/nuplayground"), + "rm ."); + + assert!(output.contains("may not be removed")); +} + +#[test] +fn rm_error_if_attempting_to_delete_two_dot_as_argument() { + nu_error!(output, + cwd("tests/fixtures/nuplayground"), + "rm .."); + + assert!(output.contains("may not be removed")); +} diff --git a/tests/fixtures/formats/skinfolds.unsupported b/tests/fixtures/formats/skinfolds.unsupported deleted file mode 100644 index b1e49fd9f9..0000000000 --- a/tests/fixtures/formats/skinfolds.unsupported +++ /dev/null @@ -1 +0,0 @@ -"ABS:3.0-PEC:3.0" diff --git a/tests/fixtures/nuplayground/.gitignore b/tests/fixtures/nuplayground/.gitignore new file mode 100644 index 0000000000..67db37f04b --- /dev/null +++ b/tests/fixtures/nuplayground/.gitignore @@ -0,0 +1,2 @@ +rm_test +*.txt diff --git a/tests/helpers/mod.rs b/tests/helpers/mod.rs index d0ec5839bd..e61dab7ec5 100644 --- a/tests/helpers/mod.rs +++ b/tests/helpers/mod.rs @@ -1,11 +1,13 @@ -use std::path::PathBuf; +#![allow(dead_code)] + +pub use std::path::PathBuf; #[macro_export] macro_rules! nu { ($out:ident, $cwd:expr, $commands:expr) => { - use std::error::Error; - use std::io::prelude::*; - use std::process::{Command, Stdio}; + pub use std::io::prelude::*; + pub use std::process::{Command, Stdio}; + pub use std::error::Error; let commands = &*format!( " @@ -75,6 +77,22 @@ macro_rules! nu_error { }; } +pub fn create_file_at(full_path: &str) { + std::fs::write(PathBuf::from(full_path), "fake data".as_bytes()).expect("can not create file"); +} + +pub fn file_exists_at(full_path: &str) -> bool { + PathBuf::from(full_path).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 create_directory_at(full_path: &str) { + std::fs::create_dir(PathBuf::from(full_path)).expect("can not create directory"); +} + pub fn executable_path() -> PathBuf { let mut buf = PathBuf::new(); buf.push("target");