Rename calc to math eval and allow it to optionally take an expression as an argument (#2195)

* Rename `calc` to `math eval` and allow it to optionally take the expression as an argument

* Moved calc tests to math eval
Also added 2 tests and changed 1 test

* Move calc docs to math eval
This commit is contained in:
Shaurya Shubham 2020-07-18 09:41:19 +05:30 committed by GitHub
parent 04e8aa31fe
commit ffe3e2c16b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 207 additions and 170 deletions

View File

@ -264,7 +264,6 @@ pub fn create_default_context(
whole_stream_command(Cpy), whole_stream_command(Cpy),
whole_stream_command(Date), whole_stream_command(Date),
whole_stream_command(Cal), whole_stream_command(Cal),
whole_stream_command(Calc),
whole_stream_command(Mkdir), whole_stream_command(Mkdir),
whole_stream_command(Mv), whole_stream_command(Mv),
whole_stream_command(Kill), whole_stream_command(Kill),
@ -367,6 +366,7 @@ pub fn create_default_context(
whole_stream_command(AutoenvUnTrust), whole_stream_command(AutoenvUnTrust),
whole_stream_command(Math), whole_stream_command(Math),
whole_stream_command(MathAverage), whole_stream_command(MathAverage),
whole_stream_command(MathEval),
whole_stream_command(MathMedian), whole_stream_command(MathMedian),
whole_stream_command(MathMinimum), whole_stream_command(MathMinimum),
whole_stream_command(MathMode), whole_stream_command(MathMode),

View File

@ -14,7 +14,6 @@ pub(crate) mod autoenv_untrust;
pub(crate) mod autoview; pub(crate) mod autoview;
pub(crate) mod build_string; pub(crate) mod build_string;
pub(crate) mod cal; pub(crate) mod cal;
pub(crate) mod calc;
pub(crate) mod cd; pub(crate) mod cd;
pub(crate) mod char_; pub(crate) mod char_;
pub(crate) mod classified; pub(crate) mod classified;
@ -134,7 +133,6 @@ pub(crate) use autoenv_trust::AutoenvTrust;
pub(crate) use autoenv_untrust::AutoenvUnTrust; pub(crate) use autoenv_untrust::AutoenvUnTrust;
pub(crate) use build_string::BuildString; pub(crate) use build_string::BuildString;
pub(crate) use cal::Cal; pub(crate) use cal::Cal;
pub(crate) use calc::Calc;
pub(crate) use char_::Char; pub(crate) use char_::Char;
pub(crate) use compact::Compact; pub(crate) use compact::Compact;
pub(crate) use config::{ pub(crate) use config::{
@ -192,8 +190,8 @@ pub(crate) use last::Last;
pub(crate) use lines::Lines; pub(crate) use lines::Lines;
pub(crate) use ls::Ls; pub(crate) use ls::Ls;
pub(crate) use math::{ pub(crate) use math::{
Math, MathAverage, MathMaximum, MathMedian, MathMinimum, MathMode, MathStddev, MathSummation, Math, MathAverage, MathEval, MathMaximum, MathMedian, MathMinimum, MathMode, MathStddev,
MathVariance, MathSummation, MathVariance,
}; };
pub(crate) use merge::Merge; pub(crate) use merge::Merge;
pub(crate) use mkdir::Mkdir; pub(crate) use mkdir::Mkdir;

View File

@ -1,88 +0,0 @@
use crate::commands::WholeStreamCommand;
use crate::prelude::*;
use nu_errors::ShellError;
use nu_protocol::{Primitive, ReturnSuccess, UntaggedValue, Value};
pub struct Calc;
#[async_trait]
impl WholeStreamCommand for Calc {
fn name(&self) -> &str {
"calc"
}
fn usage(&self) -> &str {
"Parse a math expression into a number"
}
async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
calc(args, registry).await
}
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Calculate math in the pipeline",
example: "echo '10 / 4' | calc",
result: Some(vec![UntaggedValue::decimal(2.5).into()]),
}]
}
}
pub async fn calc(
args: CommandArgs,
_registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let input = args.input;
let name = args.call_info.name_tag.span;
Ok(input
.map(move |input| {
if let Ok(string) = input.as_string() {
match parse(&string, &input.tag) {
Ok(value) => ReturnSuccess::value(value),
Err(err) => Err(ShellError::labeled_error(
"Calculation error",
err,
&input.tag.span,
)),
}
} else {
Err(ShellError::labeled_error(
"Expected a string from pipeline",
"requires string input",
name,
))
}
})
.to_output_stream())
}
pub fn parse(math_expression: &str, tag: impl Into<Tag>) -> Result<Value, String> {
use std::f64;
let num = meval::eval_str(math_expression);
match num {
Ok(num) => {
if num == f64::INFINITY || num == f64::NEG_INFINITY {
return Err(String::from("cannot represent result"));
}
Ok(UntaggedValue::from(Primitive::from(num)).into_value(tag))
}
Err(error) => Err(error.to_string()),
}
}
#[cfg(test)]
mod tests {
use super::Calc;
#[test]
fn examples_work_as_expected() {
use crate::examples::test as test_examples;
test_examples(Calc {})
}
}

View File

@ -0,0 +1,107 @@
use crate::commands::WholeStreamCommand;
use crate::prelude::*;
use nu_errors::ShellError;
use nu_protocol::{Primitive, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
use nu_source::Tagged;
pub struct SubCommand;
#[derive(Deserialize)]
pub struct SubCommandArgs {
expression: Option<Tagged<String>>,
}
#[async_trait]
impl WholeStreamCommand for SubCommand {
fn name(&self) -> &str {
"math eval"
}
fn usage(&self) -> &str {
"Evaluate a math expression into a number"
}
fn signature(&self) -> Signature {
Signature::build("math eval").desc(self.usage()).optional(
"math expression",
SyntaxShape::String,
"the math expression to evaluate",
)
}
async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
eval(args, registry).await
}
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Evalulate math in the pipeline",
example: "echo '10 / 4' | math eval",
result: Some(vec![UntaggedValue::decimal(2.5).into()]),
}]
}
}
pub async fn eval(
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
let name = args.call_info.name_tag.span;
let (SubCommandArgs { expression }, input) = args.process(registry).await?;
Ok(input
.map(move |x| {
if let Some(Tagged {
tag,
item: expression,
}) = &expression
{
UntaggedValue::string(expression).into_value(tag)
} else {
x
}
})
.map(move |input| {
if let Ok(string) = input.as_string() {
match parse(&string, &input.tag) {
Ok(value) => ReturnSuccess::value(value),
Err(err) => Err(ShellError::labeled_error(
"Math evaluation error",
err,
&input.tag.span,
)),
}
} else {
Err(ShellError::labeled_error(
"Expected a string from pipeline",
"requires string input",
name,
))
}
})
.to_output_stream())
}
pub fn parse<T: Into<Tag>>(math_expression: &str, tag: T) -> Result<Value, String> {
match meval::eval_str(math_expression) {
Ok(num) if num.is_infinite() || num.is_nan() => Err("cannot represent result".to_string()),
Ok(num) => Ok(UntaggedValue::from(Primitive::from(num)).into_value(tag)),
Err(error) => Err(error.to_string().to_lowercase()),
}
}
#[cfg(test)]
mod tests {
use super::SubCommand;
#[test]
fn examples_work_as_expected() {
use crate::examples::test as test_examples;
test_examples(SubCommand {})
}
}

