use crate::error::{AsynError, AsynResult, AsynStatus};
pub fn sscanf_int(value: &str) -> Option<i32> {
let s = value.trim_start();
let (negative, digits) = match s.strip_prefix('-') {
Some(rest) => (true, rest),
None => (false, s.strip_prefix('+').unwrap_or(s)),
};
let run: String = digits.chars().take_while(|c| c.is_ascii_digit()).collect();
if run.is_empty() {
return None;
}
let magnitude: i64 = run.parse().unwrap_or(i64::from(i32::MAX) + 1);
let signed = if negative { -magnitude } else { magnitude };
Some(signed.clamp(i64::from(i32::MIN), i64::from(i32::MAX)) as i32)
}
pub fn sscanf_uint(value: &str) -> Option<u32> {
let s = value.trim_start();
if s.starts_with('-') {
return None;
}
let digits = s.strip_prefix('+').unwrap_or(s);
let run: String = digits.chars().take_while(|c| c.is_ascii_digit()).collect();
if run.is_empty() {
return None;
}
Some(
run.parse::<u64>()
.unwrap_or(u64::from(u32::MAX))
.min(u64::from(u32::MAX)) as u32,
)
}
pub fn bad_number() -> AsynError {
AsynError::Status {
status: AsynStatus::Error,
message: "Bad number".into(),
}
}
pub fn invalid_option_value(key: &str) -> AsynError {
AsynError::Status {
status: AsynStatus::Error,
message: format!("Invalid {key} value."),
}
}
pub fn parse_yn_option(key: &str, value: &str) -> AsynResult<bool> {
if value.eq_ignore_ascii_case("Y") {
Ok(true)
} else if value.eq_ignore_ascii_case("N") {
Ok(false)
} else {
Err(invalid_option_value(key))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn sscanf_int_is_a_prefix_parse_like_c() {
assert_eq!(sscanf_int("9600"), Some(9600));
assert_eq!(
sscanf_int("9600x"),
Some(9600),
"C stops at the first non-digit"
);
assert_eq!(
sscanf_int(" 9600 "),
Some(9600),
"%d skips leading whitespace"
);
assert_eq!(sscanf_int("-1"), Some(-1));
assert_eq!(sscanf_int("+300"), Some(300));
assert_eq!(
sscanf_int("0x10"),
Some(0),
"%d is decimal: it reads 0 and stops at 'x'"
);
assert_eq!(sscanf_int(""), None);
assert_eq!(sscanf_int("fast"), None);
assert_eq!(sscanf_int("x9600"), None);
assert_eq!(sscanf_int("-"), None);
}
#[test]
fn sscanf_uint_takes_the_unsigned_prefix() {
assert_eq!(sscanf_uint("250"), Some(250));
assert_eq!(sscanf_uint("250ms"), Some(250));
assert_eq!(sscanf_uint(" 0"), Some(0));
assert_eq!(sscanf_uint(""), None);
assert_eq!(sscanf_uint("on"), None);
assert_eq!(sscanf_uint("-1"), None);
}
#[test]
fn the_texts_are_cs_texts() {
assert_eq!(bad_number().message(), "Bad number");
assert_eq!(
invalid_option_value("clocal").message(),
"Invalid clocal value."
);
assert_eq!(
parse_yn_option("crtscts", "maybe").unwrap_err().message(),
"Invalid crtscts value."
);
assert!(parse_yn_option("ixon", "y").unwrap());
assert!(!parse_yn_option("ixon", "N").unwrap());
}
}