1use crate::math::utils::run_with_elementwise;
2use nu_engine::command_prelude::*;
3
4#[derive(Clone)]
5pub struct MathAbs;
6
7impl Command for MathAbs {
8 fn name(&self) -> &str {
9 "math abs"
10 }
11
12 fn signature(&self) -> Signature {
13 Signature::build("math abs")
14 .input_output_types(vec![
15 (Type::Number, Type::Number),
16 (Type::Duration, Type::Duration),
17 (Type::Filesize, Type::Filesize),
18 (
19 Type::List(Box::new(Type::Number)),
20 Type::List(Box::new(Type::Number)),
21 ),
22 (
23 Type::List(Box::new(Type::Duration)),
24 Type::List(Box::new(Type::Duration)),
25 ),
26 (
27 Type::List(Box::new(Type::Filesize)),
28 Type::List(Box::new(Type::Filesize)),
29 ),
30 (Type::Range, Type::List(Box::new(Type::Number))),
31 (Type::record(), Type::record()),
32 ])
33 .rest(
34 "columns",
35 SyntaxShape::CellPath,
36 "The cell-paths/columns to operate on.",
37 )
38 .allow_variants_without_examples(true)
39 .category(Category::Math)
40 }
41
42 fn description(&self) -> &str {
43 "Returns the absolute value of a number."
44 }
45
46 fn search_terms(&self) -> Vec<&str> {
47 vec!["absolute", "modulus", "positive", "distance"]
48 }
49
50 fn is_const(&self) -> bool {
51 true
52 }
53
54 fn run(
55 &self,
56 engine_state: &EngineState,
57 stack: &mut Stack,
58 call: &Call,
59 input: PipelineData,
60 ) -> Result<PipelineData, ShellError> {
61 let cell_paths: Vec<CellPath> = call.rest(engine_state, stack, 0)?;
62 let head = call.head;
63 run_with_elementwise(
64 input,
65 cell_paths,
66 head,
67 engine_state.signals(),
68 false,
69 move |value| abs_helper(value, head),
70 )
71 }
72
73 fn run_const(
74 &self,
75 working_set: &StateWorkingSet,
76 call: &Call,
77 input: PipelineData,
78 ) -> Result<PipelineData, ShellError> {
79 let cell_paths: Vec<CellPath> = call.rest_const(working_set, 0)?;
80 let head = call.head;
81 run_with_elementwise(
82 input,
83 cell_paths,
84 head,
85 working_set.permanent().signals(),
86 false,
87 move |value| abs_helper(value, head),
88 )
89 }
90
91 fn examples(&self) -> Vec<Example<'_>> {
92 vec![
93 Example {
94 description: "Compute absolute value of each number in a list of numbers.",
95 example: "[-50 -100.0 25] | math abs",
96 result: Some(Value::list(
97 vec![
98 Value::test_int(50),
99 Value::test_float(100.0),
100 Value::test_int(25),
101 ],
102 Span::test_data(),
103 )),
104 },
105 Example {
106 description: "Compute the absolute value of list-valued columns in a record.",
107 example: "{alice: [-1 -2 -3], bob: [-4 -5]} | math abs",
108 result: Some(Value::test_record(record! {
109 "alice" => Value::list(
110 vec![Value::test_int(1), Value::test_int(2), Value::test_int(3)],
111 Span::test_data(),
112 ),
113 "bob" => Value::list(
114 vec![Value::test_int(4), Value::test_int(5)],
115 Span::test_data(),
116 ),
117 })),
118 },
119 Example {
120 description: "Compute the absolute value of a single column using a cell path.",
121 example: "{alice: [-1 -2 -3], bob: [-4 -5]} | math abs alice",
122 result: Some(Value::test_record(record! {
123 "alice" => Value::list(
124 vec![Value::test_int(1), Value::test_int(2), Value::test_int(3)],
125 Span::test_data(),
126 ),
127 "bob" => Value::list(
128 vec![Value::test_int(-4), Value::test_int(-5)],
129 Span::test_data(),
130 ),
131 })),
132 },
133 ]
134 }
135}
136
137fn abs_helper(val: Value, head: Span) -> Value {
138 let span = val.span();
139 match val {
140 Value::Int { val, .. } => match val.checked_abs() {
141 Some(abs) => Value::int(abs, span),
142 None => Value::error(
143 ShellError::OperatorOverflow {
144 msg: "absolute value operation overflowed".into(),
145 span,
146 help: Some(format!(
147 "the absolute value of {val} cannot be represented as a 64-bit integer"
148 )),
149 },
150 span,
151 ),
152 },
153 Value::Float { val, .. } => Value::float(val.abs(), span),
154 Value::Duration { val, .. } => match val.checked_abs() {
155 Some(abs) => Value::duration(abs, span),
156 None => Value::error(
157 ShellError::OperatorOverflow {
158 msg: "absolute value operation overflowed".into(),
159 span,
160 help: Some(
161 "the absolute value of the minimum duration cannot be represented".into(),
162 ),
163 },
164 span,
165 ),
166 },
167 Value::Filesize { val, .. } => match val.get().checked_abs() {
168 Some(abs) => Value::filesize(abs, span),
169 None => Value::error(
170 ShellError::OperatorOverflow {
171 msg: "absolute value operation overflowed".into(),
172 span,
173 help: Some(
174 "the absolute value of the minimum filesize cannot be represented".into(),
175 ),
176 },
177 span,
178 ),
179 },
180 Value::Error { .. } => val,
181 other => Value::error(
182 ShellError::OnlySupportsThisInputType {
183 exp_input_type: crate::math::utils::NUMERIC_INPUT_TYPES.into(),
184 wrong_type: other.get_type().to_string(),
185 dst_span: head,
186 src_span: other.span(),
187 },
188 head,
189 ),
190 }
191}
192
193#[cfg(test)]
194mod test {
195 use super::*;
196
197 #[test]
198 fn test_examples() -> nu_test_support::Result {
199 nu_test_support::test().examples(MathAbs)
200 }
201}