diff --git a/ua-parser/src/lib.rs b/ua-parser/src/lib.rs index b28a880..6a51b69 100644 --- a/ua-parser/src/lib.rs +++ b/ua-parser/src/lib.rs @@ -2,8 +2,6 @@ #![warn(missing_docs)] #![allow(clippy::empty_docs)] #![doc = include_str!("../README.md")] - -use regex::Captures; use serde::Deserialize; pub use regex_filtered::{BuildError, ParseError}; @@ -123,6 +121,7 @@ impl<'a> Extractor<'a> { pub mod user_agent { use serde::Deserialize; use std::borrow::Cow; + use std::cell::LazyCell; use crate::resolvers::{FallbackResolver, FamilyResolver}; use regex_filtered::BuildError; @@ -234,16 +233,16 @@ pub mod user_agent { /// but there is no group in the regex pub fn extract(&'a self, ua: &'a str) -> Option> { let (idx, re) = self.matcher.matching(ua).next()?; - let c = re.captures(ua)?; + let c = LazyCell::new(move || re.captures(ua)); let (f, v1, v2, v3, v4) = &self.repl[idx]; Some(ValueRef { - family: f.resolve(&c), - major: v1.resolve(&c), - minor: v2.resolve(&c), - patch: v3.resolve(&c), - patch_minor: v4.resolve(&c), + family: f.resolve(&c)?, + major: v1.resolve(&c)?, + minor: v2.resolve(&c)?, + patch: v3.resolve(&c)?, + patch_minor: v4.resolve(&c)?, }) } } @@ -300,6 +299,7 @@ pub mod user_agent { pub mod os { use serde::Deserialize; use std::borrow::Cow; + use std::cell::LazyCell; use regex_filtered::{BuildError, ParseError}; @@ -396,16 +396,16 @@ pub mod os { /// returns `None` if the UA string could not be matched. pub fn extract(&'a self, ua: &'a str) -> Option> { let (idx, re) = self.matcher.matching(ua).next()?; - let c = re.captures(ua)?; + let c = LazyCell::new(move || re.captures(ua)); let (o, v1, v2, v3, v4) = &self.repl[idx]; Some(ValueRef { - os: o.resolve(&c), - major: v1.resolve(&c), - minor: v2.resolve(&c), - patch: v3.resolve(&c), - patch_minor: v4.resolve(&c), + os: o.resolve(&c)?, + major: v1.resolve(&c)?, + minor: v2.resolve(&c)?, + patch: v3.resolve(&c)?, + patch_minor: v4.resolve(&c)?, }) } } @@ -460,6 +460,7 @@ pub mod os { pub mod device { use serde::Deserialize; use std::borrow::Cow; + use std::cell::LazyCell; use regex_filtered::{BuildError, ParseError}; @@ -558,14 +559,14 @@ pub mod device { /// the input. pub fn extract(&'a self, ua: &'a str) -> Option> { let (idx, re) = self.matcher.matching(ua).next()?; - let c = re.captures(ua)?; + let c = LazyCell::new(move || re.captures(ua)); let (d, v1, v2) = &self.repl[idx]; Some(ValueRef { - device: d.resolve(&c), - brand: v1.resolve(&c), - model: v2.resolve(&c), + device: d.resolve(&c)?, + brand: v1.resolve(&c)?, + model: v2.resolve(&c)?, }) } } diff --git a/ua-parser/src/resolvers.rs b/ua-parser/src/resolvers.rs index 9f52161..80c7d73 100644 --- a/ua-parser/src/resolvers.rs +++ b/ua-parser/src/resolvers.rs @@ -7,9 +7,17 @@ use crate::Error; use regex::Captures; use std::borrow::Cow; +use std::cell::LazyCell; -fn get<'s>(c: &Captures<'s>, group: usize) -> Option<&'s str> { - c.get(group).map(|g| g.as_str()).filter(|s| !s.is_empty()) +type C<'s, F> = LazyCell>, F>; + +fn get<'s, F>(c: &C<'s, F>, group: usize) -> Option> +where + F: FnOnce() -> Option>, +{ + LazyCell::force(c) + .as_ref() + .map(|c| c.get(group).map(|g| g.as_str()).filter(|s| !s.is_empty())) } // TODO: @@ -50,13 +58,16 @@ impl<'a> Resolver<'a> { } } - pub(crate) fn resolve(&'a self, c: &Captures<'a>) -> Cow<'a, str> { - match self { + pub(crate) fn resolve(&'a self, c: &C<'a, F>) -> Option> + where + F: FnOnce() -> Option>, + { + Some(match self { Self::Replacement(s) => (**s).into(), - Self::Capture(i) => get(c, *i).unwrap_or("").into(), + Self::Capture(i) => get(c, *i)?.unwrap_or("").into(), Self::Template(t) => { let mut r = String::new(); - c.expand(t, &mut r); + LazyCell::force(c).as_ref()?.expand(t, &mut r); let trimmed = r.trim(); if r.len() == trimmed.len() { r.into() @@ -64,7 +75,7 @@ impl<'a> Resolver<'a> { trimmed.to_string().into() } } - } + }) } } @@ -90,14 +101,17 @@ impl<'a> OptResolver<'a> { } } - pub(crate) fn resolve(&'a self, c: &Captures<'a>) -> Option> { - match self { + pub(crate) fn resolve(&'a self, c: &C<'a, F>) -> Option>> + where + F: FnOnce() -> Option>, + { + Some(match self { Self::None => None, Self::Replacement(s) => Some((**s).into()), - Self::Capture(i) => get(c, *i).map(From::from), + Self::Capture(i) => get(c, *i)?.map(From::from), Self::Template(t) => { let mut r = String::new(); - c.expand(t, &mut r); + LazyCell::force(c).as_ref()?.expand(t, &mut r); let trimmed = r.trim(); if trimmed.is_empty() { None @@ -107,7 +121,7 @@ impl<'a> OptResolver<'a> { Some(trimmed.to_string().into()) } } - } + }) } } @@ -135,12 +149,15 @@ impl<'a> FamilyResolver<'a> { } } - pub(crate) fn resolve(&'a self, c: &super::Captures<'a>) -> Cow<'a, str> { - match self { - FamilyResolver::Capture => get(c, 1).unwrap_or("").into(), + pub(crate) fn resolve(&'a self, c: &C<'a, F>) -> Option> + where + F: FnOnce() -> Option>, + { + Some(match self { + FamilyResolver::Capture => get(c, 1)?.unwrap_or("").into(), FamilyResolver::Replacement(s) => (**s).into(), - FamilyResolver::Template(t) => t.replace("$1", get(c, 1).unwrap_or("")).into(), - } + FamilyResolver::Template(t) => t.replace("$1", get(c, 1)?.unwrap_or("")).into(), + }) } } @@ -161,11 +178,14 @@ impl<'a> FallbackResolver<'a> { Self::None } } - pub(crate) fn resolve(&'a self, c: &super::Captures<'a>) -> Option<&'a str> { - match self { + pub(crate) fn resolve(&'a self, c: &C<'a, F>) -> Option> + where + F: FnOnce() -> Option>, + { + Some(match self { FallbackResolver::None => None, - FallbackResolver::Capture(n) => get(c, *n), + FallbackResolver::Capture(n) => get(c, *n)?, FallbackResolver::Replacement(r) => Some(r), - } + }) } }