aboutsummaryrefslogtreecommitdiffhomepage
path: root/crates/mozart-core/src
diff options
context:
space:
mode:
authornsfisis <nsfisis@gmail.com>2026-02-23 01:34:55 +0900
committernsfisis <nsfisis@gmail.com>2026-02-23 01:58:28 +0900
commitec3d69446cf07409b9c91de3d2e63856f33b26fd (patch)
tree06f6c7eb0633809c8f58ab098adb9899cc57af90 /crates/mozart-core/src
parent1ab3d928a2d350ce407205d9ee6ea9569cd38424 (diff)
downloadphp-mozart-ec3d69446cf07409b9c91de3d2e63856f33b26fd.tar.gz
php-mozart-ec3d69446cf07409b9c91de3d2e63856f33b26fd.tar.zst
php-mozart-ec3d69446cf07409b9c91de3d2e63856f33b26fd.zip
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 ^<installed_version> 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 <noreply@anthropic.com>
Diffstat (limited to 'crates/mozart-core/src')
-rw-r--r--crates/mozart-core/src/lib.rs2
-rw-r--r--crates/mozart-core/src/wildcard.rs86
2 files changed, 88 insertions, 0 deletions
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"));
+ }
+}