spreadsheet_to_json/
is_truthy.rs

1use std::sync::Arc;
2
3use simple_string_patterns::*;
4
5/// Only match string representations of booleans, integers or floats
6pub fn is_truthy_core(txt: &str, empty_is_false: bool) -> Option<bool> {
7  let test_str = txt.trim().to_lowercase();
8  match test_str.as_str() {
9    "" => {
10      if empty_is_false {
11        Some(false)
12      } else {
13        None
14      }
15    },
16    "0" | "-1" | "false" => Some(false),
17    "1" | "2" | "true" => Some(true),
18    _ => if test_str.is_digits_only() {
19      if let Some(int_val) = test_str.to_first_number::<u8>() {
20        Some(int_val > 0)
21      } else {
22        None
23      }
24    } else {
25      None
26    }
27  }
28}
29
30/// match boolean with core boolean/number + common English-like words
31pub fn is_truthy_standard(txt: &str, empty_is_false: bool) -> Option<bool> {
32  let test_str = txt.trim().to_lowercase();
33  if let Some(core_match) = is_truthy_core(txt, empty_is_false) {
34    Some(core_match)
35  } else {
36    match test_str.as_str() {
37      "no" | "not" | "none" | "n" | "f" => Some(false),
38      "ok" | "okay" |"y" | "yes" | "t" => Some(true),
39      _ => None
40    }
41  }
42}
43
44/// Validate a string cell from an array of truthy options with patterns that may be true or false
45/// if unmatched return None, otherwise Some(true) or Some(false)
46#[allow(dead_code)]
47pub fn is_truthy_custom(txt: &str, opts: &[TruthyOption], use_defaults: bool, empty_is_false: bool) -> Option<bool> {
48  // Will return the first matched letter sequence
49  let txt = txt.trim();
50  for opt in opts {
51    let letters = opt.sample();
52    if opt.starts_with {
53      if opt.case_sensitive {
54        if txt.starts_with(&letters) {
55          return Some(opt.is_true)
56        }
57      } else {
58        if txt.starts_with_ci_alphanum(&letters) {
59          return Some(opt.is_true)
60        }
61      }
62    } else {
63      if opt.case_sensitive {
64        if txt == letters {
65          return Some(opt.is_true)
66        }
67      } else {
68        if txt.to_lowercase() == letters.to_lowercase() {
69          return Some(opt.is_true);
70        }
71      }
72    }
73  }
74  if use_defaults {
75    is_truthy_core(txt, empty_is_false)
76  } else {
77    None
78  }
79}
80
81/// Truth Option that may be case-sensitive and match either the start or anywhere within a string
82/// It's assumed truthy field use consistent naming conventions, but this allows some flexibility
83/// without using full regular expressions
84#[derive(Debug, Clone)]
85pub struct TruthyOption {
86  pub is_true: bool,
87  pub pattern: Arc<str>,
88  pub case_sensitive: bool,
89  pub starts_with: bool
90}
91
92impl TruthyOption {
93  pub fn new(is_true: bool, pattern: &str, case_sensitive: bool, starts_with: bool) -> Self {
94    Self {
95      is_true,
96      pattern: Arc::from(pattern),
97      case_sensitive,
98      starts_with
99    }
100  }
101
102  pub fn sample(&self) -> String {
103    self.pattern.to_string()
104  }
105}
106
107pub fn extract_truth_patterns(opts: &[TruthyOption], is_true: bool) -> Vec<String> {
108  opts.into_iter()
109        .filter(|&o| o.is_true == is_true)
110        .map(|o| o.pattern.to_string()).collect()
111}
112
113/// convert a custom string setting into a full TruthyOptiuon
114/// e.g. truthy|ok,good|failed,bad will be translated into two true options (ok or good) and two false options (failed and bad)
115/// case_sensitive and starts_with are applied globally
116#[allow(dead_code)]
117pub fn split_truthy_custom_option_str(custom_str: &str, case_sensitive: bool, starts_with: bool) -> Vec<TruthyOption> {
118  let (head, tail) = custom_str.to_head_tail(":");
119  let (first, second) = tail.to_head_tail(",");
120  if first.len() > 0 {
121    if head.starts_with_ci_alphanum("tr") {
122      return to_truth_options(&first, &second, case_sensitive, starts_with)
123    }
124  }
125  vec![]
126}
127
128
129/// split a comma-separated string of true and false options into a list of TruthyOptions
130/// alternative true or false options may be split by | (pipe) characters
131pub fn to_truth_options(true_str: &str, false_str: &str, case_sensitive: bool, starts_with: bool) -> Vec<TruthyOption> {
132  let mut matchers:Vec<TruthyOption> = vec![];
133  let true_parts = true_str.to_segments("|");
134  let false_parts = false_str.to_segments("|");
135  for match_str in true_parts {
136    matchers.push(TruthyOption::new(true, &match_str, case_sensitive, starts_with));
137  }
138  for match_str in false_parts {
139    matchers.push(TruthyOption::new(false, &match_str, case_sensitive, starts_with));
140  }
141  matchers
142}
143
144
145
146#[cfg(test)]
147mod tests {
148  use super::*;
149
150  #[test]
151  fn test_truthy_1() {
152      let sample = "1";
153      assert_eq!(is_truthy_standard(sample, false), Some(true));
154
155      let sample = "0";
156      assert_eq!(is_truthy_standard(sample, false), Some(false));
157
158      let sample = "false";
159      assert_eq!(is_truthy_standard(sample, false), Some(false));
160  }
161
162  #[test]
163  fn test_truthy_2() {
164      let sample = "n";
165      assert_eq!(is_truthy_standard(sample, false), Some(false));
166
167      let sample = "Ok";
168      assert_eq!(is_truthy_standard(sample, false), Some(true));
169
170      let sample = "false";
171      assert_eq!(is_truthy_standard(sample, false), Some(false));
172  }
173
174  #[test]
175  fn test_truthy_custom() {
176
177    let custom_setting_str = "truthy:si|vero,no|falso";
178    let custom_flags = split_truthy_custom_option_str(custom_setting_str, false, false);
179
180    // yes will be neither true nor false, because we're using custom true/false flags
181    let sample = "yes";
182    assert_eq!(is_truthy_custom(sample, &custom_flags, true, false), None);
183
184    // will skip normal English "false" and only use "falso" or "no" for false
185    let sample = "false";
186    assert_eq!(is_truthy_custom(sample, &custom_flags, false, false), None);
187
188    let sample = "si";
189    assert_eq!(is_truthy_custom(sample, &custom_flags, true, true), Some(true));
190  }
191}