refactor(adb): rm regex dependency from PackageId::new by adding some utils.

and:
- refactor `adb` `version` assertions to call `next` explicitly,
rather than matching on iteration count.
- re-add debug-assertions for `pm list packages`
This commit is contained in:
Rudxain 2025-07-08 00:21:28 -04:00
commit 5633b077c2
No known key found for this signature in database
GPG key ID: 0DAC837DDEF8E96C
2 changed files with 72 additions and 30 deletions

View file

@ -38,13 +38,14 @@
//! For comprehensive info about ADB,
//! [see this](https://android.googlesource.com/platform/packages/modules/adb/+/refs/heads/master/docs/)
use regex::Regex;
use serde::{Deserialize, Serialize};
use std::sync::LazyLock;
#[cfg(target_os = "windows")]
use std::os::windows::process::CommandExt;
use crate::core::utils::is_all_w_c;
pub fn to_trimmed_utf8(v: Vec<u8>) -> String {
String::from_utf8(v)
.expect("ADB should always output valid ASCII (or UTF-8, at least)")
@ -133,33 +134,32 @@ impl ACommand {
/// ```
#[expect(clippy::panic_in_result_fn, reason = "Assertions are fine")]
pub fn version(mut self) -> Result<String, String> {
#[cfg(debug_assertions)]
static TRIPLE: LazyLock<Regex> = LazyLock::new(|| {
Regex::new(r"^Android Debug Bridge version \d+.\d+.\d+$")
.unwrap_or_else(|_| unreachable!())
});
#[cfg(debug_assertions)]
static DISTRO: LazyLock<Regex> = LazyLock::new(|| {
Regex::new(r"^Version \d+.\d+.\d+-\S+$").unwrap_or_else(|_| unreachable!())
});
self.0.arg("version");
let out = self.run()?;
#[cfg(debug_assertions)]
for (i, ln) in out.lines().enumerate() {
assert!(match i {
0 => TRIPLE.is_match(ln),
1 => DISTRO.is_match(ln),
2 =>
// missing test for valid path
ln.starts_with("Installed as ")
&& (ln.ends_with("adb") || ln.ends_with("adb.exe")),
// missing test for x86/ARM (both 64b)
3 => ln.starts_with("Running on "),
_ => unreachable!("Expected < 5 lines"),
{
use regex::Regex;
static TRIPLE: LazyLock<Regex> = LazyLock::new(|| {
Regex::new(r"^Android Debug Bridge version \d+.\d+.\d+$")
.unwrap_or_else(|_| unreachable!())
});
static DISTRO: LazyLock<Regex> = LazyLock::new(|| {
Regex::new(r"^Version \d+.\d+.\d+-\S+$").unwrap_or_else(|_| unreachable!())
});
let mut lns = out.lines();
assert!(lns.next().is_some_and(|ln| TRIPLE.is_match(ln)));
assert!(lns.next().is_some_and(|ln| DISTRO.is_match(ln)));
// missing test for valid path
assert!(lns.next().is_some_and(|ln| ln.starts_with("Installed as ")
&& (ln.ends_with("adb") || ln.ends_with("adb.exe"))));
// missing test for x86/ARM (both 64b)
assert!(lns.next().is_some_and(|ln| ln.starts_with("Running on ")));
if lns.next().is_some() {
unreachable!("Expected < 5 lines")
}
}
Ok(out)
@ -230,6 +230,19 @@ impl ShellCommand {
}
}
#[must_use]
pub const fn is_pkg_component(s: &[u8]) -> bool {
if s.is_empty() {
return false;
}
s[0].is_ascii_alphabetic()
&& if s.len() > 1 {
is_all_w_c(s.split_at(1).1)
} else {
true
}
}
/// String with the invariant of being a valid package-name.
/// See its `new` constructor for more info.
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq, Hash)]
@ -238,12 +251,16 @@ impl PackageId {
/// Creates a package-ID if it's valid according to
/// [this](https://developer.android.com/build/configure-app-module#set-application-id)
pub fn new(p_id: Box<str>) -> Option<Self> {
static RE: LazyLock<Regex> = LazyLock::new(|| {
Regex::new(r"^[a-zA-Z][a-zA-Z0-9_]*(?:\.[a-zA-Z][a-zA-Z0-9_]*)+$")
.unwrap_or_else(|_| unreachable!())
});
if RE.is_match(p_id.as_ref()) {
let mut components = p_id.split('.');
for _ in 0..2 {
if !components
.next()
.is_some_and(|comp| is_pkg_component(comp.as_bytes()))
{
return None;
}
}
if components.all(|comp| is_pkg_component(comp.as_bytes())) {
Some(Self(p_id))
} else {
None
@ -316,7 +333,10 @@ impl PmCommand {
.lines()
.map(|p_ln| {
debug_assert!(p_ln.starts_with(PACK_PREFIX));
String::from(&p_ln[PACK_PREFIX.len()..])
let p = &p_ln[PACK_PREFIX.len()..];
#[cfg(debug_assertions)]
assert!(PackageId::new(p.into()).is_some() || p == "android");
String::from(p)
})
.collect()
})

View file

@ -19,6 +19,28 @@ use std::{
pub const NAME: &str = "UAD-ng";
pub const EXPORT_FILE_NAME: &str = "selection_export.txt";
/// Returns `true` if `c` matches the regex `\w`
#[inline]
#[must_use]
pub const fn is_w(c: u8) -> bool {
// https://github.com/rust-lang/rust/issues/93279
// https://github.com/rust-lang/rust/issues/83623
(c == b'_') | c.is_ascii_alphanumeric()
}
/// Returns `true` if `s` matches the regex `^\w+$`
#[must_use]
pub const fn is_all_w_c(s: &[u8]) -> bool {
let mut i = 0;
while i < s.len() {
if !is_w(s[i]) {
return false;
}
i += 1;
}
true
}
// Takes a time-stamp parameter,
// for purity and testability.
//