From 243450fee9853c2fea66ae0642dabb8db5227d6f Mon Sep 17 00:00:00 2001 From: nsfisis Date: Mon, 8 Jun 2026 02:44:38 +0900 Subject: feat(phase-c): resolve closure-capture phase-b TODOs in config/create-project Port Config::process() to the real Preg::replace_callback, whose closure borrows &self/flags and calls get_with_flags; surface get() errors via a captured cell so process now returns Result. Switch the replace_callback shim bounds to FnMut to allow the error-capturing closure. Wire CreateProjectCommand suggestion collection through the Rc reporter's interior mutation. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../src/composer/pcre/preg.rs | 8 ++-- .../shirabe/src/command/create_project_command.rs | 7 +++- crates/shirabe/src/config.rs | 46 ++++++++++++---------- 3 files changed, 35 insertions(+), 26 deletions(-) diff --git a/crates/shirabe-external-packages/src/composer/pcre/preg.rs b/crates/shirabe-external-packages/src/composer/pcre/preg.rs index e16643a..ea189eb 100644 --- a/crates/shirabe-external-packages/src/composer/pcre/preg.rs +++ b/crates/shirabe-external-packages/src/composer/pcre/preg.rs @@ -211,7 +211,7 @@ impl Preg { todo!() } - pub fn replace_callback) -> String>( + pub fn replace_callback) -> String>( _pattern: &str, _replacement: F, _subject: &str, @@ -219,7 +219,7 @@ impl Preg { todo!() } - pub fn replace_callback4) -> String>( + pub fn replace_callback4) -> String>( _pattern: &str, _replacement: F, _subject: &str, @@ -228,7 +228,7 @@ impl Preg { todo!() } - pub fn replace_callback5) -> String>( + pub fn replace_callback5) -> String>( _pattern: &str, _replacement: F, _subject: &str, @@ -238,7 +238,7 @@ impl Preg { todo!() } - pub fn replace_callback6) -> String>( + pub fn replace_callback6) -> String>( _pattern: &str, _replacement: F, _subject: &str, diff --git a/crates/shirabe/src/command/create_project_command.rs b/crates/shirabe/src/command/create_project_command.rs index 77cbea7..f8fc511 100644 --- a/crates/shirabe/src/command/create_project_command.rs +++ b/crates/shirabe/src/command/create_project_command.rs @@ -972,8 +972,11 @@ impl CreateProjectCommand { im.notify_installs(io.clone()); // collect suggestions - // TODO(phase-b): self.suggested_packages_reporter is on the outer scope via &self - // self.suggested_packages_reporter.add_suggestions_from_package(&*package); + self.suggested_packages_reporter + .as_ref() + .unwrap() + .borrow_mut() + .add_suggestions_from_package(package.clone()); let installed_from_vcs = package.get_installation_source().as_deref() == Some("source"); diff --git a/crates/shirabe/src/config.rs b/crates/shirabe/src/config.rs index d1f0702..d6bee9e 100644 --- a/crates/shirabe/src/config.rs +++ b/crates/shirabe/src/config.rs @@ -14,7 +14,8 @@ use shirabe_php_shim::{ E_USER_DEPRECATED, FILTER_VALIDATE_URL, PHP_URL_HOST, PHP_URL_SCHEME, PhpMixed, RuntimeException, array_key_exists, array_merge, array_reverse, array_search_mixed, array_unique, current, empty, filter_var, implode, in_array, is_array, is_int, is_string, key, - max, parse_url, reset, rtrim, strtolower, strtoupper, strtr, substr, trigger_error, + max, parse_url, php_to_string, reset, rtrim, strtolower, strtoupper, strtr, substr, + trigger_error, }; use std::cell::RefCell; @@ -618,7 +619,7 @@ impl Config { } else { val }; - let processed = self.process(raw_val, flags); + let processed = self.process(raw_val, flags)?; let mut val_str = rtrim(processed.as_string().unwrap_or(""), Some("/\\")); val_str = Platform::expand_path(&val_str); @@ -740,7 +741,7 @@ impl Config { "home" => { let v = self.config.get(key).cloned().unwrap_or(PhpMixed::Null); let expanded = Platform::expand_path(v.as_string().unwrap_or("")); - let processed = self.process(PhpMixed::String(expanded), flags); + let processed = self.process(PhpMixed::String(expanded), flags)?; Ok(PhpMixed::String(rtrim( processed.as_string().unwrap_or(""), Some("/\\"), @@ -905,7 +906,7 @@ impl Config { return Ok(PhpMixed::Null); } - Ok(self.process(v, flags)) + self.process(v, flags) } "audit" => { @@ -981,7 +982,7 @@ impl Config { } let v = self.config.get(key).cloned().unwrap_or(PhpMixed::Null); - Ok(self.process(v, flags)) + self.process(v, flags) } } } @@ -1073,26 +1074,31 @@ impl Config { /// @param int $flags Options (see class constants) /// /// @return string|mixed - fn process(&self, value: PhpMixed, flags: i64) -> PhpMixed { + fn process(&self, value: PhpMixed, flags: i64) -> Result { if !is_string(&value) { - return value; + return Ok(value); } let value_str = value.as_string().unwrap_or("").to_string(); - // TODO(phase-b): Preg::replace_callback with a closure that calls &mut self.get_with_flags - let mut result = value_str.clone(); - let mut m: IndexMap = IndexMap::new(); - if Preg::is_match_strict_groups3(r"#\{\$(.+)\}#", &value_str, Some(&mut m)).unwrap_or(false) - { - let key_match = m.get(&CaptureKey::ByIndex(1)).cloned().unwrap_or_default(); - let replacement = self - .get_with_flags(&key_match, flags) - .ok() - .and_then(|v| v.as_string().map(|s| s.to_string())) - .unwrap_or_default(); - result = result.replace(&format!("{{${}}}", key_match), &replacement); + let mut error = None; + let result = Preg::replace_callback( + r"#\{\$(.+)\}#", + |m: &IndexMap| -> String { + let key_match = m.get(&CaptureKey::ByIndex(1)).cloned().unwrap_or_default(); + match self.get_with_flags(&key_match, flags) { + Ok(v) => php_to_string(&v), + Err(e) => { + error = Some(e); + String::new() + } + } + }, + &value_str, + )?; + if let Some(e) = error { + return Err(e); } - PhpMixed::String(result) + Ok(PhpMixed::String(result)) } /// Turns relative paths in absolute paths without realpath() -- cgit v1.3.1