From 5633b077c269188739de2e8e4d3fcb9052f3f2bb Mon Sep 17 00:00:00 2001 From: Rudxain <76864299+Rudxain@users.noreply.github.com> Date: Tue, 8 Jul 2025 00:21:28 -0400 Subject: [PATCH] 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` --- src/core/adb.rs | 80 +++++++++++++++++++++++++++++------------------ src/core/utils.rs | 22 +++++++++++++ 2 files changed, 72 insertions(+), 30 deletions(-) diff --git a/src/core/adb.rs b/src/core/adb.rs index 9383d35..785598f 100644 --- a/src/core/adb.rs +++ b/src/core/adb.rs @@ -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) -> 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 { - #[cfg(debug_assertions)] - static TRIPLE: LazyLock = LazyLock::new(|| { - Regex::new(r"^Android Debug Bridge version \d+.\d+.\d+$") - .unwrap_or_else(|_| unreachable!()) - }); - #[cfg(debug_assertions)] - static DISTRO: LazyLock = 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 = LazyLock::new(|| { + Regex::new(r"^Android Debug Bridge version \d+.\d+.\d+$") + .unwrap_or_else(|_| unreachable!()) }); + static DISTRO: LazyLock = 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) -> Option { - static RE: LazyLock = 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() }) diff --git a/src/core/utils.rs b/src/core/utils.rs index 9108c48..560b68a 100644 --- a/src/core/utils.rs +++ b/src/core/utils.rs @@ -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. //