[med-svn] [r-cran-glue] 01/02: New upstream version 1.1.1
Andreas Tille
tille at debian.org
Sat Sep 30 11:39:47 UTC 2017
This is an automated email from the git hooks/post-receive script.
tille pushed a commit to branch master
in repository r-cran-glue.
commit b754e459735926b16f9613c85735400a5056d70d
Author: Andreas Tille <tille at debian.org>
Date: Sat Sep 30 13:39:14 2017 +0200
New upstream version 1.1.1
---
DESCRIPTION | 23 ++++
LICENSE | 2 +
MD5 | 20 +++
NAMESPACE | 17 +++
NEWS.md | 24 ++++
R/glue.R | 205 +++++++++++++++++++++++++++
R/quoting.R | 25 ++++
R/utils.R | 59 ++++++++
README.md | 136 ++++++++++++++++++
man/as_glue.Rd | 16 +++
man/collapse.Rd | 31 +++++
man/glue.Rd | 65 +++++++++
man/quoting.Rd | 26 ++++
man/trim.Rd | 41 ++++++
src/glue.c | 169 +++++++++++++++++++++++
src/init.c | 17 +++
src/trim.c | 104 ++++++++++++++
tests/testthat.R | 4 +
tests/testthat/test-collapse.R | 36 +++++
tests/testthat/test-glue.R | 306 +++++++++++++++++++++++++++++++++++++++++
tests/testthat/test-quoting.R | 37 +++++
21 files changed, 1363 insertions(+)
diff --git a/DESCRIPTION b/DESCRIPTION
new file mode 100644
index 0000000..085f2cf
--- /dev/null
+++ b/DESCRIPTION
@@ -0,0 +1,23 @@
+Package: glue
+Title: Interpreted String Literals
+Version: 1.1.1
+Authors at R: person("Jim", "Hester", email = "james.f.hester at gmail.com", role = c("aut", "cre"))
+Description: An implementation of interpreted string literals, inspired by
+ Python's Literal String Interpolation <https://www.python.org/dev/peps/pep-0498/> and Docstrings
+ <https://www.python.org/dev/peps/pep-0257/> and Julia's Triple-Quoted String Literals
+ <https://docs.julialang.org/en/stable/manual/strings/#triple-quoted-string-literals>.
+Depends: R (>= 3.0.0)
+Suggests: testthat, covr, magrittr
+Imports: methods
+License: MIT + file LICENSE
+Encoding: UTF-8
+LazyData: true
+RoxygenNote: 6.0.1
+URL: https://github.com/tidyverse/glue
+BugReports: https://github.com/tidyverse/glue/issues
+NeedsCompilation: yes
+Packaged: 2017-06-16 20:43:57 UTC; jhester
+Author: Jim Hester [aut, cre]
+Maintainer: Jim Hester <james.f.hester at gmail.com>
+Repository: CRAN
+Date/Publication: 2017-06-21 16:58:04 UTC
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..74f1fa5
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,2 @@
+YEAR: 2016
+COPYRIGHT HOLDER: Jim Hester
diff --git a/MD5 b/MD5
new file mode 100644
index 0000000..e465622
--- /dev/null
+++ b/MD5
@@ -0,0 +1,20 @@
+72f5ccfd0b45afdb6322b55706e43cd1 *DESCRIPTION
+e2965db868cda3b9ce7b138d8ca0e6bc *LICENSE
+3023c02be14302be414a033b320150f0 *NAMESPACE
+30b708333d07888a2609cf8504300116 *NEWS.md
+858d262f41a65cbda70396fb0376f201 *R/glue.R
+f25d8ed7f568473df44c353211f35c9d *R/quoting.R
+5b08b26995895d52d23cf420410af203 *R/utils.R
+01e9ce19a9e51dba11f6c745f77eeb47 *README.md
+428090ffb747ed472a50927771a36cce *man/as_glue.Rd
+d10e30c9bc0e43bf0e6c26e0dc538912 *man/collapse.Rd
+fc6862968d9748444945e399a558592a *man/glue.Rd
+5665d168b1fb64b49e96dfd9302184d9 *man/quoting.Rd
+cb80548db4ef7abb94efcf3bdcd1747d *man/trim.Rd
+4263ac45e523eb8ffcac5f256fd501a5 *src/glue.c
+57c4e91a5ecf31acbbc061d6650584bb *src/init.c
+b4073f521cc4cda21bebf9ee940ebda8 *src/trim.c
+2b2d5c82e65ffac3ce2300a7ba32fa68 *tests/testthat.R
+01d71156a148c78d2332399e61f4df6b *tests/testthat/test-collapse.R
+ac32d193a2bfb810c359f1c107674c64 *tests/testthat/test-glue.R
+05436a79623c08d7daa419af36fe110e *tests/testthat/test-quoting.R
diff --git a/NAMESPACE b/NAMESPACE
new file mode 100644
index 0000000..a7bef96
--- /dev/null
+++ b/NAMESPACE
@@ -0,0 +1,17 @@
+# Generated by roxygen2: do not edit by hand
+
+S3method(as.character,glue)
+S3method(as_glue,character)
+S3method(as_glue,default)
+S3method(as_glue,glue)
+S3method(print,glue)
+export(as_glue)
+export(backtick)
+export(collapse)
+export(double_quote)
+export(glue)
+export(glue_data)
+export(single_quote)
+importFrom(methods,setOldClass)
+useDynLib(glue,glue_)
+useDynLib(glue,trim_)
diff --git a/NEWS.md b/NEWS.md
new file mode 100644
index 0000000..155b5ad
--- /dev/null
+++ b/NEWS.md
@@ -0,0 +1,24 @@
+# glue 1.1.1
+
+Another fix for PROTECT / REPROTECT found by the rchk static analyzer.
+
+# glue 1.1.0
+
+Fix for PROTECT errors when resizing output strings.
+
+`glue()` always returns 'UTF-8' strings, converting inputs if in other
+encodings if needed.
+
+`to()` and `to_data()` have been removed.
+
+`glue()` and `glue_data()` can now take alternative delimiters to `{` and `}`.
+This is useful if you are writing to a format that uses a lot of braces, such
+as LaTeX. (#23)
+
+`collapse()` now returns 0 length output if given 0 length input (#28).
+
+# glue 0.0.0.9000
+
+* Fix `glue()` to admit `.` as an embedded expression in a string (#15, @egnha).
+
+* Added a `NEWS.md` file to track changes to the package.
diff --git a/R/glue.R b/R/glue.R
new file mode 100644
index 0000000..a23c7e2
--- /dev/null
+++ b/R/glue.R
@@ -0,0 +1,205 @@
+#' Format and interpolate a string
+#'
+#' Expressions enclosed by braces will be evaluated as R code. Single braces
+#' can be inserted by doubling them.
+#' @param .x \[`listish`]\cr An environment, list or data frame used to lookup values.
+#' @param ... \[`expressions`]\cr Expressions string(s) to format, multiple inputs are concatenated together before formatting.
+#' @param .sep \[`character(1)`: \sQuote{""}]\cr Separator used to separate elements.
+#' @param .envir \[`environment`: `parent.frame()`]\cr Environment to evaluate each expression in. Expressions are
+#' evaluated from left to right. If `.x` is an environment, the expressions are
+#' evaluated in that environment and `.envir` is ignored.
+#' @param .open \[`character(1)`: \sQuote{\\\{}]\cr The opening delimiter. Doubling the
+#' full delimiter escapes it.
+#' @param .close \[`character(1)`: \sQuote{\\\}}]\cr The closing delimiter. Doubling the
+#' full delimiter escapes it.
+#' @seealso <https://www.python.org/dev/peps/pep-0498/> and
+#' <https://www.python.org/dev/peps/pep-0257> upon which this is based.
+#' @examples
+#' name <- "Fred"
+#' age <- 50
+#' anniversary <- as.Date("1991-10-12")
+#' glue('My name is {name},',
+#' 'my age next year is {age + 1},',
+#' 'my anniversary is {format(anniversary, "%A, %B %d, %Y")}.')
+#'
+#' # single braces can be inserted by doubling them
+#' glue("My name is {name}, not {{name}}.")
+#'
+#' # Named arguments can also be supplied
+#' glue('My name is {name},',
+#' ' my age next year is {age + 1},',
+#' ' my anniversary is {format(anniversary, "%A, %B %d, %Y")}.',
+#' name = "Joe",
+#' age = 40,
+#' anniversary = as.Date("2001-10-12"))
+#'
+#' # `glue_data()` is useful in magrittr pipes
+#' library(magrittr)
+#' mtcars %>% glue_data("{rownames(.)} has {hp} hp")
+#'
+#' # Alternative delimiters can also be used if needed
+#' one <- "1"
+#' glue("The value of $e^{2\\pi i}$ is $<<one>>$.", .open = "<<", .close = ">>")
+#' @useDynLib glue glue_
+#' @name glue
+#' @export
+glue_data <- function(.x, ..., .sep = "", .envir = parent.frame(), .open = "{", .close = "}") {
+
+ # Perform all evaluations in a temporary environment
+ if (is.environment(.x)) {
+ env <- new.env(parent = .x)
+ .envir <- NULL
+ } else {
+ env <- new.env(parent = .envir)
+ }
+
+ # Capture unevaluated arguments
+ dots <- eval(substitute(alist(...)))
+ named <- has_names(dots)
+
+ # Evaluate named arguments, add results to environment
+ assign_args(dots[named], envir = env, data = .x)
+
+ # Concatenate unnamed arguments together
+ unnamed_args <- eval_args(dots[!named], envir = env, data = .x)
+
+ lengths <- lengths(unnamed_args)
+ if (any(lengths == 0)) {
+ return(as_glue(character(0)))
+ }
+ if (any(lengths != 1)) {
+ stop("All unnamed arguments must be length 1", call. = FALSE)
+ }
+ unnamed_args <- paste0(unnamed_args, collapse = .sep)
+ unnamed_args <- trim(unnamed_args)
+
+ # Parse any glue strings
+ res <- .Call(glue_, unnamed_args,
+ function(expr)
+ enc2utf8(
+ as.character(
+ eval2(parse(text = expr), envir = env, data = .x))),
+ .open, .close)
+
+ if (any(lengths(res) == 0)) {
+ return(as_glue(character(0)))
+ }
+
+ res <- do.call(paste0, recycle_columns(res))
+
+ as_glue(res)
+}
+
+#' @export
+#' @rdname glue
+glue <- function(..., .sep = "", .envir = parent.frame(), .open = "{", .close = "}") {
+ glue_data(.x = NULL, ..., .sep = .sep, .envir = .envir, .open = .open, .close = .close)
+}
+
+#' Collapse a character vector
+#'
+#' Collapses a character vector of any length into a length 1 vector.
+#' @param x The character vector to collapse.
+#' @param width The maximum string width before truncating with `...`.
+#' @param last String used to separate the last two items if `x` has at least
+#' 2 items.
+#' @inheritParams base::paste
+#' @examples
+#' collapse(glue("{1:10}"))
+#'
+#' # Wide values can be truncated
+#' collapse(glue("{1:10}"), width = 5)
+#'
+#' collapse(1:4, ",", last = " and ")
+#' #> 1, 2, 3 and 4
+#' @export
+collapse <- function(x, sep = "", width = Inf, last = "") {
+ if (length(x) == 0) {
+ return(as_glue(character()))
+ }
+ if (nzchar(last) && length(x) > 1) {
+ res <- collapse(x[seq(1, length(x) - 1)], sep = sep, width = Inf)
+ return(collapse(glue(res, last, x[length(x)]), width = width))
+ }
+ x <- paste0(x, collapse = sep)
+ if (width < Inf) {
+ x_width <- nchar(x, "width")
+ too_wide <- x_width > width
+ if (too_wide) {
+ x <- paste0(substr(x, 1, width - 3), "...")
+ }
+ }
+ as_glue(x)
+}
+
+#' Trim a character vector
+#'
+#' This trims a character vector according to the trimming rules used by glue.
+#' These follow similar rules to [Python Docstrings](https://www.python.org/dev/peps/pep-0257),
+#' with the following features.
+#'
+#' - Leading and trailing whitespace from the first and last lines is removed.
+#' - A uniform amount of indentation is stripped from the second line on, equal
+#' to the minimum indentation of all non-blank lines after the first.
+#' - Lines can be continued across newlines by using `\\`.
+#' @param x A character vector to trim.
+#' @examples
+#' glue("
+#' A formatted string
+#' Can have multiple lines
+#' with additional indention preserved
+#' ")
+#'
+#' glue("
+#' \\ntrailing or leading newlines can be added explicitly\\n
+#' ")
+#'
+#' glue("
+#' A formatted string \\
+#' can also be on a \\
+#' single line
+#' ")
+
+#' @useDynLib glue trim_
+trim <- function(x) {
+ .Call(trim_, x)
+}
+
+#' @export
+print.glue <- function(x, ..., sep = "\n") {
+ cat(x, ..., sep = sep)
+
+ invisible(x)
+}
+
+#' Coerce object to glue
+#' @param x object to be coerced.
+#' @param ... further arguments passed to methods.
+#' @export
+as_glue <- function(x, ...) {
+ UseMethod("as_glue")
+}
+
+#' @export
+as_glue.default <- function(x, ...) {
+ as_glue(as.character(x))
+}
+
+#' @export
+as_glue.glue <- function(x, ...) {
+ x
+}
+
+#' @export
+as_glue.character <- function(x, ...) {
+ structure(x, class = c("glue", "character"))
+}
+
+#' @export
+as.character.glue <- function(x, ...) {
+ unclass(x)
+}
+
+#' @importFrom methods setOldClass
+
+setOldClass(c("glue", "character"))
diff --git a/R/quoting.R b/R/quoting.R
new file mode 100644
index 0000000..df1e5f6
--- /dev/null
+++ b/R/quoting.R
@@ -0,0 +1,25 @@
+#' Quoting operators
+#'
+#' These functions make it easy to quote each individual element and are useful
+#' in conjunction with `collapse()`.
+#' @param x A character to quote.
+#' @name quoting
+#' @export
+#' @examples
+#' x <- 1:5
+#' glue('Values of x: {collapse(backtick(x), sep = ", ", last = " and ")}')
+single_quote <- function(x) {
+ encodeString(x, quote = "'")
+}
+
+#' @rdname quoting
+#' @export
+double_quote <- function(x) {
+ encodeString(x, quote = '"')
+}
+
+#' @rdname quoting
+#' @export
+backtick <- function(x) {
+ encodeString(x, quote = "`")
+}
diff --git a/R/utils.R b/R/utils.R
new file mode 100644
index 0000000..f5d617d
--- /dev/null
+++ b/R/utils.R
@@ -0,0 +1,59 @@
+has_names <- function(x) {
+ nms <- names(x)
+ if (is.null(nms)) {
+ rep(FALSE, length(x))
+ } else {
+ !(is.na(nms) | nms == "")
+ }
+}
+
+# Use an explicit for loop rather than lapply to show evaluation order matters
+eval_args <- function(args, envir, data) {
+ res <- vector("list", length(args))
+ for (i in seq_along(args)) {
+ res[[i]] <- eval2(args[[i]], envir = envir, data = data)
+ }
+ names(res) <- names(args)
+ res
+}
+
+assign_args <- function(args, envir, data) {
+ res <- vector("list", length(args))
+ nms <- names(args)
+ for (i in seq_along(args)) {
+ assign(nms[[i]], eval2(args[[i]], envir = envir, data = data), envir = envir)
+ }
+}
+
+eval2 <- function(x, envir = parent.frame(), data = NULL) {
+ if (is.null(data)) {
+ eval(x, envir = envir)
+ } else {
+ eval(x, envir = data, enclos = envir)
+ }
+}
+
+# From tibble::recycle_columns
+recycle_columns <- function (x)
+{
+ if (length(x) == 0) {
+ return(x)
+ }
+ lengths <- vapply(x, NROW, integer(1))
+ if (any(lengths) == 0) {
+ return(character())
+ }
+ max <- max(lengths)
+ bad_len <- lengths != 1L & lengths != max
+ if (any(bad_len)) {
+ stop(call. = FALSE,
+ ngettext(max,
+ "Variables must be length 1",
+ paste0("Variables must be length 1 or ", max), domain = NA))
+ }
+ short <- lengths == 1
+ if (max != 1L && any(short)) {
+ x[short] <- lapply(x[short], rep, max)
+ }
+ x
+}
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..3283421
--- /dev/null
+++ b/README.md
@@ -0,0 +1,136 @@
+
+<!-- README.md is generated from README.Rmd. Please edit that file -->
+glue
+====
+
+[![CRAN\_Status\_Badge](http://www.r-pkg.org/badges/version/glue)](http://cran.r-project.org/package=glue) [![Travis-CI Build Status](http://travis-ci.org/tidyverse/glue.svg?branch=master)](http://travis-ci.org/tidyverse/glue) [![Coverage Status](http://img.shields.io/codecov/c/github/tidyverse/glue/master.svg)](http://codecov.io/github/tidyverse/glue?branch=master) [![AppVeyor Build Status](http://ci.appveyor.com/api/projects/status/github/tidyverse/glue?branch=master&svg=true)](http:// [...]
+
+Glue strings to data in R. Small, fast, dependency free interpreted string literals.
+
+Installation
+------------
+
+``` r
+# install.packages("devtools")
+devtools::install_github("tidyverse/glue")
+```
+
+Usage
+-----
+
+##### Long strings are broken by line and concatenated together.
+
+``` r
+name <- "Fred"
+age <- 50
+anniversary <- as.Date("1991-10-12")
+glue('My name is {name},',
+ ' my age next year is {age + 1},',
+ ' my anniversary is {format(anniversary, "%A, %B %d, %Y")}.')
+#> My name is Fred, my age next year is 51, my anniversary is Saturday, October 12, 1991.
+```
+
+##### Named arguments are used to assign temporary variables.
+
+``` r
+glue('My name is {name},',
+ ' my age next year is {age + 1},',
+ ' my anniversary is {format(anniversary, "%A, %B %d, %Y")}.',
+ name = "Joe",
+ age = 40,
+ anniversary = as.Date("2001-10-12"))
+#> My name is Joe, my age next year is 41, my anniversary is Friday, October 12, 2001.
+```
+
+##### `glue_data()` is useful with [magrittr](http://cran.r-project.org/package=magrittr) pipes.
+
+``` r
+`%>%` <- magrittr::`%>%`
+head(mtcars) %>% glue_data("{rownames(.)} has {hp} hp")
+#> Mazda RX4 has 110 hp
+#> Mazda RX4 Wag has 110 hp
+#> Datsun 710 has 93 hp
+#> Hornet 4 Drive has 110 hp
+#> Hornet Sportabout has 175 hp
+#> Valiant has 105 hp
+```
+
+##### Leading whitespace and blank lines from the first and last lines are automatically trimmed.
+
+This lets you indent the strings naturally in code.
+
+``` r
+glue("
+ A formatted string
+ Can have multiple lines
+ with additional indention preserved
+ ")
+#> A formatted string
+#> Can have multiple lines
+#> with additional indention preserved
+```
+
+##### An additional newline can be used if you want a leading or trailing newline.
+
+``` r
+glue("
+
+ leading or trailing newlines can be added explicitly
+
+ ")
+#>
+#> leading or trailing newlines can be added explicitly
+```
+
+##### `\\` at the end of a line continues it without a new line.
+
+``` r
+glue("
+ A formatted string \\
+ can also be on a \\
+ single line
+ ")
+#> A formatted string can also be on a single line
+```
+
+##### A literal brace is inserted by using doubled braces.
+
+``` r
+name <- "Fred"
+glue("My name is {name}, not {{name}}.")
+#> My name is Fred, not {name}.
+```
+
+##### Alternative delimiters can be specified with `.open` and `.close`.
+
+``` r
+one <- "1"
+glue("The value of $e^{2\\pi i}$ is $<<one>>$.", .open = "<<", .close = ">>")
+#> The value of $e^{2\pi i}$ is $1$.
+```
+
+##### All valid R code works in expressions, including braces and escaping.
+
+Backslashes do need to be doubled just like in all R strings.
+
+``` r
+ `foo}\`` <- "foo"
+glue("{
+ {
+ '}\\'' # { and } in comments, single quotes
+ \"}\\\"\" # or double quotes are ignored
+ `foo}\\`` # as are { in backticks
+ }
+ }")
+#> foo
+```
+
+Other implementations
+=====================
+
+Some other implementations of string interpolation in R (although not using identical syntax).
+
+- [stringr::str\_interp](http://stringr.tidyverse.org/reference/str_interp.html)
+- [pystr::pystr\_format](http://cran.r-project.org/package=pystr)
+- [R.utils::gstring](http://cran.r-project.org/package=R.utils)
+- [rprintf](http://cran.r-project.org/package=rprintf)
diff --git a/man/as_glue.Rd b/man/as_glue.Rd
new file mode 100644
index 0000000..f424be9
--- /dev/null
+++ b/man/as_glue.Rd
@@ -0,0 +1,16 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/glue.R
+\name{as_glue}
+\alias{as_glue}
+\title{Coerce object to glue}
+\usage{
+as_glue(x, ...)
+}
+\arguments{
+\item{x}{object to be coerced.}
+
+\item{...}{further arguments passed to methods.}
+}
+\description{
+Coerce object to glue
+}
diff --git a/man/collapse.Rd b/man/collapse.Rd
new file mode 100644
index 0000000..775b647
--- /dev/null
+++ b/man/collapse.Rd
@@ -0,0 +1,31 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/glue.R
+\name{collapse}
+\alias{collapse}
+\title{Collapse a character vector}
+\usage{
+collapse(x, sep = "", width = Inf, last = "")
+}
+\arguments{
+\item{x}{The character vector to collapse.}
+
+\item{sep}{a character string to separate the terms. Not
+ \code{\link{NA_character_}}.}
+
+\item{width}{The maximum string width before truncating with \code{...}.}
+
+\item{last}{String used to separate the last two items if \code{x} has at least
+2 items.}
+}
+\description{
+Collapses a character vector of any length into a length 1 vector.
+}
+\examples{
+collapse(glue("{1:10}"))
+
+# Wide values can be truncated
+collapse(glue("{1:10}"), width = 5)
+
+collapse(1:4, ",", last = " and ")
+#> 1, 2, 3 and 4
+}
diff --git a/man/glue.Rd b/man/glue.Rd
new file mode 100644
index 0000000..aee6e90
--- /dev/null
+++ b/man/glue.Rd
@@ -0,0 +1,65 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/glue.R
+\name{glue}
+\alias{glue}
+\alias{glue_data}
+\alias{glue}
+\title{Format and interpolate a string}
+\usage{
+glue_data(.x, ..., .sep = "", .envir = parent.frame(), .open = "{",
+ .close = "}")
+
+glue(..., .sep = "", .envir = parent.frame(), .open = "{", .close = "}")
+}
+\arguments{
+\item{.x}{[\code{listish}]\cr An environment, list or data frame used to lookup values.}
+
+\item{...}{[\code{expressions}]\cr Expressions string(s) to format, multiple inputs are concatenated together before formatting.}
+
+\item{.sep}{[\code{character(1)}: \sQuote{""}]\cr Separator used to separate elements.}
+
+\item{.envir}{[\code{environment}: \code{parent.frame()}]\cr Environment to evaluate each expression in. Expressions are
+evaluated from left to right. If \code{.x} is an environment, the expressions are
+evaluated in that environment and \code{.envir} is ignored.}
+
+\item{.open}{[\code{character(1)}: \sQuote{\{}]\cr The opening delimiter. Doubling the
+full delimiter escapes it.}
+
+\item{.close}{[\code{character(1)}: \sQuote{\}}]\cr The closing delimiter. Doubling the
+full delimiter escapes it.}
+}
+\description{
+Expressions enclosed by braces will be evaluated as R code. Single braces
+can be inserted by doubling them.
+}
+\examples{
+name <- "Fred"
+age <- 50
+anniversary <- as.Date("1991-10-12")
+glue('My name is {name},',
+ 'my age next year is {age + 1},',
+ 'my anniversary is {format(anniversary, "\%A, \%B \%d, \%Y")}.')
+
+# single braces can be inserted by doubling them
+glue("My name is {name}, not {{name}}.")
+
+# Named arguments can also be supplied
+glue('My name is {name},',
+ ' my age next year is {age + 1},',
+ ' my anniversary is {format(anniversary, "\%A, \%B \%d, \%Y")}.',
+ name = "Joe",
+ age = 40,
+ anniversary = as.Date("2001-10-12"))
+
+# `glue_data()` is useful in magrittr pipes
+library(magrittr)
+mtcars \%>\% glue_data("{rownames(.)} has {hp} hp")
+
+# Alternative delimiters can also be used if needed
+one <- "1"
+glue("The value of $e^{2\\\\pi i}$ is $<<one>>$.", .open = "<<", .close = ">>")
+}
+\seealso{
+\url{https://www.python.org/dev/peps/pep-0498/} and
+\url{https://www.python.org/dev/peps/pep-0257} upon which this is based.
+}
diff --git a/man/quoting.Rd b/man/quoting.Rd
new file mode 100644
index 0000000..e25e6f6
--- /dev/null
+++ b/man/quoting.Rd
@@ -0,0 +1,26 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/quoting.R
+\name{quoting}
+\alias{quoting}
+\alias{single_quote}
+\alias{double_quote}
+\alias{backtick}
+\title{Quoting operators}
+\usage{
+single_quote(x)
+
+double_quote(x)
+
+backtick(x)
+}
+\arguments{
+\item{x}{A character to quote.}
+}
+\description{
+These functions make it easy to quote each individual element and are useful
+in conjunction with \code{collapse()}.
+}
+\examples{
+x <- 1:5
+glue('Values of x: {collapse(backtick(x), sep = ", ", last = " and ")}')
+}
diff --git a/man/trim.Rd b/man/trim.Rd
new file mode 100644
index 0000000..4e8ab9f
--- /dev/null
+++ b/man/trim.Rd
@@ -0,0 +1,41 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/glue.R
+\name{trim}
+\alias{trim}
+\title{Trim a character vector}
+\usage{
+trim(x)
+}
+\arguments{
+\item{x}{A character vector to trim.}
+}
+\description{
+This trims a character vector according to the trimming rules used by glue.
+These follow similar rules to \href{https://www.python.org/dev/peps/pep-0257}{Python Docstrings},
+with the following features.
+}
+\details{
+\itemize{
+\item Leading and trailing whitespace from the first and last lines is removed.
+\item A uniform amount of indentation is stripped from the second line on, equal
+to the minimum indentation of all non-blank lines after the first.
+\item Lines can be continued across newlines by using \code{\\}.
+}
+}
+\examples{
+glue("
+ A formatted string
+ Can have multiple lines
+ with additional indention preserved
+ ")
+
+glue("
+ \\\\ntrailing or leading newlines can be added explicitly\\\\n
+ ")
+
+glue("
+ A formatted string \\\\
+ can also be on a \\\\
+ single line
+ ")
+}
diff --git a/src/glue.c b/src/glue.c
new file mode 100644
index 0000000..1bd5b95
--- /dev/null
+++ b/src/glue.c
@@ -0,0 +1,169 @@
+#include <stdlib.h>
+#include <string.h>
+#include "Rinternals.h"
+
+SEXP set(SEXP x, int i, SEXP val) {
+ size_t len = Rf_length(x);
+ if (i >= len) {
+ // Gives us the growth sequence 3, 5, 9, 17, ...
+ // This works well because for the glue case the final number of elements
+ // will always be odd, and for common cases is 3 or 5.
+ len = (len * 2) - 1;
+ x = Rf_lengthgets(x, len);
+ }
+ SET_VECTOR_ELT(x, i, val);
+ return x;
+}
+
+SEXP glue_(SEXP x, SEXP f, SEXP open_arg, SEXP close_arg) {
+ typedef enum {
+ text,
+ escape,
+ single_quote,
+ double_quote,
+ backtick,
+ delim,
+ comment
+ } states;
+
+ const char* xx = Rf_translateCharUTF8(STRING_ELT(x, 0));
+ size_t str_len = strlen(xx);
+
+ char* str = (char*)malloc(str_len + 1);
+
+ const char* open = CHAR(STRING_ELT(open_arg, 0));
+ size_t open_len = strlen(open);
+
+ const char* close = CHAR(STRING_ELT(close_arg, 0));
+ size_t close_len = strlen(close);
+
+ SEXP out = Rf_allocVector(VECSXP, 3);
+ PROTECT_INDEX out_idx;
+ PROTECT_WITH_INDEX(out, &out_idx);
+
+ size_t j = 0;
+ size_t k = 0;
+ int delim_level = 0;
+ size_t start = 0;
+ states state = text;
+ states prev_state = text;
+ for (size_t i = 0; i < str_len; ++i) {
+ switch (state) {
+ case text: {
+ if (strncmp(&xx[i], open, open_len) == 0) {
+ /* check for open delim doubled */
+ if (strncmp(&xx[i + open_len], open, open_len) == 0) {
+ i += open_len;
+ } else {
+ state = delim;
+ delim_level = 1;
+ start = i + open_len;
+ break;
+ }
+ }
+ if (strncmp(&xx[i], close, close_len) == 0 &&
+ strncmp(&xx[i + close_len], close, close_len) == 0) {
+ i += close_len;
+ }
+
+ str[j++] = xx[i];
+ break;
+ }
+ case escape: {
+ state = prev_state;
+ break;
+ }
+ case single_quote: {
+ if (xx[i] == '\\') {
+ prev_state = single_quote;
+ state = escape;
+ } else if (xx[i] == '\'') {
+ state = delim;
+ }
+ break;
+ }
+ case double_quote: {
+ if (xx[i] == '\\') {
+ prev_state = double_quote;
+ state = escape;
+ } else if (xx[i] == '\"') {
+ state = delim;
+ }
+ break;
+ }
+ case backtick: {
+ if (xx[i] == '\\') {
+ prev_state = backtick;
+ state = escape;
+ } else if (xx[i] == '`') {
+ state = delim;
+ }
+ break;
+ }
+ case comment: {
+ if (xx[i] == '\n') {
+ state = delim;
+ }
+ break;
+ }
+ case delim: {
+ if (strncmp(&xx[i], open, open_len) == 0) {
+ ++delim_level;
+ i += open_len - 1;
+ } else if (strncmp(&xx[i], close, close_len) == 0) {
+ --delim_level;
+ i += close_len - 1;
+ } else {
+ switch (xx[i]) {
+ case '\'':
+ state = single_quote;
+ break;
+ case '"':
+ state = double_quote;
+ break;
+ case '`':
+ state = backtick;
+ break;
+ case '#':
+ state = comment;
+ break;
+ };
+ }
+ if (delim_level == 0) {
+ // Result of the current glue statement
+ SEXP expr = PROTECT(Rf_ScalarString(
+ Rf_mkCharLen(&xx[start], (i - close_len) + 1 - start)));
+ SEXP call = PROTECT(Rf_lang2(f, expr));
+ SEXP result = PROTECT(Rf_eval(call, R_GlobalEnv));
+
+ // text in between last glue statement
+ str[j] = '\0';
+ SEXP str_ = PROTECT(Rf_ScalarString(Rf_mkCharLenCE(str, j, CE_UTF8)));
+ REPROTECT(out = set(out, k++, str_), out_idx);
+
+ REPROTECT(out = set(out, k++, result), out_idx);
+
+ // Clear the string buffer
+ memset(str, 0, j);
+ j = 0;
+ UNPROTECT(4);
+ state = text;
+ }
+ break;
+ }
+ };
+ }
+
+ str[j] = '\0';
+ REPROTECT(out = Rf_lengthgets(out, k + 1), out_idx);
+ SET_VECTOR_ELT(out, k, Rf_ScalarString(Rf_mkCharLenCE(str, j, CE_UTF8)));
+
+ if (state == delim) {
+ Rf_error("Expecting '%s'", close);
+ }
+
+ free(str);
+
+ UNPROTECT(1);
+ return out;
+}
diff --git a/src/init.c b/src/init.c
new file mode 100644
index 0000000..d73bba1
--- /dev/null
+++ b/src/init.c
@@ -0,0 +1,17 @@
+#include <R.h>
+#include <R_ext/Rdynload.h>
+#include <Rinternals.h>
+#include <stdlib.h> // for NULL
+
+/* .Call calls */
+extern SEXP glue_(SEXP, SEXP);
+extern SEXP trim_(SEXP);
+
+static const R_CallMethodDef CallEntries[] = {{"glue_", (DL_FUNC)&glue_, 4},
+ {"trim_", (DL_FUNC)&trim_, 1},
+ {NULL, NULL, 0}};
+
+void R_init_glue(DllInfo *dll) {
+ R_registerRoutines(dll, NULL, CallEntries, NULL, NULL);
+ R_useDynamicSymbols(dll, FALSE);
+}
diff --git a/src/trim.c b/src/trim.c
new file mode 100644
index 0000000..8ab7f22
--- /dev/null
+++ b/src/trim.c
@@ -0,0 +1,104 @@
+#include <stdlib.h>
+#include <stdbool.h>
+#include "Rinternals.h"
+#include <string.h> // for strlen()
+
+SEXP trim_(SEXP x) {
+
+ size_t len = LENGTH(x);
+
+ SEXP out = PROTECT(Rf_allocVector(STRSXP, len));
+ for (size_t num = 0; num < len;++num) {
+ const char* xx = Rf_translateCharUTF8(STRING_ELT(x, num));
+ size_t str_len = strlen(xx);
+ if (str_len == 0) {
+ SET_STRING_ELT(out, num, R_BlankString);
+ continue;
+ }
+
+ char * str = (char *) malloc(str_len + 1);
+ size_t start = 0;
+ size_t i = 0;
+ bool new_line = false;
+ /* remove first blank line */
+ while(i < str_len) {
+ if (xx[i] == '\n') {
+ ++i;
+ start = i;
+ new_line = true;
+ break;
+ } else if (xx[i] == ' ' || xx[i] == '\t') {
+ ++i;
+ } else {
+ break;
+ }
+ }
+
+ i = start;
+ size_t indent = 0;
+
+ /* Maximum size of size_t */
+ size_t min_indent = (size_t)-1;
+
+ /* find minimum indent */
+ while(i < str_len) {
+ if (xx[i] == '\n') {
+ new_line = true;
+ } else if (new_line) {
+ if (xx[i] == ' ' || xx[i] == '\t') {
+ ++indent;
+ } else {
+ if (indent < min_indent) {
+ min_indent = indent;
+ }
+ new_line = false;
+ }
+ }
+ ++i;
+ }
+ if (indent < min_indent) {
+ min_indent = indent;
+ }
+
+ new_line = true;
+ i = start;
+ size_t j = 0;
+
+ /* copy the string removing the minimum indent from new lines */
+ while(i < str_len) {
+ if (xx[i] == '\n') {
+ new_line = true;
+ } else if (xx[i] == '\\' && i + 1 < str_len && xx[i + 1] == '\n') {
+ new_line = true;
+ i+=2;
+ continue;
+ } else if (new_line) {
+ if (i + min_indent < str_len && (xx[i] == ' ' || xx[i] == '\t')) {
+ i += min_indent;
+ }
+ new_line = false;
+ }
+ str[j++] = xx[i++];
+ }
+ str[j] = '\0';
+
+ /* Remove trailing whitespace up to the first newline */
+ size_t end = j;
+ while(j > 0) {
+ if (str[j] == '\n') {
+ end = j;
+ break;
+ } else if (str[j] == '\0' || str[j] == ' ' || str[j] == '\t') {
+ --j;
+ } else {
+ break;
+ }
+ }
+ str[end] = '\0';
+ SET_STRING_ELT(out, num, Rf_mkCharCE(str, CE_UTF8));
+ free(str);
+ }
+
+ UNPROTECT(1);
+ return out;
+}
diff --git a/tests/testthat.R b/tests/testthat.R
new file mode 100644
index 0000000..80a98e4
--- /dev/null
+++ b/tests/testthat.R
@@ -0,0 +1,4 @@
+library(testthat)
+library(glue)
+
+test_check("glue")
diff --git a/tests/testthat/test-collapse.R b/tests/testthat/test-collapse.R
new file mode 100644
index 0000000..4b1812a
--- /dev/null
+++ b/tests/testthat/test-collapse.R
@@ -0,0 +1,36 @@
+context("collapse")
+
+test_that("collapse works like paste(collapse=)", {
+ # Always return 0 length outputs for 0 length inputs.
+ #expect_identical(paste(collapse = "", character(0)), collapse(character(0)))
+
+ expect_identical(as_glue(paste(collapse = "", "")), collapse(""))
+
+ expect_identical(as_glue(paste(collapse = "", 1:10)), collapse(1:10))
+
+ expect_identical(as_glue(paste(collapse = " ", 1:10)), collapse(1:10, sep = " "))
+})
+test_that("collapse truncates", {
+ expect_identical(as_glue("12345678910"), collapse(1:10, width = 11))
+ expect_identical(as_glue("12345678910"), collapse(1:10, width = 100))
+ expect_identical(as_glue("1234567..."), collapse(1:10, width = 10))
+ expect_identical(as_glue("123..."), collapse(1:10, width = 6))
+ expect_identical(as_glue("1..."), collapse(1:10, width = 4))
+ expect_identical(as_glue("..."), collapse(1:10, width = 0))
+})
+
+test_that("last argument to collapse", {
+ expect_equal(collapse(character(), last = " and "), as_glue(character()))
+ expect_equal(collapse("", last = " and "), as_glue(""))
+ expect_equal(collapse(1, last = " and "), as_glue("1"))
+ expect_equal(collapse(1:2, last = " and "),as_glue( "1 and 2"))
+ expect_equal(collapse(1:4, ", ", last = " and "), as_glue("1, 2, 3 and 4"))
+
+ expect_equal(collapse(1:4, ", ", last = " and ", width = 5), as_glue("1,..."))
+
+ expect_equal(collapse(1:4, ", ", last = " and ", width = 10), as_glue("1, 2, 3..."))
+})
+
+test_that("collapse returns 0 length output for 0 length input", {
+ expect_identical(collapse(character()), as_glue(character()))
+})
diff --git a/tests/testthat/test-glue.R b/tests/testthat/test-glue.R
new file mode 100644
index 0000000..67c0b2e
--- /dev/null
+++ b/tests/testthat/test-glue.R
@@ -0,0 +1,306 @@
+context("glue")
+
+test_that("inputs are concatenated, interpolated variables recycled", {
+ expect_identical(as_glue(c("testastring1", "testastring2")), glue("test", "a", "string", "{1:2}"))
+})
+test_that("glue errors if the expression fails", {
+ expect_error(glue("{NoTfOuNd}"), "object .* not found")
+})
+
+test_that("glue errors if invalid format", {
+ expect_error(glue("x={x"), "Expecting '}'")
+})
+
+test_that("glue returns length 1 string from length 1 input", {
+ expect_identical(as_glue(""), glue(""))
+})
+
+test_that("glue works with single expressions", {
+ foo <- "foo"
+ expect_identical(as_glue(foo), glue("{foo}"))
+
+ foo <- 1L
+ expect_identical(as_glue(foo), glue("{foo}"))
+
+ foo <- as.raw(1)
+ expect_identical(as_glue(foo), glue("{foo}"))
+
+ foo <- TRUE
+ expect_identical(as_glue(foo), glue("{foo}"))
+
+ foo <- as.Date("2016-01-01")
+ expect_identical(as_glue(foo), glue("{foo}"))
+})
+
+test_that("glue works with repeated expressions", {
+ foo <- "foo"
+ expect_identical(as_glue(paste(foo, foo)), glue("{foo} {foo}"))
+
+ foo <- 1L
+ expect_identical(as_glue(paste(as.character(foo), as.character(foo))), glue("{foo} {foo}"))
+
+ foo <- as.raw(1)
+ expect_identical(as_glue(paste(as.character(foo), as.character(foo))), glue("{foo} {foo}"))
+
+ foo <- TRUE
+ expect_identical(as_glue(paste(as.character(foo), as.character(foo))), glue("{foo} {foo}"))
+
+ foo <- as.Date("2016-01-01")
+ expect_identical(as_glue(paste(as.character(foo), as.character(foo))), glue("{foo} {foo}"))
+})
+
+test_that("glue works with multiple expressions", {
+ foo <- "foo"
+ bar <- "bar"
+ expect_identical(as_glue(paste(foo, bar)), glue("{foo} {bar}"))
+
+ foo <- 1L
+ bar <- 2L
+ expect_identical(as_glue(paste(as.character(foo), as.character(bar))), glue("{foo} {bar}"))
+
+ foo <- as.raw(1)
+ bar <- as.raw(2)
+ expect_identical(as_glue(paste(as.character(foo), as.character(bar))), glue("{foo} {bar}"))
+
+ foo <- TRUE
+ bar <- FALSE
+ expect_identical(as_glue(paste(as.character(foo), as.character(bar))), glue("{foo} {bar}"))
+
+ foo <- as.Date("2016-01-01")
+ bar <- as.Date("2016-01-02")
+ expect_identical(as_glue(paste(as.character(foo), as.character(bar))), glue("{foo} {bar}"))
+})
+
+test_that("glue with doubled braces are converted glue single braces", {
+ expect_identical(as_glue("{foo}"), glue("{{foo}}"))
+})
+
+test_that("glue works with complex expressions", {
+ `foo}\`` <- "foo"
+
+ expect_identical(as_glue(`foo}\``), glue("{
+ {
+ '}\\'' # { and } in comments, single quotes
+ \"}\\\"\" # or double quotes are ignored
+ `foo}\\`` # as are { in backticks
+ }
+ }"))
+})
+
+test_that("glue works with large outputs", {
+ # initial buffer allocates input string length + 1024, 40 * 26 = 1040
+ foo <- paste(rep(letters, 40), collapse = "")
+
+ # re-allocation on result
+ expect_identical(as_glue(foo), glue("{foo}"))
+
+ # re-allocation on input
+ bar <- paste(rep(letters, 40), collapse = "")
+ additional <- " some more text that requires an allocation"
+ expect_identical(as_glue(paste0(bar, additional)), glue("{bar}", additional))
+})
+
+test_that("glue works with named arguments", {
+ name <- "Fred"
+ res <- glue('My name is {name},',
+ ' my age next year is {age + 1},',
+ ' a dot is a {.}',
+ name = "Joe",
+ age = 40,
+ . = "'.'")
+
+ expect_identical(
+ as_glue("My name is Joe, my age next year is 41, a dot is a '.'"),
+ res
+ )
+
+ expect_identical("Fred", name)
+})
+
+test_that("glue evaluates arguments in the expected environment", {
+ x <- 2
+ fun <- function() {
+ x <- 1
+ glue("x: {x}, x+1: {y}", y = x + 1, .envir = parent.frame())
+ }
+
+ expect_identical(as_glue("x: 2, x+1: 3"), fun())
+})
+
+test_that("glue assigns arguments in the environment", {
+ expect_identical(as_glue("1"), glue::glue("{b}", a = 1, b = a))
+})
+
+test_that("error if non length 1 inputs", {
+ expect_error(glue(1:2, "{1:2}"), "All unnamed arguments must be length 1")
+})
+
+test_that("error if not simple recycling", {
+ expect_error(glue("{1:2}{1:10}"), "Variables must be length 1 or 10")
+})
+
+test_that("recycle_columns returns if zero length input", {
+ expect_identical(list(), recycle_columns(list()))
+ expect_identical(character(), recycle_columns(list(character())))
+})
+
+test_that("glue_data evaluates in the object first, then enclosure, then parent", {
+ x <- 1
+ y <- 1
+ z <- 1
+ fun <- function(env = environment()) {
+ y <- 2
+ glue_data(list(x = 3), "{x} {y} {z}", .envir = env)
+ }
+
+ # The function environment
+ expect_identical(as_glue("3 2 1"), fun())
+
+ # This environment
+ env <- environment()
+ expect_identical(as_glue("3 1 1"), fun(env))
+
+ # A new environment
+ env2 <- new.env(parent = emptyenv())
+ env2$x <- 3
+ env2$y <- 3
+ env2$z <- 3
+
+ expect_identical(as_glue("3 3 3"), glue_data(env2, "{x} {y} {z}"))
+})
+
+test_that("trim works", {
+ expect_identical("", trim(""))
+ expect_identical(character(), trim(character()))
+ expect_identical("test", trim("test"))
+ expect_identical(c("foo", "bar"), trim(c("foo", "bar")))
+ expect_identical(c("foo", "bar"), trim(c("\nfoo", "bar\n")))
+ expect_identical("test",
+ trim(
+ "test"))
+ expect_identical("test",
+ x <- trim(
+ "test
+ "))
+ expect_identical("test",
+ trim("
+ test
+ "))
+ expect_identical("test",
+ trim(
+ "test"))
+ expect_identical("test\n test2",
+ trim("
+ test
+ test2
+ "))
+ expect_identical("test\n test2\n test3",
+ trim("
+ test
+ test2
+ test3
+ "))
+
+ expect_identical("\ntest\n",
+ trim("
+
+ test
+
+ "))
+})
+
+test_that("trim strips escaped newlines", {
+ expect_identical(
+ "foo bar baz",
+ trim("foo bar \\\nbaz"))
+
+ expect_identical(
+ "foo bar baz",
+ trim("foo bar \\
+ baz"))
+
+ expect_identical(
+ "foo bar baz\n",
+ trim("foo bar baz\n\n"))
+
+ expect_identical(
+ "\nfoo bar baz",
+ trim("\n\nfoo bar baz"))
+})
+
+test_that("converting glue to character", {
+ expect_identical("foo bar", as.character(glue("foo bar")))
+})
+
+test_that("converting glue to glue", {
+ expect_identical(as_glue("foo bar"), as_glue(glue("foo bar")))
+})
+
+test_that("printing glue identical to cat()", {
+ expect_output(print(glue("foo\nbar")), "foo\nbar")
+})
+
+test_that("length 0 inputs produce length 0 outputs", {
+ expect_identical(as_glue(character(0)), glue("foo", character(0)))
+
+ expect_identical(as_glue(character(0)), glue("foo", "{character(0)}"))
+ expect_identical(as_glue(character(0)), glue("foo {character(0)}"))
+})
+
+test_that("values are trimmed before evaluation", {
+
+ x <- " a1\n b2\n c3"
+
+ expect_identical(
+as_glue(
+"A
+ a1
+ b2
+ c3
+B"),
+glue("
+ A
+ {x}
+ B
+ "))
+})
+
+test_that("glue works with alternative delimiters", {
+ expect_identical(as_glue("{1}"), glue("{1}", .open = "", .close = ""))
+ expect_identical(as_glue("{{}}"), glue("{{}}", .open = "", .close = ""))
+
+ expect_identical(as_glue("1"), glue("<<1>>", .open = "<<", .close = ">>"))
+ expect_identical(as_glue("<<>>"), glue("<<<<>>>>", .open = "<<", .close = ">>"))
+
+ expect_identical(as_glue("1"), glue("{{1}}", .open = "{{", .close = "}}"))
+ expect_identical(as_glue("1"), glue("{{ {{1}} }}", .open = "{{", .close = "}}"))
+ expect_identical(as_glue("1"), glue("{{ {{{1}}} }}", .open = "{{", .close = "}}"))
+ expect_identical(as_glue("1"), glue("{{ {{{{1}}}} }}", .open = "{{", .close = "}}"))
+
+ expect_identical(as_glue("a"), glue("[letters[[1]]]", .open = "[", .close = "]"))
+
+ expect_identical(as_glue("a"), glue("[[ letters[[1]] ]]", .open = "[[", .close = "]]"))
+})
+
+test_that("glue always returns UTF-8 encoded strings regardless of input encodings", {
+ x <- "fa\xE7ile"
+ Encoding(x) <- "latin1"
+
+ x_out <- as_glue(enc2utf8(x))
+
+ expect_identical(x_out, glue(x))
+ expect_identical(x_out, glue("{x}"))
+
+ y <- "p\u00E4o"
+ Encoding(y) <- "UTF-8"
+
+ y_out <- as_glue(enc2utf8(y))
+
+ expect_identical(y_out, glue(y))
+ expect_identical(y_out, glue("{y}"))
+
+ xy_out <- as_glue(paste0(x_out, y_out))
+
+ expect_identical(xy_out, glue(x, y))
+ expect_identical(xy_out, glue("{x}{y}"))
+})
diff --git a/tests/testthat/test-quoting.R b/tests/testthat/test-quoting.R
new file mode 100644
index 0000000..6056717
--- /dev/null
+++ b/tests/testthat/test-quoting.R
@@ -0,0 +1,37 @@
+context("quoting")
+
+test_that("single_quote works", {
+ expect_identical(single_quote(character()), character())
+ expect_identical(single_quote(""), "''")
+ expect_identical(single_quote(1:5),
+ c("'1'",
+ "'2'",
+ "'3'",
+ "'4'",
+ "'5'"
+ ))
+})
+
+test_that("double_quote works", {
+ expect_identical(double_quote(character()), character())
+ expect_identical(double_quote(""), '""')
+ expect_identical(double_quote(1:5),
+ c('"1"',
+ '"2"',
+ '"3"',
+ '"4"',
+ '"5"'
+ ))
+})
+
+test_that("backtick works", {
+ expect_identical(backtick(character()), character())
+ expect_identical(backtick(""), '``')
+ expect_identical(backtick(1:5),
+ c("`1`",
+ "`2`",
+ "`3`",
+ "`4`",
+ "`5`"
+ ))
+})
--
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/debian-med/r-cran-glue.git
More information about the debian-med-commit
mailing list