aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authornsfisis <nsfisis@gmail.com>2026-08-18 01:57:02 +0900
committernsfisis <nsfisis@gmail.com>2026-08-18 01:57:02 +0900
commit6aeda8b237fcbf7a56ca0e8c0fff415477d31d22 (patch)
tree13942695ae0e7c749cfcdb12d5824daf7134c008
parentfed0a6e7ac361af9b963c1f62411b1a85478230c (diff)
downloadphp-shirabe-6aeda8b237fcbf7a56ca0e8c0fff415477d31d22.tar.gz
php-shirabe-6aeda8b237fcbf7a56ca0e8c0fff415477d31d22.tar.zst
php-shirabe-6aeda8b237fcbf7a56ca0e8c0fff415477d31d22.zip
refactor(preg): make preg_match_all yield matches per occurrence
PHP's PREG_PATTERN_ORDER is column-oriented, but 7 of the 10 call sites read it row-wise, rebuilding each occurrence by indexing every column at the same offset. Return an iterator of PregMatches instead, which is also what the set-order and offset-capture variants were carrying, so the three functions collapse into one and PregMatchesAll, PregMatchesAllWithOffsets, CaptureKey and preg_match_map! all go away. The offset-capture call sites are served by the new PregMatches get_offset/name_offset accessors. The search stays eager: regex::Captures borrows only the subject, so the matches outlive the pattern resolved for the call, and PHP's preg_match_all is eager too. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
-rw-r--r--crates/shirabe-class-map-generator/src/php_file_parser.rs40
-rw-r--r--crates/shirabe-php-shim/src/preg.rs176
-rw-r--r--crates/shirabe-symfony-console/src/completion/completion_input.rs8
-rw-r--r--crates/shirabe-symfony-console/src/formatter/output_formatter.rs88
-rw-r--r--crates/shirabe/src/command/init_command.rs26
-rw-r--r--crates/shirabe/src/downloader/git_downloader.rs45
-rw-r--r--crates/shirabe/src/package/version/version_bumper.rs32
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)),
);
}