From bc81e09c265b5b09864a159897f25bd8063f9ee6 Mon Sep 17 00:00:00 2001 From: Peter Hamilton Date: Sat, 8 Jul 2023 20:47:27 -0700 Subject: [PATCH 1/3] Add configurability for unicode and skipping parsers - Unicode support greatly increases memory and latency and is not generally needed. - Compiling regex for unused parsers is just a waste of memory. Both unicode support and device/os/user_agent parsers can now be configured via `UserAgentParserBuilder`. --- Cargo.toml | 4 +- README.md | 10 ++ benches/benchmark.rs | 70 ++++++++- examples/full_parser.rs | 9 ++ examples/no_unicode_parser.rs | 10 ++ examples/os_only_no_unicode_parser.rs | 13 ++ src/client.rs | 5 +- src/lib.rs | 97 ++++++++++--- src/parser/device.rs | 15 +- src/parser/mod.rs | 195 ++++++++++++++++++++++---- src/parser/os.rs | 22 +-- src/parser/user_agent.rs | 20 +-- 12 files changed, 389 insertions(+), 81 deletions(-) create mode 100644 examples/full_parser.rs create mode 100644 examples/no_unicode_parser.rs create mode 100644 examples/os_only_no_unicode_parser.rs diff --git a/Cargo.toml b/Cargo.toml index 953fe31..3ed90cd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,7 +15,7 @@ keywords = ["user", "agent", "parser", "uap", "uaparser"] [dependencies] lazy_static = "1.4.0" -regex = "1.5.5" +regex = "1.9.1" serde = "1.0.137" serde_yaml = "0.8.24" serde_derive = "1.0.137" @@ -26,4 +26,4 @@ criterion = "0.3.5" [[bench]] name = "benchmark" -harness = false +harness = false \ No newline at end of file diff --git a/README.md b/README.md index c4930ff..7200836 100644 --- a/README.md +++ b/README.md @@ -13,3 +13,13 @@ To get to the docs, clone the repo and run `cargo doc --open` to build the docum - `git submodule update --init` to get started - `cargo test` - `cargo test -- --nocapture` for the full results + +## Performance and Benchmarking +`cargo bench` will run a criterion benchmark suite. + +To see memory usage of the compiled regex list you can run the examples with a tool that tracks memory usage. + +Example (on MacOS): +``` +/usr/bin/time -l cargo run --examples full_parser +``` \ No newline at end of file diff --git a/benches/benchmark.rs b/benches/benchmark.rs index 6bd7ec8..a02b3b6 100644 --- a/benches/benchmark.rs +++ b/benches/benchmark.rs @@ -2,7 +2,7 @@ use std::{fs::File, time::Duration}; use criterion::{black_box, criterion_group, criterion_main, Criterion}; use serde_derive::Deserialize; -use uaparser::{Parser, UserAgentParser}; +use uaparser::{Parser, UserAgentParserBuilder}; #[derive(Deserialize, Debug)] struct TestCase { @@ -15,7 +15,11 @@ struct TestCases { } fn bench_os(c: &mut Criterion) { - let parser = UserAgentParser::from_yaml("./src/core/regexes.yaml") + let parser = UserAgentParserBuilder::new() + .device(false) + .os(true) + .user_agent(false) + .build_from_yaml("./src/core/regexes.yaml") .expect("Parser creation failed"); let file = File::open("./src/core/tests/test_os.yaml").unwrap(); @@ -28,10 +32,30 @@ fn bench_os(c: &mut Criterion) { } }) }); + + let parser = UserAgentParserBuilder::new() + .device(false) + .os(true) + .user_agent(false) + .unicode(false) + .build_from_yaml("./src/core/regexes.yaml") + .expect("Parser creation failed"); + + c.bench_function("parse_os unicode disabled", |b| { + b.iter(|| { + for case in &test_cases.test_cases { + black_box(parser.parse_os(&case.user_agent_string)); + } + }) + }); } fn bench_device(c: &mut Criterion) { - let parser = UserAgentParser::from_yaml("./src/core/regexes.yaml") + let parser = UserAgentParserBuilder::new() + .device(true) + .os(false) + .user_agent(false) + .build_from_yaml("./src/core/regexes.yaml") .expect("Parser creation failed"); let file = File::open("./src/core/tests/test_device.yaml").unwrap(); @@ -44,13 +68,33 @@ fn bench_device(c: &mut Criterion) { } }) }); + + let parser = UserAgentParserBuilder::new() + .device(true) + .os(false) + .user_agent(false) + .unicode(false) + .build_from_yaml("./src/core/regexes.yaml") + .expect("Parser creation failed"); + + c.bench_function("parse_device unicode disabled", |b| { + b.iter(|| { + for case in &test_cases.test_cases { + black_box(parser.parse_device(&case.user_agent_string)); + } + }) + }); } fn bench_ua(c: &mut Criterion) { - let parser = UserAgentParser::from_yaml("./src/core/regexes.yaml") + let parser = UserAgentParserBuilder::new() + .device(false) + .os(false) + .user_agent(true) + .build_from_yaml("./src/core/regexes.yaml") .expect("Parser creation failed"); - let file = std::fs::File::open("./src/core/tests/test_ua.yaml").unwrap(); + let file = File::open("./src/core/tests/test_ua.yaml").unwrap(); let test_cases: TestCases = serde_yaml::from_reader(file).unwrap(); c.bench_function("parse_user_agent", |b| { @@ -60,6 +104,22 @@ fn bench_ua(c: &mut Criterion) { } }) }); + + let parser = UserAgentParserBuilder::new() + .device(false) + .os(false) + .user_agent(true) + .unicode(false) + .build_from_yaml("./src/core/regexes.yaml") + .expect("Parser creation failed"); + + c.bench_function("parse_user_agent unicode disabled", |b| { + b.iter(|| { + for case in &test_cases.test_cases { + black_box(parser.parse_user_agent(&case.user_agent_string)); + } + }) + }); } criterion_group!( diff --git a/examples/full_parser.rs b/examples/full_parser.rs new file mode 100644 index 0000000..4841e93 --- /dev/null +++ b/examples/full_parser.rs @@ -0,0 +1,9 @@ +use uaparser::{Parser, UserAgentParserBuilder}; + +fn main() { + let parser = UserAgentParserBuilder::new() + .build_from_yaml("./src/core/regexes.yaml") + .expect("Parser creation failed"); + + println!("{:?}", parser.parse("Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36")) +} diff --git a/examples/no_unicode_parser.rs b/examples/no_unicode_parser.rs new file mode 100644 index 0000000..0a1f722 --- /dev/null +++ b/examples/no_unicode_parser.rs @@ -0,0 +1,10 @@ +use uaparser::{Parser, UserAgentParserBuilder}; + +fn main() { + let parser = UserAgentParserBuilder::new() + .unicode(false) + .build_from_yaml("./src/core/regexes.yaml") + .expect("Parser creation failed"); + + println!("{:?}", parser.parse("Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36")) +} diff --git a/examples/os_only_no_unicode_parser.rs b/examples/os_only_no_unicode_parser.rs new file mode 100644 index 0000000..340d67f --- /dev/null +++ b/examples/os_only_no_unicode_parser.rs @@ -0,0 +1,13 @@ +use uaparser::{Parser, UserAgentParserBuilder}; + +fn main() { + let parser = UserAgentParserBuilder::new() + .unicode(false) + .device(false) + .os(true) + .user_agent(false) + .build_from_yaml("./src/core/regexes.yaml") + .expect("Parser creation failed"); + + println!("{:?}", parser.parse("Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36")) +} diff --git a/src/client.rs b/src/client.rs index 95a5e78..ffb19d7 100644 --- a/src/client.rs +++ b/src/client.rs @@ -1,4 +1,7 @@ -use super::{Deserialize, Device, Serialize, UserAgent, OS}; +use super::Device; +use super::UserAgent; +use super::OS; +use super::{Deserialize, Serialize}; /// Houses the `Device`, `OS`, and `UserAgent` structs, which each get parsed /// out from a user agent string by a `UserAgentParser`. diff --git a/src/lib.rs b/src/lib.rs index 9c384b1..644a374 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -18,6 +18,23 @@ //! assert_eq!(client.os, os); //! assert_eq!(client.user_agent, user_agent); //! ``` +//! +//! Alternatively you can use the `UserAgentParserBuilder` to create a parser: +//! ```rust +//! # use uaparser::*; +//! let ua_parser = UserAgentParserBuilder::new().build_from_yaml("./src/core/regexes.yaml").expect("Parser creation failed"); +//! let user_agent_string = +//! String::from("Mozilla/5.0 (X11; Linux x86_64; rv:2.0b8pre) Gecko/20101031 Firefox-4.0/4.0b8pre"); +//! let client = ua_parser.parse(&user_agent_string); +//! +//! let device = ua_parser.parse_device(&user_agent_string); +//! let os = ua_parser.parse_os(&user_agent_string); +//! let user_agent = ua_parser.parse_user_agent(&user_agent_string); +//! +//! assert_eq!(client.device, device); +//! assert_eq!(client.os, os); +//! assert_eq!(client.user_agent, user_agent); +//! ``` #![deny(clippy::all)] #![deny(clippy::pedantic)] @@ -29,17 +46,20 @@ use serde_derive::{Deserialize, Serialize}; mod client; mod device; -mod file; +pub use device::Device; + mod os; -mod parser; +pub use os::OS; + mod user_agent; +pub use user_agent::UserAgent; + +mod file; +mod parser; -pub use parser::{Error, UserAgentParser}; +pub use parser::{Error, UserAgentParser, UserAgentParserBuilder}; pub use client::Client; -pub use device::Device; -pub use os::OS; -pub use user_agent::UserAgent; pub trait Parser { fn parse<'a>(&self, user_agent: &'a str) -> Client<'a>; @@ -59,7 +79,24 @@ mod tests { use std::{borrow::Cow, fmt::Debug}; #[test] - fn parse_os() { + fn parse_os_with_unicode() { + let parser = UserAgentParserBuilder::new() + .unicode(true) + .build_from_yaml("./src/core/regexes.yaml") + .expect("Parser creation failed"); + do_parse_os_test_with_parser(&parser) + } + + #[test] + fn parse_os_without_unicode() { + let parser = UserAgentParserBuilder::new() + .unicode(false) + .build_from_yaml("./src/core/regexes.yaml") + .expect("Parser creation failed"); + do_parse_os_test_with_parser(&parser) + } + + fn do_parse_os_test_with_parser(parser: &UserAgentParser) { #[derive(Deserialize, Debug)] struct OSTestCases<'a> { test_cases: Vec>, @@ -75,9 +112,6 @@ mod tests { patch_minor: Option>, } - let parser = UserAgentParser::from_yaml("./src/core/regexes.yaml") - .expect("Parser creation failed"); - let test_os = std::fs::File::open("./src/core/tests/test_os.yaml") .expect("test_device.yaml failed to load"); @@ -132,7 +166,24 @@ mod tests { } #[test] - fn parse_device() { + fn parse_device_with_unicode() { + let parser = UserAgentParserBuilder::new() + .unicode(true) + .build_from_yaml("./src/core/regexes.yaml") + .expect("Parser creation failed"); + do_parse_device_test_with_parser(&parser) + } + + #[test] + fn parse_device_without_unicode() { + let parser = UserAgentParserBuilder::new() + .unicode(false) + .build_from_yaml("./src/core/regexes.yaml") + .expect("Parser creation failed"); + do_parse_device_test_with_parser(&parser) + } + + fn do_parse_device_test_with_parser(parser: &UserAgentParser) { #[derive(Deserialize, Debug)] struct DeviceTestCases<'a> { test_cases: Vec>, @@ -146,9 +197,6 @@ mod tests { model: Option>, } - let parser = UserAgentParser::from_yaml("./src/core/regexes.yaml") - .expect("Parser creation failed"); - let file = std::fs::File::open("./src/core/tests/test_device.yaml") .expect("test_device.yaml failed to load"); @@ -190,7 +238,23 @@ mod tests { } #[test] - fn parse_user_agent() { + fn parse_user_agent_with_unicode() { + let parser = UserAgentParserBuilder::new() + .unicode(true) + .build_from_yaml("./src/core/regexes.yaml") + .expect("Parser creation failed"); + do_parse_user_agent_test_with_parser(&parser) + } + + #[test] + fn parse_user_agent_without_unicode() { + let parser = UserAgentParserBuilder::new() + .unicode(false) + .build_from_yaml("./src/core/regexes.yaml") + .expect("Parser creation failed"); + do_parse_user_agent_test_with_parser(&parser) + } + fn do_parse_user_agent_test_with_parser(parser: &UserAgentParser) { #[derive(Deserialize, Debug)] struct UserAgentTestCases<'a> { test_cases: Vec>, @@ -205,9 +269,6 @@ mod tests { patch: Option>, } - let parser = UserAgentParser::from_yaml("./src/core/regexes.yaml") - .expect("Parser creation failed"); - let test_ua = std::fs::File::open("./src/core/tests/test_ua.yaml") .expect("test_device.yaml failed to load"); diff --git a/src/parser/device.rs b/src/parser/device.rs index ba917a1..d2f570d 100644 --- a/src/parser/device.rs +++ b/src/parser/device.rs @@ -7,7 +7,7 @@ pub enum Error { #[derive(Debug)] pub struct Matcher { - regex: regex::Regex, + regex: regex::bytes::Regex, device_replacement: Option, brand_replacement: Option, model_replacement: Option, @@ -20,11 +20,11 @@ impl<'a> SubParser<'a> for Matcher { type Item = Device<'a>; fn try_parse(&self, text: &'a str) -> Option { - if !self.regex.is_match(text) { + if !self.regex.is_match(text.as_bytes()) { return None; } - if let Some(captures) = self.regex.captures(text) { + if let Some(captures) = self.regex.captures(text.as_bytes()) { let family: Cow<'a, str> = if let Some(device_replacement) = &self.device_replacement { replace_cow( @@ -35,7 +35,7 @@ impl<'a> SubParser<'a> for Matcher { } else { captures .get(1) - .map(|x| x.as_str()) + .and_then(match_to_str) .and_then(none_if_empty) .map(Cow::Borrowed)? }; @@ -56,7 +56,7 @@ impl<'a> SubParser<'a> for Matcher { } else { captures .get(1) - .map(|x| x.as_str()) + .and_then(match_to_str) .and_then(none_if_empty) .map(Cow::Borrowed) }; @@ -73,15 +73,16 @@ impl<'a> SubParser<'a> for Matcher { } impl Matcher { - pub fn try_from(entry: DeviceParserEntry) -> Result { + pub fn try_from(entry: DeviceParserEntry, unicode: bool) -> Result { let regex_with_flags = if entry.regex_flag.as_ref().map_or(true, String::is_empty) { entry.regex } else { format!("(?{}){}", entry.regex_flag.unwrap_or_default(), entry.regex) }; - let regex = regex::RegexBuilder::new(&clean_escapes(®ex_with_flags)) + let regex = regex::bytes::RegexBuilder::new(&clean_escapes(®ex_with_flags)) .size_limit(20 * (1 << 20)) + .unicode(unicode) .build(); Ok(Matcher { diff --git a/src/parser/mod.rs b/src/parser/mod.rs index bbafcb4..4dd0bf6 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -3,22 +3,21 @@ use std::borrow::Cow; use derive_more::{Display, From}; use regex::Regex; +use super::{client::Client, file::RegexFile, Parser, SubParser}; + +mod device; use super::{ - client::Client, - device::Device, - file::{DeviceParserEntry, OSParserEntry, RegexFile, UserAgentParserEntry}, - os::OS, - parser::{ - device::Error as DeviceError, os::Error as OSError, - user_agent::Error as UserAgentError, - }, - user_agent::UserAgent, - Parser, SubParser, + device::Device, file::DeviceParserEntry, parser::device::Error as DeviceError, }; -mod device; mod os; +use super::{file::OSParserEntry, os::OS, parser::os::Error as OSError}; + mod user_agent; +use super::{ + file::UserAgentParserEntry, parser::user_agent::Error as UserAgentError, + user_agent::UserAgent, +}; #[derive(Debug, Display, From)] pub enum Error { @@ -80,6 +79,14 @@ impl UserAgentParser { UserAgentParser::from_file(file) } + fn _build_from_yaml( + path: &str, + builder: UserAgentParserBuilder, + ) -> Result { + let file = std::fs::File::open(path)?; + Self::_build_from_file(file, builder) + } + /// Attempts to construct a `UserAgentParser` from a slice of raw bytes. The /// intention with providing this function is to allow using the /// `include_bytes!` macro to compile the `regexes.yaml` file into the @@ -92,7 +99,21 @@ impl UserAgentParser { /// ``` pub fn from_bytes(bytes: &[u8]) -> Result { let regex_file: RegexFile = serde_yaml::from_slice(bytes)?; - UserAgentParser::try_from(regex_file) + Self::try_from(regex_file) + } + + fn _build_from_bytes( + bytes: &[u8], + builder: UserAgentParserBuilder, + ) -> Result { + let regex_file: RegexFile = serde_yaml::from_slice(bytes)?; + Self::_try_from( + regex_file, + builder.device, + builder.os, + builder.user_agent, + builder.unicode, + ) } /// Attempts to construct a `UserAgentParser` from a reference to an open @@ -100,26 +121,63 @@ impl UserAgentParser { /// all the various implementations of the UA Parser library. pub fn from_file(file: std::fs::File) -> Result { let regex_file: RegexFile = serde_yaml::from_reader(file)?; - UserAgentParser::try_from(regex_file) + Self::try_from(regex_file) + } + + fn _build_from_file( + file: std::fs::File, + builder: UserAgentParserBuilder, + ) -> Result { + let regex_file: RegexFile = serde_yaml::from_reader(file)?; + Self::_try_from( + regex_file, + builder.device, + builder.os, + builder.user_agent, + builder.unicode, + ) } pub fn try_from(regex_file: RegexFile) -> Result { - let mut device_matchers = Vec::with_capacity(regex_file.device_parsers.len()); - let mut os_matchers = Vec::with_capacity(regex_file.os_parsers.len()); - let mut user_agent_matchers = - Vec::with_capacity(regex_file.user_agent_parsers.len()); + Self::_try_from(regex_file, true, true, true, true) + } - for parser in regex_file.device_parsers { - device_matchers.push(device::Matcher::try_from(parser)?); - } + fn _try_from( + regex_file: RegexFile, + device: bool, + os: bool, + user_agent: bool, + unicode: bool, + ) -> Result { + let device_matchers = if device { + let mut matchers = Vec::with_capacity(regex_file.device_parsers.len()); + for parser in regex_file.device_parsers { + matchers.push(device::Matcher::try_from(parser, unicode)?); + } + matchers + } else { + vec![] + }; - for parser in regex_file.os_parsers { - os_matchers.push(os::Matcher::try_from(parser)?); - } + let os_matchers = if os { + let mut matchers = Vec::with_capacity(regex_file.os_parsers.len()); + for parser in regex_file.os_parsers { + matchers.push(os::Matcher::try_from(parser, unicode)?); + } + matchers + } else { + vec![] + }; - for parser in regex_file.user_agent_parsers { - user_agent_matchers.push(user_agent::Matcher::try_from(parser)?); - } + let user_agent_matchers = if user_agent { + let mut matchers = Vec::with_capacity(regex_file.user_agent_parsers.len()); + for parser in regex_file.user_agent_parsers { + matchers.push(user_agent::Matcher::try_from(parser, unicode)?); + } + matchers + } else { + vec![] + }; Ok(UserAgentParser { device_matchers, @@ -129,6 +187,74 @@ impl UserAgentParser { } } +pub struct UserAgentParserBuilder { + device: bool, + os: bool, + user_agent: bool, + unicode: bool, +} + +impl UserAgentParserBuilder { + pub fn new() -> Self { + UserAgentParserBuilder { + device: true, + os: true, + user_agent: true, + unicode: true, + } + } + + /// Enable or disable unicode support. This is enabled by default. + /// Unicode regexes are much more complex and take up more memory. + /// Most uaparser implementation do not support unicode, so disabling + /// this is generally safe to do. + pub fn unicode(mut self, yes: bool) -> Self { + self.unicode = yes; + return self; + } + + /// Enable or disable device parsing. This is enabled by default. + /// Because all regexes are compiled up front, disabling this will + /// save a decent amount of memory. + pub fn device(mut self, yes: bool) -> Self { + self.device = yes; + return self; + } + + /// Enable or disable os parsing. This is enabled by default. + /// Because all regexes are compiled up front, disabling this will + /// save a decent amount of memory. + pub fn os(mut self, yes: bool) -> Self { + self.os = yes; + return self; + } + + /// Enable or disable user agent parsing. This is enabled by default. + /// Because all regexes are compiled up front, disabling this will + /// save a decent amount of memory. + pub fn user_agent(mut self, yes: bool) -> Self { + self.user_agent = yes; + return self; + } + + pub fn build_from_yaml(self, path: &str) -> Result { + UserAgentParser::_build_from_yaml(path, self) + } + /// Attempts to construct a `UserAgentParser` from a slice of raw bytes. The + /// intention with providing this function is to allow using the + /// `include_bytes!` macro to compile the `regexes.yaml` file into the + /// the library by a consuming application. + /// + /// ```rust + /// # use uaparser::*; + /// let regexes = include_bytes!("../../src/core/regexes.yaml"); + /// let parser = UserAgentParserBuilder::new().build_from_bytes(regexes); + /// ``` + pub fn build_from_bytes(self, bytes: &[u8]) -> Result { + UserAgentParser::_build_from_bytes(bytes, self) + } +} + #[inline] pub(self) fn none_if_empty>(s: T) -> Option { if s.as_ref().is_empty() { @@ -147,17 +273,26 @@ pub(self) fn has_group(replacement: &str) -> bool { pub(self) fn replace_cow<'a>( replacement: &str, replacement_has_group: bool, - captures: ®ex::Captures, + captures: ®ex::bytes::Captures, ) -> Cow<'a, str> { if replacement_has_group && captures.len() > 0 { - let mut target = String::with_capacity(31); - captures.expand(replacement, &mut target); - Cow::Owned(target.trim().to_owned()) + let mut target = vec![]; + let raw_replacement = replacement.as_bytes(); + captures.expand(raw_replacement, &mut target); + std::str::from_utf8(&target) + .map(|s| Cow::Owned(s.trim().to_owned())) + // What is the behavior if we can't parse a string??? + .unwrap_or_else(|_| Cow::Owned(replacement.to_owned())) } else { Cow::Owned(replacement.to_owned()) } } +#[inline] +pub(self) fn match_to_str(m: regex::bytes::Match) -> Option<&str> { + std::str::from_utf8(m.as_bytes()).ok() +} + lazy_static::lazy_static! { static ref INVALID_ESCAPES: Regex = Regex::new("\\\\([! /])").unwrap(); } diff --git a/src/parser/os.rs b/src/parser/os.rs index f8efba4..bc90183 100644 --- a/src/parser/os.rs +++ b/src/parser/os.rs @@ -8,7 +8,7 @@ pub enum Error { #[derive(Debug)] #[allow(clippy::struct_excessive_bools)] pub struct Matcher { - regex: regex::Regex, + regex: regex::bytes::Regex, os_replacement: Option, os_v1_replacement: Option, os_v2_replacement: Option, @@ -23,18 +23,18 @@ impl<'a> SubParser<'a> for Matcher { type Item = OS<'a>; fn try_parse(&self, text: &'a str) -> Option { - if !self.regex.is_match(text) { + if !self.regex.is_match(text.as_bytes()) { return None; } - if let Some(captures) = self.regex.captures(text) { + if let Some(captures) = self.regex.captures(text.as_bytes()) { let family: Cow<'a, str> = if let Some(os_replacement) = &self.os_replacement { replace_cow(os_replacement, self.os_replacement_has_group, &captures) } else { captures .get(1) - .map(|x| x.as_str()) + .and_then(match_to_str) .and_then(none_if_empty) .map(Cow::Borrowed)? }; @@ -49,7 +49,7 @@ impl<'a> SubParser<'a> for Matcher { } else { captures .get(2) - .map(|x| x.as_str()) + .and_then(match_to_str) .and_then(none_if_empty) .map(Cow::Borrowed) }; @@ -64,7 +64,7 @@ impl<'a> SubParser<'a> for Matcher { } else { captures .get(3) - .map(|x| x.as_str()) + .and_then(match_to_str) .and_then(none_if_empty) .map(Cow::Borrowed) }; @@ -79,14 +79,14 @@ impl<'a> SubParser<'a> for Matcher { } else { captures .get(4) - .map(|x| x.as_str()) + .and_then(match_to_str) .and_then(none_if_empty) .map(Cow::Borrowed) }; let patch_minor: Option> = captures .get(5) - .map(|x| x.as_str()) + .and_then(match_to_str) .and_then(none_if_empty) .map(Cow::Borrowed); @@ -104,8 +104,10 @@ impl<'a> SubParser<'a> for Matcher { } impl Matcher { - pub fn try_from(entry: OSParserEntry) -> Result { - let regex = regex::Regex::new(&clean_escapes(&entry.regex)); + pub fn try_from(entry: OSParserEntry, unicode: bool) -> Result { + let regex = regex::bytes::RegexBuilder::new(&clean_escapes(&entry.regex)) + .unicode(unicode) + .build(); Ok(Matcher { regex: regex?, diff --git a/src/parser/user_agent.rs b/src/parser/user_agent.rs index de1d601..96afaff 100644 --- a/src/parser/user_agent.rs +++ b/src/parser/user_agent.rs @@ -7,7 +7,7 @@ pub enum Error { #[derive(Debug)] pub struct Matcher { - regex: regex::Regex, + regex: regex::bytes::Regex, family_replacement_has_group: bool, family_replacement: Option, v1_replacement: Option, @@ -19,7 +19,7 @@ impl<'a> SubParser<'a> for Matcher { type Item = UserAgent<'a>; fn try_parse(&self, text: &'a str) -> Option { - if let Some(captures) = self.regex.captures(text) { + if let Some(captures) = self.regex.captures(text.as_bytes()) { let family: Cow<'a, str> = if let Some(family_replacement) = &self.family_replacement { replace_cow( @@ -30,7 +30,7 @@ impl<'a> SubParser<'a> for Matcher { } else { captures .get(1) - .map(|x| x.as_str()) + .and_then(match_to_str) .and_then(none_if_empty) .map(Cow::Borrowed)? }; @@ -42,7 +42,7 @@ impl<'a> SubParser<'a> for Matcher { .or_else(|| { captures .get(2) - .map(|x| x.as_str()) + .and_then(match_to_str) .and_then(none_if_empty) .map(Cow::Borrowed) }); @@ -54,7 +54,7 @@ impl<'a> SubParser<'a> for Matcher { .or_else(|| { captures .get(3) - .map(|x| x.as_str()) + .and_then(match_to_str) .and_then(none_if_empty) .map(Cow::Borrowed) }); @@ -66,7 +66,7 @@ impl<'a> SubParser<'a> for Matcher { .or_else(|| { captures .get(4) - .map(|x| x.as_str()) + .and_then(match_to_str) .and_then(none_if_empty) .map(Cow::Borrowed) }); @@ -84,8 +84,12 @@ impl<'a> SubParser<'a> for Matcher { } impl Matcher { - pub fn try_from(entry: UserAgentParserEntry) -> Result { - let regex = regex::RegexBuilder::new(&clean_escapes(&entry.regex)) + pub fn try_from( + entry: UserAgentParserEntry, + unicode: bool, + ) -> Result { + let regex = regex::bytes::RegexBuilder::new(&clean_escapes(&entry.regex)) + .unicode(unicode) .size_limit(20 * (1 << 20)) .build(); From c58b0324fab83a45cb05f2645ba94d4a41986f83 Mon Sep 17 00:00:00 2001 From: David Armstrong Lewis <6754950+davidarmstronglewis@users.noreply.github.com> Date: Mon, 17 Jul 2023 20:45:25 -0700 Subject: [PATCH 2/3] Hide UserAgentParserBuilder behind builder method --- Cargo.toml | 2 +- benches/benchmark.rs | 14 ++--- examples/full_parser.rs | 4 +- examples/no_unicode_parser.rs | 4 +- examples/os_only_no_unicode_parser.rs | 4 +- flake.lock | 66 +++++++++++++++++------ nix/shell.nix | 7 ++- src/lib.rs | 16 +++--- src/parser/builder.rs | 69 ++++++++++++++++++++++++ src/parser/mod.rs | 75 +++------------------------ 10 files changed, 155 insertions(+), 106 deletions(-) create mode 100644 src/parser/builder.rs diff --git a/Cargo.toml b/Cargo.toml index 3ed90cd..0b3e9ed 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,4 +26,4 @@ criterion = "0.3.5" [[bench]] name = "benchmark" -harness = false \ No newline at end of file +harness = false diff --git a/benches/benchmark.rs b/benches/benchmark.rs index a02b3b6..1ca4f1e 100644 --- a/benches/benchmark.rs +++ b/benches/benchmark.rs @@ -2,7 +2,7 @@ use std::{fs::File, time::Duration}; use criterion::{black_box, criterion_group, criterion_main, Criterion}; use serde_derive::Deserialize; -use uaparser::{Parser, UserAgentParserBuilder}; +use uaparser::{Parser, UserAgentParser}; #[derive(Deserialize, Debug)] struct TestCase { @@ -15,7 +15,7 @@ struct TestCases { } fn bench_os(c: &mut Criterion) { - let parser = UserAgentParserBuilder::new() + let parser = UserAgentParser::builder() .device(false) .os(true) .user_agent(false) @@ -33,7 +33,7 @@ fn bench_os(c: &mut Criterion) { }) }); - let parser = UserAgentParserBuilder::new() + let parser = UserAgentParser::builder() .device(false) .os(true) .user_agent(false) @@ -51,7 +51,7 @@ fn bench_os(c: &mut Criterion) { } fn bench_device(c: &mut Criterion) { - let parser = UserAgentParserBuilder::new() + let parser = UserAgentParser::builder() .device(true) .os(false) .user_agent(false) @@ -69,7 +69,7 @@ fn bench_device(c: &mut Criterion) { }) }); - let parser = UserAgentParserBuilder::new() + let parser = UserAgentParser::builder() .device(true) .os(false) .user_agent(false) @@ -87,7 +87,7 @@ fn bench_device(c: &mut Criterion) { } fn bench_ua(c: &mut Criterion) { - let parser = UserAgentParserBuilder::new() + let parser = UserAgentParser::builder() .device(false) .os(false) .user_agent(true) @@ -105,7 +105,7 @@ fn bench_ua(c: &mut Criterion) { }) }); - let parser = UserAgentParserBuilder::new() + let parser = UserAgentParser::builder() .device(false) .os(false) .user_agent(true) diff --git a/examples/full_parser.rs b/examples/full_parser.rs index 4841e93..9572615 100644 --- a/examples/full_parser.rs +++ b/examples/full_parser.rs @@ -1,7 +1,7 @@ -use uaparser::{Parser, UserAgentParserBuilder}; +use uaparser::{Parser, UserAgentParser}; fn main() { - let parser = UserAgentParserBuilder::new() + let parser = UserAgentParser::builder() .build_from_yaml("./src/core/regexes.yaml") .expect("Parser creation failed"); diff --git a/examples/no_unicode_parser.rs b/examples/no_unicode_parser.rs index 0a1f722..040b7a2 100644 --- a/examples/no_unicode_parser.rs +++ b/examples/no_unicode_parser.rs @@ -1,7 +1,7 @@ -use uaparser::{Parser, UserAgentParserBuilder}; +use uaparser::{Parser, UserAgentParser}; fn main() { - let parser = UserAgentParserBuilder::new() + let parser = UserAgentParser::builder() .unicode(false) .build_from_yaml("./src/core/regexes.yaml") .expect("Parser creation failed"); diff --git a/examples/os_only_no_unicode_parser.rs b/examples/os_only_no_unicode_parser.rs index 340d67f..04b337c 100644 --- a/examples/os_only_no_unicode_parser.rs +++ b/examples/os_only_no_unicode_parser.rs @@ -1,7 +1,7 @@ -use uaparser::{Parser, UserAgentParserBuilder}; +use uaparser::{Parser, UserAgentParser}; fn main() { - let parser = UserAgentParserBuilder::new() + let parser = UserAgentParser::builder() .unicode(false) .device(false) .os(true) diff --git a/flake.lock b/flake.lock index 327dc99..346a064 100644 --- a/flake.lock +++ b/flake.lock @@ -1,12 +1,15 @@ { "nodes": { "flake-utils": { + "inputs": { + "systems": "systems" + }, "locked": { - "lastModified": 1644229661, - "narHash": "sha256-1YdnJAsNy69bpcjuoKdOYQX0YxZBiCYZo4Twxerqv7k=", + "lastModified": 1689068808, + "narHash": "sha256-6ixXo3wt24N/melDWjq70UuHQLxGV8jZvooRanIHXw0=", "owner": "numtide", "repo": "flake-utils", - "rev": "3cecb5b042f7f209c56ffd8371b2711a290ec797", + "rev": "919d646de7be200f3bf08cb76ae1f09402b6f9b4", "type": "github" }, "original": { @@ -16,12 +19,15 @@ } }, "flake-utils_2": { + "inputs": { + "systems": "systems_2" + }, "locked": { - "lastModified": 1637014545, - "narHash": "sha256-26IZAc5yzlD9FlDT54io1oqG/bBoyka+FJk5guaX4x4=", + "lastModified": 1681202837, + "narHash": "sha256-H+Rh19JDwRtpVPAWp64F+rlEtxUWBAQW28eAi3SRSzg=", "owner": "numtide", "repo": "flake-utils", - "rev": "bba5dcc8e0b20ab664967ad83d24d64cb64ec4f4", + "rev": "cfacdce06f30d2b68473a46042957675eebb3401", "type": "github" }, "original": { @@ -32,11 +38,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1644972330, - "narHash": "sha256-6V2JFpTUzB9G+KcqtUR1yl7f6rd9495YrFECslEmbGw=", + "lastModified": 1688646010, + "narHash": "sha256-kCeza5eKI2NEi8k0EoeZfv3lN1r1Vwx+L/VA6I8tmG4=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "19574af0af3ffaf7c9e359744ed32556f34536bd", + "rev": "5daaa32204e9c46b05cd709218b7ba733d07e80c", "type": "github" }, "original": { @@ -48,11 +54,11 @@ }, "nixpkgs_2": { "locked": { - "lastModified": 1637453606, - "narHash": "sha256-Gy6cwUswft9xqsjWxFYEnx/63/qzaFUwatcbV5GF/GQ=", + "lastModified": 1681358109, + "narHash": "sha256-eKyxW4OohHQx9Urxi7TQlFBTDWII+F+x2hklDOQPB50=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "8afc4e543663ca0a6a4f496262cd05233737e732", + "rev": "96ba1c52e54e74c3197f4d43026b3f3d92e83ff9", "type": "github" }, "original": { @@ -75,11 +81,11 @@ "nixpkgs": "nixpkgs_2" }, "locked": { - "lastModified": 1645064920, - "narHash": "sha256-MA1TWy+XGnFWHH1EAxZ943UOEBqYTToubV9w4+r/cbE=", + "lastModified": 1689302058, + "narHash": "sha256-yD74lcHTrw4niXcE9goJLbzsgyce48rQQoy5jK5ZK40=", "owner": "oxalica", "repo": "rust-overlay", - "rev": "66a6dd1e56fca9a42dd18c3616dc66c25ff8d204", + "rev": "7b8dbbf4c67ed05a9bf3d9e658c12d4108bc24c8", "type": "github" }, "original": { @@ -87,6 +93,36 @@ "repo": "rust-overlay", "type": "github" } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + }, + "systems_2": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } } }, "root": "root", diff --git a/nix/shell.nix b/nix/shell.nix index 1a9fe89..07f9ad9 100644 --- a/nix/shell.nix +++ b/nix/shell.nix @@ -6,10 +6,15 @@ mkShell { nativeBuildInputs = [ (rust-bin.stable.latest.default.override { - extensions = [ "rust-src" ]; + extensions = [ + "rust-src" + "rust-analysis" + "clippy" + ]; }) cargo-criterion cargo-edit + cargo-watch gnuplot ]; } diff --git a/src/lib.rs b/src/lib.rs index 644a374..a136949 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -22,7 +22,7 @@ //! Alternatively you can use the `UserAgentParserBuilder` to create a parser: //! ```rust //! # use uaparser::*; -//! let ua_parser = UserAgentParserBuilder::new().build_from_yaml("./src/core/regexes.yaml").expect("Parser creation failed"); +//! let ua_parser = UserAgentParser::builder().build_from_yaml("./src/core/regexes.yaml").expect("Parser creation failed"); //! let user_agent_string = //! String::from("Mozilla/5.0 (X11; Linux x86_64; rv:2.0b8pre) Gecko/20101031 Firefox-4.0/4.0b8pre"); //! let client = ua_parser.parse(&user_agent_string); @@ -57,7 +57,7 @@ pub use user_agent::UserAgent; mod file; mod parser; -pub use parser::{Error, UserAgentParser, UserAgentParserBuilder}; +pub use parser::{Error, UserAgentParser}; pub use client::Client; @@ -80,7 +80,7 @@ mod tests { #[test] fn parse_os_with_unicode() { - let parser = UserAgentParserBuilder::new() + let parser = UserAgentParser::builder() .unicode(true) .build_from_yaml("./src/core/regexes.yaml") .expect("Parser creation failed"); @@ -89,7 +89,7 @@ mod tests { #[test] fn parse_os_without_unicode() { - let parser = UserAgentParserBuilder::new() + let parser = UserAgentParser::builder() .unicode(false) .build_from_yaml("./src/core/regexes.yaml") .expect("Parser creation failed"); @@ -167,7 +167,7 @@ mod tests { #[test] fn parse_device_with_unicode() { - let parser = UserAgentParserBuilder::new() + let parser = UserAgentParser::builder() .unicode(true) .build_from_yaml("./src/core/regexes.yaml") .expect("Parser creation failed"); @@ -176,7 +176,7 @@ mod tests { #[test] fn parse_device_without_unicode() { - let parser = UserAgentParserBuilder::new() + let parser = UserAgentParser::builder() .unicode(false) .build_from_yaml("./src/core/regexes.yaml") .expect("Parser creation failed"); @@ -239,7 +239,7 @@ mod tests { #[test] fn parse_user_agent_with_unicode() { - let parser = UserAgentParserBuilder::new() + let parser = UserAgentParser::builder() .unicode(true) .build_from_yaml("./src/core/regexes.yaml") .expect("Parser creation failed"); @@ -248,7 +248,7 @@ mod tests { #[test] fn parse_user_agent_without_unicode() { - let parser = UserAgentParserBuilder::new() + let parser = UserAgentParser::builder() .unicode(false) .build_from_yaml("./src/core/regexes.yaml") .expect("Parser creation failed"); diff --git a/src/parser/builder.rs b/src/parser/builder.rs new file mode 100644 index 0000000..e54854b --- /dev/null +++ b/src/parser/builder.rs @@ -0,0 +1,69 @@ +use crate::{Error, UserAgentParser}; + +pub struct UserAgentParserBuilder { + pub(super) device: bool, + pub(super) os: bool, + pub(super) user_agent: bool, + pub(super) unicode: bool, +} + +impl UserAgentParserBuilder { + pub(super) fn new() -> Self { + UserAgentParserBuilder { + device: true, + os: true, + user_agent: true, + unicode: true, + } + } + + /// Enable or disable unicode support. This is enabled by default. + /// Unicode regexes are much more complex and take up more memory. + /// Most uaparser implementation do not support unicode, so disabling + /// this is generally safe to do. + pub fn with_unicode_support(mut self, enabled: bool) -> Self { + self.unicode = enabled; + return self; + } + + /// Enable or disable device parsing. This is enabled by default. + /// Because all regexes are compiled up front, disabling this will + /// save a decent amount of memory. + pub fn with_device(mut self, enabled: bool) -> Self { + self.device = enabled; + return self; + } + + /// Enable or disable os parsing. This is enabled by default. + /// Because all regexes are compiled up front, disabling this will + /// save a decent amount of memory. + pub fn with_os(mut self, enabled: bool) -> Self { + self.os = enabled; + return self; + } + + /// Enable or disable user agent parsing. This is enabled by default. + /// Because all regexes are compiled up front, disabling this will + /// save a decent amount of memory. + pub fn with_user_agent(mut self, enabled: bool) -> Self { + self.user_agent = enabled; + return self; + } + + pub fn build_from_yaml(self, path: &str) -> Result { + UserAgentParser::_build_from_yaml(path, self) + } + /// Attempts to construct a `UserAgentParser` from a slice of raw bytes. The + /// intention with providing this function is to allow using the + /// `include_bytes!` macro to compile the `regexes.yaml` file into the + /// the library by a consuming application. + /// + /// ```rust + /// # use uaparser::*; + /// let regexes = include_bytes!("../../src/core/regexes.yaml"); + /// let parser = UserAgentParser::builder().build_from_bytes(regexes); + /// ``` + pub fn build_from_bytes(self, bytes: &[u8]) -> Result { + UserAgentParser::_build_from_bytes(bytes, self) + } +} diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 4dd0bf6..0342ff7 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -19,6 +19,9 @@ use super::{ user_agent::UserAgent, }; +mod builder; +use self::builder::UserAgentParserBuilder; + #[derive(Debug, Display, From)] pub enum Error { IO(std::io::Error), @@ -73,6 +76,10 @@ impl Parser for UserAgentParser { } impl UserAgentParser { + pub fn builder() -> UserAgentParserBuilder { + UserAgentParserBuilder::new() + } + /// Attempts to construct a `UserAgentParser` from the path to a file pub fn from_yaml(path: &str) -> Result { let file = std::fs::File::open(path)?; @@ -187,74 +194,6 @@ impl UserAgentParser { } } -pub struct UserAgentParserBuilder { - device: bool, - os: bool, - user_agent: bool, - unicode: bool, -} - -impl UserAgentParserBuilder { - pub fn new() -> Self { - UserAgentParserBuilder { - device: true, - os: true, - user_agent: true, - unicode: true, - } - } - - /// Enable or disable unicode support. This is enabled by default. - /// Unicode regexes are much more complex and take up more memory. - /// Most uaparser implementation do not support unicode, so disabling - /// this is generally safe to do. - pub fn unicode(mut self, yes: bool) -> Self { - self.unicode = yes; - return self; - } - - /// Enable or disable device parsing. This is enabled by default. - /// Because all regexes are compiled up front, disabling this will - /// save a decent amount of memory. - pub fn device(mut self, yes: bool) -> Self { - self.device = yes; - return self; - } - - /// Enable or disable os parsing. This is enabled by default. - /// Because all regexes are compiled up front, disabling this will - /// save a decent amount of memory. - pub fn os(mut self, yes: bool) -> Self { - self.os = yes; - return self; - } - - /// Enable or disable user agent parsing. This is enabled by default. - /// Because all regexes are compiled up front, disabling this will - /// save a decent amount of memory. - pub fn user_agent(mut self, yes: bool) -> Self { - self.user_agent = yes; - return self; - } - - pub fn build_from_yaml(self, path: &str) -> Result { - UserAgentParser::_build_from_yaml(path, self) - } - /// Attempts to construct a `UserAgentParser` from a slice of raw bytes. The - /// intention with providing this function is to allow using the - /// `include_bytes!` macro to compile the `regexes.yaml` file into the - /// the library by a consuming application. - /// - /// ```rust - /// # use uaparser::*; - /// let regexes = include_bytes!("../../src/core/regexes.yaml"); - /// let parser = UserAgentParserBuilder::new().build_from_bytes(regexes); - /// ``` - pub fn build_from_bytes(self, bytes: &[u8]) -> Result { - UserAgentParser::_build_from_bytes(bytes, self) - } -} - #[inline] pub(self) fn none_if_empty>(s: T) -> Option { if s.as_ref().is_empty() { From d7a94b392c3ae88b0e5db6f173bf65a8976d6305 Mon Sep 17 00:00:00 2001 From: Peter Hamilton Date: Thu, 20 Jul 2023 10:24:04 -0600 Subject: [PATCH 3/3] Update tests/bench/examples with new builder syntax --- benches/benchmark.rs | 42 +++++++++++++-------------- examples/no_unicode_parser.rs | 2 +- examples/os_only_no_unicode_parser.rs | 8 ++--- src/lib.rs | 12 ++++---- 4 files changed, 32 insertions(+), 32 deletions(-) diff --git a/benches/benchmark.rs b/benches/benchmark.rs index 1ca4f1e..f7dd815 100644 --- a/benches/benchmark.rs +++ b/benches/benchmark.rs @@ -16,9 +16,9 @@ struct TestCases { fn bench_os(c: &mut Criterion) { let parser = UserAgentParser::builder() - .device(false) - .os(true) - .user_agent(false) + .with_device(false) + .with_os(true) + .with_user_agent(false) .build_from_yaml("./src/core/regexes.yaml") .expect("Parser creation failed"); @@ -34,10 +34,10 @@ fn bench_os(c: &mut Criterion) { }); let parser = UserAgentParser::builder() - .device(false) - .os(true) - .user_agent(false) - .unicode(false) + .with_device(false) + .with_os(true) + .with_user_agent(false) + .with_unicode_support(false) .build_from_yaml("./src/core/regexes.yaml") .expect("Parser creation failed"); @@ -52,9 +52,9 @@ fn bench_os(c: &mut Criterion) { fn bench_device(c: &mut Criterion) { let parser = UserAgentParser::builder() - .device(true) - .os(false) - .user_agent(false) + .with_device(true) + .with_os(false) + .with_user_agent(false) .build_from_yaml("./src/core/regexes.yaml") .expect("Parser creation failed"); @@ -70,10 +70,10 @@ fn bench_device(c: &mut Criterion) { }); let parser = UserAgentParser::builder() - .device(true) - .os(false) - .user_agent(false) - .unicode(false) + .with_device(true) + .with_os(false) + .with_user_agent(false) + .with_unicode_support(false) .build_from_yaml("./src/core/regexes.yaml") .expect("Parser creation failed"); @@ -88,9 +88,9 @@ fn bench_device(c: &mut Criterion) { fn bench_ua(c: &mut Criterion) { let parser = UserAgentParser::builder() - .device(false) - .os(false) - .user_agent(true) + .with_device(false) + .with_os(false) + .with_user_agent(true) .build_from_yaml("./src/core/regexes.yaml") .expect("Parser creation failed"); @@ -106,10 +106,10 @@ fn bench_ua(c: &mut Criterion) { }); let parser = UserAgentParser::builder() - .device(false) - .os(false) - .user_agent(true) - .unicode(false) + .with_device(false) + .with_os(false) + .with_user_agent(true) + .with_unicode_support(false) .build_from_yaml("./src/core/regexes.yaml") .expect("Parser creation failed"); diff --git a/examples/no_unicode_parser.rs b/examples/no_unicode_parser.rs index 040b7a2..adc1fc6 100644 --- a/examples/no_unicode_parser.rs +++ b/examples/no_unicode_parser.rs @@ -2,7 +2,7 @@ use uaparser::{Parser, UserAgentParser}; fn main() { let parser = UserAgentParser::builder() - .unicode(false) + .with_unicode_support(false) .build_from_yaml("./src/core/regexes.yaml") .expect("Parser creation failed"); diff --git a/examples/os_only_no_unicode_parser.rs b/examples/os_only_no_unicode_parser.rs index 04b337c..92d460e 100644 --- a/examples/os_only_no_unicode_parser.rs +++ b/examples/os_only_no_unicode_parser.rs @@ -2,10 +2,10 @@ use uaparser::{Parser, UserAgentParser}; fn main() { let parser = UserAgentParser::builder() - .unicode(false) - .device(false) - .os(true) - .user_agent(false) + .with_unicode_support(false) + .with_device(false) + .with_os(true) + .with_user_agent(false) .build_from_yaml("./src/core/regexes.yaml") .expect("Parser creation failed"); diff --git a/src/lib.rs b/src/lib.rs index a136949..4743502 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -81,7 +81,7 @@ mod tests { #[test] fn parse_os_with_unicode() { let parser = UserAgentParser::builder() - .unicode(true) + .with_unicode_support(true) .build_from_yaml("./src/core/regexes.yaml") .expect("Parser creation failed"); do_parse_os_test_with_parser(&parser) @@ -90,7 +90,7 @@ mod tests { #[test] fn parse_os_without_unicode() { let parser = UserAgentParser::builder() - .unicode(false) + .with_unicode_support(false) .build_from_yaml("./src/core/regexes.yaml") .expect("Parser creation failed"); do_parse_os_test_with_parser(&parser) @@ -168,7 +168,7 @@ mod tests { #[test] fn parse_device_with_unicode() { let parser = UserAgentParser::builder() - .unicode(true) + .with_unicode_support(true) .build_from_yaml("./src/core/regexes.yaml") .expect("Parser creation failed"); do_parse_device_test_with_parser(&parser) @@ -177,7 +177,7 @@ mod tests { #[test] fn parse_device_without_unicode() { let parser = UserAgentParser::builder() - .unicode(false) + .with_unicode_support(false) .build_from_yaml("./src/core/regexes.yaml") .expect("Parser creation failed"); do_parse_device_test_with_parser(&parser) @@ -240,7 +240,7 @@ mod tests { #[test] fn parse_user_agent_with_unicode() { let parser = UserAgentParser::builder() - .unicode(true) + .with_unicode_support(true) .build_from_yaml("./src/core/regexes.yaml") .expect("Parser creation failed"); do_parse_user_agent_test_with_parser(&parser) @@ -249,7 +249,7 @@ mod tests { #[test] fn parse_user_agent_without_unicode() { let parser = UserAgentParser::builder() - .unicode(false) + .with_unicode_support(false) .build_from_yaml("./src/core/regexes.yaml") .expect("Parser creation failed"); do_parse_user_agent_test_with_parser(&parser)