View File

@ -1,5 +1,6 @@
pub mod avg; pub mod avg;
pub mod command; pub mod command;
pub mod eval;
pub mod max; pub mod max;
pub mod median; pub mod median;
pub mod min; pub mod min;
@ -11,6 +12,7 @@ pub mod variance;
pub use avg::SubCommand as MathAverage; pub use avg::SubCommand as MathAverage;
pub use command::Command as Math; pub use command::Command as Math;
pub use eval::SubCommand as MathEval;
pub use max::SubCommand as MathMaximum; pub use max::SubCommand as MathMaximum;
pub use median::SubCommand as MathMedian; pub use median::SubCommand as MathMedian;
pub use min::SubCommand as MathMinimum; pub use min::SubCommand as MathMinimum;

View File

@ -1,49 +0,0 @@
use nu_test_support::{nu, pipeline};
#[test]
fn calculates_two_plus_two() {
let actual = nu!(
cwd: ".", pipeline(
r#"
echo "2 + 2" | calc
"#
));
assert!(actual.out.contains("4.0"));
}
#[test]
fn calculates_two_to_the_power_six() {
let actual = nu!(
cwd: ".", pipeline(
r#"
echo "2 ^ 6" | calc
"#
));
assert!(actual.out.contains("64.0"));
}
#[test]
fn calculates_three_multiplied_by_five() {
let actual = nu!(
cwd: ".", pipeline(
r#"
echo "3 * 5" | calc
"#
));
assert!(actual.out.contains("15.0"));
}
#[test]
fn calculates_twenty_four_divided_by_two() {
let actual = nu!(
cwd: ".", pipeline(
r#"
echo "24 / 2" | calc
"#
));
assert!(actual.out.contains("12.0"));
}

View File

@ -0,0 +1,73 @@
use nu_test_support::{nu, pipeline};
#[test]
fn evaluates_two_plus_two() {
let actual = nu!(
cwd: ".", pipeline(
r#"
math eval "2 + 2"
"#
));
assert!(actual.out.contains("4.0"));
}
#[test]
fn evaluates_two_to_the_power_four() {
let actual = nu!(
cwd: ".", pipeline(
r#"
echo "2 ^ 4" | math eval
"#
));
assert!(actual.out.contains("16.0"));
}
#[test]
fn evaluates_three_multiplied_by_five() {
let actual = nu!(
cwd: ".", pipeline(
r#"
echo "3 * 5" | math eval
"#
));
assert!(actual.out.contains("15.0"));
}
#[test]
fn evaluates_twenty_four_divided_by_two() {
let actual = nu!(
cwd: ".", pipeline(
r#"
echo "24 / 2" | math eval
"#
));
assert!(actual.out.contains("12.0"));
}
#[test]
fn evaluates_twenty_eight_minus_seven() {
let actual = nu!(
cwd: ".", pipeline(
r#"
echo "28 - 7" | math eval
"#
));
assert!(actual.out.contains("21"));
}
#[test]
fn evaluates_pi() {
let actual = nu!(
cwd: ".", pipeline(
r#"
math eval pi
"#
));
assert!(actual.out.contains("3.14"));
}

