-
Notifications
You must be signed in to change notification settings - Fork 6
/
Copy pathcore.R
151 lines (121 loc) · 5.8 KB
/
core.R
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
.onLoad <- function(libname, pkgname) {
options(variable_identifier=c("{{", "}}"))
}
#' Infuse a template with values.
#'
#' For more info and usage examples see the README on the \href{https://github.com/Bart6114/infuser}{\code{infuser} github page}.
#' To help prevent \href{https://xkcd.com/327/}{SQL injection attacks} (or other injection attacks), use a transformation function to escape special characters and provide it through the \code{transform_function} argument. \code{\link[dbplyr]{build_sql}} is a great default escaping function for SQL templating. For templating in other languages you will need to build/specify your own escaping function.
#'
#' @param file_or_string the template file or a character string containing the template
#' @param ... different keys with related values, used to fill in the template (if first passed item is a list/environment the contents of this will be processed instead)
#' @param variable_identifier the opening and closing character that denounce a variable in the template, defaults to \code{c("{{", "}}")} and can be set persistently using e.g. \code{options(variable_identifier=c("{{", "}}"))}
#' @param default_char the character use to specify a default after
#' @param collapse_char the character used to collapse a supplied vector
#' @param transform_function a function through which all specified values are passed, can be used to make inputs safe(r). dplyr::build_sql is a good default for SQL templating.
#' @param verbose verbosity level
#' @param simple_character if \code{TRUE} returns only a character vector, else adds the \code{infuser} class to the returned object.
#' @param strict if \code{TRUE} stops processing when a requested parameter is not supplied, else will simply leave the parameter as-is
#' @export
infuse <- function(file_or_string, ..., variable_identifier = getOption("variable_identifier"),
default_char = "|", collapse_char = ",",
transform_function = function(value) return(value),
verbose=getOption("verbose"),
simple_character = FALSE,
strict = FALSE){
template <-
read_template(file_or_string)
params_requested <-
variables_requested(template,
variable_identifier = variable_identifier,
default_char = default_char,
verbose = verbose)
params_supplied <- list(...)
## no params supplied, return unaltered template
if(length(params_supplied)==0) return(template)
## if a list or environment is passed as the first argument, only process this
if("key_value_list" %in% names(params_supplied)) warning("specification of key_value_list no longer required; simply pass the list/environment as the first parameter")
if(inherits(params_supplied[[1]], "list") || inherits(params_supplied[[1]], "environment")){
params_supplied <- params_supplied[[1]]
}
for(param in names(params_requested)){
pattern <- paste0(variable_identifier[1],
"\\s*?",
param,
"\\s*?" ,
variable_identifier[2],
"|", # or match with default in place
variable_identifier[1],
"\\s*?",
param,
"\\s*?\\",
default_char,
".*?",
variable_identifier[2])
if(param %in% names(params_supplied)){
## param is supplied
template<-
gsub(pattern,
## do this as a paste function e.g. if user supplied c(1,2,3)
## pass it through the transform function
transform_function(
paste(params_supplied[[param]], collapse=collapse_char)
),
template,
perl = TRUE)
} else if(!is.na(params_requested[[param]])){
## param is not supplied but a default is declared in the template
template<-
gsub(pattern,
params_requested[[param]],
template,
perl = TRUE)
if(verbose) warning(paste0("Requested parameter '", param, "' not supplied -- using default variable instead"))
} else {
## don't do anything but give a warning
if(strict){
stop(paste0("Requested parameter '", param, "' not supplied"))
}
warning(paste0("Requested parameter '", param, "' not supplied -- leaving template as-is"))
}
}
## add 'infuse' class to the character string, done to control show method
if(!simple_character){
class(template) <- append(class(template), "infuse")
}
template
}
#' Shows which variables are requested by the template
#'
#' @param file_or_string the template file or a string containing the template
#' @param variable_identifier the opening and closing character that denounce a variable in the template
#' @param default_char the character use to specify a default after
#' @param verbose verbosity level
#' @export
variables_requested <- function(file_or_string, variable_identifier = c("{{", "}}"), default_char = "|", verbose=FALSE){
template <-
read_template(file_or_string)
regex_expr <- paste0(variable_identifier[1],
"(.*?)",
variable_identifier[2])
params <-
regmatches(template, gregexpr(regex_expr, template, perl=T))[[1]]
params <-
gsub(regex_expr, "\\1", params, perl=T)
params_splitted <-
strsplit(params, default_char, fixed=T)
param_list <- list()
for(param in params_splitted){
key <- trim(param[[1]])
if(length(param) > 1){
value <- trim(param[[2]])
} else{
value <- NA
}
param_list[key] <- value
}
# print out params requested by the template (and available default variables)
if(verbose){
print_requested_params(param_list)
}
param_list
}