miniextendr_api/env_flag.rs
1//! Shared truthiness parsing for environment-variable flags.
2//!
3//! Two flags in this crate read boolean-ish env vars — `MINIEXTENDR_BACKTRACE`
4//! (`backtrace.rs`) and `_R_CHECK_LIMIT_CORES_` (`optionals/parallel.rs`) — and
5//! they used to disagree on what counts as "on" (`true`/`1` vs. anything but
6//! `false`). `parse_bool` is the single source of truth, deliberately aligned
7//! with R's own `config_val_to_logical` (`tools/R/utils.R`): a case-insensitive,
8//! whitespace-trimmed match against explicit truthy/falsy sets.
9//!
10//! Recognized values (case-insensitive, surrounding whitespace trimmed):
11//! - truthy: `yes`, `true`, `1`, `on`
12//! - falsy: `no`, `false`, `0`, `off`, `` (empty)
13//!
14//! Anything else is unrecognized (`None`) — the caller supplies the default,
15//! so each flag keeps control of its own "garbage" policy (see the two call
16//! sites for their deliberately different defaults).
17
18/// Parse an environment-flag value as a boolean, using R's
19/// `config_val_to_logical` rules (see module docs). Returns `None` for values
20/// outside the recognized truthy/falsy sets so the caller picks the default.
21pub(crate) fn parse_bool(v: &str) -> Option<bool> {
22 match v.trim().to_ascii_lowercase().as_str() {
23 "yes" | "true" | "1" | "on" => Some(true),
24 "no" | "false" | "0" | "off" | "" => Some(false),
25 _ => None,
26 }
27}
28
29#[cfg(test)]
30mod tests {
31 use super::*;
32
33 #[test]
34 fn recognizes_truthy_values_case_insensitively() {
35 for v in [
36 "yes", "YES", "true", "TRUE", "True", "1", "on", "ON", " true ",
37 ] {
38 assert_eq!(parse_bool(v), Some(true), "{v:?}");
39 }
40 }
41
42 #[test]
43 fn recognizes_falsy_values_case_insensitively() {
44 for v in ["no", "NO", "false", "FALSE", "0", "off", "OFF", "", " "] {
45 assert_eq!(parse_bool(v), Some(false), "{v:?}");
46 }
47 }
48
49 #[test]
50 fn returns_none_for_unrecognized() {
51 for v in ["garbage", "2", "tru", "yesno"] {
52 assert_eq!(parse_bool(v), None, "{v:?}");
53 }
54 }
55}