aboutsummaryrefslogtreecommitdiffhomepage
path: root/crates/mozart-core/src/validation.rs
diff options
context:
space:
mode:
authornsfisis <nsfisis@gmail.com>2026-05-08 20:06:29 +0900
committernsfisis <nsfisis@gmail.com>2026-05-08 20:06:29 +0900
commitb286af9ffe78d50b63bf5fda7fc796ab20f2552f (patch)
tree3b3cb80e790aaa6b457cfeca1e7efab7126e016a /crates/mozart-core/src/validation.rs
parent5cb8fc4e306970764e84bb850da2c56f844c3b12 (diff)
downloadphp-mozart-b286af9ffe78d50b63bf5fda7fc796ab20f2552f.tar.gz
php-mozart-b286af9ffe78d50b63bf5fda7fc796ab20f2552f.tar.zst
php-mozart-b286af9ffe78d50b63bf5fda7fc796ab20f2552f.zip
fix(reinstall): align with Composer's ReinstallCommand pipeline
Switch to Composer::require() for the entrypoint, drop the Mozart-only --dry-run / --no-dev flags, mirror selection inline using a port of BasePackage::packageNameToRegexp, read autoloader options from composer.config(), and route the autoload dump through composer.autoload_generator(). Empty-result and unmatched-pattern warnings now emit on stderr with <warning> markup, matching $io->writeError. Plugin/script-event dispatch and Transaction-based operation building remain TODO until the installer_executor lands. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Diffstat (limited to 'crates/mozart-core/src/validation.rs')
-rw-r--r--crates/mozart-core/src/validation.rs52
1 files changed, 52 insertions, 0 deletions
diff --git a/crates/mozart-core/src/validation.rs b/crates/mozart-core/src/validation.rs
index 24f1705..c27eb91 100644
--- a/crates/mozart-core/src/validation.rs
+++ b/crates/mozart-core/src/validation.rs
@@ -115,6 +115,19 @@ pub fn parse_require_string(s: &str) -> Result<(String, String), String> {
))
}
+/// Mirror of `Composer\Package\BasePackage::packageNameToRegexp`. Each
+/// character of `pattern` is `regex::escape`d, then `\*` is rewritten
+/// to `.*`, and the result is anchored and compiled case-insensitively.
+/// Used by selection commands (`reinstall`, `remove`, `update`, …) to
+/// expand `vendor/*`-style globs against installed/locked package names.
+pub fn package_name_to_regexp(pattern: &str) -> Regex {
+ let escaped = regex::escape(pattern).replace("\\*", ".*");
+ // PHP wraps with `{^...$}i`; in Rust we anchor with `\A`/`\z` and
+ // pass `(?i)` for case-insensitivity.
+ Regex::new(&format!(r"(?i)\A{escaped}\z"))
+ .expect("package_name_to_regexp pattern always compiles")
+}
+
#[cfg(test)]
mod tests {
use super::*;
@@ -207,6 +220,45 @@ mod tests {
}
#[test]
+ fn test_package_name_to_regexp_exact() {
+ let re = package_name_to_regexp("monolog/monolog");
+ assert!(re.is_match("monolog/monolog"));
+ assert!(re.is_match("Monolog/Monolog"));
+ assert!(!re.is_match("psr/log"));
+ assert!(!re.is_match("monolog/monolog-extra"));
+ assert!(!re.is_match("xmonolog/monolog"));
+ }
+
+ #[test]
+ fn test_package_name_to_regexp_wildcard() {
+ let re = package_name_to_regexp("psr/*");
+ assert!(re.is_match("psr/log"));
+ assert!(re.is_match("psr/container"));
+ assert!(re.is_match("psr/log/sub"));
+ assert!(!re.is_match("monolog/monolog"));
+
+ let re = package_name_to_regexp("*/log");
+ assert!(re.is_match("psr/log"));
+ assert!(re.is_match("monolog/log"));
+ assert!(!re.is_match("psr/container"));
+
+ let re = package_name_to_regexp("symfony/*/bridge");
+ assert!(re.is_match("symfony/http/bridge"));
+ assert!(!re.is_match("symfony/bridge"));
+
+ let re = package_name_to_regexp("*");
+ assert!(re.is_match("anything/at/all"));
+ }
+
+ #[test]
+ fn test_package_name_to_regexp_escapes_metacharacters() {
+ // `.` must be literal, not "any character"
+ let re = package_name_to_regexp("foo.bar/baz");
+ assert!(re.is_match("foo.bar/baz"));
+ assert!(!re.is_match("fooXbar/baz"));
+ }
+
+ #[test]
fn test_parse_require_string() {
let (name, ver) = parse_require_string("foo/bar:^1.0").unwrap();
assert_eq!(name, "foo/bar");