diff --git a/README.md b/README.md index 459697a29e..48bd3ad329 100644 --- a/README.md +++ b/README.md @@ -120,6 +120,7 @@ Nu adheres closely to a set of goals that make up its design philosophy. As feat | command | description | | ------------- | ------------- | | cd path | Change to a new path | +| cp source path | Copy files | | ls (path) | View the contents of the current or given path | | ps | View current processes | | sysinfo | View information about the current system | @@ -144,6 +145,7 @@ Nu adheres closely to a set of goals that make up its design philosophy. As feat | to-json | Convert table into .json text | | to-toml | Convert table into .toml text | | to-yaml | Convert table into .yaml text | +| to-csv | Convert table into .csv text | ## Filters on text (unstructured data) | command | description | diff --git a/src/cli.rs b/src/cli.rs index 15b6a50b85..6437756da4 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -178,6 +178,7 @@ pub async fn cli() -> Result<(), Box> { command("to-yaml", Box::new(to_yaml::to_yaml)), command("sort-by", Box::new(sort_by::sort_by)), Arc::new(Remove), + Arc::new(Copycp), Arc::new(Open), Arc::new(Where), Arc::new(Config), diff --git a/src/commands.rs b/src/commands.rs index e1ba9b7935..95ec6a4005 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 cp; crate mod rm; crate mod classified; crate mod clip; @@ -44,6 +45,7 @@ crate mod where_; crate use command::command; crate use config::Config; +crate use cp::Copycp; crate use rm::Remove; crate use open::Open; crate use skip_while::SkipWhile; diff --git a/src/commands/cp.rs b/src/commands/cp.rs new file mode 100644 index 0000000000..d0fc343d95 --- /dev/null +++ b/src/commands/cp.rs @@ -0,0 +1,80 @@ +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; + +pub struct Copycp; + +impl Command for Copycp { + fn run(&self, args: CommandArgs) -> Result { + cp(args) + } + + fn name(&self) -> &str { + "cp" + } + + 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: false, + is_filter: false, + } + } +} + +pub fn cp(args: CommandArgs) -> Result { + let mut source = args.env.lock().unwrap().path().to_path_buf(); + let mut destination = args.env.lock().unwrap().path().to_path_buf(); + + let mut dst = String::new(); + + match args + .nth(0) + .ok_or_else(|| ShellError::string(&format!("No file or directory specified")))? + .as_string()? + .as_str() + { + file => { + source.push(file); + } + } + + match args + .nth(1) + .ok_or_else(|| ShellError::string(&format!("No file or directory specified")))? + .as_string()? + .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() + ))); + } + } + + match std::fs::copy(source, destination) { + Err(_error) => Err(ShellError::string("can not copy file")), + Ok(_) => Ok(OutputStream::empty()), + } +} diff --git a/src/commands/rm.rs b/src/commands/rm.rs index 5760a34440..f3dc55b9a9 100644 --- a/src/commands/rm.rs +++ b/src/commands/rm.rs @@ -24,7 +24,7 @@ impl Command for Remove { positional: vec![PositionalType::mandatory("file", SyntaxType::Path)], rest_positional: false, named, - is_sink: true, + is_sink: false, is_filter: false, } } diff --git a/src/commands/to_csv.rs b/src/commands/to_csv.rs index 04a60590d5..df1574b955 100644 --- a/src/commands/to_csv.rs +++ b/src/commands/to_csv.rs @@ -1,12 +1,8 @@ use crate::object::{Primitive, Value}; use crate::prelude::*; -use log::debug; use csv::WriterBuilder; pub fn value_to_csv_value(v: &Value) -> Value { - - debug!("value_to_csv_value(Value::Object(v)) where v = {:?}", v); - match v { Value::Primitive(Primitive::String(s)) => Value::Primitive(Primitive::String(s.clone())), Value::Primitive(Primitive::Nothing) => Value::Primitive(Primitive::Nothing), @@ -21,9 +17,6 @@ pub fn to_string(v: &Value) -> Result> { match v { Value::List(_l) => return Ok(String::from("[list list]")), Value::Object(o) => { - - debug!("to_csv:to_string(Value::Object(v)) where v = {:?}", v); - let mut wtr = WriterBuilder::new().from_writer(vec![]); let mut fields: VecDeque = VecDeque::new(); let mut values: VecDeque = VecDeque::new(); diff --git a/tests/commands_test.rs b/tests/commands_test.rs index dd9863e05b..ff317eb7f2 100644 --- a/tests/commands_test.rs +++ b/tests/commands_test.rs @@ -7,7 +7,8 @@ use helpers as h; fn lines() { nu!(output, cwd("tests/fixtures/formats"), - "open cargo_sample.toml --raw | lines | skip-while $it != \"[dependencies]\" | skip 1 | first 1 | split-column \"=\" | get Column1 | trim | echo $it"); + "open cargo_sample.toml --raw | lines | skip-while $it != \"[dependencies]\" | skip 1 | first 1 | split-column \"=\" | get Column1 | trim | echo $it" + ); assert_eq!(output, "rustyline"); } @@ -38,7 +39,8 @@ fn open_can_parse_toml() { fn open_can_parse_json() { nu!(output, cwd("tests/fixtures/formats"), - "open sgml_description.json | get glossary.GlossDiv.GlossList.GlossEntry.GlossSee | echo $it"); + "open sgml_description.json | get glossary.GlossDiv.GlossList.GlossEntry.GlossSee | echo $it" + ); assert_eq!(output, "markup") } @@ -96,6 +98,52 @@ 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 (playground_path, tests_dir) = h::setup_playground_for("cp_test"); + + let full_path = format!("{}/{}", playground_path, tests_dir ); + let expected_file = format!("{}/{}", full_path , "sample.ini" ); + + nu!( + _output, + cwd(&playground_path), + "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 (playground_path, tests_dir) = h::setup_playground_for("cp_test_2"); + + let full_path = format!("{}/{}", playground_path, tests_dir ); + let expected_file = format!("{}/{}", full_path , "sample.ini" ); + + nu!( + _output, + cwd(&playground_path), + "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() { + let (playground_path, _) = h::setup_playground_for("cp_test_3"); + + nu_error!( + output, + cwd(&playground_path), + "cp ../formats cp_test_3" + ); + + assert!(output.contains("../formats")); + assert!(output.contains("is a directory (not copied)")); +} + #[test] fn rm_can_remove_a_file() { let directory = "tests/fixtures/nuplayground"; @@ -149,4 +197,4 @@ 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")); -} +} \ No newline at end of file diff --git a/tests/filters_test.rs b/tests/filters_test.rs index d7552c583e..14e5b46f86 100644 --- a/tests/filters_test.rs +++ b/tests/filters_test.rs @@ -7,7 +7,8 @@ use helpers::in_directory as cwd; fn can_convert_table_to_csv_text_and_from_csv_text_back_into_table() { nu!(output, cwd("tests/fixtures/formats"), - "open caco3_plastics.csv | to-csv | from-csv | first 1 | get origin | echo $it"); + "open caco3_plastics.csv | to-csv | from-csv | first 1 | get origin | echo $it" + ); assert_eq!(output, "SPAIN"); } @@ -16,7 +17,8 @@ fn can_convert_table_to_csv_text_and_from_csv_text_back_into_table() { fn can_convert_table_to_json_text_and_from_json_text_back_into_table() { nu!(output, cwd("tests/fixtures/formats"), - "open sgml_description.json | to-json | from-json | get glossary.GlossDiv.GlossList.GlossEntry.GlossSee | echo $it"); + "open sgml_description.json | to-json | from-json | get glossary.GlossDiv.GlossList.GlossEntry.GlossSee | echo $it" + ); assert_eq!(output, "markup"); } @@ -47,7 +49,8 @@ fn can_convert_table_to_yaml_text_and_from_yaml_text_back_into_table() { fn can_sort_by_column() { nu!(output, cwd("tests/fixtures/formats"), - "open cargo_sample.toml --raw | lines | skip 1 | first 4 | split-column \"=\" | sort-by Column1 | skip 1 | first 1 | get Column1 | trim | echo $it"); + "open cargo_sample.toml --raw | lines | skip 1 | first 4 | split-column \"=\" | sort-by Column1 | skip 1 | first 1 | get Column1 | trim | echo $it" + ); assert_eq!(output, "description"); } @@ -56,7 +59,8 @@ fn can_sort_by_column() { fn can_split_by_column() { nu!(output, cwd("tests/fixtures/formats"), - "open cargo_sample.toml --raw | lines | skip 1 | first 1 | split-column \"=\" | get Column1 | trim | echo $it"); + "open cargo_sample.toml --raw | lines | skip 1 | first 1 | split-column \"=\" | get Column1 | trim | echo $it" + ); assert_eq!(output, "name"); } diff --git a/tests/fixtures/nuplayground/.gitignore b/tests/fixtures/nuplayground/.gitignore index 09fb65d850..56353ae29e 100644 --- a/tests/fixtures/nuplayground/.gitignore +++ b/tests/fixtures/nuplayground/.gitignore @@ -1,2 +1,2 @@ -*_test +*_test* *.txt