An external program needs an input file with some control parameters, and I wish to generate those automatically using R. Usually, I simply use paste("parameter1: ", param1, ...)
to create the long string of text, and output to a file, but the script rapidly becomes unreadable. This problem is probably well suited to whisker,
library(whisker)
template= 'Hello {{name}}
You have just won ${{value}}!
'
data <- list( name = "Chris", value= 124)
whisker.render(template, data)
My issue here, is that there's no safe-checking that data
contains all the required variables, e.g.
whisker.render(template, data[-1])
will silently ignore the fact that I forgot to specify a name. My end-program will crash, however, if I fail to produce a complete config file.
Another templating system is provided by brew
; it has the advantage of actually evaluating things, and potentially this can also help detect missing variables,
library(brew)
template2 = 'Hello <%= name %>
You have just won $<%= value %>!
'
data <- list( name = "Chris", value= 124)
own_brew <- function(template, values){
attach(values, pos=2)
out = capture.output(brew(text = template))
detach(values, pos=2)
cat(out, sep='\n')
invisible(out)
}
own_brew(template2, data)
own_brew(template2, data[-1]) # error
However, I am stuck with two issues:
attach() ... detach()
is not ideal, (gives warnings every now and then), or at least I don't know how to use it properly. I tried to define an environment for brew()
, but it was too restrictive and didn't know about base
functions anymore...
even though an error occurs, a string is still returned by the function. I tried to wrap the call in try()
but I have no experience in error handling. How do I tell it to quit the function producing no output?
Edit: I have updated the brew
solution to use a new environment instead of attach()
, and stop execution in case of an error. (?capture.output
suggests that it was not the right function to use here, since "An attempt is made to write output as far as possible to file if there is an error in evaluating the expressions"...)
own_brew <- function(template, values, file=""){
env <- as.environment(values)
parent.env(env) <- .GlobalEnv
a <- textConnection("cout", "w")
out <- try(brew(text = template, envir=env, output=a))
if(inherits(out, "try-error")){
close(a)
stop()
}
cat(cout, file=file, sep="\n")
close(a)
invisible(cout)
}
There must be an easier way with tryCatch
, but I can't understand a single thing in its help page.
I welcome other suggestions on the more general problem.
Using regular expressions to retrieve the variable names from the template, you could validate before rendering, e.g.,
render <- function(template, data) {
vars <- unlist(regmatches(template, gregexpr('(?<=\\{\\{)[[:alnum:]_.]+(?=\\}\\})', template, perl=TRUE)))
stopifnot(all(vars %in% names(data)))
whisker.render(template, data)
}
render(template, data)
The new glue
package provides another alternative,
library(glue)
template <- 'Hello {name} You have just won ${value}!'
data <- list( name = "Chris", value= 124)
glue_data(template, .x=data)
# Hello Chris You have just won $124!
glue_data(template, .x=data[-1])
# Error in eval(expr, envir, enclos) : object 'name' not found
While preparing the stringr
answer I noticed that OP's questions concerning the usage of brew()
hasn't been addressed so far. In particular, the OP was asking how to provide his data
to the environment and how to prevent a character string being returned in case of an error.
The OP has created a function own_brew()
which wraps the call to brew()
. Although, there are alternative package available now, I feel the original question deserves an answer.
This is my attempt to improve baptiste's version:
own_brew <- function(template, values, file=""){
a <- textConnection("cout", "w")
out <- brew::brew(text = template, envir=list2env(values), output=a)
close(a)
if (inherits(out, "try-error")) stop()
cat(cout, file=file, sep="\n")
invisible(cout)
}
The main differences are that list2env()
is used to pass the list of values
to brew()
and that the call to try()
is avoided by testing the return value out
for an error.
template <- "Hello <%= name %> You have just won $<%= value %>!"
data <- list( name = "Chris", value= 124)
own_brew(template, data)
Hello Chris You have just won $124!
own_brew(template, data[-1L])
Error in cat(name) : object 'name' not found Error in own_brew(template, data[-1L]) :
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With