#[must_use]
pub fn is_yaml11_bool(value: &str) -> bool {
matches!(
value,
"yes"
| "Yes"
| "YES"
| "no"
| "No"
| "NO"
| "on"
| "On"
| "ON"
| "off"
| "Off"
| "OFF"
| "y"
| "Y"
| "n"
| "N"
)
}
#[must_use]
pub fn is_yaml11_octal(value: &str) -> bool {
if value.len() <= 1 {
return false;
}
let mut chars = value.chars();
if chars.next() != Some('0') {
return false;
}
chars.all(|c| matches!(c, '0'..='7'))
}
#[must_use]
pub fn yaml11_bool_canonical(value: &str) -> &'static str {
match value {
"yes" | "Yes" | "YES" | "on" | "On" | "ON" | "y" | "Y" => "true",
_ => "false",
}
}
#[must_use]
pub fn parse_integer(value: &str) -> Option<i64> {
let (neg, rest) = value.strip_prefix('-').map_or_else(
|| (false, value.strip_prefix('+').unwrap_or(value)),
|r| (true, r),
);
if rest.is_empty() {
return None;
}
let magnitude: i64 = if let Some(oct) = rest.strip_prefix("0o") {
if oct.is_empty() {
return None;
}
i64::from_str_radix(oct, 8).ok()?
} else if let Some(hex) = rest.strip_prefix("0x") {
if hex.is_empty() {
return None;
}
i64::from_str_radix(hex, 16).ok()?
} else {
if rest.len() > 1 && rest.starts_with('0') {
return None;
}
if !rest.chars().all(|c| c.is_ascii_digit()) {
return None;
}
rest.parse::<i64>().ok()?
};
Some(if neg { -magnitude } else { magnitude })
}
#[must_use]
pub fn parse_float(value: &str) -> Option<f64> {
match value {
".inf" | ".Inf" | ".INF" => return Some(f64::INFINITY),
"-.inf" | "-.Inf" | "-.INF" => return Some(f64::NEG_INFINITY),
".nan" | ".NaN" | ".NAN" => return Some(f64::NAN),
_ => {}
}
let stripped = value.strip_prefix('+').unwrap_or(value);
let signed = stripped.strip_prefix('-').unwrap_or(stripped);
if signed.contains('.') || signed.contains('e') || signed.contains('E') {
return value.trim_start_matches('+').parse::<f64>().ok();
}
None
}
#[cfg(test)]
#[expect(clippy::approx_constant, clippy::unwrap_used, reason = "test code")]
mod tests {
use rstest::rstest;
use super::*;
#[rstest]
#[case::decimal_positive("42", 42)]
#[case::decimal_zero("0", 0)]
#[case::decimal_negative("-1", -1)]
#[case::decimal_plus_prefix("+100", 100)]
#[case::octal("0o17", 15)]
#[case::octal_negative("-0o10", -8)]
#[case::hex_uppercase("0xFF", 255)]
#[case::hex_negative("-0x1A", -26)]
#[case::hex_lowercase("0xdeadbeef", 0xdead_beef)]
fn parse_integer_returns_some(#[case] input: &str, #[case] expected: i64) {
assert_eq!(parse_integer(input), Some(expected));
}
#[rstest]
#[case::leading_zeros_triple("007")]
#[case::leading_zeros_double("00")]
#[case::empty_octal_prefix("0o")]
#[case::empty_hex_prefix("0x")]
#[case::bare_plus("+")]
#[case::bare_minus("-")]
#[case::empty("")]
fn parse_integer_returns_none(#[case] input: &str) {
assert_eq!(parse_integer(input), None);
}
#[rstest]
#[case::decimal_pi("3.14", 3.14)]
#[case::decimal_negative("-0.5", -0.5)]
#[case::decimal_positive_signed("+1.0", 1.0)]
#[case::exponent("1e10", 1e10)]
#[case::exponent_negative("1.5E-3", 1.5e-3)]
#[case::inf_lowercase(".inf", f64::INFINITY)]
#[case::inf_titlecase(".Inf", f64::INFINITY)]
#[case::inf_uppercase(".INF", f64::INFINITY)]
#[case::neg_inf_lowercase("-.inf", f64::NEG_INFINITY)]
#[case::neg_inf_titlecase("-.Inf", f64::NEG_INFINITY)]
#[case::neg_inf_uppercase("-.INF", f64::NEG_INFINITY)]
fn parse_float_returns_value(#[case] input: &str, #[case] expected: f64) {
assert_eq!(parse_float(input), Some(expected));
}
#[rstest]
#[case::nan_lowercase(".nan")]
#[case::nan_titlecase(".NaN")]
#[case::nan_uppercase(".NAN")]
fn parse_float_returns_nan(#[case] input: &str) {
assert!(parse_float(input).unwrap().is_nan());
}
#[rstest]
#[case::integer("42")]
#[case::alpha("abc")]
#[case::empty("")]
fn parse_float_returns_none(#[case] input: &str) {
assert_eq!(parse_float(input), None);
}
#[rstest]
#[case::yes_lowercase("yes")]
#[case::yes_titlecase("Yes")]
#[case::yes_uppercase("YES")]
#[case::no_lowercase("no")]
#[case::no_titlecase("No")]
#[case::no_uppercase("NO")]
#[case::on_lowercase("on")]
#[case::on_titlecase("On")]
#[case::on_uppercase("ON")]
#[case::off_lowercase("off")]
#[case::off_titlecase("Off")]
#[case::off_uppercase("OFF")]
#[case::y_lowercase("y")]
#[case::y_uppercase("Y")]
#[case::n_lowercase("n")]
#[case::n_uppercase("N")]
fn is_yaml11_bool_returns_true(#[case] input: &str) {
assert!(is_yaml11_bool(input));
}
#[rstest]
#[case::yaml12_true_lowercase("true")]
#[case::yaml12_true_titlecase("True")]
#[case::yaml12_true_uppercase("TRUE")]
#[case::yaml12_false_lowercase("false")]
#[case::yaml12_false_titlecase("False")]
#[case::yaml12_false_uppercase("FALSE")]
#[case::empty("")]
#[case::mixed_case_yes("yEs")]
#[case::mixed_case_no("nO")]
#[case::prefix_yes("yess")]
#[case::suffix_no("noo")]
#[case::space_padded(" yes")]
#[case::number_zero("0")]
#[case::number_one("1")]
fn is_yaml11_bool_returns_false(#[case] input: &str) {
assert!(!is_yaml11_bool(input));
}
#[rstest]
#[case::two_digits_leading_zero("01")]
#[case::three_digits("007")]
#[case::max_octal_digit("077")]
#[case::longer_value("0755")]
#[case::larger_value("01234567")]
fn is_yaml11_octal_returns_true(#[case] input: &str) {
assert!(is_yaml11_octal(input));
}
#[rstest]
#[case::zero_alone("0")]
#[case::yaml12_octal("0o17")]
#[case::decimal_no_leading_zero("42")]
#[case::has_digit_eight("08")]
#[case::has_digit_nine("09")]
#[case::leading_zero_with_alpha("0x10")]
#[case::signed_octal("-07")]
#[case::empty("")]
#[case::just_letters("abc")]
#[case::embedded_space("0 7")]
#[case::leading_whitespace(" 07")]
fn is_yaml11_octal_returns_false(#[case] input: &str) {
assert!(!is_yaml11_octal(input));
}
#[rstest]
#[case::yes_lowercase("yes")]
#[case::yes_titlecase("Yes")]
#[case::yes_uppercase("YES")]
#[case::on_lowercase("on")]
#[case::on_titlecase("On")]
#[case::on_uppercase("ON")]
#[case::y_lowercase("y")]
#[case::y_uppercase("Y")]
fn yaml11_bool_canonical_returns_true(#[case] input: &str) {
assert_eq!(yaml11_bool_canonical(input), "true");
}
#[rstest]
#[case::no_lowercase("no")]
#[case::no_titlecase("No")]
#[case::no_uppercase("NO")]
#[case::off_lowercase("off")]
#[case::off_titlecase("Off")]
#[case::off_uppercase("OFF")]
#[case::n_lowercase("n")]
#[case::n_uppercase("N")]
fn yaml11_bool_canonical_returns_false(#[case] input: &str) {
assert_eq!(yaml11_bool_canonical(input), "false");
}
}