use std::str::FromStr;
use anyhow::{Context as _, Result, bail};
pub fn parse_n_floats<T, const N: usize>(s: &str, names: [&str; N], flag: &str) -> Result<[T; N]>
where
T: FromStr,
T::Err: std::error::Error + Send + Sync + 'static,
{
let parts: Vec<&str> = s.split(',').collect();
if parts.len() != N {
bail!(
"{flag} expects {N} comma-separated values ({}), got {}",
names.join(","),
parts.len(),
);
}
let mut out: [Option<T>; N] = std::array::from_fn(|_| None);
for ((raw, name), slot) in parts.iter().zip(names.iter()).zip(out.iter_mut()) {
let value = raw
.trim()
.parse::<T>()
.with_context(|| format!("parsing {name} from {flag}"))?;
*slot = Some(value);
}
Ok(out.map(|v| v.expect("len-N traversal filled every slot")))
}
#[cfg(test)]
mod tests {
#![allow(
clippy::float_cmp,
reason = "tests rely on bit-exact comparisons of constant or stored f32/f64 values."
)]
use super::*;
#[test]
fn parses_four_f64() {
let r: [f64; 4] = parse_n_floats("1,2,3,4", ["a", "b", "c", "d"], "--flag")
.expect("valid four-float input");
assert_eq!(r, [1.0, 2.0, 3.0, 4.0]);
}
#[test]
fn parses_f32_just_as_well() {
let r: [f32; 4] = parse_n_floats("1,2,3,4", ["a", "b", "c", "d"], "--grib-bbox")
.expect("valid four-float input");
assert_eq!(r, [1.0, 2.0, 3.0, 4.0]);
}
#[test]
fn tolerates_whitespace_around_components() {
let r: [f64; 2] = parse_n_floats(" 30 , -60 ", ["lon", "lat"], "--flag")
.expect("whitespace should be tolerated");
assert_eq!(r, [30.0, -60.0]);
}
#[test]
fn arity_mismatch_too_few_reports_expected_and_actual() {
let r: Result<[f64; 4]> = parse_n_floats("1,2,3", ["a", "b", "c", "d"], "--flag");
let err = r.expect_err("3 components, expected 4").to_string();
assert!(err.contains("--flag"));
assert!(err.contains("expects 4"));
assert!(err.contains("got 3"));
}
#[test]
fn arity_mismatch_too_many_is_rejected() {
let r: Result<[f64; 4]> = parse_n_floats("1,2,3,4,5", ["a", "b", "c", "d"], "--flag");
r.unwrap_err();
}
#[test]
fn non_numeric_error_carries_field_and_flag_context() {
let r: Result<[f64; 2]> = parse_n_floats("north,40", ["lon", "lat"], "--start");
let err = format!("{:#}", r.expect_err("non-numeric input"));
assert!(err.contains("--start"));
assert!(err.contains("lon"));
}
}