nu_command/conversions/into/
bool.rs

1use nu_cmd_base::input_handler::{CmdArgument, operate};
2use nu_engine::command_prelude::*;
3
4#[derive(Clone)]
5pub struct IntoBool;
6
7impl Command for IntoBool {
8    fn name(&self) -> &str {
9        "into bool"
10    }
11
12    fn signature(&self) -> Signature {
13        Signature::build("into bool")
14            .input_output_types(vec![
15                (Type::Int, Type::Bool),
16                (Type::Number, Type::Bool),
17                (Type::String, Type::Bool),
18                (Type::Bool, Type::Bool),
19                (Type::Nothing, Type::Bool),
20                (Type::List(Box::new(Type::Any)), Type::table()),
21                (Type::table(), Type::table()),
22                (Type::record(), Type::record()),
23            ])
24            .switch(
25                "relaxed",
26                "Relaxes conversion to also allow null and any strings.",
27                None,
28            )
29            .allow_variants_without_examples(true)
30            .rest(
31                "rest",
32                SyntaxShape::CellPath,
33                "For a data structure input, convert data at the given cell paths.",
34            )
35            .category(Category::Conversions)
36    }
37
38    fn description(&self) -> &str {
39        "Convert value to boolean."
40    }
41
42    fn search_terms(&self) -> Vec<&str> {
43        vec!["convert", "boolean", "true", "false", "1", "0"]
44    }
45
46    fn run(
47        &self,
48        engine_state: &EngineState,
49        stack: &mut Stack,
50        call: &Call,
51        input: PipelineData,
52    ) -> Result<PipelineData, ShellError> {
53        let relaxed = call
54            .has_flag(engine_state, stack, "relaxed")
55            .unwrap_or(false);
56        into_bool(engine_state, stack, call, input, relaxed)
57    }
58
59    fn examples(&self) -> Vec<Example<'_>> {
60        vec![
61            Example {
62                description: "Convert value to boolean in table",
63                example: "[[value]; ['false'] ['1'] [0] [1.0] [true]] | into bool value",
64                result: Some(Value::test_list(vec![
65                    Value::test_record(record! {
66                        "value" => Value::test_bool(false),
67                    }),
68                    Value::test_record(record! {
69                        "value" => Value::test_bool(true),
70                    }),
71                    Value::test_record(record! {
72                        "value" => Value::test_bool(false),
73                    }),
74                    Value::test_record(record! {
75                        "value" => Value::test_bool(true),
76                    }),
77                    Value::test_record(record! {
78                        "value" => Value::test_bool(true),
79                    }),
80                ])),
81            },
82            Example {
83                description: "Convert bool to boolean",
84                example: "true | into bool",
85                result: Some(Value::test_bool(true)),
86            },
87            Example {
88                description: "convert int to boolean",
89                example: "1 | into bool",
90                result: Some(Value::test_bool(true)),
91            },
92            Example {
93                description: "convert float to boolean",
94                example: "0.3 | into bool",
95                result: Some(Value::test_bool(true)),
96            },
97            Example {
98                description: "convert float string to boolean",
99                example: "'0.0' | into bool",
100                result: Some(Value::test_bool(false)),
101            },
102            Example {
103                description: "convert string to boolean",
104                example: "'true' | into bool",
105                result: Some(Value::test_bool(true)),
106            },
107            Example {
108                description: "interpret a null as false",
109                example: "null | into bool --relaxed",
110                result: Some(Value::test_bool(false)),
111            },
112            Example {
113                description: "interpret any non-false, non-zero string as true",
114                example: "'something' | into bool --relaxed",
115                result: Some(Value::test_bool(true)),
116            },
117        ]
118    }
119}
120
121struct IntoBoolCmdArgument {
122    cell_paths: Option<Vec<CellPath>>,
123    relaxed: bool,
124}
125
126impl CmdArgument for IntoBoolCmdArgument {
127    fn take_cell_paths(&mut self) -> Option<Vec<CellPath>> {
128        self.cell_paths.take()
129    }
130}
131
132fn into_bool(
133    engine_state: &EngineState,
134    stack: &mut Stack,
135    call: &Call,
136    input: PipelineData,
137    relaxed: bool,
138) -> Result<PipelineData, ShellError> {
139    let cell_paths = Some(call.rest(engine_state, stack, 0)?).filter(|v| !v.is_empty());
140    let args = IntoBoolCmdArgument {
141        cell_paths,
142        relaxed,
143    };
144    operate(action, args, input, call.head, engine_state.signals())
145}
146
147fn strict_string_to_boolean(s: &str, span: Span) -> Result<bool, ShellError> {
148    match s.trim().to_ascii_lowercase().as_str() {
149        "true" => Ok(true),
150        "false" => Ok(false),
151        o => {
152            let val = o.parse::<f64>();
153            match val {
154                Ok(f) => Ok(f != 0.0),
155                Err(_) => Err(ShellError::CantConvert {
156                    to_type: "boolean".to_string(),
157                    from_type: "string".to_string(),
158                    span,
159                    help: Some(
160                        r#"the strings "true" and "false" can be converted into a bool"#
161                            .to_string(),
162                    ),
163                }),
164            }
165        }
166    }
167}
168
169fn action(input: &Value, args: &IntoBoolCmdArgument, span: Span) -> Value {
170    let err = || {
171        Value::error(
172            ShellError::OnlySupportsThisInputType {
173                exp_input_type: "bool, int, float or string".into(),
174                wrong_type: input.get_type().to_string(),
175                dst_span: span,
176                src_span: input.span(),
177            },
178            span,
179        )
180    };
181
182    match (input, args.relaxed) {
183        (Value::Error { .. } | Value::Bool { .. }, _) => input.clone(),
184        // In strict mode is this an error, while in relaxed this is just `false`
185        (Value::Nothing { .. }, false) => err(),
186        (Value::String { val, .. }, false) => match strict_string_to_boolean(val, span) {
187            Ok(val) => Value::bool(val, span),
188            Err(error) => Value::error(error, span),
189        },
190        _ => match input.coerce_bool() {
191            Ok(val) => Value::bool(val, span),
192            Err(_) => err(),
193        },
194    }
195}
196
197#[cfg(test)]
198mod test {
199    use super::*;
200
201    #[test]
202    fn test_examples() {
203        use crate::test_examples;
204
205        test_examples(IntoBool {})
206    }
207
208    #[test]
209    fn test_strict_handling() {
210        let span = Span::test_data();
211        let args = IntoBoolCmdArgument {
212            cell_paths: vec![].into(),
213            relaxed: false,
214        };
215
216        assert!(action(&Value::test_nothing(), &args, span).is_error());
217        assert!(action(&Value::test_string("abc"), &args, span).is_error());
218        assert!(action(&Value::test_string("true"), &args, span).is_true());
219        assert!(action(&Value::test_string("FALSE"), &args, span).is_false());
220    }
221
222    #[test]
223    fn test_relaxed_handling() {
224        let span = Span::test_data();
225        let args = IntoBoolCmdArgument {
226            cell_paths: vec![].into(),
227            relaxed: true,
228        };
229
230        assert!(action(&Value::test_nothing(), &args, span).is_false());
231        assert!(action(&Value::test_string("abc"), &args, span).is_true());
232        assert!(action(&Value::test_string("true"), &args, span).is_true());
233        assert!(action(&Value::test_string("FALSE"), &args, span).is_false());
234    }
235}