diff options
Diffstat (limited to 'crates')
7 files changed, 116 insertions, 299 deletions
diff --git a/crates/shirabe-class-map-generator/src/php_file_parser.rs b/crates/shirabe-class-map-generator/src/php_file_parser.rs index 7cc96228..b7fb595c 100644 --- a/crates/shirabe-class-map-generator/src/php_file_parser.rs +++ b/crates/shirabe-class-map-generator/src/php_file_parser.rs @@ -2,9 +2,9 @@ use crate::php_file_cleaner::PhpFileCleaner; use shirabe_php_shim::{ - CaptureKey, PHP_EOL, RuntimeException, file_exists, file_get_contents, function_exists, - is_file, is_readable, ltrim, php_strip_whitespace, preg_match_all, str_replace_array, strrpos, - substr, trim, + PHP_EOL, RuntimeException, file_exists, file_get_contents, function_exists, is_file, + is_readable, ltrim, php_strip_whitespace, preg_match_all, str_replace_array, strrpos, substr, + trim, }; use std::sync::OnceLock; @@ -58,7 +58,7 @@ impl PhpFileParser { // return early if there is no chance of matching anything in this file let pattern = format!("{{\\b(?:class|interface|trait{})\\s}}i", extra_types); - let max_matches = preg_match_all(&pattern, &contents).occurrence_count(); + let max_matches = preg_match_all(&pattern, &contents).count(); if max_matches == 0 { return Ok(vec![]); } @@ -89,21 +89,10 @@ impl PhpFileParser { let mut classes = vec![]; let mut namespace = String::new(); - let len = matches - .get(&CaptureKey::ByName("type".to_owned())) - .map(|v| v.len()) - .unwrap_or(0); - for i in 0..len { - let ns = matches - .get(&CaptureKey::ByName("ns".to_owned())) - .and_then(|v| v.get(i)) - .and_then(|s| s.as_deref()); + for r#match in matches { + let ns = r#match.name("ns"); if ns.is_some_and(|ns| !ns.is_empty()) { - let nsname = matches - .get(&CaptureKey::ByName("nsname".to_owned())) - .and_then(|v| v.get(i)) - .and_then(|s| s.as_deref()) - .unwrap_or(""); + let nsname = r#match.name("nsname").unwrap_or(""); namespace = str_replace_array( &[ " ".to_string(), @@ -115,10 +104,8 @@ impl PhpFileParser { nsname, ) + "\\"; } else { - let name = matches - .get(&CaptureKey::ByName("name".to_owned())) - .and_then(|v| v.get(i)) - .and_then(|s| s.as_deref()) + let name = r#match + .name("name") .expect("the `name` group participates whenever `ns` does not"); // skip anon classes extending/implementing if name == "extends" { @@ -136,14 +123,7 @@ impl PhpFileParser { &["_".to_string(), "__".to_string()], stripped, ) - } else if matches - .get(&CaptureKey::ByName("type".to_owned())) - .and_then(|v| v.get(i)) - .and_then(|s| s.as_deref()) - .unwrap_or("") - .to_lowercase() - == "enum" - { + } else if r#match.name("type").unwrap_or("").to_lowercase() == "enum" { // something like: // enum Foo: int { HERP = '123'; } // The regex above captures the colon, which isn't part of diff --git a/crates/shirabe-php-shim/src/preg.rs b/crates/shirabe-php-shim/src/preg.rs index c4060a34..a9809205 100644 --- a/crates/shirabe-php-shim/src/preg.rs +++ b/crates/shirabe-php-shim/src/preg.rs @@ -16,62 +16,6 @@ use indexmap::IndexMap; use std::sync::{Arc, LazyLock, Mutex}; -#[derive(Debug, Clone, PartialOrd, Ord, PartialEq, Eq, Hash)] -pub enum CaptureKey { - ByIndex(usize), - ByName(String), -} - -/// Defines a newtype over `IndexMap` for one of the `$matches` shapes the `preg_*` functions fill -/// in. -macro_rules! preg_match_map { - ($(#[$attr:meta])* $vis:vis struct $name:ident($key:ty => $value:ty);) => { - $(#[$attr])* - #[derive(Debug, Default, Clone, PartialEq, Eq)] - $vis struct $name(::indexmap::IndexMap<$key, $value>); - - impl $name { - pub fn new() -> Self { - Self(::indexmap::IndexMap::new()) - } - - pub fn clear(&mut self) { - self.0.clear(); - } - - pub fn get<Q>(&self, key: &Q) -> Option<&$value> - where - Q: ?Sized + ::std::hash::Hash + ::indexmap::Equivalent<$key>, - { - self.0.get(key) - } - - pub fn insert(&mut self, key: $key, value: $value) -> Option<$value> { - self.0.insert(key, value) - } - - pub fn iter(&self) -> ::indexmap::map::Iter<'_, $key, $value> { - self.0.iter() - } - } - - impl IntoIterator for $name { - type Item = ($key, $value); - type IntoIter = ::indexmap::map::IntoIter<$key, $value>; - - fn into_iter(self) -> Self::IntoIter { - self.0.into_iter() - } - } - - impl FromIterator<($key, $value)> for $name { - fn from_iter<I: IntoIterator<Item = ($key, $value)>>(iter: I) -> Self { - Self(iter.into_iter().collect()) - } - } - }; -} - /// A single match's `$matches`: the `regex::Captures` the search produced, read by either the named /// or the numbered form of a capture group. `'h` is the lifetime of the searched subject, which the /// group values borrow from. @@ -96,35 +40,16 @@ impl<'h> PregMatches<'h> { pub fn name(&self, name: &str) -> Option<&'h str> { self.caps.name(name).map(|group| group.as_str()) } -} - -preg_match_map! { - /// `PREG_PATTERN_ORDER` `$matches`: one entry per capture group, holding that group's value - /// across every match occurrence. - pub struct PregMatchesAll(CaptureKey => Vec<Option<String>>); -} -preg_match_map! { - /// `PREG_OFFSET_CAPTURE` counterpart of `PregMatchesAll`, pairing each value with the byte - /// offset it was captured at (`-1` for a group that did not participate). - pub struct PregMatchesAllWithOffsets(CaptureKey => Vec<(Option<String>, i64)>); -} - -impl PregMatchesAll { - /// The number PHP's `preg_match_all` returns: every column holds one entry per occurrence. - pub fn occurrence_count(&self) -> usize { - self.get(&CaptureKey::ByIndex(0)) - .expect("group 0 is always present") - .len() + /// The byte offset the capture group at `index` starts at, under the same rules as `get`. + /// `PREG_OFFSET_CAPTURE` reports a non-participating group at offset `-1`. + pub fn get_offset(&self, index: usize) -> Option<usize> { + self.caps.get(index).map(|group| group.start()) } -} -impl PregMatchesAllWithOffsets { - /// The number PHP's `preg_match_all` returns: every column holds one entry per occurrence. - pub fn occurrence_count(&self) -> usize { - self.get(&CaptureKey::ByIndex(0)) - .expect("group 0 is always present") - .len() + /// The byte offset of the capture group called `name`, under the same rules as `get_offset`. + pub fn name_offset(&self, name: &str) -> Option<usize> { + self.caps.name(name).map(|group| group.start()) } } @@ -166,84 +91,21 @@ pub fn preg_match<'h>(pattern: impl PregPattern, subject: &'h str) -> Option<Pre Some(PregMatches::new(caps)) } -// The number of occurrences the caller would get from PHP's return value is the length of any -// one column, as `PregMatchesAll::occurrence_count` reports it. -pub fn preg_match_all(pattern: impl PregPattern, subject: &str) -> PregMatchesAll { - let __resolved = pattern.resolve(); - let re = __resolved.regex(); - let group_count = re.captures_len(); - let names: Vec<Option<&str>> = re.capture_names().collect(); - - // PREG_PATTERN_ORDER: one column per group, one row per match occurrence. - let mut groups: Vec<Vec<Option<String>>> = vec![Vec::new(); group_count]; - for caps in re.captures_iter(subject) { - for (g, column) in groups.iter_mut().enumerate() { - let value = caps.get(g).map(|m| m.as_str().to_string()); - column.push(value); - } - } - - let mut matches = PregMatchesAll::new(); - for (g, column) in groups.into_iter().enumerate() { - if let Some(Some(name)) = names.get(g) { - matches.insert(CaptureKey::ByName((*name).to_string()), column.clone()); - } - matches.insert(CaptureKey::ByIndex(g), column); - } - - matches -} - -// PREG_SET_ORDER: the outer vec is indexed by match occurrence, the inner by -// capture group (a `$matches` row). A non-participating group is reported as -// None. -pub fn preg_match_all_set_order( +// Every occurrence of the pattern in `subject`, in match order. The search runs eagerly, as PHP's +// does: a match borrows `subject` alone, so the matches outlive the compiled pattern, which is only +// resolved for the duration of this call. +pub fn preg_match_all<'h>( pattern: impl PregPattern, - subject: &str, -) -> Vec<Vec<Option<String>>> { - let __resolved = pattern.resolve(); - let re = __resolved.regex(); - re.captures_iter(subject) - .map(|caps| { - (0..caps.len()) - .map(|g| caps.get(g).map(|m| m.as_str().to_string())) - .collect() - }) - .collect() -} - -// A non-participating group is reported as None, at offset -1. The number of occurrences the -// caller would get from PHP's return value is the length of any one column, as -// `PregMatchesAllWithOffsets::occurrence_count` reports it. -pub fn preg_match_all_offset_capture( - pattern: impl PregPattern, - subject: &str, -) -> PregMatchesAllWithOffsets { + subject: &'h str, +) -> impl Iterator<Item = PregMatches<'h>> { let __resolved = pattern.resolve(); - let re = __resolved.regex(); - let group_count = re.captures_len(); - let names: Vec<Option<&str>> = re.capture_names().collect(); - - let mut groups: Vec<Vec<(Option<String>, i64)>> = vec![Vec::new(); group_count]; - for caps in re.captures_iter(subject) { - for (g, column) in groups.iter_mut().enumerate() { - let entry = match caps.get(g) { - Some(m) => (Some(m.as_str().to_string()), m.start() as i64), - None => (None, -1), - }; - column.push(entry); - } - } - - let mut matches = PregMatchesAllWithOffsets::new(); - for (g, column) in groups.into_iter().enumerate() { - if let Some(Some(name)) = names.get(g) { - matches.insert(CaptureKey::ByName((*name).to_string()), column.clone()); - } - matches.insert(CaptureKey::ByIndex(g), column); - } + let matches: Vec<PregMatches<'h>> = __resolved + .regex() + .captures_iter(subject) + .map(PregMatches::new) + .collect(); - matches + matches.into_iter() } pub fn preg_grep<T: AsRef<str>>( diff --git a/crates/shirabe-symfony-console/src/completion/completion_input.rs b/crates/shirabe-symfony-console/src/completion/completion_input.rs index 41c15ec5..e7f1e0a2 100644 --- a/crates/shirabe-symfony-console/src/completion/completion_input.rs +++ b/crates/shirabe-symfony-console/src/completion/completion_input.rs @@ -3,7 +3,7 @@ use crate::input::ArgvInput; use crate::input::InputDefinition; use crate::input::InputOption; -use shirabe_php_shim::{CaptureKey, PhpMixed, php_regex, preg_match_all}; +use shirabe_php_shim::{PhpMixed, php_regex, preg_match_all}; /// An input specialized for shell completion. /// @@ -36,13 +36,11 @@ impl CompletionInput { Self::from_tokens( tokens - .get(&CaptureKey::ByIndex(0)) - .expect("group 0 is always present") - .iter() .map(|token| { token - .clone() + .get(0) .expect("group 0 participates whenever the pattern matches") + .to_string() }) .collect(), current_index, diff --git a/crates/shirabe-symfony-console/src/formatter/output_formatter.rs b/crates/shirabe-symfony-console/src/formatter/output_formatter.rs index f4c55573..cc457351 100644 --- a/crates/shirabe-symfony-console/src/formatter/output_formatter.rs +++ b/crates/shirabe-symfony-console/src/formatter/output_formatter.rs @@ -6,10 +6,7 @@ use crate::formatter::output_formatter_style::OutputFormatterStyle; use crate::formatter::output_formatter_style_interface::OutputFormatterStyleInterface; use crate::formatter::output_formatter_style_stack::OutputFormatterStyleStack; use crate::formatter::wrappable_output_formatter_interface::WrappableOutputFormatterInterface; -use shirabe_php_shim::{ - CaptureKey, php_regex, preg_match, preg_match_all, preg_match_all_offset_capture, - preg_match_all_set_order, preg_replace, -}; +use shirabe_php_shim::{PregMatches, php_regex, preg_match, preg_match_all, preg_replace}; use shirabe_symfony_string::b; /// Formatter class for console output. @@ -109,43 +106,37 @@ impl OutputFormatter { return Ok(Some(style.borrow().clone_box())); } - let matches = preg_match_all_set_order(php_regex!("/([^=]+)=([^;]+)(;|$)/"), string); + let matches: Vec<PregMatches> = + preg_match_all(php_regex!("/([^=]+)=([^;]+)(;|$)/"), string).collect(); if matches.is_empty() { return Ok(None); } let mut style = OutputFormatterStyle::new(None, None, vec![]); for r#match in &matches { - let mut r#match: Vec<String> = r#match - .iter() - .map(|group| { - group - .clone() - .expect("every group participates whenever the pattern matches") - }) - .collect(); - shirabe_php_shim::array_shift(&mut r#match); - r#match[0] = shirabe_php_shim::strtolower(&r#match[0]); + let key = shirabe_php_shim::strtolower( + r#match + .get(1) + .expect("every group participates whenever the pattern matches"), + ); + let value = r#match + .get(2) + .expect("every group participates whenever the pattern matches"); - if r#match[0] == "fg" { - style.set_foreground(Some(&shirabe_php_shim::strtolower(&r#match[1]))); - } else if r#match[0] == "bg" { - style.set_background(Some(&shirabe_php_shim::strtolower(&r#match[1]))); - } else if r#match[0] == "href" { - let url = preg_replace(php_regex!("{\\\\([<>])}"), "$1", &r#match[1]); + if key == "fg" { + style.set_foreground(Some(&shirabe_php_shim::strtolower(value))); + } else if key == "bg" { + style.set_background(Some(&shirabe_php_shim::strtolower(value))); + } else if key == "href" { + let url = preg_replace(php_regex!("{\\\\([<>])}"), "$1", value); style.set_href(&url); - } else if r#match[0] == "options" { - let options = preg_match_all( - php_regex!("([^,;]+)"), - &shirabe_php_shim::strtolower(&r#match[1]), - ); - let options = options - .get(&CaptureKey::ByIndex(0)) - .expect("group 0 is always present"); + } else if key == "options" { + let value = shirabe_php_shim::strtolower(value); + let options = preg_match_all(php_regex!("([^,;]+)"), &value); for option in options { style.set_option( option - .as_deref() + .get(0) .expect("group 0 participates whenever the pattern matches"), ); } @@ -296,19 +287,17 @@ impl WrappableOutputFormatterInterface for OutputFormatter { let open_tag_regex = "[a-z](?:[^\\\\<>]* | \\\\.)*"; let close_tag_regex = "[a-z][^<>]*"; let mut current_line_length: i64 = 0; - let matches = preg_match_all_offset_capture( + let matches = preg_match_all( format!("#<(({open_tag_regex}) | /({close_tag_regex})?)>#ix"), message, ); - let full_matches = matches - .get(&CaptureKey::ByIndex(0)) - .cloned() - .unwrap_or_default(); - for (i, match_) in full_matches.iter().enumerate() { - let pos = match_.1; + for match_ in matches { + let pos = match_ + .get_offset(0) + .expect("group 0 participates whenever the pattern matches") + as i64; let text = match_ - .0 - .clone() + .get(0) .expect("group 0 participates whenever the pattern matches"); if pos != 0 && shirabe_php_shim::byte_at(message, (pos - 1) as usize) == b'\\' { @@ -320,29 +309,22 @@ impl WrappableOutputFormatterInterface for OutputFormatter { let applied = self.apply_current_style(&segment, &output, width, &mut current_line_length); output.push_str(&applied); - offset = pos + shirabe_php_shim::strlen(&text); + offset = pos + shirabe_php_shim::strlen(text); // opening tag? - let open = shirabe_php_shim::byte_at(&text, 1) != b'/'; + let open = shirabe_php_shim::byte_at(text, 1) != b'/'; let tag = if open { - matches - .get(&CaptureKey::ByIndex(1)) - .expect("group 1 exists in the tag pattern")[i] - .0 - .clone() + match_ + .get(1) .expect("group 1 participates whenever the pattern matches") } else { - matches - .get(&CaptureKey::ByIndex(3)) - .and_then(|group| group.get(i)) - .and_then(|m| m.0.clone()) - .unwrap_or_default() + match_.get(3).unwrap_or_default() }; if !open && tag.is_empty() { // </> self.style_stack.pop(None)?.ok(); - } else if let Some(style) = self.create_style_from_string(&tag)? { + } else if let Some(style) = self.create_style_from_string(tag)? { if open { self.style_stack.push(style); } else { @@ -350,7 +332,7 @@ impl WrappableOutputFormatterInterface for OutputFormatter { } } else { let applied = - self.apply_current_style(&text, &output, width, &mut current_line_length); + self.apply_current_style(text, &output, width, &mut current_line_length); output.push_str(&applied); } } diff --git a/crates/shirabe/src/command/init_command.rs b/crates/shirabe/src/command/init_command.rs index 54ebc995..add7c237 100644 --- a/crates/shirabe/src/command/init_command.rs +++ b/crates/shirabe/src/command/init_command.rs @@ -21,7 +21,7 @@ use crate::util::Silencer; use indexmap::IndexMap; use shirabe_php_shim::Catch as _; use shirabe_php_shim::{ - CaptureKey, FILE_IGNORE_NEW_LINES, InvalidArgumentException, PHP_EOL, PHP_SERVER, PhpMixed, + FILE_IGNORE_NEW_LINES, InvalidArgumentException, PHP_EOL, PHP_SERVER, PhpMixed, array_flip_strings, array_intersect_key, array_map, basename, empty, explode, file, file_exists, file_get_contents, file_put_contents, get_current_user, impl_php_class, implode, is_dir, is_string, php_regex, preg_is_match, preg_match, preg_match_all, preg_quote, @@ -167,21 +167,15 @@ impl InitCommand { ) == 0 { *self.git_config.borrow_mut() = Some(IndexMap::new()); - let m = preg_match_all(php_regex!(r"{^([^=]+)=(.*)$}m"), &output); - if m.occurrence_count() > 0 { - let keys: Vec<Option<String>> = - m.get(&CaptureKey::ByIndex(1)).cloned().unwrap_or_default(); - let values: Vec<Option<String>> = - m.get(&CaptureKey::ByIndex(2)).cloned().unwrap_or_default(); - for (key, value) in keys.iter().zip(values.iter()) { - self.git_config.borrow_mut().as_mut().unwrap().insert( - key.clone() - .expect("group 1 participates whenever the pattern matches"), - value - .clone() - .expect("group 2 participates whenever the pattern matches"), - ); - } + for m in preg_match_all(php_regex!(r"{^([^=]+)=(.*)$}m"), &output) { + self.git_config.borrow_mut().as_mut().unwrap().insert( + m.get(1) + .expect("group 1 participates whenever the pattern matches") + .to_string(), + m.get(2) + .expect("group 2 participates whenever the pattern matches") + .to_string(), + ); } return self.git_config.borrow().clone().unwrap_or_default(); diff --git a/crates/shirabe/src/downloader/git_downloader.rs b/crates/shirabe/src/downloader/git_downloader.rs index a8a4344d..2d506fdf 100644 --- a/crates/shirabe/src/downloader/git_downloader.rs +++ b/crates/shirabe/src/downloader/git_downloader.rs @@ -18,10 +18,9 @@ use crate::util::ProcessExecutor; use crate::util::Url; use indexmap::IndexMap; use shirabe_php_shim::{ - CaptureKey, CmpOp, PhpMixed, RuntimeException, array_map, basename, dirname, impl_php_class, - implode, in_array_strict, is_dir, php_regex, preg_is_match, preg_match, preg_match_all, - preg_quote, preg_replace, preg_split, realpath, rtrim, strlen, strpos, substr, trim, - version_compare, + CmpOp, PhpMixed, RuntimeException, array_map, basename, dirname, impl_php_class, implode, + in_array_strict, is_dir, php_regex, preg_is_match, preg_match, preg_match_all, preg_quote, + preg_replace, preg_split, realpath, rtrim, strlen, strpos, substr, trim, version_compare, }; #[derive(Debug)] @@ -101,21 +100,21 @@ impl GitDownloader { }; let head_ref = head_match.get(1).unwrap_or_default().to_string(); - let branches_match = preg_match_all( + let candidate_branches: Vec<String> = preg_match_all( format!("{{^{} refs/heads/(.+)$}}mi", preg_quote(&head_ref, None)), &refs, - ); - if branches_match.occurrence_count() == 0 { + ) + .map(|branch_match| { + branch_match + .get(1) + .expect("group 1 participates whenever the pattern matches") + .to_string() + }) + .collect(); + if candidate_branches.is_empty() { // not on a branch, we are either on a not-modified tag or some sort of detached head, so skip this return Ok(None); } - let candidate_branches: Vec<String> = branches_match - .get(&CaptureKey::ByIndex(1)) - .cloned() - .unwrap_or_default() - .into_iter() - .map(|branch| branch.expect("group 1 participates whenever the pattern matches")) - .collect(); // use the first match as branch name for now let mut branch = candidate_branches[0].clone(); @@ -128,21 +127,23 @@ impl GitDownloader { // try to find matching branch names in remote repos for candidate in &candidate_branches { - let m = preg_match_all( + let matches: Vec<String> = preg_match_all( format!( "{{^[a-f0-9]+ refs/remotes/((?:[^/]+)/{})$}}mi", preg_quote(candidate, None) ), &refs, - ); - if m.occurrence_count() > 0 { - let matches: Vec<Option<String>> = - m.get(&CaptureKey::ByIndex(1)).cloned().unwrap_or_default(); + ) + .map(|m| { + m.get(1) + .expect("group 1 participates whenever the pattern matches") + .to_string() + }) + .collect(); + if !matches.is_empty() { for match_ in matches { branch = candidate.clone(); - remote_branches.push( - match_.expect("group 1 participates whenever the pattern matches"), - ); + remote_branches.push(match_); } break; } diff --git a/crates/shirabe/src/package/version/version_bumper.rs b/crates/shirabe/src/package/version/version_bumper.rs index 075663d4..4bb95ef1 100644 --- a/crates/shirabe/src/package/version/version_bumper.rs +++ b/crates/shirabe/src/package/version/version_bumper.rs @@ -5,9 +5,7 @@ use crate::package::dumper::ArrayDumper; use crate::package::loader::ArrayLoader; use crate::package::version::VersionParser; use crate::util::Platform; -use shirabe_php_shim::{ - CaptureKey, php_regex, preg_is_match, preg_match_all_offset_capture, preg_replace, -}; +use shirabe_php_shim::{php_regex, preg_is_match, preg_match_all, preg_replace}; use shirabe_semver::Intervals; use shirabe_semver::constraint::AnyConstraint; @@ -78,19 +76,21 @@ impl VersionBumper { major = major ); - let matches = preg_match_all_offset_capture(&pattern, &pretty_constraint); - if matches.occurrence_count() > 0 { - let mut modified = pretty_constraint.clone(); - let constraint_matches = matches - .get(&CaptureKey::ByName("constraint".to_string())) - .cloned() - .unwrap_or_default(); - for match_ in constraint_matches.iter().rev() { - let match_str = match_ - .0 - .as_deref() + // Collected eagerly: a match borrows `pretty_constraint`, which the returns below move. + let constraint_matches: Vec<(String, i64)> = preg_match_all(&pattern, &pretty_constraint) + .map(|match_| { + let constraint = match_ + .name("constraint") + .expect("the `constraint` group participates whenever the pattern matches"); + let offset = match_ + .name_offset("constraint") .expect("the `constraint` group participates whenever the pattern matches"); - let match_offset = match_.1; + (constraint.to_string(), offset as i64) + }) + .collect(); + if !constraint_matches.is_empty() { + let mut modified = pretty_constraint.clone(); + for (match_str, match_offset) in constraint_matches.into_iter().rev() { let suffix = if match_str.matches('.').count() == 2 && version_without_suffix.matches('.').count() == 1 { @@ -119,7 +119,7 @@ impl VersionBumper { &modified, &replacement, match_offset, - Some(Platform::strlen(match_str)), + Some(Platform::strlen(&match_str)), ); } |
