[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