Skip to content

Commit

Permalink
fix: Embed 'braces' Javascript library within package
Browse files Browse the repository at this point in the history
``expand_braces()``, ``str_expand_braces()``, and ``glob()`` now support a new argument ``engine``:

  * If `'r'` use a pure R parser.
  * If `'v8'` use the 'braces' Javascript parser via the suggested V8 package.
  * If `NULL` use `'v8'` if `'V8'` package detected else use `'r'`;
    in either case send a `message()` about the choice
    unless `getOption(bracer.engine.inform')` is `FALSE`.

The 'braces' Javascript parser can handle some edge cases that the pure R parser cannot.

closes #4
  • Loading branch information
trevorld committed May 16, 2021
1 parent 83d9fa8 commit 64416e7
Show file tree
Hide file tree
Showing 15 changed files with 4,245 additions and 232 deletions.
4 changes: 4 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,7 @@ after_success:
r_github_packages:
- r-lib/covr
- jimhester/lintr
addons:
apt:
packages:
- libv8-dev
20 changes: 13 additions & 7 deletions DESCRIPTION
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
Package: bracer
Title: Brace Expansions
Version: 1.1.2
Version: 1.2.0
Authors@R:
person(given = "Trevor",
family = "Davis",
role = c("aut", "cre"),
email = "[email protected]")
c(person(given = "Trevor",
family = "Davis",
role = c("aut", "cre"),
email = "[email protected]"),
person(given = "Jon",
family = "Schlinkert",
role = "aut",
comment = "Author of the 'braces' Javascript library"))
Description: Performs brace expansions on strings. Made popular by Unix shells, brace expansion allows users to concisely generate certain character vectors by taking a single string and (recursively) expanding the comma-separated lists and double-period-separated integer and character sequences enclosed within braces in that string. The double-period-separated numeric integer expansion also supports padding the resulting numbers with zeros.
URL: https://github.com/trevorld/bracer
BugReports: https://github.com/trevorld/bracer/issues
Expand All @@ -14,5 +18,7 @@ License: MIT + file LICENSE
Encoding: UTF-8
LazyData: true
Suggests:
testthat
RoxygenNote: 7.0.0
testthat,
V8
Roxygen: list(markdown = TRUE)
RoxygenNote: 7.1.1
4 changes: 3 additions & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
YEAR: 2019
YEAR: 2019-2021
COPYRIGHT HOLDER: Trevor L. Davis
YEAR: 2014-2018
COPYRIGHT HOLDER: Jon Schlinkert
19 changes: 16 additions & 3 deletions NEWS.md
Original file line number Diff line number Diff line change
@@ -1,16 +1,29 @@
bracer 1.2.0
============

