use std::path::Path;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CommandStem(String);
impl CommandStem {
#[must_use]
pub fn of(command: &str) -> Self {
Self(
Path::new(command)
.file_stem()
.map_or_default(|stem| stem.to_string_lossy().to_ascii_lowercase()),
)
}
#[must_use]
pub fn as_str(&self) -> &str {
&self.0
}
#[must_use]
pub fn is(&self, name: &str) -> bool {
debug_assert!(
name.bytes().all(|byte| !byte.is_ascii_uppercase()),
"{name} must be lowercase"
);
self.0 == name
}
#[must_use]
pub fn is_any(&self, names: &[&str]) -> bool {
names.iter().any(|name| self.is(name))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn stem_drops_directory_and_extension_and_lowercases() {
for command in ["npx", "/a/b/NPX", "/a/Npx.CMD", "npx.cmd"] {
assert_eq!(CommandStem::of(command).as_str(), "npx", "{command}");
}
assert_eq!(CommandStem::of("").as_str(), "");
assert_eq!(CommandStem::of("/").as_str(), "");
}
#[test]
fn is_any_matches_any_listed_name_regardless_of_case() {
let stem = CommandStem::of("Bunx.exe");
assert!(stem.is_any(&["npx", "bunx"]));
assert!(!stem.is_any(&["npx", "pnpx"]));
assert!(!stem.is_any(&[]));
}
}