├── .Rbuildignore ├── .gitignore ├── .travis.yml ├── DESCRIPTION ├── LICENSE ├── NAMESPACE ├── R ├── core.R └── helpers.R ├── README.Rmd ├── README.md ├── infuser.Rproj ├── inst └── extdata │ └── sql1.sql ├── man ├── infuse.Rd ├── print.infuse.Rd ├── print_requested_params.Rd ├── read_template.Rd ├── trim.Rd └── variables_requested.Rd ├── tests ├── testthat.R └── testthat │ ├── test_core_functionality.R │ └── test_helpers_functionality.R └── vignettes └── getting_started.Rmd /.Rbuildignore: -------------------------------------------------------------------------------- 1 | ^.*\.Rproj$ 2 | ^\.Rproj\.user$ 3 | README.Rmd 4 | README.md 5 | ^\.travis\.yml$ 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .Rproj.user 2 | .Rhistory 3 | .RData 4 | inst/doc 5 | .DS_Store 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: r 2 | warnings_are_errors: false 3 | sudo: required 4 | 5 | env: 6 | global: 7 | - CRAN: http://cran.rstudio.com 8 | - R_BUILD_ARGS="--no-manual" 9 | - R_CHECK_ARGS="--no-manual" 10 | 11 | notifications: 12 | email: 13 | on_success: change 14 | on_failure: change 15 | 16 | r_github_packages: 17 | - jimhester/covr 18 | 19 | after_success: 20 | - Rscript -e 'library(covr);coveralls()' 21 | -------------------------------------------------------------------------------- /DESCRIPTION: -------------------------------------------------------------------------------- 1 | Package: infuser 2 | Type: Package 3 | Title: A Very Basic Templating Engine 4 | Version: 0.2.8 5 | Date: 2017-10-03 6 | Author: Bart Smeets 7 | Maintainer: Bart Smeets 8 | Description: This light-weight templating package provides flexible and 9 | user-friendly value substitution in string templates or template files 10 | (e.g. SQL files) at runtime. 11 | License: MIT + file LICENSE 12 | LazyData: TRUE 13 | Suggests: 14 | testthat, 15 | knitr, 16 | rmarkdown, 17 | dbplyr, 18 | magrittr 19 | URL: https://github.com/bart6114/infuser/ 20 | BugReports: https://github.com/bart6114/infuser/issues 21 | RoxygenNote: 6.0.1 22 | VignetteBuilder: knitr 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | YEAR: 2015 2 | COPYRIGHT HOLDER: Bart Smeets 3 | -------------------------------------------------------------------------------- /NAMESPACE: -------------------------------------------------------------------------------- 1 | # Generated by roxygen2: do not edit by hand 2 | 3 | S3method(print,infuse) 4 | export(infuse) 5 | export(variables_requested) 6 | -------------------------------------------------------------------------------- /R/core.R: -------------------------------------------------------------------------------- 1 | .onLoad <- function(libname, pkgname) { 2 | options(variable_identifier=c("{{", "}}")) 3 | } 4 | 5 | 6 | #' Infuse a template with values. 7 | #' 8 | #' For more info and usage examples see the README on the \href{https://github.com/Bart6114/infuser}{\code{infuser} github page}. 9 | #' 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. 10 | #' 11 | #' @param file_or_string the template file or a character string containing the template 12 | #' @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) 13 | #' @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("{{", "}}"))} 14 | #' @param default_char the character use to specify a default after 15 | #' @param collapse_char the character used to collapse a supplied vector 16 | #' @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. 17 | #' @param verbose verbosity level 18 | #' @param simple_character if \code{TRUE} returns only a character vector, else adds the \code{infuser} class to the returned object. 19 | #' @param strict if \code{TRUE} stops processing when a requested parameter is not supplied, else will simply leave the parameter as-is 20 | #' @export 21 | infuse <- function(file_or_string, ..., variable_identifier = getOption("variable_identifier"), 22 | default_char = "|", collapse_char = ",", 23 | transform_function = function(value) return(value), 24 | verbose=getOption("verbose"), 25 | simple_character = FALSE, 26 | strict = FALSE){ 27 | 28 | template <- 29 | read_template(file_or_string) 30 | 31 | params_requested <- 32 | variables_requested(template, 33 | variable_identifier = variable_identifier, 34 | default_char = default_char, 35 | verbose = verbose) 36 | 37 | 38 | params_supplied <- list(...) 39 | 40 | ## no params supplied, return unaltered template 41 | if(length(params_supplied)==0) return(template) 42 | 43 | ## if a list or environment is passed as the first argument, only process this 44 | 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") 45 | 46 | if(inherits(params_supplied[[1]], "list") || inherits(params_supplied[[1]], "environment")){ 47 | params_supplied <- params_supplied[[1]] 48 | } 49 | 50 | 51 | for(param in names(params_requested)){ 52 | 53 | pattern <- paste0(variable_identifier[1], 54 | "\\s*?", 55 | param, 56 | "\\s*?" , 57 | variable_identifier[2], 58 | "|", # or match with default in place 59 | variable_identifier[1], 60 | "\\s*?", 61 | param, 62 | "\\s*?\\", 63 | default_char, 64 | ".*?", 65 | variable_identifier[2]) 66 | 67 | if(param %in% names(params_supplied)){ 68 | ## param is supplied 69 | template<- 70 | gsub(pattern, 71 | ## do this as a paste function e.g. if user supplied c(1,2,3) 72 | ## pass it through the transform function 73 | transform_function( 74 | paste(params_supplied[[param]], collapse=collapse_char) 75 | ), 76 | template, 77 | perl = TRUE) 78 | 79 | } else if(!is.na(params_requested[[param]])){ 80 | ## param is not supplied but a default is declared in the template 81 | template<- 82 | gsub(pattern, 83 | params_requested[[param]], 84 | template, 85 | perl = TRUE) 86 | if(verbose) warning(paste0("Requested parameter '", param, "' not supplied -- using default variable instead")) 87 | } else { 88 | ## don't do anything but give a warning 89 | if(strict){ 90 | stop(paste0("Requested parameter '", param, "' not supplied")) 91 | } 92 | warning(paste0("Requested parameter '", param, "' not supplied -- leaving template as-is")) 93 | } 94 | 95 | } 96 | 97 | ## add 'infuse' class to the character string, done to control show method 98 | if(!simple_character){ 99 | class(template) <- append(class(template), "infuse") 100 | } 101 | 102 | template 103 | 104 | } 105 | 106 | #' Shows which variables are requested by the template 107 | #' 108 | #' @param file_or_string the template file or a string containing the template 109 | #' @param variable_identifier the opening and closing character that denounce a variable in the template 110 | #' @param default_char the character use to specify a default after 111 | #' @param verbose verbosity level 112 | #' @export 113 | variables_requested <- function(file_or_string, variable_identifier = c("{{", "}}"), default_char = "|", verbose=FALSE){ 114 | template <- 115 | read_template(file_or_string) 116 | 117 | regex_expr <- paste0(variable_identifier[1], 118 | "(.*?)", 119 | variable_identifier[2]) 120 | 121 | params <- 122 | regmatches(template, gregexpr(regex_expr, template, perl=T))[[1]] 123 | 124 | params <- 125 | gsub(regex_expr, "\\1", params, perl=T) 126 | 127 | params_splitted <- 128 | strsplit(params, default_char, fixed=T) 129 | 130 | param_list <- list() 131 | 132 | for(param in params_splitted){ 133 | key <- trim(param[[1]]) 134 | if(length(param) > 1){ 135 | value <- trim(param[[2]]) 136 | } else{ 137 | value <- NA 138 | } 139 | param_list[key] <- value 140 | } 141 | 142 | # print out params requested by the template (and available default variables) 143 | if(verbose){ 144 | print_requested_params(param_list) 145 | } 146 | 147 | 148 | 149 | param_list 150 | 151 | } 152 | -------------------------------------------------------------------------------- /R/helpers.R: -------------------------------------------------------------------------------- 1 | #' Returns string w/o leading or trailing whitespace 2 | #' 3 | #' @param x to string to strip 4 | trim <- function (x) gsub("^\\s+|\\s+$", "", x) 5 | 6 | 7 | #' Reads the template from a file or string 8 | #' 9 | #' @param file_or_string the file or character string to read 10 | read_template<-function(file_or_string){ 11 | # check if input is file 12 | # else asume it is a string 13 | if(file.exists(file_or_string)){ 14 | template <- paste0(readLines(file_or_string, warn = FALSE),collapse= "\n") 15 | } else if(nchar(file_or_string) > 0){ 16 | template <- file_or_string 17 | } else { 18 | stop("Input is neither an existing file nor a character string") 19 | } 20 | 21 | template 22 | } 23 | 24 | #' Prints requested parameters 25 | #' 26 | #' @param params_requested the list of requested parameters 27 | print_requested_params<-function(params_requested){ 28 | message("Variables requested by template:") 29 | for(param in names(params_requested)){ 30 | if(!is.na(params_requested[[param]])){ 31 | default <- params_requested[[param]] 32 | message(">> ", paste0(param, " (default = ", default, ")")) 33 | } else { 34 | message(">> ", param) 35 | } 36 | 37 | } 38 | } 39 | 40 | #' prints/shows the result of the \code{infuse} function using the \code{cat} function 41 | #' 42 | #' @param x output of the \code{infuse} function 43 | #' @param ... further arguments passed to or from other methods. 44 | #' @export 45 | print.infuse<-function(x, ...){ 46 | cat(x, "\n") 47 | } 48 | -------------------------------------------------------------------------------- /README.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: "infuser" 3 | output: md_document 4 | --- 5 | 6 | [![Travis Build Status](https://travis-ci.org/Bart6114/infuser.svg)](https://travis-ci.org/Bart6114/infuser) 7 | [![Coverage Status](https://coveralls.io/repos/Bart6114/infuser/badge.svg)](https://coveralls.io/r/Bart6114/infuser) 8 | [![CRAN_Status_Badge](http://www.r-pkg.org/badges/version/infuser)](http://cran.r-project.org/web/packages/infuser) 9 | [![Downloads](http://cranlogs.r-pkg.org/badges/infuser)](http://cran.rstudio.com/package=infuser) 10 | 11 | ```infuser``` is a simple and very basic templating engine for R. It replaces parameters within templates with specified values. Templates can be either contained in a string or in a file. 12 | 13 | ## Installation 14 | 15 | ```{r, eval=FALSE} 16 | install.packages("infuser") 17 | ``` 18 | 19 | If you want to use the most up-to-date version, install using ```devtools::install_github```. 20 | 21 | ```{r, eval=FALSE} 22 | devtools::install_github("Bart6114/infuser") 23 | ``` 24 | 25 | ## Hello World 26 | 27 | A simple Hello World example that makes use of the `magrittr` piping workflow. 28 | 29 | ```{r} 30 | library(magrittr) 31 | library(infuser) 32 | 33 | "Hello {{var1}}!" %>% 34 | infuse(var1="world") 35 | ``` 36 | 37 | ## Usage 38 | 39 | ### Working with character strings as templates 40 | 41 | Let's have a look at an example string. 42 | 43 | ```{r} 44 | my_sql<-"SELECT * FROM Customers 45 | WHERE Year = {{year}} 46 | AND Month = {{month|3}};" 47 | ``` 48 | 49 | Here the variable parameters are enclosed by ```{{``` and ```}}``` characters. See ```?infuse``` to use your own specification. 50 | 51 | From now on, we suppose the character string ```my_sql``` is our template. To show the parameters requested by the template you can run the following. 52 | 53 | ```{r, results='hide'} 54 | library(infuser) 55 | variables_requested(my_sql, verbose = TRUE) 56 | ``` 57 | 58 | To fill in the template simply provide the requested parameters. 59 | 60 | ```{r} 61 | infuse(my_sql, year=2016, month=8) 62 | ``` 63 | 64 | You can also provide a named list with the requested parameters. 65 | 66 | ```{r} 67 | my_list<- 68 | list(year=2016, 69 | month=8) 70 | 71 | infuse(my_sql, my_list) 72 | ``` 73 | 74 | If a default value is available in the template, it will be used if the parameter is not specified. 75 | 76 | ```{r} 77 | infuse(my_sql, year=2016) 78 | ``` 79 | 80 | ### Working with text files as templates 81 | 82 | Just like we're using a string here, a text file can be used. An example text file can be found in the package as follows: 83 | 84 | ```{r} 85 | example_file<- 86 | system.file("extdata", "sql1.sql", package="infuser") 87 | 88 | example_file 89 | ``` 90 | 91 | Again, we can check which parameters are requested by the template. 92 | 93 | ```{r, results='hide'} 94 | variables_requested(example_file, verbose = TRUE) 95 | ``` 96 | 97 | And provide their values. 98 | 99 | ```{r} 100 | infuse(example_file, year = 2016, month = 12) 101 | ``` 102 | 103 | ### Infusing vectors 104 | 105 | It is quite easy to infuse a vector. 106 | 107 | ```{r} 108 | years <- c(2013,2014,2015) 109 | sql_string <- "SELECT * FROM T1 WHERE Year IN ({{years}})" 110 | 111 | infuse(sql_string, years=years) 112 | ``` 113 | 114 | You can also specify the collapse character. 115 | 116 | ```{r} 117 | infuse(sql_string, years=years, collapse_char = ";") 118 | ``` 119 | 120 | 121 | 122 | ### Processing / transforming your inputs 123 | 124 | A ```transform_function``` can be specified in the ```infuse``` command. This allows for pre-processing of the parameter values before inserting them in the template. 125 | 126 | What we don't want to happen is the following: 127 | 128 | ```{r} 129 | sql<-"INSERT INTO Students (Name) VALUES ({{name}})" 130 | name <- "Robert'); DROP TABLE Students;--" 131 | 132 | infuse(sql, name = name) 133 | ``` 134 | 135 | Yikes! A way to solve this is to specify your own custom transform function. 136 | 137 | ```{r} 138 | my_transform_function<-function(v){ 139 | # replace single quotes with double quotes 140 | v<-gsub("'", "''", v) 141 | # encloses the string in single quotes 142 | v<-paste0("'",v,"'") 143 | 144 | return(v) 145 | } 146 | 147 | infuse(sql, name = name, transform_function = my_transform_function) 148 | ``` 149 | 150 | Of course you can also use functions from other packages. Specifically for SQL I advise you to take a look at the ```dbplyr::build_sql``` function. 151 | 152 | 153 | ```{r} 154 | infuse(sql, name = name, transform_function = dbplyr::build_sql) 155 | ``` 156 | 157 | ## Issues / questions 158 | 159 | Simply create a new issue at this GitHub repository. 160 | 161 | 162 | ## Changes 163 | 164 | ### v.0.2.8 165 | 166 | - added `strict` parameter to `infuser`, if set to `TRUE` will stop processing in case of incomplete set of supplied template parameters 167 | 168 | ### v.0.2.7 169 | 170 | - change `dpyr::build_sql` to `dbplyr::build_sql` 171 | 172 | ### v.0.2.6 173 | - set `readLines`'s `warn` parameter to `FALSE` 174 | 175 | ### v.0.2.5 176 | - fixed bug/typo related to #10 177 | 178 | ### v.0.2.4 179 | - fixed testing to adhere to new `testthat` standards 180 | 181 | ### v.0.2.2 182 | - dropped `key_value_list` parameter (will be interpreted automatically) 183 | - fixed passing of the `variable_identifier` 184 | - ability to make `variable_identifier` persistent 185 | 186 | ### v.0.2.1 187 | - fixed: incorrect replacement of parameters with same prefix 188 | 189 | ### v.0.2.0 190 | - consolidating adjustments and releasing version on CRAN 191 | 192 | ### v.0.1.4 193 | - updated print function for output of infuse (uses ```cat``` for now on) 194 | 195 | ### v0.1.3 196 | - added optional ```collapse_char``` argument to ```infuse``` command 197 | 198 | ### v0.1.2 199 | - added optional ```key_value_list``` argument to ```infuse``` command 200 | 201 | ### v0.1.1 202 | - added optional ```transform_function``` argument to ```infuse``` command 203 | 204 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Travis Build 2 | Status](https://travis-ci.org/Bart6114/infuser.svg)](https://travis-ci.org/Bart6114/infuser) 3 | [![Coverage 4 | Status](https://coveralls.io/repos/Bart6114/infuser/badge.svg)](https://coveralls.io/r/Bart6114/infuser) 5 | [![CRAN\_Status\_Badge](http://www.r-pkg.org/badges/version/infuser)](http://cran.r-project.org/web/packages/infuser) 6 | [![Downloads](http://cranlogs.r-pkg.org/badges/infuser)](http://cran.rstudio.com/package=infuser) 7 | 8 | `infuser` is a simple and very basic templating engine for R. It 9 | replaces parameters within templates with specified values. Templates 10 | can be either contained in a string or in a file. 11 | 12 | Installation 13 | ------------ 14 | 15 | install.packages("infuser") 16 | 17 | If you want to use the most up-to-date version, install using 18 | `devtools::install_github`. 19 | 20 | devtools::install_github("Bart6114/infuser") 21 | 22 | Hello World 23 | ----------- 24 | 25 | A simple Hello World example that makes use of the `magrittr` piping 26 | workflow. 27 | 28 | library(magrittr) 29 | library(infuser) 30 | 31 | "Hello {{var1}}!" %>% 32 | infuse(var1="world") 33 | 34 | ## Hello world! 35 | 36 | Usage 37 | ----- 38 | 39 | ### Working with character strings as templates 40 | 41 | Let's have a look at an example string. 42 | 43 | my_sql<-"SELECT * FROM Customers 44 | WHERE Year = {{year}} 45 | AND Month = {{month|3}};" 46 | 47 | Here the variable parameters are enclosed by `{{` and `}}` characters. 48 | See `?infuse` to use your own specification. 49 | 50 | From now on, we suppose the character string `my_sql` is our template. 51 | To show the parameters requested by the template you can run the 52 | following. 53 | 54 | library(infuser) 55 | variables_requested(my_sql, verbose = TRUE) 56 | 57 | ## Variables requested by template: 58 | 59 | ## >> year 60 | 61 | ## >> month (default = 3) 62 | 63 | To fill in the template simply provide the requested parameters. 64 | 65 | infuse(my_sql, year=2016, month=8) 66 | 67 | ## SELECT * FROM Customers 68 | ## WHERE Year = 2016 69 | ## AND Month = 8; 70 | 71 | You can also provide a named list with the requested parameters. 72 | 73 | my_list<- 74 | list(year=2016, 75 | month=8) 76 | 77 | infuse(my_sql, my_list) 78 | 79 | ## SELECT * FROM Customers 80 | ## WHERE Year = 2016 81 | ## AND Month = 8; 82 | 83 | If a default value is available in the template, it will be used if the 84 | parameter is not specified. 85 | 86 | infuse(my_sql, year=2016) 87 | 88 | ## SELECT * FROM Customers 89 | ## WHERE Year = 2016 90 | ## AND Month = 3; 91 | 92 | ### Working with text files as templates 93 | 94 | Just like we're using a string here, a text file can be used. An example 95 | text file can be found in the package as follows: 96 | 97 | example_file<- 98 | system.file("extdata", "sql1.sql", package="infuser") 99 | 100 | example_file 101 | 102 | ## [1] "/Library/Frameworks/R.framework/Versions/3.3/Resources/library/infuser/extdata/sql1.sql" 103 | 104 | Again, we can check which parameters are requested by the template. 105 | 106 | variables_requested(example_file, verbose = TRUE) 107 | 108 | ## Variables requested by template: 109 | 110 | ## >> month (default = 3) 111 | 112 | ## >> year 113 | 114 | And provide their values. 115 | 116 | infuse(example_file, year = 2016, month = 12) 117 | 118 | ## SELECT LAT_N, CITY, TEMP_F 119 | ## FROM STATS, STATION 120 | ## WHERE MONTH = 12 121 | ## AND YEAR = 2016 122 | ## AND STATS.ID = STATION.ID 123 | ## ORDER BY TEMP_F; 124 | 125 | ### Infusing vectors 126 | 127 | It is quite easy to infuse a vector. 128 | 129 | years <- c(2013,2014,2015) 130 | sql_string <- "SELECT * FROM T1 WHERE Year IN ({{years}})" 131 | 132 | infuse(sql_string, years=years) 133 | 134 | ## SELECT * FROM T1 WHERE Year IN (2013,2014,2015) 135 | 136 | You can also specify the collapse character. 137 | 138 | infuse(sql_string, years=years, collapse_char = ";") 139 | 140 | ## SELECT * FROM T1 WHERE Year IN (2013;2014;2015) 141 | 142 | ### Processing / transforming your inputs 143 | 144 | A `transform_function` can be specified in the `infuse` command. This 145 | allows for pre-processing of the parameter values before inserting them 146 | in the template. 147 | 148 | What we don't want to happen is the following: 149 | 150 | sql<-"INSERT INTO Students (Name) VALUES ({{name}})" 151 | name <- "Robert'); DROP TABLE Students;--" 152 | 153 | infuse(sql, name = name) 154 | 155 | ## INSERT INTO Students (Name) VALUES (Robert'); DROP TABLE Students;--) 156 | 157 | Yikes! A way to solve this is to specify your own custom transform 158 | function. 159 | 160 | my_transform_function<-function(v){ 161 | # replace single quotes with double quotes 162 | v<-gsub("'", "''", v) 163 | # encloses the string in single quotes 164 | v<-paste0("'",v,"'") 165 | 166 | return(v) 167 | } 168 | 169 | infuse(sql, name = name, transform_function = my_transform_function) 170 | 171 | ## INSERT INTO Students (Name) VALUES ('Robert''); DROP TABLE Students;--') 172 | 173 | Of course you can also use functions from other packages. Specifically 174 | for SQL I advise you to take a look at the `dbplyr::build_sql` function. 175 | 176 | infuse(sql, name = name, transform_function = dbplyr::build_sql) 177 | 178 | ## Warning: Installed Rcpp (0.12.12) different from Rcpp used to build dplyr (0.12.11). 179 | ## Please reinstall dplyr to avoid random crashes or undefined behavior. 180 | 181 | ## INSERT INTO Students (Name) VALUES ('Robert''); DROP TABLE Students;--') 182 | 183 | Issues / questions 184 | ------------------ 185 | 186 | Simply create a new issue at this GitHub repository. 187 | 188 | Changes 189 | ------- 190 | 191 | ### v.0.2.8 192 | 193 | - added `strict` parameter to `infuser`, if set to `TRUE` will stop 194 | processing in case of incomplete set of supplied template parameters 195 | 196 | ### v.0.2.7 197 | 198 | - change `dpyr::build_sql` to `dbplyr::build_sql` 199 | 200 | ### v.0.2.6 201 | 202 | - set `readLines`'s `warn` parameter to `FALSE` 203 | 204 | ### v.0.2.5 205 | 206 | - fixed bug/typo related to \#10 207 | 208 | ### v.0.2.4 209 | 210 | - fixed testing to adhere to new `testthat` standards 211 | 212 | ### v.0.2.2 213 | 214 | - dropped `key_value_list` parameter (will be interpreted 215 | automatically) 216 | - fixed passing of the `variable_identifier` 217 | - ability to make `variable_identifier` persistent 218 | 219 | ### v.0.2.1 220 | 221 | - fixed: incorrect replacement of parameters with same prefix 222 | 223 | ### v.0.2.0 224 | 225 | - consolidating adjustments and releasing version on CRAN 226 | 227 | ### v.0.1.4 228 | 229 | - updated print function for output of infuse (uses `cat` for now on) 230 | 231 | ### v0.1.3 232 | 233 | - added optional `collapse_char` argument to `infuse` command 234 | 235 | ### v0.1.2 236 | 237 | - added optional `key_value_list` argument to `infuse` command 238 | 239 | ### v0.1.1 240 | 241 | - added optional `transform_function` argument to `infuse` command 242 | -------------------------------------------------------------------------------- /infuser.Rproj: -------------------------------------------------------------------------------- 1 | Version: 1.0 2 | 3 | RestoreWorkspace: Default 4 | SaveWorkspace: Default 5 | AlwaysSaveHistory: Default 6 | 7 | EnableCodeIndexing: Yes 8 | UseSpacesForTab: Yes 9 | NumSpacesForTab: 2 10 | Encoding: UTF-8 11 | 12 | RnwWeave: Sweave 13 | LaTeX: pdfLaTeX 14 | 15 | AutoAppendNewline: Yes 16 | StripTrailingWhitespace: Yes 17 | 18 | BuildType: Package 19 | PackageUseDevtools: Yes 20 | PackageInstallArgs: --no-multiarch --with-keep.source 21 | PackageCheckArgs: --as-cran 22 | PackageRoxygenize: rd,collate,namespace 23 | -------------------------------------------------------------------------------- /inst/extdata/sql1.sql: -------------------------------------------------------------------------------- 1 | SELECT LAT_N, CITY, TEMP_F 2 | FROM STATS, STATION 3 | WHERE MONTH = {{month|3}} 4 | AND YEAR = {{year}} 5 | AND STATS.ID = STATION.ID 6 | ORDER BY TEMP_F; 7 | -------------------------------------------------------------------------------- /man/infuse.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/core.R 3 | \name{infuse} 4 | \alias{infuse} 5 | \title{Infuse a template with values.} 6 | \usage{ 7 | infuse(file_or_string, ..., 8 | variable_identifier = getOption("variable_identifier"), 9 | default_char = "|", collapse_char = ",", 10 | transform_function = function(value) return(value), 11 | verbose = getOption("verbose"), simple_character = FALSE, 12 | strict = FALSE) 13 | } 14 | \arguments{ 15 | \item{file_or_string}{the template file or a character string containing the template} 16 | 17 | \item{...}{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)} 18 | 19 | \item{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("{{", "}}"))}} 20 | 21 | \item{default_char}{the character use to specify a default after} 22 | 23 | \item{collapse_char}{the character used to collapse a supplied vector} 24 | 25 | \item{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.} 26 | 27 | \item{verbose}{verbosity level} 28 | 29 | \item{simple_character}{if \code{TRUE} returns only a character vector, else adds the \code{infuser} class to the returned object.} 30 | 31 | \item{strict}{if \code{TRUE} stops processing when a requested parameter is not supplied, else will simply leave the parameter as-is} 32 | } 33 | \description{ 34 | For more info and usage examples see the README on the \href{https://github.com/Bart6114/infuser}{\code{infuser} github page}. 35 | 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. 36 | } 37 | -------------------------------------------------------------------------------- /man/print.infuse.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/helpers.R 3 | \name{print.infuse} 4 | \alias{print.infuse} 5 | \title{prints/shows the result of the \code{infuse} function using the \code{cat} function} 6 | \usage{ 7 | \method{print}{infuse}(x, ...) 8 | } 9 | \arguments{ 10 | \item{x}{output of the \code{infuse} function} 11 | 12 | \item{...}{further arguments passed to or from other methods.} 13 | } 14 | \description{ 15 | prints/shows the result of the \code{infuse} function using the \code{cat} function 16 | } 17 | -------------------------------------------------------------------------------- /man/print_requested_params.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/helpers.R 3 | \name{print_requested_params} 4 | \alias{print_requested_params} 5 | \title{Prints requested parameters} 6 | \usage{ 7 | print_requested_params(params_requested) 8 | } 9 | \arguments{ 10 | \item{params_requested}{the list of requested parameters} 11 | } 12 | \description{ 13 | Prints requested parameters 14 | } 15 | -------------------------------------------------------------------------------- /man/read_template.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/helpers.R 3 | \name{read_template} 4 | \alias{read_template} 5 | \title{Reads the template from a file or string} 6 | \usage{ 7 | read_template(file_or_string) 8 | } 9 | \arguments{ 10 | \item{file_or_string}{the file or character string to read} 11 | } 12 | \description{ 13 | Reads the template from a file or string 14 | } 15 | -------------------------------------------------------------------------------- /man/trim.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/helpers.R 3 | \name{trim} 4 | \alias{trim} 5 | \title{Returns string w/o leading or trailing whitespace} 6 | \usage{ 7 | trim(x) 8 | } 9 | \arguments{ 10 | \item{x}{to string to strip} 11 | } 12 | \description{ 13 | Returns string w/o leading or trailing whitespace 14 | } 15 | -------------------------------------------------------------------------------- /man/variables_requested.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/core.R 3 | \name{variables_requested} 4 | \alias{variables_requested} 5 | \title{Shows which variables are requested by the template} 6 | \usage{ 7 | variables_requested(file_or_string, variable_identifier = c("{{", "}}"), 8 | default_char = "|", verbose = FALSE) 9 | } 10 | \arguments{ 11 | \item{file_or_string}{the template file or a string containing the template} 12 | 13 | \item{variable_identifier}{the opening and closing character that denounce a variable in the template} 14 | 15 | \item{default_char}{the character use to specify a default after} 16 | 17 | \item{verbose}{verbosity level} 18 | } 19 | \description{ 20 | Shows which variables are requested by the template 21 | } 22 | -------------------------------------------------------------------------------- /tests/testthat.R: -------------------------------------------------------------------------------- 1 | library(testthat) 2 | library(infuser) 3 | 4 | test_check("infuser") 5 | -------------------------------------------------------------------------------- /tests/testthat/test_core_functionality.R: -------------------------------------------------------------------------------- 1 | library(infuser) 2 | 3 | ########################################### 4 | context("replacing simple string parameters") 5 | 6 | 7 | SQL_string <- "SELECT LAT_N, CITY, TEMP_F 8 | FROM STATS, STATION 9 | WHERE MONTH = {{month}} 10 | AND YEAR = {{year}} 11 | AND STATS.ID = STATION.ID 12 | ORDER BY TEMP_F;" 13 | 14 | SQL_string_with_whitespaces <- "SELECT LAT_N, CITY, TEMP_F 15 | FROM STATS, STATION 16 | WHERE MONTH = {{month}} 17 | AND YEAR = {{year}} 18 | AND STATS.ID = STATION.ID 19 | ORDER BY TEMP_F;" 20 | 21 | SQL_string_with_defaults <- "SELECT LAT_N, CITY, TEMP_F 22 | FROM STATS, STATION 23 | WHERE MONTH = {{ month|3}} 24 | AND YEAR = {{year}} 25 | AND STATS.ID = STATION.ID 26 | ORDER BY TEMP_F;" 27 | 28 | SQL_string_wanted <-"SELECT LAT_N, CITY, TEMP_F 29 | FROM STATS, STATION 30 | WHERE MONTH = 3 31 | AND YEAR = 2020 32 | AND STATS.ID = STATION.ID 33 | ORDER BY TEMP_F;" 34 | 35 | 36 | test_that("string replacements occurs as expected",{ 37 | expect_equivalent(infuse(SQL_string, month=3, year=2020, simple_character=TRUE), SQL_string_wanted) 38 | expect_equivalent(infuse(SQL_string_with_whitespaces, month=3, year=2020, simple_character=TRUE), SQL_string_wanted) 39 | }) 40 | 41 | test_that("string replacements occurs as expected when providing a list instead of arguments",{ 42 | expect_warning(infuse(SQL_string, key_value_list=list(month=3, year=2020)), simple_character=TRUE) #deprecated 43 | expect_equivalent(infuse(SQL_string, list(month=3, year=2020), simple_character=TRUE), SQL_string_wanted) 44 | expect_equivalent(infuse(SQL_string_with_whitespaces, list(month=3, year=2020), simple_character=TRUE), SQL_string_wanted) 45 | }) 46 | 47 | 48 | context("replacing string parameters with defaults") 49 | 50 | test_that("string replacements occurs as expected with defaults in place",{ 51 | expect_equivalent(infuse(SQL_string_with_defaults, year=2020, simple_character=TRUE), SQL_string_wanted) 52 | }) 53 | 54 | test_that("parameters with same prefix are replaced as expected",{ 55 | expect_equivalent(infuse("test-{{output}}-{{outputA}}", output = "do", outputA = "a", simple_character=TRUE), "test-do-a") 56 | }) 57 | 58 | context("replacing parameters in template file") 59 | 60 | test_that("string replacements occurs as expected with defaults in place",{ 61 | expect_equivalent(infuse(system.file("extdata", "sql1.sql", package = "infuser"), year=2020, simple_character=TRUE), SQL_string_wanted) 62 | }) 63 | 64 | ########################################### 65 | context("replacing parameters with multiple occurences") 66 | 67 | SQL_string <- "SELECT LAT_N, CITY, TEMP_F 68 | FROM STATS, STATION 69 | WHERE MONTH = {{month|3}} 70 | AND YEAR = {{year}} 71 | AND YEAR2 = {{year}} 72 | AND STATS.ID = STATION.ID 73 | ORDER BY TEMP_F;" 74 | 75 | SQL_string_wanted <-"SELECT LAT_N, CITY, TEMP_F 76 | FROM STATS, STATION 77 | WHERE MONTH = 3 78 | AND YEAR = 2020 79 | AND YEAR2 = 2020 80 | AND STATS.ID = STATION.ID 81 | ORDER BY TEMP_F;" 82 | 83 | 84 | test_that("string replacements occurs as expected with defaults in place",{ 85 | expect_equivalent(infuse(SQL_string, year=2020, simple_character=TRUE), SQL_string_wanted) 86 | }) 87 | 88 | ########################################### 89 | context("providing a vector and collapsing it with a specified character") 90 | 91 | template <- "hello {{var1}}" 92 | to_infuse <- c(1,2,3) 93 | should_be <- "hello 1,2,3" 94 | should_be2 <- "hello 1|2|3" 95 | 96 | test_that("infusing of vector with default ',' works",{ 97 | expect_equivalent(infuse(template, var1 = to_infuse, simple_character=TRUE), should_be) 98 | }) 99 | 100 | 101 | test_that("infusing of vector with specified char works",{ 102 | expect_equivalent(infuse(template, var1 = to_infuse, collapse_char = "|", simple_character=TRUE), should_be2) 103 | }) 104 | 105 | 106 | ########################################### 107 | context("custom transform function") 108 | 109 | sql<-"INSERT INTO Students (Name) VALUES ({{name}})" 110 | name <- "Robert'); DROP TABLE Students;--" 111 | 112 | my_transform_function<-function(v){ 113 | # replace single quotes with double quotes 114 | v<-gsub("'", "''", v) 115 | # encloses the string in single quotes 116 | v<-paste0("'",v,"'") 117 | 118 | return(v) 119 | } 120 | 121 | BOBBY_wanted <- "INSERT INTO Students (Name) VALUES ('Robert''); DROP TABLE Students;--')" 122 | 123 | test_that("the custom transform function works",{ 124 | expect_equivalent(infuse(sql, name = name, transform_function = my_transform_function, simple_character=TRUE), BOBBY_wanted) 125 | }) 126 | 127 | 128 | ############################################### 129 | context("variable identifiers") 130 | 131 | test_that("variable identifiers are correctly used",{ 132 | expect_equivalent( 133 | infuse("${test}", variable_identifier = c("\\${", "}"), test = "123", simple_character=TRUE), "123") 134 | }) 135 | 136 | 137 | test_that("variable identifiers are correctly used when set as an option",{ 138 | options(variable_identifier = c("\\${", "}")) 139 | expect_equivalent( 140 | infuse("${test}", test = "123", simple_character=TRUE), "123") 141 | # reset to default 142 | options(variable_identifier=c("{{", "}}")) 143 | }) 144 | 145 | test_that("empty param list is accepted",{ 146 | expect_equivalent(infuse(sql), sql) 147 | }) 148 | 149 | ############################################### 150 | context("stop on missing parameter in strict mode") 151 | 152 | test_that("stop on missing parameter",{ 153 | expect_error( 154 | infuse("{{foo}}{{bar}}", foo = "123", strict = TRUE) 155 | ) 156 | 157 | expect_error( 158 | infuse("{{foo}}{{bar}}", bar = "456", strict = TRUE) 159 | ) 160 | 161 | expect_equivalent( 162 | infuse("{{foo}}{{bar}}", foo = "123", bar = "456", strict = TRUE, simple_character = TRUE), "123456" 163 | ) 164 | 165 | }) 166 | 167 | -------------------------------------------------------------------------------- /tests/testthat/test_helpers_functionality.R: -------------------------------------------------------------------------------- 1 | library(infuser) 2 | 3 | ########################################### 4 | context("test trim function") 5 | 6 | s_given <- " hello123 " 7 | s_wanted <- "hello123" 8 | 9 | test_that("trim function workes OK",{ 10 | expect_equal(trim(s_given), s_wanted) 11 | }) 12 | 13 | 14 | ########################################### 15 | context("test read_template functionality") 16 | 17 | s_given <- "hello {{var1}}" 18 | 19 | test_that("read_template function OK",{ 20 | expect_equal(infuser:::read_template(s_given), s_given) 21 | }) 22 | 23 | ## add filereading test 24 | -------------------------------------------------------------------------------- /vignettes/getting_started.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Getting started with infuser" 3 | author: "Bart Smeets" 4 | date: "`r Sys.Date()`" 5 | output: rmarkdown::html_vignette 6 | vignette: > 7 | %\VignetteIndexEntry{Getting started} 8 | %\VignetteEngine{knitr::rmarkdown} 9 | %\VignetteEncoding{UTF-8} 10 | --- 11 | 12 | > Note: this is a copy of the README file. 13 | 14 | ```infuser``` is a simple and very basic templating engine for R. It replaces parameters within templates with specified values. Templates can be either contained in a string or in a file. 15 | 16 | ## Installation 17 | 18 | ```{r, eval=FALSE} 19 | install.packages("infuser") 20 | ``` 21 | 22 | If you want to use the most up-to-date version, install using ```devtools::install_github```. 23 | 24 | ```{r, eval=FALSE} 25 | devtools::install_github("Bart6114/infuser") 26 | ``` 27 | 28 | ## Hello World 29 | 30 | A simple Hello World example that makes use of the `magrittr` piping workflow. 31 | 32 | ```{r} 33 | library(infuser) 34 | library(magrittr) 35 | 36 | "Hello {{var1}}!" %>% 37 | infuse(var1="world") 38 | ``` 39 | 40 | ## Usage 41 | 42 | ### Working with character strings as templates 43 | 44 | Let's have a look at an example string. 45 | 46 | ```{r} 47 | my_sql<-"SELECT * FROM Customers 48 | WHERE Year = {{year}} 49 | AND Month = {{month|3}};" 50 | ``` 51 | 52 | Here the variable parameters are enclosed by ```{{``` and ```}}``` characters. See ```?infuse``` to use your own specification. 53 | 54 | From now on, we suppose the character string ```my_sql``` is our template. To show the parameters requested by the template you can run the following. 55 | 56 | ```{r, results='hide'} 57 | library(infuser) 58 | variables_requested(my_sql, verbose = TRUE) 59 | ``` 60 | 61 | To fill in the template simply provide the requested parameters. 62 | 63 | ```{r} 64 | infuse(my_sql, year=2016, month=8) 65 | ``` 66 | 67 | You can also provide a named list with the requested parameters. 68 | 69 | ```{r} 70 | my_list<- 71 | list(year=2016, 72 | month=8) 73 | 74 | infuse(my_sql, my_list) 75 | ``` 76 | 77 | If a default value is available in the template, it will be used if the parameter is not specified. 78 | 79 | ```{r} 80 | infuse(my_sql, year=2016) 81 | ``` 82 | 83 | ### Working with text files as templates 84 | 85 | Just like we're using a string here, a text file can be used. An example text file can be found in the package as follows: 86 | 87 | ```{r} 88 | example_file<- 89 | system.file("extdata", "sql1.sql", package="infuser") 90 | 91 | example_file 92 | ``` 93 | 94 | Again, we can check which parameters are requested by the template. 95 | 96 | ```{r, results='hide'} 97 | variables_requested(example_file, verbose = TRUE) 98 | ``` 99 | 100 | And provide their values. 101 | 102 | ```{r} 103 | infuse(example_file, year = 2016, month = 12) 104 | ``` 105 | 106 | ### Infusing vectors 107 | 108 | It is quite easy to infuse a vector. 109 | 110 | ```{r} 111 | years <- c(2013,2014,2015) 112 | sql_string <- "SELECT * FROM T1 WHERE Year IN ({{years}})" 113 | 114 | infuse(sql_string, years=years) 115 | ``` 116 | 117 | You can also specify the collapse character. 118 | 119 | ```{r} 120 | infuse(sql_string, years=years, collapse_char = ";") 121 | ``` 122 | 123 | 124 | 125 | ### Processing / transforming your inputs 126 | 127 | A ```transform_function``` can be specified in the ```infuse``` command. This allows for pre-processing of the parameter values before inserting them in the template. 128 | 129 | What we don't want to happen is the following: 130 | 131 | ```{r} 132 | sql<-"INSERT INTO Students (Name) VALUES ({{name}})" 133 | name <- "Robert'); DROP TABLE Students;--" 134 | 135 | infuse(sql, name = name) 136 | ``` 137 | 138 | Yikes! A way to solve this is to specify your own custom transform function. 139 | 140 | ```{r} 141 | my_transform_function<-function(v){ 142 | # replace single quotes with double quotes 143 | v<-gsub("'", "''", v) 144 | # encloses the string in single quotes 145 | v<-paste0("'",v,"'") 146 | 147 | return(v) 148 | } 149 | 150 | infuse(sql, name = name, transform_function = my_transform_function) 151 | ``` 152 | 153 | Of course you can also use functions from other packages. Specifically for SQL I advise you to take a look at the `dbplyr::build_sql` function. 154 | 155 | 156 | ```{r} 157 | infuse(sql, name = name, transform_function = dbplyr::build_sql) 158 | ``` 159 | --------------------------------------------------------------------------------