使用brew / whisker进行错误安全的模板化

时间:2013-05-16 19:15:22

标签: r templates

外部程序需要带有一些控制参数的输入文件,我希望使用R自动生成。通常,我只需使用paste("parameter1: ", param1, ...)创建长文本字符串,然后输出到文件,但是脚本迅速变得难以理解。这个问题可能非常适合胡须,

library(whisker)

template= 'Hello {{name}}
You have just won ${{value}}!
'

data <- list( name = "Chris", value= 124)

whisker.render(template, data)

我的问题在于,没有安全检查data包含所有必需的变量,例如

whisker.render(template, data[-1])

会默默地忽略我忘记指定名字的事实。但是,如果我无法生成完整的配置文件,我的终端程序将崩溃。

另一个模板系统由brew提供;它具有实际评估事物的优势,并且可能还有助于检测缺失的变量,

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

但是,我遇到两个问题:

  • attach() ... detach()不理想,(偶尔会发出警告),或者至少我不知道如何正确使用它。我试图为brew()定义一个环境,但它过于严格,不再了解base函数了......

  • 即使发生错误,该函数仍会返回一个字符串。我试图将调用包裹在try()中,但我没有处理错误的经验。如何告诉它退出不产生输出的功能?

编辑:我已更新brew解决方案以使用新环境而非attach(),并在出现错误时停止执行。 (?capture.output表明在这里使用它不是正确的函数,因为“如果在计算表达式时出现错误,则尝试尽可能地将输出写入文件”...)

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)
}

tryCatch必须有一种更简单的方法,但我无法理解其帮助页面中的任何内容。

我欢迎有关更普遍问题的其他建议。

3 个答案:

答案 0 :(得分:4)

使用正则表达式从模板中检索变量名称,您可以在渲染之前进行验证,例如

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)

答案 1 :(得分:1)

自版本1.1.0(2016年8月19日CRAN)以来,stringr package包含str_interp()函数(遗憾的是,在发布的NEWS文件中未提及)。

template <- "Hello ${name} You have just won $${value}!"
data <- list( name = "Chris", value= 124)

stringr::str_interp(template, data)
[1] "Hello Chris You have just won $124!"
stringr::str_interp(template, data[-1L])
Error in FUN(X[[i]], ...) : object 'name' not found

答案 2 :(得分:1)

在准备stringr answer时,我注意到到目前为止OP尚未解决有关brew()用法的问题。特别是,OP正在询问如何向环境提供data以及如何防止在发生错误时返回字符串。

OP创建了一个函数own_brew(),它将调用包装到brew()。虽然现在有替代套餐,我觉得最初的问题值得回答。

这是我尝试改进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)
}

主要区别在于list2env()用于将values列表传递给brew(),并且通过测试返回值{{}来避免对try()的调用1}}表示错误。

out
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])