nu_command/conversions/into/
float.rs

1use nu_cmd_base::input_handler::{CellPathOnlyArgs, operate};
2use nu_engine::command_prelude::*;
3
4#[derive(Clone)]
5pub struct IntoFloat;
6
7impl Command for IntoFloat {
8    fn name(&self) -> &str {
9        "into float"
10    }
11
12    fn signature(&self) -> Signature {
13        Signature::build("into float")
14            .input_output_types(vec![
15                (Type::Int, Type::Float),
16                (Type::String, Type::Float),
17                (Type::Bool, Type::Float),
18                (Type::Float, Type::Float),
19                (Type::table(), Type::table()),
20                (Type::record(), Type::record()),
21                (
22                    Type::List(Box::new(Type::Any)),
23                    Type::List(Box::new(Type::Float)),
24                ),
25            ])
26            .rest(
27                "rest",
28                SyntaxShape::CellPath,
29                "For a data structure input, convert data at the given cell paths.",
30            )
31            .allow_variants_without_examples(true)
32            .category(Category::Conversions)
33    }
34
35    fn description(&self) -> &str {
36        "Convert data into floating point number."
37    }
38
39    fn search_terms(&self) -> Vec<&str> {
40        vec!["convert", "number", "floating", "decimal"]
41    }
42
43    fn run(
44        &self,
45        engine_state: &EngineState,
46        stack: &mut Stack,
47        call: &Call,
48        input: PipelineData,
49    ) -> Result<PipelineData, ShellError> {
50        let cell_paths: Vec<CellPath> = call.rest(engine_state, stack, 0)?;
51        let args = CellPathOnlyArgs::from(cell_paths);
52        operate(action, args, input, call.head, engine_state.signals())
53    }
54
55    fn examples(&self) -> Vec<Example> {
56        vec![
57            Example {
58                description: "Convert string to float in table",
59                example: "[[num]; ['5.01']] | into float num",
60                result: Some(Value::test_list(vec![Value::test_record(record! {
61                    "num" => Value::test_float(5.01),
62                })])),
63            },
64            Example {
65                description: "Convert string to floating point number",
66                example: "'1.345' | into float",
67                result: Some(Value::test_float(1.345)),
68            },
69            Example {
70                description: "Coerce list of ints and floats to float",
71                example: "[4 -5.9] | into float",
72                result: Some(Value::test_list(vec![
73                    Value::test_float(4.0),
74                    Value::test_float(-5.9),
75                ])),
76            },
77            Example {
78                description: "Convert boolean to float",
79                example: "true | into float",
80                result: Some(Value::test_float(1.0)),
81            },
82        ]
83    }
84}
85
86fn action(input: &Value, _args: &CellPathOnlyArgs, head: Span) -> Value {
87    let span = input.span();
88    match input {
89        Value::Float { .. } => input.clone(),
90        Value::String { val: s, .. } => {
91            let other = s.trim();
92
93            match other.parse::<f64>() {
94                Ok(x) => Value::float(x, head),
95                Err(reason) => Value::error(
96                    ShellError::CantConvert {
97                        to_type: "float".to_string(),
98                        from_type: reason.to_string(),
99                        span,
100                        help: None,
101                    },
102                    span,
103                ),
104            }
105        }
106        Value::Int { val: v, .. } => Value::float(*v as f64, span),
107        Value::Bool { val: b, .. } => Value::float(
108            match b {
109                true => 1.0,
110                false => 0.0,
111            },
112            span,
113        ),
114        // Propagate errors by explicitly matching them before the final case.
115        Value::Error { .. } => input.clone(),
116        other => Value::error(
117            ShellError::OnlySupportsThisInputType {
118                exp_input_type: "string, int or bool".into(),
119                wrong_type: other.get_type().to_string(),
120                dst_span: head,
121                src_span: other.span(),
122            },
123            head,
124        ),
125    }
126}
127
128#[cfg(test)]
129mod tests {
130    use super::*;
131    use nu_protocol::Type::Error;
132
133    #[test]
134    fn test_examples() {
135        use crate::test_examples;
136
137        test_examples(IntoFloat {})
138    }
139
140    #[test]
141    #[allow(clippy::approx_constant)]
142    fn string_to_float() {
143        let word = Value::test_string("3.1415");
144        let expected = Value::test_float(3.1415);
145
146        let actual = action(&word, &CellPathOnlyArgs::from(vec![]), Span::test_data());
147        assert_eq!(actual, expected);
148    }
149
150    #[test]
151    fn communicates_parsing_error_given_an_invalid_floatlike_string() {
152        let invalid_str = Value::test_string("11.6anra");
153
154        let actual = action(
155            &invalid_str,
156            &CellPathOnlyArgs::from(vec![]),
157            Span::test_data(),
158        );
159
160        assert_eq!(actual.get_type(), Error);
161    }
162
163    #[test]
164    fn int_to_float() {
165        let input_int = Value::test_int(10);
166        let expected = Value::test_float(10.0);
167        let actual = action(
168            &input_int,
169            &CellPathOnlyArgs::from(vec![]),
170            Span::test_data(),
171        );
172
173        assert_eq!(actual, expected);
174    }
175}