aboutsummaryrefslogtreecommitdiffhomepage
path: root/crates/shirabe/src/util
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
commit844097edf44bf1424d28e2d5fbefda90c1c8f46c (patch)
tree968767db86e26acb022dd6ec5ac2c602d92e3708 /crates/shirabe/src/util
parent5114a8199a87c9e5584d92848e95deba22b73e98 (diff)
downloadphp-shirabe-844097edf44bf1424d28e2d5fbefda90c1c8f46c.tar.gz
php-shirabe-844097edf44bf1424d28e2d5fbefda90c1c8f46c.tar.zst
php-shirabe-844097edf44bf1424d28e2d5fbefda90c1c8f46c.zip
refactor(pcre): hand back the match instead of copying it out
Preg::match4 and Preg::replace_callback gave callers a PregMatchedGroups: an IndexMap rebuilt from the match with an owned String per group, plus a second String for a named group's name key. That is the copy PregMatches shed when it started wrapping regex::Captures, reinstated one layer up -- and nearly every regex call in the tree goes through Preg rather than the shim's preg_* directly, so almost nothing saw the borrow. PregMatchedGroups existed only to drop the null (unmatched) groups the old PregMatches held as Option<String> values. PregMatches::get reports a non-participating group as None on its own, so the two read alike and the type collapses into it. Call sites still reach groups through get(&CaptureKey::ByIndex(N)); what changes is that the value arrives as a &str borrowed from the subject, which the signatures now carry as a lifetime. Three places needed the borrow reckoned with rather than a mechanical rewrite: PhpFileCleaner::clean and Problem::get_messages read their groups out before mutating what the match borrows, and Git::get_authentication_failure names the lifetime of its url argument, which the result borrows instead of self. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Diffstat (limited to 'crates/shirabe/src/util')
-rw-r--r--crates/shirabe/src/util/composer_mirror.rs20
-rw-r--r--crates/shirabe/src/util/filesystem.rs10
-rw-r--r--crates/shirabe/src/util/forgejo_url.rs2
-rw-r--r--crates/shirabe/src/util/git.rs85
-rw-r--r--crates/shirabe/src/util/github.rs4
-rw-r--r--crates/shirabe/src/util/http/response.rs2
-rw-r--r--crates/shirabe/src/util/http_downloader.rs4
-rw-r--r--crates/shirabe/src/util/platform.rs6
-rw-r--r--crates/shirabe/src/util/process_executor.rs18
-rw-r--r--crates/shirabe/src/util/svn.rs4
-rw-r--r--crates/shirabe/src/util/url.rs36
11 files changed, 110 insertions, 81 deletions
diff --git a/crates/shirabe/src/util/composer_mirror.rs b/crates/shirabe/src/util/composer_mirror.rs
index 7344bfaf..5d9e8c31 100644
--- a/crates/shirabe/src/util/composer_mirror.rs
+++ b/crates/shirabe/src/util/composer_mirror.rs
@@ -61,14 +61,8 @@ impl ComposerMirror {
) {
format!(
"gh-{}/{}",
- gh_matches
- .get(&CaptureKey::ByIndex(1))
- .cloned()
- .unwrap_or_default(),
- gh_matches
- .get(&CaptureKey::ByIndex(2))
- .cloned()
- .unwrap_or_default(),
+ gh_matches.get(&CaptureKey::ByIndex(1)).unwrap_or_default(),
+ gh_matches.get(&CaptureKey::ByIndex(2)).unwrap_or_default(),
)
} else if let Some(bb_matches) = Preg::match3(
php_regex!(r"#^https://bitbucket\.org/([^/]+)/(.+?)(?:\.git)?/?$#"),
@@ -76,14 +70,8 @@ impl ComposerMirror {
) {
format!(
"bb-{}/{}",
- bb_matches
- .get(&CaptureKey::ByIndex(1))
- .cloned()
- .unwrap_or_default(),
- bb_matches
- .get(&CaptureKey::ByIndex(2))
- .cloned()
- .unwrap_or_default(),
+ bb_matches.get(&CaptureKey::ByIndex(1)).unwrap_or_default(),
+ bb_matches.get(&CaptureKey::ByIndex(2)).unwrap_or_default(),
)
} else {
Preg::replace(php_regex!(r"{[^a-z0-9_.-]}i"), "-", url.trim_matches('/'))
diff --git a/crates/shirabe/src/util/filesystem.rs b/crates/shirabe/src/util/filesystem.rs
index d59df55e..6302a6d2 100644
--- a/crates/shirabe/src/util/filesystem.rs
+++ b/crates/shirabe/src/util/filesystem.rs
@@ -741,8 +741,8 @@ impl Filesystem {
) {
prefix = prefix_match
.get(&shirabe_pcre::CaptureKey::ByIndex(1))
- .cloned()
- .unwrap_or_default();
+ .unwrap_or_default()
+ .to_string();
path = substr(&path, strlen(&prefix), None);
}
@@ -765,11 +765,11 @@ impl Filesystem {
// ensure c: is normalized to C:
prefix = Preg::replace_callback(
php_regex!("{(^|://)[a-z]:$}i"),
- |m: &shirabe_pcre::PregMatchedGroups| -> String {
+ |m: &shirabe_pcre::PregMatches| -> String {
let s = m
.get(&shirabe_pcre::CaptureKey::ByIndex(0))
- .cloned()
- .unwrap_or_default();
+ .unwrap_or_default()
+ .to_string();
strtoupper(&s)
},
&prefix,
diff --git a/crates/shirabe/src/util/forgejo_url.rs b/crates/shirabe/src/util/forgejo_url.rs
index e5304f5d..7f9333e0 100644
--- a/crates/shirabe/src/util/forgejo_url.rs
+++ b/crates/shirabe/src/util/forgejo_url.rs
@@ -43,8 +43,8 @@ impl ForgejoUrl {
.map(|i| {
matches
.get(&CaptureKey::ByIndex(i))
- .cloned()
.unwrap_or_default()
+ .to_string()
})
.collect();
diff --git a/crates/shirabe/src/util/git.rs b/crates/shirabe/src/util/git.rs
index 2b012d4c..e7239987 100644
--- a/crates/shirabe/src/util/git.rs
+++ b/crates/shirabe/src/util/git.rs
@@ -14,7 +14,7 @@ use crate::util::ProcessExecutor;
use crate::util::Url;
use crate::util::{AuthHelper, StoreAuth};
use indexmap::IndexMap;
-use shirabe_pcre::{CaptureKey, Preg, PregMatchedGroups};
+use shirabe_pcre::{CaptureKey, Preg, PregMatches};
use shirabe_php_shim::{
AnyThrowable, CmpOp, InvalidArgumentException, PHP_EOL, PhpMixed, RuntimeException, array_map,
clearstatcache, explode, implode, in_array_loose, in_array_strict, is_dir, php_regex,
@@ -230,13 +230,16 @@ impl Git {
php_regex!(r"{^(?:composer|origin)\s+https?://(.+):(.+)@([^/]+)}im"),
&output,
) {
- let m3 = m.get(&CaptureKey::ByIndex(3)).cloned().unwrap_or_default();
+ let m3 = m
+ .get(&CaptureKey::ByIndex(3))
+ .unwrap_or_default()
+ .to_string();
if !self.io.has_authentication(&m3) {
self.io.borrow_mut().set_authentication(
m3,
- rawurldecode(&m.get(&CaptureKey::ByIndex(1)).cloned().unwrap_or_default()),
+ rawurldecode(m.get(&CaptureKey::ByIndex(1)).unwrap_or_default()),
Some(rawurldecode(
- &m.get(&CaptureKey::ByIndex(2)).cloned().unwrap_or_default(),
+ m.get(&CaptureKey::ByIndex(2)).unwrap_or_default(),
)),
);
}
@@ -262,8 +265,14 @@ impl Git {
_ => vec![],
};
for protocol in &protocols_list {
- let m1 = m.get(&CaptureKey::ByIndex(1)).cloned().unwrap_or_default();
- let m2 = m.get(&CaptureKey::ByIndex(2)).cloned().unwrap_or_default();
+ let m1 = m
+ .get(&CaptureKey::ByIndex(1))
+ .unwrap_or_default()
+ .to_string();
+ let m2 = m
+ .get(&CaptureKey::ByIndex(2))
+ .unwrap_or_default()
+ .to_string();
let proto_url = if protocol == "ssh" {
format!("git@{}:{}", m1, m2)
} else {
@@ -291,7 +300,10 @@ impl Git {
}
// failed to checkout, first check git accessibility
- let m1 = m.get(&CaptureKey::ByIndex(1)).cloned().unwrap_or_default();
+ let m1 = m
+ .get(&CaptureKey::ByIndex(1))
+ .unwrap_or_default()
+ .to_string();
if !self.io.has_authentication(&m1) && !self.io.is_interactive() {
self.throw_exception(
&format!(
@@ -357,8 +369,14 @@ impl Git {
)
});
if let Some(m) = github_matched {
- let m1 = m.get(&CaptureKey::ByIndex(1)).cloned().unwrap_or_default();
- let m2 = m.get(&CaptureKey::ByIndex(2)).cloned().unwrap_or_default();
+ let m1 = m
+ .get(&CaptureKey::ByIndex(1))
+ .unwrap_or_default()
+ .to_string();
+ let m2 = m
+ .get(&CaptureKey::ByIndex(2))
+ .unwrap_or_default()
+ .to_string();
if !self.io.has_authentication(&m1) {
let mut git_hub_util = GitHub::new(
self.io.clone(),
@@ -421,9 +439,14 @@ impl Git {
None,
)?;
- let domain = m.get(&CaptureKey::ByIndex(2)).cloned().unwrap_or_default();
- let mut repo_with_git_part =
- m.get(&CaptureKey::ByIndex(3)).cloned().unwrap_or_default();
+ let domain = m
+ .get(&CaptureKey::ByIndex(2))
+ .unwrap_or_default()
+ .to_string();
+ let mut repo_with_git_part = m
+ .get(&CaptureKey::ByIndex(3))
+ .unwrap_or_default()
+ .to_string();
if !repo_with_git_part.ends_with(".git") {
repo_with_git_part.push_str(".git");
}
@@ -565,9 +588,18 @@ impl Git {
url,
)
}) {
- let mut m1 = m.get(&CaptureKey::ByIndex(1)).cloned().unwrap_or_default();
- let m2 = m.get(&CaptureKey::ByIndex(2)).cloned().unwrap_or_default();
- let m3 = m.get(&CaptureKey::ByIndex(3)).cloned().unwrap_or_default();
+ let mut m1 = m
+ .get(&CaptureKey::ByIndex(1))
+ .unwrap_or_default()
+ .to_string();
+ let m2 = m
+ .get(&CaptureKey::ByIndex(2))
+ .unwrap_or_default()
+ .to_string();
+ let m3 = m
+ .get(&CaptureKey::ByIndex(3))
+ .unwrap_or_default()
+ .to_string();
if m1 == "git" {
m1 = "https".to_string();
}
@@ -641,9 +673,18 @@ impl Git {
}
} else if let Some(m) = self.get_authentication_failure(url) {
// private non-github/gitlab/bitbucket repo that failed to authenticate
- let m1 = m.get(&CaptureKey::ByIndex(1)).cloned().unwrap_or_default();
- let mut m2 = m.get(&CaptureKey::ByIndex(2)).cloned().unwrap_or_default();
- let m3 = m.get(&CaptureKey::ByIndex(3)).cloned().unwrap_or_default();
+ let m1 = m
+ .get(&CaptureKey::ByIndex(1))
+ .unwrap_or_default()
+ .to_string();
+ let mut m2 = m
+ .get(&CaptureKey::ByIndex(2))
+ .unwrap_or_default()
+ .to_string();
+ let m3 = m
+ .get(&CaptureKey::ByIndex(3))
+ .unwrap_or_default()
+ .to_string();
let mut auth_parts: Option<String> = None;
if m2.contains("@") {
let parts = explode("@", &m2);
@@ -1083,7 +1124,7 @@ impl Git {
Ok(false)
}
- fn get_authentication_failure(&self, url: &str) -> Option<PregMatchedGroups> {
+ fn get_authentication_failure<'u>(&self, url: &'u str) -> Option<PregMatches<'u>> {
let m = Preg::is_match3(php_regex!(r"{^(https?://)([^/]+)(.*)$}i"), url)?;
let auth_failures = [
@@ -1172,8 +1213,8 @@ impl Git {
return Ok(Some(
matches
.get(&CaptureKey::ByIndex(1))
- .cloned()
- .unwrap_or_default(),
+ .unwrap_or_default()
+ .to_string(),
));
}
}
@@ -1295,7 +1336,7 @@ impl Git {
&& let Some(matches) =
Preg::is_match3(php_regex!(r"/^git version (\d+(?:\.\d+)+)/m"), &output)
{
- *version = Some(matches.get(&CaptureKey::ByIndex(1)).cloned());
+ *version = Some(matches.get(&CaptureKey::ByIndex(1)).map(str::to_string));
}
}
version.clone().unwrap_or(None)
diff --git a/crates/shirabe/src/util/github.rs b/crates/shirabe/src/util/github.rs
index 9c22a2b3..28a13738 100644
--- a/crates/shirabe/src/util/github.rs
+++ b/crates/shirabe/src/util/github.rs
@@ -326,7 +326,9 @@ impl GitHub {
continue;
}
if let Some(caps) = Preg::match3(php_regex!(r"{\burl=(?P<url>[^\s;]+)}"), header) {
- return caps.get(&CaptureKey::ByName("url".to_string())).cloned();
+ return caps
+ .get(&CaptureKey::ByName("url".to_string()))
+ .map(str::to_string);
}
}
diff --git a/crates/shirabe/src/util/http/response.rs b/crates/shirabe/src/util/http/response.rs
index ea961768..7c5f5248 100644
--- a/crates/shirabe/src/util/http/response.rs
+++ b/crates/shirabe/src/util/http/response.rs
@@ -68,7 +68,7 @@ impl Response {
if let Some(matches) = Preg::match3(&pattern, header)
&& let Some(s) = matches.get(&shirabe_pcre::CaptureKey::ByIndex(1))
{
- value = Some(s.clone());
+ value = Some(s.to_string());
}
}
value
diff --git a/crates/shirabe/src/util/http_downloader.rs b/crates/shirabe/src/util/http_downloader.rs
index b06d7004..c6e70117 100644
--- a/crates/shirabe/src/util/http_downloader.rs
+++ b/crates/shirabe/src/util/http_downloader.rs
@@ -246,14 +246,14 @@ impl HttpDownloader {
origin.clone(),
rawurldecode(
m.get(&CaptureKey::ByIndex(1))
- .cloned()
.unwrap_or_default()
+ .to_string()
.as_str(),
),
Some(rawurldecode(
m.get(&CaptureKey::ByIndex(2))
- .cloned()
.unwrap_or_default()
+ .to_string()
.as_str(),
)),
);
diff --git a/crates/shirabe/src/util/platform.rs b/crates/shirabe/src/util/platform.rs
index 629854b6..f2b8aea3 100644
--- a/crates/shirabe/src/util/platform.rs
+++ b/crates/shirabe/src/util/platform.rs
@@ -2,7 +2,7 @@
use crate::util::ProcessExecutor;
use crate::util::Silencer;
-use shirabe_pcre::{Preg, PregMatchedGroups};
+use shirabe_pcre::{Preg, PregMatches};
use shirabe_php_shim::{
PHP_ENV, PHP_SERVER, PhpMixed, PhpResource, RuntimeException, defined, file_exists,
file_get_contents, fstat, function_exists, getcwd, getenv, ini_get, is_readable, mb_strlen,
@@ -98,15 +98,13 @@ impl Platform {
// two forms are written as an explicit alternation: `$VAR` or `%VAR%`.
Preg::replace_callback(
php_regex!(r"#^(?:\$(?P<dvar>\w+)|%(?P<pvar>\w+)%)(?P<path>.*)#"),
- |matches: &PregMatchedGroups| -> String {
+ |matches: &PregMatches| -> String {
let var = matches
.get(&CaptureKey::ByName("dvar".to_string()))
.or_else(|| matches.get(&CaptureKey::ByName("pvar".to_string())))
- .map(|s| s.as_str())
.unwrap_or("");
let path_part = matches
.get(&CaptureKey::ByName("path".to_string()))
- .map(|s| s.as_str())
.unwrap_or("");
// Treat HOME as an alias for USERPROFILE on Windows for legacy reasons
if Platform::is_windows() && var == "HOME" {
diff --git a/crates/shirabe/src/util/process_executor.rs b/crates/shirabe/src/util/process_executor.rs
index 48fa415f..2954ed7a 100644
--- a/crates/shirabe/src/util/process_executor.rs
+++ b/crates/shirabe/src/util/process_executor.rs
@@ -7,7 +7,7 @@ use crate::signal::SignalSubscription;
use crate::util::GitHub;
use crate::util::Platform;
use indexmap::IndexMap;
-use shirabe_pcre::{CaptureKey, Preg, PregMatchedGroups};
+use shirabe_pcre::{CaptureKey, Preg, PregMatches};
use shirabe_php_shim::Catch as _;
use shirabe_php_shim::{
LogicException, PHP_EOL, PhpMixed, RuntimeException, array_intersect, array_map,
@@ -219,7 +219,10 @@ impl ProcessExecutor {
if Platform::is_windows()
&& let Some(m) = Preg::is_match3(php_regex!(r"{^([^:/\\]++) }"), &command_str)
{
- let m1 = m.get(&CaptureKey::ByIndex(1)).cloned().unwrap_or_default();
+ let m1 = m
+ .get(&CaptureKey::ByIndex(1))
+ .unwrap_or_default()
+ .to_string();
command_str = substr_replace(
&command_str,
&Self::escape(&Self::get_executable(&m1)),
@@ -831,23 +834,20 @@ impl ProcessExecutor {
};
let safe_command = Preg::replace_callback(
php_regex!(r"{://(?P<user>[^:/\s]+):(?P<password>[^@\s/]+)@}i"),
- |m: &PregMatchedGroups| -> String {
+ |m: &PregMatches| -> String {
let user_key = CaptureKey::ByName("user".to_string());
// if the username looks like a long (12char+) hex string, or a modern github token (e.g. ghp_xxx, github_pat_xxx) we obfuscate that
if Preg::is_match(
GitHub::GITHUB_TOKEN_REGEX,
- m.get(&user_key).cloned().unwrap_or_default().as_str(),
+ m.get(&user_key).unwrap_or_default(),
) {
return "://***:***@".to_string();
}
- if Preg::is_match(
- r"{^[a-f0-9]{12,}$}",
- m.get(&user_key).cloned().unwrap_or_default().as_str(),
- ) {
+ if Preg::is_match(r"{^[a-f0-9]{12,}$}", m.get(&user_key).unwrap_or_default()) {
return "://***:***@".to_string();
}
- format!("://{}:***@", m.get(&user_key).cloned().unwrap_or_default())
+ format!("://{}:***@", m.get(&user_key).unwrap_or_default())
},
&command_string,
);
diff --git a/crates/shirabe/src/util/svn.rs b/crates/shirabe/src/util/svn.rs
index 8018efa5..72ca2321 100644
--- a/crates/shirabe/src/util/svn.rs
+++ b/crates/shirabe/src/util/svn.rs
@@ -410,8 +410,8 @@ impl Svn {
*cached = Some(
matches
.get(&CaptureKey::ByIndex(1))
- .cloned()
- .unwrap_or_default(),
+ .unwrap_or_default()
+ .to_string(),
);
}
}
diff --git a/crates/shirabe/src/util/url.rs b/crates/shirabe/src/util/url.rs
index 0a3722eb..8b751b2a 100644
--- a/crates/shirabe/src/util/url.rs
+++ b/crates/shirabe/src/util/url.rs
@@ -22,9 +22,9 @@ impl Url {
) {
url = format!(
"https://api.github.com/repos/{}/{}/{}ball/{}",
- m.get(&CaptureKey::ByIndex(1)).cloned().unwrap_or_default(),
- m.get(&CaptureKey::ByIndex(2)).cloned().unwrap_or_default(),
- m.get(&CaptureKey::ByIndex(3)).cloned().unwrap_or_default(),
+ m.get(&CaptureKey::ByIndex(1)).unwrap_or_default(),
+ m.get(&CaptureKey::ByIndex(2)).unwrap_or_default(),
+ m.get(&CaptureKey::ByIndex(3)).unwrap_or_default(),
r#ref
);
} else if let Some(m) = Preg::match3(
@@ -35,9 +35,9 @@ impl Url {
) {
url = format!(
"https://api.github.com/repos/{}/{}/{}ball/{}",
- m.get(&CaptureKey::ByIndex(1)).cloned().unwrap_or_default(),
- m.get(&CaptureKey::ByIndex(2)).cloned().unwrap_or_default(),
- m.get(&CaptureKey::ByIndex(3)).cloned().unwrap_or_default(),
+ m.get(&CaptureKey::ByIndex(1)).unwrap_or_default(),
+ m.get(&CaptureKey::ByIndex(2)).unwrap_or_default(),
+ m.get(&CaptureKey::ByIndex(3)).unwrap_or_default(),
r#ref
);
} else if let Some(m) = Preg::match3(
@@ -48,9 +48,9 @@ impl Url {
) {
url = format!(
"https://api.github.com/repos/{}/{}/{}ball/{}",
- m.get(&CaptureKey::ByIndex(1)).cloned().unwrap_or_default(),
- m.get(&CaptureKey::ByIndex(2)).cloned().unwrap_or_default(),
- m.get(&CaptureKey::ByIndex(3)).cloned().unwrap_or_default(),
+ m.get(&CaptureKey::ByIndex(1)).unwrap_or_default(),
+ m.get(&CaptureKey::ByIndex(2)).unwrap_or_default(),
+ m.get(&CaptureKey::ByIndex(3)).unwrap_or_default(),
r#ref
);
}
@@ -63,10 +63,10 @@ impl Url {
) {
url = format!(
"https://bitbucket.org/{}/{}/get/{}.{}",
- m.get(&CaptureKey::ByIndex(1)).cloned().unwrap_or_default(),
- m.get(&CaptureKey::ByIndex(2)).cloned().unwrap_or_default(),
+ m.get(&CaptureKey::ByIndex(1)).unwrap_or_default(),
+ m.get(&CaptureKey::ByIndex(2)).unwrap_or_default(),
r#ref,
- m.get(&CaptureKey::ByIndex(4)).cloned().unwrap_or_default()
+ m.get(&CaptureKey::ByIndex(4)).unwrap_or_default()
);
}
} else if host == "gitlab.com" || host == "www.gitlab.com" {
@@ -78,8 +78,8 @@ impl Url {
) {
url = format!(
"https://gitlab.com/api/v4/projects/{}/repository/archive.{}?sha={}",
- m.get(&CaptureKey::ByIndex(1)).cloned().unwrap_or_default(),
- m.get(&CaptureKey::ByIndex(2)).cloned().unwrap_or_default(),
+ m.get(&CaptureKey::ByIndex(1)).unwrap_or_default(),
+ m.get(&CaptureKey::ByIndex(2)).unwrap_or_default(),
r#ref
);
}
@@ -165,12 +165,12 @@ impl Url {
|m| {
let user = m
.get(&CaptureKey::ByName("user".to_string()))
- .cloned()
- .unwrap_or_default();
+ .unwrap_or_default()
+ .to_string();
let prefix = m
.get(&CaptureKey::ByName("prefix".to_string()))
- .cloned()
- .unwrap_or_default();
+ .unwrap_or_default()
+ .to_string();
// if the username looks like a long (12char+) hex string, or a modern github token (e.g. ghp_xxx, github_pat_xxx) we obfuscate that
if Preg::is_match(GitHub::GITHUB_TOKEN_REGEX, &user) {
format!("{}***:***@", prefix)