mirror of
https://github.com/nushell/nushell.git
synced 2024-11-22 00:13:21 +01:00
Added math product
support (#2249)
* added math product: working initial impl * resolving merge conflicts * impl std::ops::Mul for Filesize * rebased with main; refactored product implementation * fixed error msg, added docs for math product
This commit is contained in:
parent
a64270829e
commit
303a9defd3
88
Cargo.lock
generated
88
Cargo.lock
generated
@ -277,9 +277,9 @@ checksum = "1d49d90015b3c36167a20fe2810c5cd875ad504b39cff3d4eae7977e6b7c1cb2"
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.0.0"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d"
|
||||
checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
|
||||
|
||||
[[package]]
|
||||
name = "backtrace"
|
||||
@ -896,6 +896,16 @@ dependencies = [
|
||||
"crossbeam-utils 0.6.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-channel"
|
||||
version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09ee0cc8804d5393478d743b035099520087a5186f3b93fa58cec08fa62407b6"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"crossbeam-utils 0.7.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-deque"
|
||||
version = "0.7.3"
|
||||
@ -913,7 +923,7 @@ version = "0.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "058ed274caafc1f60c4997b5fc07bf7dc7cca454af7c6e81edffe5f33f70dace"
|
||||
dependencies = [
|
||||
"autocfg 1.0.0",
|
||||
"autocfg 1.0.1",
|
||||
"cfg-if",
|
||||
"crossbeam-utils 0.7.2",
|
||||
"lazy_static 1.4.0",
|
||||
@ -949,7 +959,7 @@ version = "0.7.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c3c7c73a2d1e9fc0886a08b93e98eb643461230d5f1925e4036204d5f2e261a8"
|
||||
dependencies = [
|
||||
"autocfg 1.0.0",
|
||||
"autocfg 1.0.1",
|
||||
"cfg-if",
|
||||
"lazy_static 1.4.0",
|
||||
]
|
||||
@ -1343,9 +1353,9 @@ checksum = "a246d82be1c9d791c5dfde9a2bd045fc3cbba3fa2b11ad558f27d01712f00569"
|
||||
|
||||
[[package]]
|
||||
name = "encoding_rs"
|
||||
version = "0.8.23"
|
||||
version = "0.8.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e8ac63f94732332f44fe654443c46f6375d1939684c17b0afb6cb56b0456e171"
|
||||
checksum = "a51b8cf747471cb9499b6d59e59b0444f4c90eba8968c4e44874e92b5b64ace2"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
@ -1384,9 +1394,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "event-listener"
|
||||
version = "2.3.3"
|
||||
version = "2.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f14646a9e0430150a87951622ba9675472b68e384b7701b8423b30560805c7a"
|
||||
checksum = "e1cd41440ae7e4734bbd42302f63eaba892afc93a3912dad84006247f0dedb0e"
|
||||
|
||||
[[package]]
|
||||
name = "failure"
|
||||
@ -1807,9 +1817,9 @@ checksum = "aaf91faf136cb47367fa430cd46e37a788775e7fa104f8b4bcb3861dc389b724"
|
||||
|
||||
[[package]]
|
||||
name = "git2"
|
||||
version = "0.13.9"
|
||||
version = "0.13.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "83a18853c521bcf553a4e3c05ad21f3ad89c1a78a0485adc97829a4c88066a36"
|
||||
checksum = "86d97249f21e9542caeee9f8e1d150905cd875bf723f5ff771bdb4852eb83a24"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"libc",
|
||||
@ -1863,7 +1873,7 @@ version = "0.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e91b62f79061a0bc2e046024cb7ba44b08419ed238ecbd9adbd787434b9e8c25"
|
||||
dependencies = [
|
||||
"autocfg 1.0.0",
|
||||
"autocfg 1.0.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2252,7 +2262,7 @@ version = "1.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "86b45e59b16c76b11bf9738fd5d38879d3bd28ad292d7b313608becb17ae2df9"
|
||||
dependencies = [
|
||||
"autocfg 1.0.0",
|
||||
"autocfg 1.0.1",
|
||||
"hashbrown",
|
||||
"serde 1.0.115",
|
||||
]
|
||||
@ -2310,7 +2320,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "17b77027f12e53ae59a379f7074259d32eb10867e6183388020e922832d9c3fb"
|
||||
dependencies = [
|
||||
"bytes 0.4.12",
|
||||
"crossbeam-channel",
|
||||
"crossbeam-channel 0.3.9",
|
||||
"crossbeam-utils 0.6.6",
|
||||
"curl",
|
||||
"curl-sys",
|
||||
@ -2451,15 +2461,15 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.74"
|
||||
version = "0.2.76"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a2f02823cf78b754822df5f7f268fb59822e7296276d3e069d8e8cb26a14bd10"
|
||||
checksum = "755456fae044e6fa1ebbbd1b3e902ae19e73097ed4ed87bb79934a867c007bc3"
|
||||
|
||||
[[package]]
|
||||
name = "libgit2-sys"
|
||||
version = "0.12.11+1.0.1"
|
||||
version = "0.12.12+1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1f4755167bef8a2959fe4eedeece2ba0f8eb9923b209d46139031f1281d569a2"
|
||||
checksum = "0100ae90655025134424939f1f60e27e879460d451dff6afedde4f8226cbebfc"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"libc",
|
||||
@ -2710,7 +2720,7 @@ version = "0.5.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c198b026e1bbf08a937e94c6c60f9ec4a2267f5b0d2eec9c1b21b061ce2be55f"
|
||||
dependencies = [
|
||||
"autocfg 1.0.0",
|
||||
"autocfg 1.0.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2741,9 +2751,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "miniz_oxide"
|
||||
version = "0.4.0"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "be0f75932c1f6cfae3c04000e40114adf955636e19040f9c0a2c380702aa1c7f"
|
||||
checksum = "4d7559a8a40d0f97e1edea3220f698f78b1c5ab67532e49f68fde3910323b722"
|
||||
dependencies = [
|
||||
"adler",
|
||||
]
|
||||
@ -3478,7 +3488,7 @@ version = "0.2.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "090c7f9998ee0ff65aa5b723e4009f7b217707f1fb5ea551329cc4d6231fb304"
|
||||
dependencies = [
|
||||
"autocfg 1.0.0",
|
||||
"autocfg 1.0.1",
|
||||
"num-integer",
|
||||
"num-traits 0.2.12",
|
||||
"serde 1.0.115",
|
||||
@ -3490,7 +3500,7 @@ version = "0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b6b19411a9719e753aff12e5187b74d60d3dc449ec3f4dc21e3989c3f554bc95"
|
||||
dependencies = [
|
||||
"autocfg 1.0.0",
|
||||
"autocfg 1.0.1",
|
||||
"num-traits 0.2.12",
|
||||
]
|
||||
|
||||
@ -3511,7 +3521,7 @@ version = "0.1.43"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8d59457e662d541ba17869cf51cf177c0b5f0cbf476c66bdc90bf1edac4f875b"
|
||||
dependencies = [
|
||||
"autocfg 1.0.0",
|
||||
"autocfg 1.0.1",
|
||||
"num-traits 0.2.12",
|
||||
]
|
||||
|
||||
@ -3521,7 +3531,7 @@ version = "0.1.41"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7a6e6b7c748f995c4c29c5f5ae0248536e04a5739927c74ec0fa564805094b9f"
|
||||
dependencies = [
|
||||
"autocfg 1.0.0",
|
||||
"autocfg 1.0.1",
|
||||
"num-integer",
|
||||
"num-traits 0.2.12",
|
||||
]
|
||||
@ -3532,7 +3542,7 @@ version = "0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c000134b5dbf44adc5cb772486d335293351644b801551abe8f75c84cfa4aef"
|
||||
dependencies = [
|
||||
"autocfg 1.0.0",
|
||||
"autocfg 1.0.1",
|
||||
"num-bigint",
|
||||
"num-integer",
|
||||
"num-traits 0.2.12",
|
||||
@ -3553,7 +3563,7 @@ version = "0.2.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac267bcc07f48ee5f8935ab0d24f316fb722d7a1292e2913f0cc196b29ffd611"
|
||||
dependencies = [
|
||||
"autocfg 1.0.0",
|
||||
"autocfg 1.0.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -3698,7 +3708,7 @@ version = "0.9.58"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a842db4709b604f0fe5d1170ae3565899be2ad3d9cbc72dedc789ac0511f78de"
|
||||
dependencies = [
|
||||
"autocfg 1.0.0",
|
||||
"autocfg 1.0.1",
|
||||
"cc",
|
||||
"libc",
|
||||
"pkg-config",
|
||||
@ -4388,11 +4398,11 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rayon"
|
||||
version = "1.3.1"
|
||||
version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "62f02856753d04e03e26929f820d0a0a337ebe71f849801eea335d464b349080"
|
||||
checksum = "cfd016f0c045ad38b5251be2c9c0ab806917f82da4d36b2a327e5166adad9270"
|
||||
dependencies = [
|
||||
"autocfg 1.0.0",
|
||||
"autocfg 1.0.1",
|
||||
"crossbeam-deque",
|
||||
"either",
|
||||
"rayon-core",
|
||||
@ -4400,12 +4410,12 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rayon-core"
|
||||
version = "1.7.1"
|
||||
version = "1.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e92e15d89083484e11353891f1af602cc661426deb9564c298b270c726973280"
|
||||
checksum = "91739a34c4355b5434ce54c9086c5895604a9c278586d1f1aa95e04f66b525a0"
|
||||
dependencies = [
|
||||
"crossbeam-channel 0.4.3",
|
||||
"crossbeam-deque",
|
||||
"crossbeam-queue",
|
||||
"crossbeam-utils 0.7.2",
|
||||
"lazy_static 1.4.0",
|
||||
"num_cpus",
|
||||
@ -5187,9 +5197,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.38"
|
||||
version = "1.0.39"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e69abc24912995b3038597a7a593be5053eb0fb44f3cc5beec0deb421790c1f4"
|
||||
checksum = "891d8d6567fe7c7f8835a3a98af4208f3846fba258c1bc3c31d6e506239f11f9"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@ -5632,9 +5642,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "trash"
|
||||
version = "1.1.0"
|
||||
version = "1.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bcd4ea126e9b491aa0ee8043f5519cd56498413997a6c3859c8a9812aa300803"
|
||||
checksum = "329be7bb48445d16bf4c241ba9514af46f2189c715bf5fd854e38f7c95f60194"
|
||||
dependencies = [
|
||||
"winapi 0.3.9",
|
||||
]
|
||||
@ -5875,9 +5885,9 @@ checksum = "6454029bf181f092ad1b853286f23e2c507d8e8194d01d92da4a55c274a5508c"
|
||||
|
||||
[[package]]
|
||||
name = "vec-arena"
|
||||
version = "0.5.0"
|
||||
version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "17dfb54bf57c9043f4616cb03dab30eff012cc26631b797d8354b916708db919"
|
||||
checksum = "8cb18268690309760d59ee1a9b21132c126ba384f374c59a94db4bc03adeb561"
|
||||
|
||||
[[package]]
|
||||
name = "vec_map"
|
||||
|
@ -419,6 +419,7 @@ pub fn create_default_context(
|
||||
whole_stream_command(MathStddev),
|
||||
whole_stream_command(MathSummation),
|
||||
whole_stream_command(MathVariance),
|
||||
whole_stream_command(MathProduct),
|
||||
// File format output
|
||||
whole_stream_command(To),
|
||||
whole_stream_command(ToCSV),
|
||||
|
@ -199,8 +199,8 @@ pub(crate) use last::Last;
|
||||
pub(crate) use lines::Lines;
|
||||
pub(crate) use ls::Ls;
|
||||
pub(crate) use math::{
|
||||
Math, MathAverage, MathEval, MathMaximum, MathMedian, MathMinimum, MathMode, MathStddev,
|
||||
MathSummation, MathVariance,
|
||||
Math, MathAverage, MathEval, MathMaximum, MathMedian, MathMinimum, MathMode, MathProduct,
|
||||
MathStddev, MathSummation, MathVariance,
|
||||
};
|
||||
pub(crate) use merge::Merge;
|
||||
pub(crate) use mkdir::Mkdir;
|
||||
|
@ -5,6 +5,7 @@ pub mod max;
|
||||
pub mod median;
|
||||
pub mod min;
|
||||
pub mod mode;
|
||||
pub mod product;
|
||||
pub mod stddev;
|
||||
pub mod sum;
|
||||
pub mod variance;
|
||||
@ -19,6 +20,7 @@ pub use max::SubCommand as MathMaximum;
|
||||
pub use median::SubCommand as MathMedian;
|
||||
pub use min::SubCommand as MathMinimum;
|
||||
pub use mode::SubCommand as MathMode;
|
||||
pub use product::SubCommand as MathProduct;
|
||||
pub use stddev::SubCommand as MathStddev;
|
||||
pub use sum::SubCommand as MathSummation;
|
||||
pub use variance::SubCommand as MathVariance;
|
||||
|
126
crates/nu-cli/src/commands/math/product.rs
Normal file
126
crates/nu-cli/src/commands/math/product.rs
Normal file
@ -0,0 +1,126 @@
|
||||
use crate::commands::math::reducers::{reducer_for, Reduce};
|
||||
use crate::commands::math::utils::run_with_function;
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{
|
||||
hir::{convert_number_to_u64, Number},
|
||||
Primitive, Signature, UntaggedValue, Value,
|
||||
};
|
||||
|
||||
pub struct SubCommand;
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for SubCommand {
|
||||
fn name(&self) -> &str {
|
||||
"math product"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("math product")
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Finds the product of a list of numbers or tables"
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
run_with_function(
|
||||
RunnableContext {
|
||||
input: args.input,
|
||||
registry: registry.clone(),
|
||||
shell_manager: args.shell_manager,
|
||||
host: args.host,
|
||||
ctrl_c: args.ctrl_c,
|
||||
current_errors: args.current_errors,
|
||||
name: args.call_info.name_tag,
|
||||
raw_input: args.raw_input,
|
||||
},
|
||||
product,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Get the product of a list of numbers",
|
||||
example: "echo [2 3 3 4] | math product",
|
||||
result: Some(vec![UntaggedValue::int(72).into()]),
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
fn to_byte(value: &Value) -> Option<Value> {
|
||||
match &value.value {
|
||||
UntaggedValue::Primitive(Primitive::Int(num)) => Some(
|
||||
UntaggedValue::Primitive(Primitive::Filesize(convert_number_to_u64(&Number::Int(
|
||||
num.clone(),
|
||||
))))
|
||||
.into_untagged_value(),
|
||||
),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Calculate product of given values
|
||||
pub fn product(values: &[Value], name: &Tag) -> Result<Value, ShellError> {
|
||||
let prod = reducer_for(Reduce::Product);
|
||||
|
||||
let first = values.get(0).ok_or_else(|| {
|
||||
ShellError::unexpected("Cannot perform aggregate math operation on empty data")
|
||||
})?;
|
||||
|
||||
match first {
|
||||
v if v.is_filesize() => to_byte(&prod(
|
||||
UntaggedValue::int(1).into_untagged_value(),
|
||||
values
|
||||
.iter()
|
||||
.map(|v| match v {
|
||||
Value {
|
||||
value: UntaggedValue::Primitive(Primitive::Filesize(num)),
|
||||
..
|
||||
} => UntaggedValue::int(*num as usize).into_untagged_value(),
|
||||
other => other.clone(),
|
||||
})
|
||||
.collect::<Vec<_>>(),
|
||||
)?)
|
||||
.ok_or_else(|| {
|
||||
ShellError::labeled_error(
|
||||
"could not convert to decimal",
|
||||
"could not convert to decimal",
|
||||
&name.span,
|
||||
)
|
||||
}),
|
||||
|
||||
v if v.is_none() => prod(
|
||||
UntaggedValue::int(1).into_untagged_value(),
|
||||
values
|
||||
.iter()
|
||||
.map(|v| match v {
|
||||
Value {
|
||||
value: UntaggedValue::Primitive(Primitive::Nothing),
|
||||
..
|
||||
} => UntaggedValue::int(1).into_untagged_value(),
|
||||
other => other.clone(),
|
||||
})
|
||||
.collect::<Vec<_>>(),
|
||||
),
|
||||
_ => prod(UntaggedValue::int(1).into_untagged_value(), values.to_vec()),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::SubCommand;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(SubCommand {})
|
||||
}
|
||||
}
|
@ -47,6 +47,7 @@ pub fn reducer_for(
|
||||
)),
|
||||
Reduce::Minimum => Box::new(|_, values| min(values)),
|
||||
Reduce::Maximum => Box::new(|_, values| max(values)),
|
||||
Reduce::Product => Box::new(|_, values| product(values)),
|
||||
}
|
||||
}
|
||||
|
||||
@ -54,6 +55,7 @@ pub enum Reduce {
|
||||
Summation,
|
||||
Minimum,
|
||||
Maximum,
|
||||
Product,
|
||||
Default,
|
||||
}
|
||||
|
||||
@ -133,3 +135,34 @@ pub fn min(data: Vec<Value>) -> Result<Value, ShellError> {
|
||||
tag: Tag::unknown(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn product(data: Vec<Value>) -> Result<Value, ShellError> {
|
||||
if data.is_empty() {
|
||||
return Err(ShellError::unexpected(ERR_EMPTY_DATA));
|
||||
}
|
||||
|
||||
let mut prod = UntaggedValue::int(1).into_untagged_value();
|
||||
for value in data {
|
||||
match value.value {
|
||||
UntaggedValue::Primitive(_) => {
|
||||
prod = match compute_values(Operator::Multiply, &prod, &value) {
|
||||
Ok(v) => v.into_untagged_value(),
|
||||
Err((left_type, right_type)) => {
|
||||
return Err(ShellError::coerce_error(
|
||||
left_type.spanned_unknown(),
|
||||
right_type.spanned_unknown(),
|
||||
))
|
||||
}
|
||||
};
|
||||
}
|
||||
_ => {
|
||||
return Err(ShellError::labeled_error(
|
||||
"Attempted to compute the product of a value that cannot be multiplied.",
|
||||
"value appears here",
|
||||
value.tag.span,
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(prod)
|
||||
}
|
||||
|
@ -9,6 +9,7 @@ Currently the following functions are implemented:
|
||||
* `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 sum`: Finds the sum of a list of numbers or tables
|
||||
* `math product`: Finds the product of a list of numbers or tables
|
||||
|
||||
However, the mathematical functions like `min` and `max` are more permissive and also work on `Dates`.
|
||||
|
||||
@ -90,6 +91,11 @@ To get the average of the file sizes in a directory, simply pipe the size column
|
||||
───┴──────────
|
||||
```
|
||||
|
||||
```shell
|
||||
> echo [2 3 3 4] | math product
|
||||
72
|
||||
```
|
||||
|
||||
### Dates
|
||||
|
||||
```shell
|
||||
|
Loading…
Reference in New Issue
Block a user