diff --git a/Cargo.lock b/Cargo.lock index 1d2c7431dc..4775984ad7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1927,6 +1927,16 @@ dependencies = [ "rustc_version", ] +[[package]] +name = "meval" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f79496a5651c8d57cd033c5add8ca7ee4e3d5f7587a4777484640d9cb60392d9" +dependencies = [ + "fnv", + "nom 1.2.4", +] + [[package]] name = "mime" version = "0.3.14" @@ -2070,6 +2080,12 @@ version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb" +[[package]] +name = "nom" +version = "1.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5b8c256fd9471521bcb84c3cdba98921497f1a331cbc15b8030fc63b82050ce" + [[package]] name = "nom" version = "4.2.3" @@ -2170,6 +2186,7 @@ dependencies = [ "itertools 0.8.2", "language-reporting", "log", + "meval", "natural", "nom 5.1.0", "nom-tracable", diff --git a/Cargo.toml b/Cargo.toml index f20a0f9366..48e0e63823 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -123,6 +123,7 @@ termcolor = "1.1.0" natural = "0.3.0" parking_lot = "0.10.0" futures-timer = "1.0.2" +meval = "0.2" clipboard = {version = "0.5", optional = true } ptree = {version = "0.2" } diff --git a/src/cli.rs b/src/cli.rs index 4695f8b8f6..219fdaa510 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -268,6 +268,7 @@ pub async fn cli() -> Result<(), Box> { whole_stream_command(Save), per_item_command(Cpy), whole_stream_command(Date), + per_item_command(Calc), per_item_command(Mkdir), per_item_command(Move), whole_stream_command(Version), diff --git a/src/commands.rs b/src/commands.rs index d1a83f38c9..bd507f20ff 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -7,6 +7,7 @@ mod to_delimited_data; pub(crate) mod append; pub(crate) mod args; pub(crate) mod autoview; +pub(crate) mod calc; pub(crate) mod cd; pub(crate) mod classified; pub(crate) mod clip; @@ -107,6 +108,7 @@ pub(crate) use command::{ }; pub(crate) use append::Append; +pub(crate) use calc::Calc; pub(crate) use compact::Compact; pub(crate) use config::Config; pub(crate) use count::Count; diff --git a/src/commands/calc.rs b/src/commands/calc.rs new file mode 100644 index 0000000000..1f926dadbb --- /dev/null +++ b/src/commands/calc.rs @@ -0,0 +1,57 @@ +use crate::commands::PerItemCommand; +use crate::prelude::*; +use nu_errors::ShellError; +use nu_protocol::{CallInfo, Primitive, ReturnSuccess, UntaggedValue, Value}; + +pub struct Calc; + +impl PerItemCommand for Calc { + fn name(&self) -> &str { + "calc" + } + + fn usage(&self) -> &str { + "Parse a math expression into a number" + } + + fn run( + &self, + _call_info: &CallInfo, + _registry: &CommandRegistry, + raw_args: &RawCommandArgs, + input: Value, + ) -> Result { + calc(input, raw_args) + } +} + +fn calc(input: Value, args: &RawCommandArgs) -> Result { + let name_span = &args.call_info.name_tag.span; + + let output = if let Ok(string) = input.as_string() { + match parse(&string, &input.tag) { + Ok(value) => ReturnSuccess::value(value), + Err(err) => Err(ShellError::labeled_error( + "Calulation error", + err, + &input.tag.span, + )), + } + } else { + Err(ShellError::labeled_error( + "Expected a string from pipeline", + "requires string input", + name_span, + )) + }; + + Ok(vec![output].into()) +} + +pub fn parse(math_expression: &str, tag: impl Into) -> Result { + let num = meval::eval_str(math_expression); + match num { + Ok(num) => Ok(UntaggedValue::from(Primitive::from(num)).into_value(tag)), + Err(error) => Err(error.to_string()), + } +} diff --git a/tests/commands/calc.rs b/tests/commands/calc.rs new file mode 100644 index 0000000000..c4953ef8e9 --- /dev/null +++ b/tests/commands/calc.rs @@ -0,0 +1,49 @@ +use nu_test_support::{nu, pipeline}; + +#[test] +fn calculates_two_plus_two() { + let actual = nu!( + cwd: ".", pipeline( + r#" + echo "2 + 2" | calc + "# + )); + + assert!(actual.contains("4.0")); +} + +#[test] +fn calculates_two_to_the_power_six() { + let actual = nu!( + cwd: ".", pipeline( + r#" + echo "2 ^ 6" | calc + "# + )); + + assert!(actual.contains("64.0")); +} + +#[test] +fn calculates_three_multiplied_by_five() { + let actual = nu!( + cwd: ".", pipeline( + r#" + echo "3 * 5" | calc + "# + )); + + assert!(actual.contains("15.0")); +} + +#[test] +fn calculates_twenty_four_divided_by_two() { + let actual = nu!( + cwd: ".", pipeline( + r#" + echo "24 / 2" | calc + "# + )); + + assert!(actual.contains("12.0")); +} diff --git a/tests/commands/mod.rs b/tests/commands/mod.rs index 5ee8355917..8039c59310 100644 --- a/tests/commands/mod.rs +++ b/tests/commands/mod.rs @@ -1,4 +1,5 @@ mod append; +mod calc; mod cd; mod compact; mod cp;