From ec3d69446cf07409b9c91de3d2e63856f33b26fd Mon Sep 17 00:00:00 2001 From: nsfisis Date: Mon, 23 Feb 2026 01:34:55 +0900 Subject: fix(show,outdated): align outdated classification and wildcard handling with Composer - Extract matches_wildcard to mozart-core for reuse across commands - Support wildcard patterns in --package and --ignore arguments - Use ^ for semver-safe classification instead of root constraint - Replace std::process::exit(1) with bail_silent for proper cleanup Co-Authored-By: Claude Opus 4.6 --- crates/mozart-core/src/lib.rs | 2 + crates/mozart-core/src/wildcard.rs | 86 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 88 insertions(+) create mode 100644 crates/mozart-core/src/wildcard.rs (limited to 'crates/mozart-core/src') diff --git a/crates/mozart-core/src/lib.rs b/crates/mozart-core/src/lib.rs index 510257b..5383d9a 100644 --- a/crates/mozart-core/src/lib.rs +++ b/crates/mozart-core/src/lib.rs @@ -6,5 +6,7 @@ pub mod platform; pub mod suggest; pub mod validation; pub mod version_bumper; +pub mod wildcard; pub use mozart_console_macros::console_format; +pub use wildcard::matches_wildcard; diff --git a/crates/mozart-core/src/wildcard.rs b/crates/mozart-core/src/wildcard.rs new file mode 100644 index 0000000..9193549 --- /dev/null +++ b/crates/mozart-core/src/wildcard.rs @@ -0,0 +1,86 @@ +/// Match a package name against a wildcard pattern (case-insensitive). +/// `*` matches any sequence of characters. +pub fn matches_wildcard(name: &str, pattern: &str) -> bool { + let name_lower = name.to_lowercase(); + let pattern_lower = pattern.to_lowercase(); + let parts: Vec<&str> = pattern_lower.split('*').collect(); + + if parts.len() == 1 { + return name_lower == pattern_lower; + } + + let mut pos = 0usize; + for (i, part) in parts.iter().enumerate() { + if part.is_empty() { + continue; + } + match name_lower[pos..].find(*part) { + Some(found) => { + if i == 0 && found != 0 { + return false; // First segment must match at start + } + pos += found + part.len(); + } + None => return false, + } + } + + // If pattern doesn't end with *, name must be fully consumed + if !pattern_lower.ends_with('*') { + return pos == name_lower.len(); + } + + true +} + +// ─── Tests ────────────────────────────────────────────────────────────────── + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_matches_wildcard_exact() { + assert!(matches_wildcard("psr/log", "psr/log")); + } + + #[test] + fn test_matches_wildcard_star_end() { + assert!(matches_wildcard("psr/log", "psr/*")); + } + + #[test] + fn test_matches_wildcard_star_start() { + assert!(matches_wildcard("psr/log", "*/log")); + } + + #[test] + fn test_matches_wildcard_star_middle() { + assert!(matches_wildcard("monolog/monolog", "mono*/mono*")); + } + + #[test] + fn test_matches_wildcard_no_match() { + assert!(!matches_wildcard("psr/log", "symfony/*")); + } + + #[test] + fn test_matches_wildcard_case_insensitive() { + assert!(matches_wildcard("PSR/Log", "psr/*")); + } + + #[test] + fn test_matches_wildcard_star_both_ends() { + assert!(matches_wildcard("monolog/monolog", "*log*")); + } + + #[test] + fn test_matches_wildcard_no_wildcard_mismatch() { + assert!(!matches_wildcard("psr/log", "psr/log2")); + } + + #[test] + fn test_matches_wildcard_trailing_chars_fail() { + assert!(!matches_wildcard("psr/log", "psr/l")); + } +} -- cgit v1.3.1