View File

@ -1,5 +1,7 @@
mod avg; mod avg;
mod eval;
mod median; mod median;
mod sum;
use nu_test_support::{nu, pipeline}; use nu_test_support::{nu, pipeline};

View File

@ -4,7 +4,6 @@ mod autoenv;
mod autoenv_trust; mod autoenv_trust;
mod autoenv_untrust; mod autoenv_untrust;
mod cal; mod cal;
mod calc;
mod cd; mod cd;
mod compact; mod compact;
mod cp; mod cp;

View File

@ -1,6 +1,6 @@
# calc # math eval
calc is a command that takes a math expression from the pipeline and calculates that into a number. math eval is a command that takes a math expression from the pipeline and evaluates that into a number. It also optionally takes the math expression as an argument.
This command supports the following operations - This command supports the following operations -
@ -27,62 +27,62 @@ constants:
## Examples ## Examples
```shell ```shell
> echo "1+2+3" | calc > echo "1+2+3" | math eval
6.0 6.0u
``` ```
```shell ```shell
> echo "1-2+3" | calc > echo "1-2+3" | math eval
2.0 2.0
``` ```
```shell ```shell
> echo "-(-23)" | calc > echo "-(-23)" | math eval
23.0 23.0
``` ```
```shell ```shell
> echo "5^2" | calc > echo "5^2" | math eval
25.0 25.0
``` ```
```shell ```shell
> echo "5^3" | calc > echo "5^3" | math eval
125.0 125.0
``` ```
```shell ```shell
> echo "min(5,4,3,2,1,0,-100,45)" | calc > echo "min(5,4,3,2,1,0,-100,45)" | math eval
-100.0 -100.0
``` ```
```shell ```shell
> echo "max(5,4,3,2,1,0,-100,45)" | calc > echo "max(5,4,3,2,1,0,-100,45)" | math eval
45.0 45.0
``` ```
```shell ```shell
> echo sqrt(2) | calc > echo sqrt(2) | math eval
1.414213562373095 1.414213562373095
``` ```
```shell ```shell
> echo pi | calc > echo pi | math eval
3.141592653589793 3.141592653589793
``` ```
```shell ```shell
> echo e | calc > echo e | math eval
2.718281828459045 2.718281828459045
``` ```
```shell ```shell
> echo "sin(pi / 2)" | calc > echo "sin(pi / 2)" | math eval
1.0 1.0
``` ```
```shell ```shell
> echo "floor(5999/1000)" | calc > echo "floor(5999/1000)" | math eval
5.0 5.0
``` ```
@ -119,7 +119,7 @@ constants:
``` ```
```shell ```shell
> open abc.json | format "({size} + 500) * 4" | calc > open abc.json | format "({size} + 500) * 4" | math eval
───┬─────────── ───┬───────────
# │ # │
───┼─────────── ───┼───────────
@ -135,7 +135,7 @@ constants:
``` ```
```shell ```shell
> open abc.json | format "({size} - 1000) * 4" | calc > open abc.json | format "({size} - 1000) * 4" | math eval
───┬──────────── ───┬────────────
# │ # │
───┼──────────── ───┼────────────
@ -150,9 +150,9 @@ constants:
───┴──────────── ───┴────────────
``` ```
Note that since `calc` uses floating-point numbers, the result may not always be precise. Note that since `math eval` uses floating-point numbers, the result may not always be precise.
```shell ```shell
> echo "floor(5999999999999999999/1000000000000000000)" | calc > echo "floor(5999999999999999999/1000000000000000000)" | math eval
6.0 6.0
``` ```

View File

@ -4,6 +4,7 @@ Mathematical functions that generally only operate on a list of numbers (integer
Currently the following functions are implemented: Currently the following functions are implemented:
* `math avg`: Finds the average of a list of numbers or tables * `math avg`: Finds the average of a list of numbers or tables
* [`math eval`](math-eval.md): Evaluates a list of math expressions into numbers
* `math min`: Finds the minimum within a list of numbers or tables * `math min`: Finds the minimum within a list of numbers or tables
* `math max`: Finds the maximum within a list of numbers or tables * `math max`: Finds the maximum within a list of numbers or tables
* `math median`: Finds the median of a list of numbers or tables * `math median`: Finds the median of a list of numbers or tables
@ -152,11 +153,3 @@ To get the sum of the characters that make up your present working directory.
> echo [] | math avg > echo [] | math avg
error: Error: Unexpected: Cannot perform aggregate math operation on empty data error: Error: Unexpected: Cannot perform aggregate math operation on empty data
``` ```
Note `math` functions only work on list of numbers (integers, decimals, bytes) and tables of numbers, if any other types are piped into the function
then unexpected results can occur.
```shell
> echo [1 2 a ] | math avg
0
```