//! ref: composer/src/Composer/Platform/Runtime.php use indexmap::IndexMap; use shirabe_external_packages::composer::pcre::{CaptureKey, Preg}; use shirabe_php_shim::{ PhpMixed, class_exists, function_exists, html_entity_decode, implode, instantiate_class, ltrim, strip_tags, trim, }; /// Seam over the PHP runtime so PlatformRepository can be tested against mocked /// extension/constant/function probes. PHP has no such interface (the test mocks the /// concrete `Composer\Platform\Runtime` directly); it is introduced here to keep the /// consumer dependent only on trait methods. pub trait RuntimeInterface: std::fmt::Debug { fn has_constant(&self, constant_name: &str, class: Option) -> bool; fn get_constant(&self, constant_name: &str, class: Option) -> PhpMixed; /// `callable` carries the PHP callable spec (a function name string or a /// `[class, method]` list), matching PHP `invoke($callable, $arguments)`. fn invoke(&self, callable: PhpMixed, arguments: Vec) -> PhpMixed; fn has_class(&self, class: &str) -> bool; fn construct(&self, class: &str, arguments: Vec) -> anyhow::Result; fn get_extensions(&self) -> Vec; fn get_extension_version(&self, extension: &str) -> String; fn get_extension_info(&self, extension: &str) -> anyhow::Result; } #[derive(Debug)] pub struct Runtime; impl RuntimeInterface for Runtime { fn has_constant(&self, constant_name: &str, class: Option) -> bool { shirabe_php_rpc::has_constant(<rim( &format!("{}::{}", class.as_deref().unwrap_or(""), constant_name), Some(":"), )) } fn get_constant(&self, constant_name: &str, class: Option) -> PhpMixed { shirabe_php_rpc::get_constant(<rim( &format!("{}::{}", class.as_deref().unwrap_or(""), constant_name), Some(":"), )) } fn invoke(&self, callable: PhpMixed, arguments: Vec) -> PhpMixed { // PHP: return $callable(...$arguments); // Only the specific dynamic callables PlatformRepository actually reaches are // wired through php-rpc; arbitrary PHP callables are still unsupported. match (&callable, arguments.as_slice()) { (PhpMixed::String(name), [PhpMixed::String(arg)]) if name == "inet_pton" => { shirabe_php_rpc::inet_pton(arg) } (PhpMixed::String(name), []) if name == "curl_version" => { let mut version = IndexMap::new(); if let Some(v) = shirabe_php_rpc::curl_version() { version.insert("version".to_string(), PhpMixed::String(v)); } PhpMixed::Array(version) } _ => todo!(), } } fn has_class(&self, class: &str) -> bool { class_exists(class) } fn construct(&self, class: &str, arguments: Vec) -> anyhow::Result { if arguments.is_empty() { Ok(instantiate_class(class, vec![])) } else { Ok(instantiate_class(class, arguments)) } } fn get_extensions(&self) -> Vec { shirabe_php_rpc::get_loaded_extensions() } fn get_extension_version(&self, extension: &str) -> String { shirabe_php_rpc::phpversion(extension).unwrap_or_else(|| "0".to_string()) } fn get_extension_info(&self, extension: &str) -> anyhow::Result { Ok(shirabe_php_rpc::get_extension_info(extension)) } } impl Runtime { pub fn has_function(&self, f: &str) -> bool { function_exists(f) } pub fn parse_html_extension_info(html: &str) -> String { let mut result: Vec = vec![]; let mut matches: IndexMap = IndexMap::new(); if Preg::match3( r"~

\s*]*>([^<]+)\s*

~i", html, Some(&mut matches), ) { result.push(trim( &html_entity_decode( matches .get(&CaptureKey::ByIndex(1)) .map(|s| s.as_str()) .unwrap_or(""), ), None, )); result.push(String::new()); } let mut matches: IndexMap> = IndexMap::new(); if Preg::match_all3( r#"~\s*\s*(.*?)\s*\s*\s*(.*?)\s*\s*~is"#, html, Some(&mut matches), ) > 0 { let group1 = matches .get(&CaptureKey::ByIndex(1)) .cloned() .unwrap_or_default(); let group2 = matches .get(&CaptureKey::ByIndex(2)) .cloned() .unwrap_or_default(); let count = std::cmp::min(group1.len(), group2.len()); for i in 0..count { let key = trim(&html_entity_decode(&strip_tags(&group1[i])), None); let value = trim(&html_entity_decode(&strip_tags(&group2[i])), None); result.push(format!("{} => {}", key, value)); } } implode("\n", &result) } }