iwconf-win 0.1.2

A colorful, iwconfig-style WLAN CLI for Windows, backed by the native WLAN API (no netsh required).
use regex::Regex;

use crate::error::{IwconfError, Result};
use crate::wlan::Interface;

/// Converts a glob-style pattern (`*` = any run of chars, `?` = one char)
/// into an anchored regex. Everything else is escaped literally so names
/// with dots/parens/etc. in real adapter descriptions don't misbehave.
fn glob_to_regex(pattern: &str) -> String {
    let mut out = String::from("(?i)^");
    for c in pattern.chars() {
        match c {
            '*' => out.push_str(".*"),
            '?' => out.push('.'),
            _ => out.push_str(&regex::escape(&c.to_string())),
        }
    }
    out.push('$');
    out
}

/// Selects the interface(s) matching `pattern` out of `all`. Matches
/// against both the friendly name ("Wi-Fi") and the raw driver
/// description, since either is a reasonable thing to type. Returns an
/// error listing all matches if the pattern is ambiguous, so the caller
/// can narrow it down instead of silently picking one.
pub fn select<'a>(
    all: &'a [Interface],
    pattern: &str,
    as_regex: bool,
) -> Result<&'a Interface> {
    let re_src = if as_regex {
        format!("(?i){pattern}")
    } else {
        glob_to_regex(pattern)
    };
    let re = Regex::new(&re_src)
        .map_err(|e| IwconfError::Other(format!("invalid pattern '{pattern}': {e}")))?;

    let matches: Vec<&Interface> = all
        .iter()
        .filter(|iface| re.is_match(&iface.name) || re.is_match(&iface.description))
        .collect();

    match matches.len() {
        0 => Err(IwconfError::NoInterfaceMatch(pattern.to_string())),
        1 => Ok(matches[0]),
        n => Err(IwconfError::AmbiguousInterface {
            pattern: pattern.to_string(),
            count: n,
            names: matches
                .iter()
                .map(|i| i.name.clone())
                .collect::<Vec<_>>()
                .join(", "),
        }),
    }
}