M DESCRIPTION => DESCRIPTION +8 -9
@@ 1,7 1,7 @@
Package: uaparserjs
Type: Package
-Title: Parse Browser 'User-Agent' Strings into Data Frames
-Version: 0.3.0
+Title: Parse 'User-Agent' Strings
+Version: 0.3.1
Date: 2020-03-31
Authors@R: c(
person("Bob", "Rudis", , "bob@rud.is", c("aut", "cre")),
@@ 12,19 12,18 @@ Maintainer: Bob Rudis <bob@rud.is>
Description: Despite there being a section in RFC 7231
<https://tools.ietf.org/html/rfc7231#section-5.5.3> defining a suggested
structure for 'User-Agent' headers this data is notoriously difficult
- to parse consistently. A function is provided that will take in user agent
+ to parse consistently. Tools are provided that will take in user agent
strings and return structured R objects. This is a 'V8'-backed package
based on the 'ua-parser' project <https://github.com/ua-parser>.
-URL: http://github.com/hrbrmstr/uaparserjs
-BugReports: https://github.com/hrbrmstr/uaparserjs/issues
-License: Apache License
+URL: https://git.rud.is/hrbrmstr/uaparserjs
+BugReports: https://gitlab.com/hrbrmstr/uaparserjs/issues
+License: Apache License 2.0 | file LICENSE
Suggests:
testthat
Depends:
- R (>= 3.0.0)
+ R (>= 3.2.0)
Encoding: UTF-8
Imports:
- purrr,
- dplyr,
+ progress,
V8
RoxygenNote: 7.1.0
A LICENSE => LICENSE +201 -0
@@ 0,0 1,201 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
M NAMESPACE => NAMESPACE +1 -4
@@ 3,7 3,4 @@
export(get_cache)
export(ua_parse)
import(V8)
-importFrom(dplyr,as_tibble)
-importFrom(dplyr,progress_estimated)
-importFrom(purrr,map)
-importFrom(purrr,map_df)
+import(progress)
M R/uaparser.R => R/uaparser.R +32 -10
@@ 1,15 1,23 @@
+as_tibble <- function(.x) {
+
+ out <- as.data.frame(.x, stringsAsFactors = FALSE)
+ class(out) <- c("tbl_df", "tbl", "data.frame")
+ out
+
+}
+
#' Parse a vector of user agents into a data frame
#'
-#' Takes in a character vector of user agent strings and returns a \code{tibble}
+#' Takes in a character vector of user agent strings and returns a data frame classed as tibble.
#' of parsed user agents.
#'
#' @param user_agents a character vector of user agents
-#' @param .progress if \code{TRUE}, will display a progress bar in interactive mode
+#' @param .progress if `TRUE` will display a progress bar in interactive mode
#' @export
-#' @return a \code{tibble} with columns for user agent family, major & minor versions
-#' plus patch level along with os family and major & minor versions plus
+#' @return a data frame classed as tibble with columns for user agent family, major & minor versions
+#' plus patch level along with OS family and major & minor versions plus
#' device brand and model.
-#' @references \url{http://www.uaparser.org/}
+#' @references <http://www.uaparser.org/>
#' @note The regex YAML import date: 2020-03-31
#' @examples
#' ua_parse(paste0("Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/535.2 (KHTML, ",
@@ 17,19 25,33 @@
#' "Chrome/15.0.874.106 Safari/535.2", collapse=""))
ua_parse <- function(user_agents, .progress=FALSE) {
- if (.progress) pb <- progress_estimated(length(user_agents))
- purrr::map_df(user_agents, function(x) {
+ if (.progress) pb <- progress::progress_bar$new(length(user_agents))
+ if (.progress) pb$tick(0)
+
+ lapply(user_agents, function(x) {
+
if (.progress) pb$tick()$print()
+
res <- .pkgenv$cache[[x]]
+
if (length(res) > 0) {
+
res
+
} else {
- .pkgenv$cache[[x]] <- dplyr::as_tibble(as.list(unlist(.pkgenv$ctx$call("parser.parse", x))))
+
+ .pkgenv$cache[[x]] <- as_tibble(as.list(unlist(.pkgenv$ctx$call("parser.parse", x))))
.pkgenv$cache[[x]]
+
}
- })
+
+ }) -> out
+
+ out <- bind_rows(out)
+
+ as_tibble(out)
}
#' @export
-get_cache <- function() { .pkgenv$cache }>
\ No newline at end of file
+get_cache <- function() { .pkgenv$cache }
M R/uaparserjs-package.R => R/uaparserjs-package.R +9 -4
@@ 1,9 1,14 @@
-#' Parse Browser User Agent Strings into Data Frames
+#' Parse 'User-Agent' Strings
+#'
+#' Despite there being a section in RFC 7231
+#' <https://tools.ietf.org/html/rfc7231#section-5.5.3> defining a suggested
+#' structure for 'User-Agent' headers this data is notoriously difficult
+#' to parse consistently. Tools are provided that will take in user agent
+#' strings and return structured R objects. This is a 'V8'-backed package
+#' based on the 'ua-parser' project <https://github.com/ua-parser>.
#'
#' @name uaparserjs
#' @docType package
#' @author Bob Rudis (@@hrbrmstr)
-#' @importFrom purrr map map_df
-#' @import V8
-#' @importFrom dplyr as_tibble progress_estimated
+#' @import V8 progress
NULL
A R/utils-mappers.R => R/utils-mappers.R +320 -0
@@ 0,0 1,320 @@
+# NOTE: At the bottom of this source file show the equivalents to purrr mappers
+#
+# NOTE these aren't 100% equivalent to the purrr mappers but cover very common use-cases
+#
+# NOTE formula function (e.g. ~{}) are 100% supported
+#
+# NOTE: THESE DO NOT SUPPORT list EXTRACTORS
+
+set_names <- function(object = nm, nm) {
+ names(object) <- nm
+ object
+}
+
+map <- function(.x, .f, ..., .default) {
+
+ default_exists <- !missing(.default)
+
+ if (inherits(.f, "formula")) {
+ .body <- dimnames(attr(terms(.f), "factors"))[[1]]
+ .f <- function(.x, . = .x) {}
+ body(.f) <- as.expression(parse(text=.body))
+ }
+
+ nm <- names(.x)
+
+ if (inherits(.f, "function")) {
+
+ lapply(.x, function(x) {
+ res <- .f(x, ...)
+ if ((length(res) == 0) & default_exists) res <- .default
+ res
+ }) -> out
+
+ } else if (is.numeric(.f) | is.character(.f)) {
+
+ lapply(.x, function(x) {
+ res <- try(x[[.f]], silent = TRUE)
+ if (inherits(res, "try-error")) res <- NULL
+ if ((length(res) == 0) & default_exists) res <- .default
+ res
+ }) -> out
+
+ }
+
+ if (length(nm) > 0) out <- set_names(out, nm)
+
+ out
+
+}
+
+map2 <- function(.x, .y, .f, ..., .default) {
+
+ default_exists <- !missing(.default)
+
+ if (inherits(.f, "formula")) {
+ .body <- dimnames(attr(terms(.f), "factors"))[[1]]
+ .f <- function(.x, .y, . = .x) {}
+ body(.f) <- as.expression(parse(text=.body))
+ }
+
+ if (inherits(.f, "function")) {
+ mapply(
+ function(x, ...) {
+ res <- .f(x, ...)
+ if ((length(res) == 0) & default_exists) res <- .default
+ res
+ },
+ .x, .y,
+ ...,
+ SIMPLIFY=FALSE, USE.NAMES=FALSE
+ )
+ }
+
+}
+
+map_chr <- function(.x, .f, ...) {
+ nm <- names(.x)
+ out <- as.character((map(.x, .f, ..., .default = .default)))
+ if (length(nm) > 0) set_names(out, nm) else out
+}
+
+map2_chr <- function(.x, .y, .f, ...) {
+ as.character(unlist(map2(.x, .y, .f, ..., .default = .default)))
+}
+
+map_lgl <- function(.x, .f, ...) {
+ nm <- names(.x)
+ out <- as.logical(unlist(map(.x, .f, ..., .default = .default)))
+ if (length(nm) > 0) set_names(out, nm) else out
+}
+
+map2_lgl <- function(.x, .y, .f, ...) {
+ as.logical(unlist(map2(.x, .y, .f, ..., .default = .default)))
+}
+
+map_dbl <- function(.x, .f, ...) {
+ nm <- names(.x)
+ out <- as.double(unlist(map(.x, .f, ..., .default = .default)))
+ if (length(nm) > 0) set_names(out, nm) else out
+}
+
+map2_dbl <- function(.x, .y, .f, ...) {
+ as.double(unlist(map2(.x, .y, .f, ..., .default = .default)))
+}
+
+map_int <- function(.x, .f, ..., .default) {
+ nm <- names(.x)
+ out <- as.integer(unlist(map(.x, .f, ..., .default = .default)))
+ if (length(nm) > 0) set_names(out, nm) else out
+}
+
+map2_int <- function(.x, .y, .f, ...) {
+ as.integer(unlist(map2(.x, .y, .f, ..., .default = .default)))
+}
+
+
+map_df <- function(.x, .f, ..., .id=NULL) {
+
+ res <- map(.x, .f, ...)
+ out <- bind_rows(res, .id=.id)
+ out
+
+}
+
+map_dfr <- map_df
+
+map_dfc <- function(.x, .f, ...) {
+
+ res <- map(.x, .f, ...)
+ out <- bind_cols(res)
+ out
+
+}
+
+map2_df <- function(.x, .y, .f, ..., .id=NULL) {
+
+ res <- map2(.x, .y, .f, ...)
+ out <- bind_rows(res, .id = .id)
+ out
+
+}
+
+
+map2_dfc <- function(.x, .y, .f, ...) {
+
+ res <- map2(.x, .y, .f, ...)
+ out <- bind_cols(res)
+ out
+
+}
+
+# this has limitations and is more like 75% of dplyr::bind_rows()
+# this is also orders of magnitude slower than dplyr::bind_rows()
+bind_rows <- function(..., .id = NULL) {
+
+ res <- list(...)
+
+ if (length(res) == 1) res <- res[[1]]
+
+ cols <- unique(unlist(lapply(res, names), use.names = FALSE))
+
+ if (!is.null(.id)) {
+ inthere <- cols[.id %in% cols]
+ if (length(inthere) > 0) {
+ .id <- make.unique(c(inthere, .id))[2]
+ }
+ }
+
+ id_vals <- if (is.null(names(res))) 1:length(res) else names(res)
+
+ saf <- default.stringsAsFactors()
+ options(stringsAsFactors = FALSE)
+ on.exit(options(stringsAsFactors = saf))
+
+ idx <- 1
+ do.call(
+ rbind.data.frame,
+ lapply(res, function(.x) {
+ x_names <- names(.x)
+ moar_names <- setdiff(cols, x_names)
+ if (length(moar_names) > 0) {
+ for (i in 1:length(moar_names)) {
+ .x[[moar_names[i]]] <- rep(NA, length(.x[[1]]))
+ }
+ }
+ if (!is.null(.id)) {
+ .x[[.id]] <- id_vals[idx]
+ idx <<- idx + 1
+ }
+ .x
+ })
+ ) -> out
+
+ rownames(out) <- NULL
+
+ class(out) <- c("tbl_df", "tbl", "data.frame")
+
+ out
+
+}
+
+bind_cols <- function(...) {
+
+ res <- list(...)
+
+ row_mismatch <- lapply(res, nrow) != nrow(res[[1]])
+
+ if (any(row_mismatch)) {
+ first_mismatch_pos <- which(row_mismatch)[1]
+ stop(paste0("Argument ", first_mismatch_pos,
+ " must be length ", nrow(res[[1]]),
+ ", not ", nrow(res[[first_mismatch_pos]])))
+ }
+
+ if (length(res) == 1) res <- res[[1]]
+
+ col_names <- unlist(lapply(res, names), use.names = FALSE)
+ col_names <- make.unique(col_names, sep = "")
+
+ saf <- default.stringsAsFactors()
+ options(stringsAsFactors = FALSE)
+ on.exit(options(stringsAsFactors = saf))
+
+ out <- do.call(cbind.data.frame, res)
+
+ names(out) <- col_names
+ rownames(out) <- NULL
+
+ class(out) <- c("tbl_df", "tbl", "data.frame")
+
+ out
+
+}
+
+
+# set.seed(1)
+# 1:10 %>%
+# map(rnorm, n = 10) %>%
+# map_dbl(mean)
+#
+# set.seed(1)
+# 1:10 %>%
+# purrr::map(rnorm, n = 10) %>%
+# purrr::map_dbl(mean)
+#
+#
+# # Or use an anonymous function
+# set.seed(1)
+# 1:10 %>%
+# map(function(x) rnorm(10, x))
+#
+# set.seed(1)
+# 1:10 %>%
+# purrr::map(function(x) rnorm(10, x))
+#
+# # Or a formula
+# set.seed(1)
+# 1:10 %>%
+# map(~ rnorm(10, .x))
+#
+# set.seed(1)
+# 1:10 %>%
+# purrr::map(~ rnorm(10, .x))
+#
+# # Extract by name or position
+# # .default specifies value for elements that are missing or NULL
+# l1 <- list(list(a = 1L), list(a = NULL, b = 2L), list(b = 3L))
+# l1 %>% map("a", .default = "???")
+# l1 %>% purrr::map("a", .default = "???")
+#
+# l1 %>% map_int("b", .default = NA)
+# l1 %>% purrr::map_int("b", .default = NA)
+#
+# l1 %>% map_int(2, .default = NA)
+# l1 %>% purrr::map_int(2, .default = NA)
+#
+# # Supply multiple values to index deeply into a list
+# l2 <- list(
+# list(num = 1:3, letters[1:3]),
+# list(num = 101:103, letters[4:6]),
+# list()
+# )
+# l2 %>% map(c(2, 2))
+# l2 %>% purrr::map(c(2, 2))
+#
+#
+# # A more realistic example: split a data frame into pieces, fit a
+# # model to each piece, summarise and extract R^2
+# mtcars %>%
+# split(.$cyl) %>%
+# map(~ lm(mpg ~ wt, data = .x)) %>%
+# map(summary) %>%
+# map_dbl("r.squared")
+#
+# mtcars %>%
+# split(.$cyl) %>%
+# purrr::map(~ lm(mpg ~ wt, data = .x)) %>%
+# purrr::map(summary) %>%
+# purrr::map_dbl("r.squared")
+#
+#
+# # Use map_lgl(), map_dbl(), etc to reduce to a vector.
+# # * list
+# mtcars %>% map(sum)
+# mtcars %>% purrr::map(sum)
+# # * vector
+# mtcars %>% map_dbl(sum)
+# mtcars %>% purrr::map_dbl(sum)
+#
+# # If each element of the output is a data frame, use
+# # map_dfr to row-bind them together:
+# mtcars %>%
+# split(.$cyl) %>%
+# map(~ lm(mpg ~ wt, data = .x)) %>%
+# map_dfr(~ as.data.frame(t(as.matrix(coef(.)))))
+#
+# mtcars %>%
+# split(.$cyl) %>%
+# purrr::map(~ lm(mpg ~ wt, data = .x)) %>%
+# purrr::map_dfr(~ as.data.frame(t(as.matrix(coef(.)))))
M README.Rmd => README.Rmd +10 -2
@@ 39,12 39,20 @@ hrbrpkghelpr::install_block()
```{r}
library(uaparserjs)
-# current verison
+# current version
packageVersion("uaparserjs")
dplyr::glimpse(ua_parse("Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/535.2 (KHTML, like Gecko) Ubuntu/11.10 Chromium/15.0.874.106 Chrome/15.0.874.106 Safari/535.2"))
-dplyr::glimpse(ua_parse(readLines("tests/agents.txt")))
+agents <- readLines("tests/agents.txt")
+
+dplyr::glimpse(ua_parse(agents))
+
+set.seed(100)
+batch_100 <- sample(agents, 100)
+microbenchmark::microbenchmark(
+ ua_parse(batch_100)
+)
```
M README.md => README.md +25 -8
@@ 5,7 5,7 @@ developed.](https://www.repostatus.org/badges/latest/active.svg)](https://www.re
[](https://keybase.io/hrbrmstr)

+%](https://img.shields.io/badge/Signed_Commits-100%25-lightgrey.svg)
[](https://travis-ci.org/hrbrmstr/uaparserjs)
[](https://cranchecks.inf
[](https://www.r-pkg.org/pkg/uaparserjs)

+Version](https://img.shields.io/badge/R%3E%3D-3.2.0-blue.svg)

# uaparserjs
-Parse Browser ‘User-Agent’ Strings into Data Frames
+Parse ‘User-Agent’ Strings
## Description
Despite there being a section in RFC 7231
<https://tools.ietf.org/html/rfc7231#section-5.5.3> defining a suggested
structure for ‘User-Agent’ headers this data is notoriously difficult to
-parse consistently. A function is provided that will take in user agent
+parse consistently. Tools are provided that will take in user agent
strings and return structured R objects. This is a ‘V8’-backed package
based on the ‘ua-parser’ project <https://github.com/ua-parser>.
@@ 51,8 51,14 @@ The following functions are implemented:
## Installation
``` r
+remotes::install_git("https://git.rud.is/hrbrmstr/uaparserjs.git")
+# or
+remotes::install_git("https://git.sr.ht/~hrbrmstr/uaparserjs")
+# or
remotes::install_gitlab("hrbrmstr/uaparserjs")
# or
+remotes::install_bitbucket("hrbrmstr/uaparserjs")
+# or
remotes::install_github("hrbrmstr/uaparserjs")
```
@@ 66,7 72,7 @@ library(uaparserjs)
# current verison
packageVersion("uaparserjs")
-## [1] '0.3.0'
+## [1] '0.3.1'
dplyr::glimpse(ua_parse("Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/535.2 (KHTML, like Gecko) Ubuntu/11.10 Chromium/15.0.874.106 Chrome/15.0.874.106 Safari/535.2"))
## Observations: 1
@@ 81,7 87,9 @@ dplyr::glimpse(ua_parse("Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/535.2 (KHTM
## $ os.minor <chr> "10"
## $ device.family <chr> "Other"
-dplyr::glimpse(ua_parse(readLines("tests/agents.txt")))
+agents <- readLines("tests/agents.txt")
+
+dplyr::glimpse(ua_parse(agents))
## Observations: 1,091
## Variables: 13
## $ userAgent <chr> "Mozilla/5.0 (Windows; U; en-US) AppleWebKit/531.9 (KHTML, like Gecko) AdobeAIR/2.5.1", "Mozill…
@@ 97,14 105,23 @@ dplyr::glimpse(ua_parse(readLines("tests/agents.txt")))
## $ os.minor <chr> NA, NA, NA, NA, NA, "0", "2", "3", "3", "3", "0", "0", "0", "0", "0", NA, NA, NA, NA, "1", "0",…
## $ os.patch <chr> NA, NA, NA, NA, NA, "3", "2", "3", "4", "5", "1", "3", "3", "3", "4", NA, NA, NA, NA, "1", "6",…
## $ os.patchMinor <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
+
+set.seed(100)
+batch_100 <- sample(agents, 100)
+microbenchmark::microbenchmark(
+ ua_parse(batch_100)
+)
+## Unit: milliseconds
+## expr min lq mean median uq max neval
+## ua_parse(batch_100) 19.44652 20.2158 21.42222 20.66315 22.56606 31.78299 100
```
## uaparserjs Metrics
| Lang | \# Files | (%) | LoC | (%) | Blank lines | (%) | \# Lines | (%) |
| :--- | -------: | ---: | --: | ---: | ----------: | ---: | -------: | ---: |
-| R | 5 | 0.83 | 35 | 0.78 | 13 | 0.39 | 26 | 0.46 |
-| Rmd | 1 | 0.17 | 10 | 0.22 | 20 | 0.61 | 30 | 0.54 |
+| R | 6 | 0.86 | 202 | 0.93 | 94 | 0.81 | 125 | 0.81 |
+| Rmd | 1 | 0.14 | 16 | 0.07 | 22 | 0.19 | 30 | 0.19 |
## Code of Conduct
M man/ua_parse.Rd => man/ua_parse.Rd +5 -5
@@ 9,15 9,15 @@ ua_parse(user_agents, .progress = FALSE)
\arguments{
\item{user_agents}{a character vector of user agents}
-\item{.progress}{if \code{TRUE}, will display a progress bar in interactive mode}
+\item{.progress}{if `TRUE` will display a progress bar in interactive mode}
}
\value{
-a \code{tibble} with columns for user agent family, major & minor versions
- plus patch level along with os family and major & minor versions plus
+a data frame classed as tibble with columns for user agent family, major & minor versions
+ plus patch level along with OS family and major & minor versions plus
device brand and model.
}
\description{
-Takes in a character vector of user agent strings and returns a \code{tibble}
+Takes in a character vector of user agent strings and returns a data frame classed as tibble.
of parsed user agents.
}
\note{
@@ 29,5 29,5 @@ ua_parse(paste0("Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/535.2 (KHTML, ",
"Chrome/15.0.874.106 Safari/535.2", collapse=""))
}
\references{
-\url{http://www.uaparser.org/}
+<http://www.uaparser.org/>
}
M man/uaparserjs.Rd => man/uaparserjs.Rd +9 -1
@@ 3,7 3,15 @@
\docType{package}
\name{uaparserjs}
\alias{uaparserjs}
-\title{Parse Browser User Agent Strings into Data Frames}
+\title{Parse 'User-Agent' Strings}
+\description{
+Despite there being a section in RFC 7231
+<https://tools.ietf.org/html/rfc7231#section-5.5.3> defining a suggested
+structure for 'User-Agent' headers this data is notoriously difficult
+to parse consistently. Tools are provided that will take in user agent
+strings and return structured R objects. This is a 'V8'-backed package
+based on the 'ua-parser' project <https://github.com/ua-parser>.
+}
\author{
Bob Rudis (@hrbrmstr)
}