From b8d46b0495d00815a699932ced0b43955c949ab9 Mon Sep 17 00:00:00 2001 From: nsfisis Date: Sat, 1 Aug 2026 04:52:50 +0900 Subject: feat(php-src): add a BSD-licensed crate for php-src derived code The audit in .ken/php-shim-copying.md judged 14 functions in shirabe-php-shim (plus php_wordwrap in shirabe-external-packages) to be line-by-line transcriptions or structural imitations of php-src. PHP's relicensing to 3-clause BSD makes keeping them legal, but the boundary between BSD-derived and MIT code was invisible in the source tree. Moving them into their own crate puts the license into the build metadata (so NOTICE generation follows the binary), makes a reverse dependency a compile error, and encodes the origin in the module path, which mirrors php-src's ext tree. Each function records its origin in a fixed-format doc comment, and a new php_src_derivation_boundary linter fails if `php-src` appears in any Rust source outside the crate. Public paths under shirabe_php_shim:: are unchanged: functions that are themselves derived are re-exported with `pub use`, and the wrappers that only validate arguments stay on the MIT side. This also resolves the duplicate wordwrap implementation. shirabe_php_shim::wordwrap was todo!(), so SymfonyStyle::block panicked, while shirabe-external-packages carried its own copy. Both now go through the single port, verified against real PHP on 13 cases covering multi-character breaks and cut. Co-Authored-By: Claude Opus 5 (1M context) --- .../src/symfony/string/code_point_string.rs | 83 +--------------------- 1 file changed, 1 insertion(+), 82 deletions(-) (limited to 'crates/shirabe-external-packages') diff --git a/crates/shirabe-external-packages/src/symfony/string/code_point_string.rs b/crates/shirabe-external-packages/src/symfony/string/code_point_string.rs index 97ee2508..dcbaa1f6 100644 --- a/crates/shirabe-external-packages/src/symfony/string/code_point_string.rs +++ b/crates/shirabe-external-packages/src/symfony/string/code_point_string.rs @@ -42,7 +42,7 @@ impl CodePointString { let mut j: usize = 0; // PHP seeds both `$b` and `$i` at -1; mirror with signed indices. let mut i: i64 = -1; - let mask = php_wordwrap(&mask, width, "#", cut); + let mask = shirabe_php_shim::wordwrap(&mask, width, "#", cut); let mask_bytes = mask.as_bytes(); let mut b: i64 = -1; @@ -91,84 +91,3 @@ impl CodePointString { ) } } - -/// Port of PHP's built-in `wordwrap()` (`PHP_FUNCTION(wordwrap)` in ext/standard/string.c). -/// Byte-based, matching PHP's single-byte/multi-byte break and cut handling. -fn php_wordwrap(text: &str, linelength: i64, breakchar: &str, docut: bool) -> String { - let text = text.as_bytes(); - let breakchar = breakchar.as_bytes(); - let textlen = text.len() as i64; - let breaklen = breakchar.len() as i64; - - if textlen == 0 { - return String::new(); - } - - let mut laststart: i64 = 0; - let mut lastspace: i64 = 0; - - // Special case for a single-character break that needs no extra storage. - if breaklen == 1 && !docut { - let mut out = text.to_vec(); - let mut current = 0i64; - while current < textlen { - let c = out[current as usize]; - if c == breakchar[0] { - laststart = current + 1; - lastspace = current + 1; - } else if c == b' ' { - if current - laststart >= linelength { - out[current as usize] = breakchar[0]; - laststart = current + 1; - } - lastspace = current; - } else if current - laststart >= linelength && laststart != lastspace { - out[lastspace as usize] = breakchar[0]; - laststart = lastspace + 1; - } - current += 1; - } - return String::from_utf8_lossy(&out).into_owned(); - } - - // Multiple character line break or forced cut. - let mut out: Vec = Vec::new(); - let mut current = 0i64; - while current < textlen { - // When we hit an existing break, copy to the new buffer and fix up laststart/lastspace. - if text[current as usize] == breakchar[0] - && current + breaklen < textlen - && &text[current as usize..(current + breaklen) as usize] == breakchar - { - out.extend_from_slice(&text[laststart as usize..(current + breaklen) as usize]); - current += breaklen - 1; - laststart = current + 1; - lastspace = current + 1; - } else if text[current as usize] == b' ' { - if current - laststart >= linelength { - out.extend_from_slice(&text[laststart as usize..current as usize]); - out.extend_from_slice(breakchar); - laststart = current + 1; - } - lastspace = current; - } else if current - laststart >= linelength && docut && laststart >= lastspace { - out.extend_from_slice(&text[laststart as usize..current as usize]); - out.extend_from_slice(breakchar); - laststart = current; - lastspace = current; - } else if current - laststart >= linelength && laststart < lastspace { - out.extend_from_slice(&text[laststart as usize..lastspace as usize]); - out.extend_from_slice(breakchar); - laststart = lastspace + 1; - lastspace += 1; - } - current += 1; - } - - // Copy over any stragglers. - if laststart != current { - out.extend_from_slice(&text[laststart as usize..current as usize]); - } - - String::from_utf8_lossy(&out).into_owned() -} -- cgit v1.3.1