* ``expand_braces()``, ``str_expand_braces()``, and ``glob()`` now support a new argument ``engine`` (#4):

* If `'r'` use a pure R parser.
* If `'v8'` use the 'braces' Javascript parser via the suggested V8 package.
* If `NULL` use `'v8'` if `'V8'` package detected else use `'r'`;
in either case send a `message()` about the choice
unless `getOption(bracer.engine.inform')` is `FALSE`.

The 'braces' Javascript parser can handle some edge cases that the pure R parser cannot.

bracer 1.1.1
============

* ``expand_braces`` now handles vectorized input and returns one character vector with all the brace expansions. New function ``str_expand_braces`` offers an alternative that instead returns a list of character vectors.
* ``expand_braces()`` now handles vectorized input and returns one character vector with all the brace expansions. New function ``str_expand_braces()`` offers an alternative that instead returns a list of character vectors.
* New function ``glob`` provides a wrapper around ``Sys.glob`` that supports
both brace and wildcard expansion on file paths.

bracer 1.0.1
============

* ``expand_braces`` can now parse nested braces.
* ``expand_braces()`` can now parse nested braces.

bracer 0.1
==========

* Initial version of ``expand_braces`` function which has partial support for Bash-style brace expansion.
* Initial version of ``expand_braces()`` function which has partial support for Bash-style brace expansion.
41 changes: 3 additions & 38 deletions R/expand.R → R/engine-r.R
Original file line number Diff line number Diff line change
@@ -1,42 +1,9 @@
#' Bash-style brace expansion
#'
#' \code{expand_braces} performs brace expansions on strings,
#' \code{str_expand_braces} is an alternate function that returns a list of character vectors.
#' Made popular by Unix shells, brace expansion allows users to concisely generate
#' certain character vectors by taking a single string and (recursively) expanding
#' the comma-separated lists and double-period-separated integer and character
#' sequences enclosed within braces in that string.
#' The double-period-separated numeric integer expansion also supports padding the resulting numbers with zeros.
#' @param string input character vector
#' @return \code{expand_braces} returns a character vector while
#' \code{str_expand_braces} returns a list of character vectors.
#'
#' @examples
#' expand_braces("Foo{A..F}")
#' expand_braces("Foo{01..10}")
#' expand_braces("Foo{A..E..2}{1..5..2}")
#' expand_braces("Foo{-01..1}")
#' expand_braces("Foo{{d..d},{bar,biz}}.{py,bash}")
#' expand_braces(c("Foo{A..F}", "Bar.{py,bash}", "{{Biz}}"))
#' str_expand_braces(c("Foo{A..F}", "Bar.{py,bash}", "{{Biz}}"))
#' @import stringr
#' @export
expand_braces <- function(string) {
c(str_expand_braces(string), recursive = TRUE)
}

#' @rdname expand_braces
#' @export
str_expand_braces <- function(string) {
lapply(string, expand_braces_helper)
}

brace_token <- "(?<!\\\\)\\{([^}]|\\\\\\})*(?<!\\\\)\\}"
has_brace <- function(string) {
str_detect(string, brace_token)
}

expand_braces_helper <- function(string, process = TRUE) {
expand_braces_r <- function(string, process = TRUE) {
locations <- get_locations(string) # Find brace starts and ends
n <- nrow(locations)
if (n == 0) return(process_string(string))
Expand Down Expand Up @@ -117,7 +84,7 @@ expand_brace <- function(string, locations, i) {
} else if (has_periods(inner)) {
expand_periods(inner)
} else if (has_brace(inner)) {
paste0("{", expand_braces_helper(inner, FALSE), "}")
paste0("{", expand_braces_r(inner, FALSE), "}")
} else {
brace
}
Expand All @@ -131,7 +98,7 @@ has_comma <- function(string) {

expand_comma <- function(string) {
elements <- str_split(string, comma_token)[[1]]
elements <- lapply(elements, expand_braces_helper, FALSE)
elements <- lapply(elements, expand_braces_r, FALSE)
elements <- c(elements, recursive = TRUE)
elements
}
Expand Down Expand Up @@ -184,8 +151,6 @@ expand_periods <- function(string) {
} else {
values
}
} else {
paste0("{", string, "}")
}
}

Expand Down
12 changes: 12 additions & 0 deletions R/engine-v8.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
bracer_env <- new.env()

expand_braces_v8 <- function(string) {
if (is.null(bracer_env$js)) {
bracer_env$js <- V8::v8()
invisible(bracer_env$js$source(system.file("js/braces_bundle.js", package = "bracer")))
}
bracer_env$js$assign("string", string)
cmd <- str_glue("output = braces.expand(string, {{ rangeLimit: Infinity }});")
invisible(bracer_env$js$eval(cmd))
bracer_env$js$get("output")
}
55 changes: 55 additions & 0 deletions R/expand_braces.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
#' Bash-style brace expansion
#'
#' \code{expand_braces} performs brace expansions on strings,
#' \code{str_expand_braces} is an alternate function that returns a list of character vectors.
#' Made popular by Unix shells, brace expansion allows users to concisely generate
#' certain character vectors by taking a single string and (recursively) expanding
#' the comma-separated lists and double-period-separated integer and character
#' sequences enclosed within braces in that string.
#' The double-period-separated numeric integer expansion also supports padding the resulting numbers with zeros.
#' @param string input character vector
#' @param engine If `'r'` use a pure R parser.
#' If `'v8'` use the 'braces' Javascript parser via the suggested V8 package.
#' If `NULL` use `'v8'` if `'V8'` package detected else use `'r'`;
#' in either case send a `message()` about the choice
#' unless `getOption(bracer.engine.inform')` is `FALSE`.
#' @return \code{expand_braces} returns a character vector while
#' \code{str_expand_braces} returns a list of character vectors.
#'
#' @examples
#' expand_braces("Foo{A..F}")
#' expand_braces("Foo{01..10}")
#' expand_braces("Foo{A..E..2}{1..5..2}")
#' expand_braces("Foo{-01..1}")
#' expand_braces("Foo{{d..d},{bar,biz}}.{py,bash}")
#' expand_braces(c("Foo{A..F}", "Bar.{py,bash}", "{{Biz}}"))
#' str_expand_braces(c("Foo{A..F}", "Bar.{py,bash}", "{{Biz}}"))
#' @import stringr
#' @export
expand_braces <- function(string, engine = getOption("bracer.engine", NULL)) {
c(str_expand_braces(string, engine = engine), recursive = TRUE)
}

#' @rdname expand_braces
#' @export
str_expand_braces <- function(string, engine = getOption("bracer.engine", NULL)) {
if (!is.null(engine)) engine <- tolower(engine)
if (is.null(engine)) {
if (requireNamespace("V8", quietly = TRUE)) {
engine <- "v8"
if (!isTRUE(getOption("bracer.engine.inform")))
message("Setting 'engine' argument to 'v8' (suggested package 'V8' detected)")
} else {
engine <- "r"
if (!isTRUE(getOption("bracer.engine.inform")))
warning("Setting 'engine' argument to 'r' (suggested package 'V8' not detected)")
}
}
if (engine == "v8" && !requireNamespace("V8", quietly = TRUE)) {
engine <- "r"
message("Suggested package 'V8' not detected. Instead setting 'engine' argument to 'r'")
}
switch(engine,
v8 = lapply(string, expand_braces_v8),
r = lapply(string, expand_braces_r))
}
5 changes: 3 additions & 2 deletions R/glob.R
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,14 @@
#' \code{expand_braces} to support both brace and wildcard
#' expansion on file paths.
#' @param paths character vector of patterns for relative or absolute filepaths.
#' @inheritParams expand_braces
#' @param ... Passed to \code{Sys.glob}
#' @examples
#' dir <- system.file("R", package="bracer")
#' path <- file.path(dir, "*.{R,r,S,s}")
#' glob(path)
#' @export
glob <- function(paths, ...) {
paths <- expand_braces(paths)
glob <- function(paths, ..., engine = getOption("bracer.engine", NULL)) {
paths <- expand_braces(paths, engine = engine)
Sys.glob(paths, ...)
}
33 changes: 30 additions & 3 deletions README.Rmd
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ Examples

```{r examples}
library("bracer")
options(bracer.engine = "r")
expand_braces("Foo{A..F}")
expand_braces("Foo{01..10}")
expand_braces("Foo{A..E..2}{1..5..2}")
Expand Down Expand Up @@ -49,13 +50,39 @@ To install the developmental version use the following command in R:
remotes::install_github("trevorld/bracer")
```

Caveats
-------
Installing the suggested ``V8`` package will enable use of the javascript alternative parser:

``bracer`` currently does not properly support the "correct" (Bash-style) brace expansion under several edge conditions such as:
```{r install_v8, eval=FALSE}
install.packages("V8")
```

Limitations of pure R parser and alternative javascript parser
--------------------------------------------------------------

The ``bracer`` pure R parser currently does not properly support the "correct" (Bash-style) brace expansion under several edge conditions such as:

1. Unbalanced braces e.g. ``{{a,d}`` (but you could use an escaped brace instead ``\\{{a,d}``)
2. Using surrounding quotes to escape terms e.g. ``{'a,b','c'}`` (but you could use an escaped comma instead ``{a\\,b,c}``)
3. Escaped braces within comma-separated lists e.g. ``{a,b\\}c,d}``
4. (Non-escaping) backslashes before braces e.g. ``{a,\\\\{a,b}c}``
5. Sequences from letters to non-letter ASCII characters e.g. ``X{a..#}X``

```{r r_engine}
options(bracer.engine = "r")
expand_braces("{{a,d}")
expand_braces("{'a,b','c'}")
expand_braces("{a,b\\}c,d}")
expand_braces("{a,\\\\{a,b}c}")
expand_braces("X{a..#}X")
```

However if the 'V8' suggested R package is installed we can instead use an embedded version of the [braces](https://github.com/micromatch/braces) Javascript library which can correctly handle these edge cases. To do so we need to set the bracer "engine" to "v8".

```{r v8_engine}
options(bracer.engine = "v8")
expand_braces("{{a,d}")
expand_braces("{'a,b','c'}")
expand_braces("{a,b\\}c,d}")
expand_braces("{a,\\\\{a,b}c}")
expand_braces("X{a..#}X")
```
Loading

0 comments on commit 64416e7

Please sign in to comment.