aboutsummaryrefslogtreecommitdiffhomepage
path: root/crates/shirabe/src/config
diff options
context:
space:
mode:
authornsfisis <nsfisis@gmail.com>2026-06-07 18:47:59 +0900
committernsfisis <nsfisis@gmail.com>2026-06-07 19:29:26 +0900
commite8d6a57d157ab778108bcb044a3dfa5952d4a5ae (patch)
treeeaa30eeee79b2c04ca95be843ae6a9dda69af246 /crates/shirabe/src/config
parentb8a50e25aafb51a6d73f9695362854cc5ed58117 (diff)
downloadphp-shirabe-e8d6a57d157ab778108bcb044a3dfa5952d4a5ae.tar.gz
php-shirabe-e8d6a57d157ab778108bcb044a3dfa5952d4a5ae.tar.zst
php-shirabe-e8d6a57d157ab778108bcb044a3dfa5952d4a5ae.zip
feat(phase-c): resolve dynamic-dispatch phase-b TODOs
Replace the PHP `call_user_func([$obj, $method], ...)` / `$obj->{'get'.$x}()` dynamic dispatches (category E) with static Rust dispatch. - json_config_source: inject the clean-update as a typed `FnOnce(&mut JsonManipulator) -> Result<bool>` closure instead of a method-name string, dropping the call_user_func_array round-trip through PhpMixed args. The auth-config method override moves into the add/remove_config_setting closures. - config_command: dispatch addConfigSetting/addProperty on the concrete JsonConfigSource via match. - locker: select getRequires/getDevRequires and getReplaces/getProvides via match on the existing handle getters (previously stubbed to empty Vec). - create_project_command: reuse the existing get_links_for_type helper. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Diffstat (limited to 'crates/shirabe/src/config')
-rw-r--r--crates/shirabe/src/config/json_config_source.rs149
1 files changed, 82 insertions, 67 deletions
diff --git a/crates/shirabe/src/config/json_config_source.rs b/crates/shirabe/src/config/json_config_source.rs
index 532c1d8..8a8d40a 100644
--- a/crates/shirabe/src/config/json_config_source.rs
+++ b/crates/shirabe/src/config/json_config_source.rs
@@ -4,8 +4,8 @@ use crate::util::Silencer;
use anyhow::Result;
use indexmap::IndexMap;
use shirabe_php_shim::{
- PHP_EOL, PhpMixed, RuntimeException, array_unshift, call_user_func_array, chmod, explode,
- file_get_contents, file_put_contents, implode, is_writable, sprintf,
+ PHP_EOL, PhpMixed, RuntimeException, array_unshift, chmod, explode, file_get_contents,
+ file_put_contents, implode, is_writable, sprintf,
};
use crate::config::ConfigSourceInterface;
@@ -33,9 +33,9 @@ impl JsonConfigSource {
/// @param mixed ...$args
fn manipulate_json(
&mut self,
- method: &str,
+ clean: impl FnOnce(&mut JsonManipulator) -> Result<bool>,
// TODO(phase-b): callback signature uses &mut $config (PHP reference) and variadic args
- fallback: Box<dyn Fn(&mut PhpMixed, &mut Vec<PhpMixed>)>,
+ fallback: impl FnOnce(&mut PhpMixed, &mut Vec<PhpMixed>),
mut args: Vec<PhpMixed>,
) -> Result<()> {
let contents;
@@ -73,35 +73,8 @@ impl JsonConfigSource {
let new_file = !self.file.borrow().exists();
- // override manipulator method for auth config files
- let mut method = method.to_string();
- if self.auth_config && method == "addConfigSetting" {
- method = "addSubNode".to_string();
- let parts = explode(".", args[0].as_string().unwrap_or(""));
- let main_node = parts.get(0).cloned().unwrap_or_default();
- let name = parts.get(1).cloned().unwrap_or_default();
- args = vec![
- PhpMixed::String(main_node),
- PhpMixed::String(name),
- args[1].clone(),
- ];
- } else if self.auth_config && method == "removeConfigSetting" {
- method = "removeSubNode".to_string();
- let parts = explode(".", args[0].as_string().unwrap_or(""));
- let main_node = parts.get(0).cloned().unwrap_or_default();
- let name = parts.get(1).cloned().unwrap_or_default();
- args = vec![PhpMixed::String(main_node), PhpMixed::String(name)];
- }
-
// try to update cleanly
- // PHP: call_user_func_array([$manipulator, $method], $args)
- let manipulator_result: bool = call_user_func_array(
- // TODO(phase-b): callable [manipulator, method] requires bound-method dispatch
- todo!("[manipulator, method] callable"),
- &PhpMixed::List(args.iter().map(|a| Box::new(a.clone())).collect()),
- )
- .as_bool()
- .unwrap_or(false);
+ let manipulator_result: bool = clean(&mut manipulator)?;
if manipulator_result {
file_put_contents(
self.file.borrow().get_path(),
@@ -262,13 +235,15 @@ impl ConfigSourceInterface for JsonConfigSource {
fn add_repository(&mut self, name: &str, config: PhpMixed, append: bool) -> Result<()> {
let name_owned = name.to_string();
+ let clean_name = name_owned.clone();
+ let clean_config = config.clone();
self.manipulate_json(
- "addRepository",
- Box::new(move |cfg: &mut PhpMixed, args: &mut Vec<PhpMixed>| {
+ move |m| m.add_repository(&clean_name, clean_config, append),
+ move |cfg: &mut PhpMixed, args: &mut Vec<PhpMixed>| {
// TODO(phase-b): port the closure body — args are [$cfg, $repo, $repoConfig, $append]
let _ = (cfg, args);
todo!("addRepository fallback closure body");
- }),
+ },
vec![PhpMixed::String(name_owned), config, PhpMixed::Bool(append)],
)
}
@@ -282,13 +257,16 @@ impl ConfigSourceInterface for JsonConfigSource {
) -> Result<()> {
let name_owned = name.to_string();
let reference_name_owned = reference_name.to_string();
+ let clean_name = name_owned.clone();
+ let clean_reference_name = reference_name_owned.clone();
+ let clean_config = config.clone();
self.manipulate_json(
- "insertRepository",
- Box::new(move |cfg: &mut PhpMixed, args: &mut Vec<PhpMixed>| {
+ move |m| m.insert_repository(&clean_name, clean_config, &clean_reference_name, offset),
+ move |cfg: &mut PhpMixed, args: &mut Vec<PhpMixed>| {
// TODO(phase-b): port the closure body
let _ = (cfg, args);
todo!("insertRepository fallback closure body");
- }),
+ },
vec![
PhpMixed::String(name_owned),
config,
@@ -299,15 +277,15 @@ impl ConfigSourceInterface for JsonConfigSource {
}
fn set_repository_url(&mut self, name: &str, url: &str) -> Result<()> {
- let _name_owned = name.to_string();
- let _url_owned = url.to_string();
+ let clean_name = name.to_string();
+ let clean_url = url.to_string();
self.manipulate_json(
- "setRepositoryUrl",
- Box::new(move |cfg: &mut PhpMixed, args: &mut Vec<PhpMixed>| {
+ move |m| m.set_repository_url(&clean_name, &clean_url),
+ move |cfg: &mut PhpMixed, args: &mut Vec<PhpMixed>| {
// PHP: foreach ($config['repositories'] ?? [] as $index => $repository) { ... }
let _ = (cfg, args);
todo!("setRepositoryUrl fallback closure body");
- }),
+ },
vec![
PhpMixed::String(name.to_string()),
PhpMixed::String(url.to_string()),
@@ -316,71 +294,105 @@ impl ConfigSourceInterface for JsonConfigSource {
}
fn remove_repository(&mut self, name: &str) -> Result<()> {
+ let clean_name = name.to_string();
self.manipulate_json(
- "removeRepository",
- Box::new(move |cfg: &mut PhpMixed, args: &mut Vec<PhpMixed>| {
+ move |m| m.remove_repository(&clean_name),
+ move |cfg: &mut PhpMixed, args: &mut Vec<PhpMixed>| {
let _ = (cfg, args);
todo!("removeRepository fallback closure body");
- }),
+ },
vec![PhpMixed::String(name.to_string())],
)
}
fn add_config_setting(&mut self, name: &str, value: PhpMixed) -> Result<()> {
let auth_config = self.auth_config;
+ let clean_name = name.to_string();
+ let clean_value = value.clone();
self.manipulate_json(
- "addConfigSetting",
- Box::new(move |cfg: &mut PhpMixed, args: &mut Vec<PhpMixed>| {
+ // override manipulator method for auth config files
+ move |m| {
+ if auth_config {
+ let parts = explode(".", &clean_name);
+ m.add_sub_node(
+ parts.get(0).map(String::as_str).unwrap_or(""),
+ parts.get(1).map(String::as_str).unwrap_or(""),
+ clean_value,
+ false,
+ )
+ } else {
+ m.add_config_setting(&clean_name, clean_value)
+ }
+ },
+ move |cfg: &mut PhpMixed, args: &mut Vec<PhpMixed>| {
// PHP: [$key, $host] = explode('.', $key, 2);
let _ = (cfg, args, auth_config);
todo!("addConfigSetting fallback closure body");
- }),
+ },
vec![PhpMixed::String(name.to_string()), value],
)
}
fn remove_config_setting(&mut self, name: &str) -> Result<()> {
let auth_config = self.auth_config;
+ let clean_name = name.to_string();
self.manipulate_json(
- "removeConfigSetting",
- Box::new(move |cfg: &mut PhpMixed, args: &mut Vec<PhpMixed>| {
+ // override manipulator method for auth config files
+ move |m| {
+ if auth_config {
+ let parts = explode(".", &clean_name);
+ m.remove_sub_node(
+ parts.get(0).map(String::as_str).unwrap_or(""),
+ parts.get(1).map(String::as_str).unwrap_or(""),
+ )
+ } else {
+ m.remove_config_setting(&clean_name)
+ }
+ },
+ move |cfg: &mut PhpMixed, args: &mut Vec<PhpMixed>| {
let _ = (cfg, args, auth_config);
todo!("removeConfigSetting fallback closure body");
- }),
+ },
vec![PhpMixed::String(name.to_string())],
)
}
fn add_property(&mut self, name: &str, value: PhpMixed) -> Result<()> {
+ let clean_name = name.to_string();
+ let clean_value = value.clone();
self.manipulate_json(
- "addProperty",
- Box::new(move |cfg: &mut PhpMixed, args: &mut Vec<PhpMixed>| {
+ move |m| m.add_property(&clean_name, clean_value),
+ move |cfg: &mut PhpMixed, args: &mut Vec<PhpMixed>| {
let _ = (cfg, args);
todo!("addProperty fallback closure body");
- }),
+ },
vec![PhpMixed::String(name.to_string()), value],
)
}
fn remove_property(&mut self, name: &str) -> Result<()> {
+ let clean_name = name.to_string();
self.manipulate_json(
- "removeProperty",
- Box::new(move |cfg: &mut PhpMixed, args: &mut Vec<PhpMixed>| {
+ move |m| m.remove_property(&clean_name),
+ move |cfg: &mut PhpMixed, args: &mut Vec<PhpMixed>| {
let _ = (cfg, args);
todo!("removeProperty fallback closure body");
- }),
+ },
vec![PhpMixed::String(name.to_string())],
)
}
fn add_link(&mut self, r#type: &str, name: &str, value: &str) -> Result<()> {
+ let clean_type = r#type.to_string();
+ let clean_name = name.to_string();
+ let clean_value = value.to_string();
self.manipulate_json(
- "addLink",
- Box::new(move |cfg: &mut PhpMixed, args: &mut Vec<PhpMixed>| {
+ move |m| m.add_link(&clean_type, &clean_name, &clean_value, false),
+ move |cfg: &mut PhpMixed, args: &mut Vec<PhpMixed>| {
// PHP: $config[$type][$name] = $value;
let _ = (cfg, args);
todo!("addLink fallback closure body");
- }),
+ },
vec![
PhpMixed::String(r#type.to_string()),
PhpMixed::String(name.to_string()),
@@ -390,25 +402,28 @@ impl ConfigSourceInterface for JsonConfigSource {
}
fn remove_link(&mut self, r#type: &str, name: &str) -> Result<()> {
+ let clean_type = r#type.to_string();
+ let clean_name = name.to_string();
self.manipulate_json(
- "removeSubNode",
- Box::new(move |cfg: &mut PhpMixed, args: &mut Vec<PhpMixed>| {
+ move |m| m.remove_sub_node(&clean_type, &clean_name),
+ move |cfg: &mut PhpMixed, args: &mut Vec<PhpMixed>| {
// PHP: unset($config[$type][$name]);
let _ = (cfg, args);
todo!("removeLink fallback (unset subnode) closure body");
- }),
+ },
vec![
PhpMixed::String(r#type.to_string()),
PhpMixed::String(name.to_string()),
],
)?;
+ let clean_type = r#type.to_string();
self.manipulate_json(
- "removeMainKeyIfEmpty",
- Box::new(move |cfg: &mut PhpMixed, args: &mut Vec<PhpMixed>| {
+ move |m| m.remove_main_key_if_empty(&clean_type),
+ move |cfg: &mut PhpMixed, args: &mut Vec<PhpMixed>| {
// PHP: if (0 === count($config[$type])) { unset($config[$type]); }
let _ = (cfg, args);
todo!("removeLink fallback (unset main key if empty) closure body");
- }),
+ },
vec![PhpMixed::String(r#type.to_string())],
)
}