bve 0.0.1

Remaking OpenBVE using a modern architecture, using Unity and Rust.
use serde::de;
use serde::de::Visitor;
use serde::{Deserialize, Deserializer};
use std::fmt;
use std::fmt::Formatter;

#[derive(Debug, Copy, Clone, PartialOrd, PartialEq, Default)]
pub struct LooseNumericBool(pub bool);

impl<'de> Deserialize<'de> for LooseNumericBool {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        deserializer.deserialize_str(LooseNumericBoolVisitor)
    }
}

struct LooseNumericBoolVisitor;

impl<'de> Visitor<'de> for LooseNumericBoolVisitor {
    type Value = LooseNumericBool;

    fn expecting(&self, formatter: &mut Formatter<'_>) -> Result<(), fmt::Error> {
        write!(formatter, "Expecting integer to convert to bool.")
    }

    fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
    where
        E: de::Error,
    {
        let parsed = parse_loose_numeric_bool(v);

        parsed.ok_or_else(|| serde::de::Error::custom(format!("Error parsing the numeric bool {}", v)))
    }
}

pub fn parse_loose_numeric_bool(input: &str) -> Option<LooseNumericBool> {
    tracing::trace!(input, "Parsing numeric bool...");

    let mut filtered: String = input.chars().filter(|c| !c.is_whitespace()).collect();

    let first_letter = filtered.chars().next().as_ref().map(char::to_ascii_lowercase);
    match first_letter {
        Some('t') => return Some(LooseNumericBool(true)),
        Some('f') => return Some(LooseNumericBool(false)),
        _ => {}
    }

    while !filtered.is_empty() {
        let parsed: Result<i64, _> = filtered.parse();
        match parsed {
            Ok(v) => {
                let output = v != 0;
                tracing::trace!(output, %filtered, "Parsed loose bool");
                return Some(LooseNumericBool(output));
            }
            Err(_) => filtered.pop(),
        };
    }

    None
}

#[cfg(test)]
mod test {
    use crate::parse::util::{parse_loose_numeric_bool, LooseNumericBool};
    use serde_test::{assert_de_tokens, Token};

    #[bve_derive::bve_test]
    #[test]
    fn loose_bool() {
        let b = LooseNumericBool(false);
        assert_de_tokens(&b, &[Token::Str("0")]);
        assert_de_tokens(&b, &[Token::Str("0xxxx0")]);
        assert_de_tokens(&b, &[Token::Str("0.1")]);
        let b = LooseNumericBool(true);
        assert_de_tokens(&b, &[Token::Str("1")]);
        assert_de_tokens(&b, &[Token::Str("1xxxx0")]);
        assert_de_tokens(&b, &[Token::Str("1.1")]);
        assert_eq!(parse_loose_numeric_bool(""), None);
        assert_eq!(parse_loose_numeric_bool("True"), Some(LooseNumericBool(true)));
        assert_eq!(parse_loose_numeric_bool("False"), Some(LooseNumericBool(false)));
        assert_eq!(parse_loose_numeric_bool("t"), Some(LooseNumericBool(true)));
        assert_eq!(parse_loose_numeric_bool("f"), Some(LooseNumericBool(false)));
    }
}