1#' Format a string. 2#' 3#' Perform a string formatting operation. 4#' 5#' The string on which this method is called can contain 6#' literal text or replacement fields delimited by braces \code{\{\}}. Each replacement field contains 7#' either the numeric index of a positional argument, or the name of a keyword argument. Returns 8#' a copy of the string where each replacement field is replaced with the string value of the 9#' corresponding argument. 10#' 11#' If \code{...} is a single argument of a \code{data.frame}-like object, \code{pystr_format} will 12#' return an \code{nrow()}-length character vector using the column names of the data.frame for 13#' the named \code{\{placeholder\}}s. 14#' 15#' @param str A character vector. 16#' @param ... Parameter values. See details and examples 17#' 18#' @return A character vector. 19#' 20#' @references \url{https://docs.python.org/3/library/stdtypes.html#str.format} 21#' 22#' @examples 23#' # Numeric placeholders 24#' 25#' pystr_format("Hello {1}, my name is {2}.", "World", "Nicole") 26#' pystr_format("Hello {1}, my name is {2}.", c("World", "Nicole")) 27#' pystr_format("Hello {1}, my name is {2}.", list("World", "Nicole")) 28#' 29#' # Named placeholders 30#' 31#' pystr_format("Hello {thing}, my name is {name}.", thing="World", name="Nicole") 32#' pystr_format("Hello {thing}, my name is {name}.", c(thing="World", name="Nicole")) 33#' pystr_format("Hello {thing}, my name is {name}.", list(thing="World", name="Nicole")) 34#' 35#' # Pass in characters and numbers 36#' 37#' pystr_format("Hello {name}, you have {n} new notifications!", name="Nicole", n=2) 38#' 39#' ## Placeholders can be used more than once 40#' 41#' pystr_format("The name is {last}. {first} {last}.", last="Bond", first="James") 42#' 43#' ## Pass in a whole data frame, matching by column names 44#' 45#' my_cars <- data.frame(car=rownames(mtcars), mtcars) 46#' head(pystr_format("The {car} gets {mpg} mpg (hwy) despite having {cyl} cylinders.", my_cars)) 47#' 48#' supers <- data.frame(first=c("Bruce", "Hal", "Clark", "Diana"), 49#' last=c("Wayne", "Jordan", "Kent", "Prince"), 50#' is=c("Batman", "Green Lantern", "Superman", "Wonder Woman")) 51#' pystr_format("{first} {last} is really {is} but you shouldn't call them {first} in public.", supers) 52#' 53#' @export 54pystr_format <- function(str, ...) { 55 args = list(...) 56 return(sapply(str, function(x) pystr_format_(x, args), USE.NAMES = FALSE)) 57} 58 59pystr_format_ <- function(str, args) { 60 # if nothing was passed in besides 'str' 61 if(length(args) == 0) { 62 return(str) 63 } 64 65 params = args 66 67 if(length(args) == 1) { 68 69 if (inherits(args[[1]], "data.frame")) { 70 71 # convert whatever else it may be besides a data.frame to a data.frame 72 # to avoid return type issues with tbl_'s and with= nonsense with data.table 73 df <- data.frame(args[[1]], stringsAsFactors=FALSE, check.names=FALSE) 74 75 pat <- "\\{[[:alnum:]]+\\}" 76 looking_for <- gsub("[\\{\\}]", "", regmatches(str, gregexpr(pat, str))[[1]]) 77 has <- colnames(df) 78 will_replace <- intersect(looking_for, has) 79 if (length(will_replace) > 0) { 80 sapply(1:nrow(df), function(i) { 81 res <- str 82 for(repl in will_replace) res <- gsub(sprintf("\\{%s\\}", repl), df[i, repl], res) 83 res 84 }) -> out 85 return(out) 86 87 } 88 89 } 90 91 if(is.null(names(args))) { 92 params = args[[1]] 93 } 94 } 95 96 if(length(params) == 0) { 97 return(str) 98 } 99 100 if(is.null(names(params))) { 101 names(params) = 1:length(params) 102 } 103 104 for(i in 1:length(params)) { 105 str = gsub(paste0("\\{", names(params[i]), "\\}"), params[[i]], str) 106 } 107 108 return(str) 109} 110