use once_cell::sync::OnceCell;
thread_local!(
static TRUE_VALUES: OnceCell<Vec<String>> = OnceCell::new();
static FALSE_VALUES: OnceCell<Vec<String>> = OnceCell::new();
);
pub fn initialize_true_values<S: ToString>(values: impl IntoIterator<Item = S>) -> bool {
let values = values.into_iter().map(|s| s.to_string()).collect();
TRUE_VALUES.with(|f| f.set(values).is_ok())
}
pub fn initialize_false_values<S: ToString>(values: impl IntoIterator<Item = S>) -> bool {
let values = values.into_iter().map(|s| s.to_string()).collect();
FALSE_VALUES.with(|f| f.set(values).is_ok())
}
#[derive(Copy, Clone, PartialEq, Default, Hash, Eq)]
pub struct LexicalBool(bool);
impl std::ops::Deref for LexicalBool {
type Target = bool;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl PartialEq<bool> for LexicalBool {
fn eq(&self, other: &bool) -> bool {
*other == self.0
}
}
impl std::fmt::Display for LexicalBool {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
impl std::fmt::Debug for LexicalBool {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
impl std::str::FromStr for LexicalBool {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let e = s.to_ascii_lowercase();
if TRUE_VALUES.with(|f| {
f.get_or_init(|| TRUTHY_VALUES.iter().map(ToString::to_string).collect())
.iter()
.any(|k| k == &e)
}) {
return Ok(LexicalBool(true));
}
if FALSE_VALUES.with(|f| {
f.get_or_init(|| FALSEY_VALUES.iter().map(ToString::to_string).collect())
.iter()
.any(|k| k == &e)
}) {
return Ok(LexicalBool(false));
}
Err(Error::InvalidInput(s.to_string()))
}
}
pub const TRUTHY_VALUES: [&str; 4] = ["true", "t", "1", "yes"];
pub const FALSEY_VALUES: [&str; 4] = ["false", "f", "0", "no"];
#[derive(Debug, Clone, PartialEq)]
pub enum Error {
InvalidInput(String),
}
impl std::fmt::Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Error::InvalidInput(input) => write!(
f,
"not a boolean: {}. only {} are allowed",
input,
TRUTHY_VALUES
.iter()
.chain(FALSEY_VALUES.iter())
.map(|val| format!("'{}''", val))
.fold(String::new(), |mut a, b| {
if !a.is_empty() {
a.push_str(", ")
}
a.push_str(&b);
a
})
),
}
}
}
impl std::error::Error for Error {}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parse_true() {
let inputs = &[("true", true), ("t", true), ("1", true), ("yes", true)];
for &(input, ok) in inputs {
assert_eq!(input.parse::<LexicalBool>().unwrap(), ok);
}
}
#[test]
fn parse_false() {
let inputs = &[("false", false), ("f", false), ("0", false), ("no", false)];
for &(input, ok) in inputs {
assert_eq!(input.parse::<LexicalBool>().unwrap(), ok);
}
}
#[test]
fn parse_custom_true() {
assert!(initialize_true_values(&["this is true", "yep", "YEP"]));
let inputs = &[
("this is true", true),
("yep", true),
("YEP", true),
("false", false),
("f", false),
("0", false),
("no", false),
];
for &(input, ok) in inputs {
assert_eq!(input.parse::<LexicalBool>().unwrap(), ok);
}
}
#[test]
fn parse_custom_false() {
assert!(initialize_false_values(&["this is false", "nope", "NOPE"]));
let inputs = &[
("this is false", false),
("nope", false),
("NOPE", false),
("true", true),
("t", true),
("1", true),
("yes", true),
];
for &(input, ok) in inputs {
assert_eq!(input.parse::<LexicalBool>().unwrap(), ok);
}
}
#[test]
fn display_and_debug() {
assert!(initialize_false_values(&["this is false", "nope", "NOPE"]));
let inputs = &[
("this is false", false),
("nope", false),
("NOPE", false),
("true", true),
("t", true),
("1", true),
("yes", true),
];
for (input, expected) in inputs {
let b = input.parse::<LexicalBool>().unwrap();
assert_eq!(format!("{}", b), format!("{}", expected));
assert_eq!(format!("{:?}", b), format!("{:?}", expected));
}
}
#[test]
fn trait_impls() {
use std::collections::HashSet;
let mut set: HashSet<LexicalBool> = HashSet::default();
assert!(set.insert("true".parse().unwrap()));
assert!(set.insert("false".parse().unwrap()));
assert!(!set.insert("true".parse().unwrap()));
assert!(!set.insert("false".parse().unwrap()));
}
}