# == title
# Simple variable interpolation in texts
#
# == param
# -... Text string in which variables are marked with certain rules
# -envir Environment where to look for variables. By default it is the environment
# where `qq` is envoked. It can also be a list in which element names are
# the variable names to be interpolated.
# -code.pattern Pattern of marks for the variables. By default it is ``@\\\\{CODE\\\\}`` which means
# you can write your variable as ``@{variable}``. This value can be a vector that all
# patterns are searched.
# -collapse If variables return vector of length larger than one, whether collapse into one string
# or return a vector
# -sep Separator character when there are multiple templates.
#
# == details
# I like variable interpolation in Perl. But in R, if you want to concatenate plain text and variables,
# you need to use functions such as `base::paste`. However, if there are so many variables, quotes, braces
# in the string you want to construct, it would be painful.
#
# This function allows you to construct strings as in Perl style. Variables are
# marked in the text with certain rule. `qq` will look up these variables in user's
# environment and replace the variable marks with their real values.
#
# For more explaination of this function, please refer to vignette.
#
# == author
# Zuguang Gu <z.gu@dkfz.de>
#
# == example
# a = 1
# b = "text"
# qq("a = @{a}, b = '@{b}'")
# qq("a = @{a}", "b = '@{b}'", sep = ", ")
#
# a = 1:2
# qq("a = @{a}, b = '@{b}'")
# qq("a = @{a}, b = '@{b}'", collapse = FALSE)
#
# a = 1
# qq("a = `a`, b = '`b`'", code.pattern = "`CODE`")
#
qq = function(..., envir = parent.frame(), code.pattern = NULL, collapse = TRUE, sep = " ") {
lt = list(...)
if(length(lt) == 1) {
text = lt[[1]]
} else {
text = paste(unlist(lt), collapse = sep)
}
if(is.null(code.pattern)) {
if(!is.null(options("code.pattern")[[1]])) {
code.pattern = options("code.pattern")[[1]]
} else {
code.pattern = qq.options("code.pattern")
}
}
if(length(text) != 1) {
stop("Now only support text with length of 1.\n")
}
if(!is.null(envir)) {
if(is.environment(envir)) {
e = envir
} else {
e = as.environment(envir)
}
} else {
e = .GlobalEnv
}
# for (i in 1:length(text)) {
# check wether there are code replacements
template = NULL
code = NULL
for(i in seq_along(code.pattern)) {
fc = find_code(code.pattern[i], text)
template = c(template, fc[[1]])
code = c(code, fc[[2]])
}
if(length(template)) { # if there is code replacement
# replace the code with its value
return_value = lapply(code, function(c) {
x = eval(parse(text = c), envir = e)
if(is.null(x)) {
return("")
} else if(length(x) == 0) {
return("")
} else {
if(is.factor(x)) {
x = as.vector(x)
}
return(x)
}
}) # anony function is the first level parent
is_return_value_vector = sapply(return_value, function(r) is.atomic(r))
if(! all(is_return_value_vector)) {
stop("All your codes should only return simple atomic vectors.\n")
}
# length of the return value
# need to test it since not all code returns a scalar
return_value_length = sapply(return_value, function(x) length(x))
if(max(return_value_length) > 1) {
# if some piece of codes return a vector
# recycle to `max(return_value_length)`
current = rep(text, max(return_value_length))
for(ai in 1:max(return_value_length)) {
for(iv in 1:length(template)) {
current[ai] = gsub(template[iv],
return_value[[iv]][(ai-1) %% length(return_value[[iv]]) + 1],
current[ai], fixed = TRUE)
}
}
if(collapse) {
text = paste(current, collapse = "")
} else {
text = current
}
} else if(max(return_value_length == 1)) { # all variable returns a scalar
current = text
for(iv in 1:length(template)) {
current = gsub(template[iv], return_value[[iv]], current, fixed = TRUE)
}
text = current
}
}
# }
return(text)
}
find_code = function(m, text) {
if(length(text) != 1) {
stop("text must be length of 1.")
}
m2 = gsub("CODE", ".+?", m)
reg = gregexpr(m2, text)[[1]]
v1 = character(0)
if(reg[1] > -1) {
v1 = sapply(1:length(reg), function(i) substr(text, as.numeric(reg)[i], as.numeric(reg)[i]+ attr(reg, "match.length")[i] - 1))
}
edge = strsplit(m, "CODE")[[1]]
v2 = gsub(paste("^", edge[1], "|", edge[2], "$", sep=""), "", v1)
return(list(template=v1, code=v2))
}
# == title
# Print a string which has been intepolated with variables
#
# == param
# -... text string in which variables are marked with certain rules
# -envir environment where to look for those variables
# -code.pattern pattern of marks for the variables
# -file pass to `base::cat`
# -sep pass to `base::cat`
# -fill pass to `base::cat`
# -labels pass to `base::cat`
# -append pass to `base::cat`
# -cat_prefix prefix string. It is prior than ``qq.options(cat_prefix)``.
# -strwrap whether call `base::strwrap` to wrap the string
# -strwrap_param parameters sent to `base::strwrap`, must be a list
# -sep2 Separation character when there are multiple templates.
#
# == details
# This function is a shortcut of
#
# cat(qq(text, envir, code.pattern), ...)
#
# Additionally, you can add global prefix:
#
# qq.options("cat_prefix" = "[INFO] ")
# qq.options("cat_prefix" = function(x) format(Sys.time(), "[\%Y-\%m-\%d \%H:\%M:\%S] "))
# qq.options("cat_prefix" = NULL)
#
# You can also add local prefix by specifying ``cat_prefix`` in `qqcat`.
#
# qqcat(text, cat_prefix = "[INFO] ")
#
# Please refer to `qq` to find more details.
#
# == author
# Zuguang Gu <z.gu@dkfz.de>
#
# == example
# a = 1
# b = "text"
# qqcat("a = @{a}, b = '@{b}'\n")
# qqcat("a = `a`, b = '`b`'\n", code.pattern = "`CODE`")
#
# qq.options("cat_prefix" = function(x) format(Sys.time(), "[\%Y-\%m-\%d \%H:\%M:\%S] "))
# qqcat("a = @{a}, b = '@{b}'\n")
# Sys.sleep(2)
# qqcat("a = @{a}, b = '@{b}'\n")
# qq.options(RESET = TRUE)
qqcat = function(..., envir = parent.frame(), code.pattern = NULL, file = "",
sep = " ", fill = FALSE, labels = NULL, append = FALSE, cat_prefix = NULL,
strwrap = qq.options("cat_strwrap"), strwrap_param = list(), sep2 = "") {
lt = list(...)
if(length(lt) == 1) {
text = lt[[1]]
} else {
text = paste(unlist(lt), collapse = sep2)
}
text = qq(text, envir = envir, code.pattern = code.pattern)
if(strwrap) {
if(!inherits(strwrap_param, "list")) {
stop("`strwrap_param` must be a list.")
}
if(grepl("\n$", text)) {
text = paste(do.call("strwrap", c(strwrap_param, list(x = text))), collapse = "\n")
text = paste0(text, "\n")
} else {
text = paste(do.call("strwrap", c(strwrap_param, list(x = text))), collapse = "\n")
}
}
cat(text, file = file, sep = sep, fill = fill, labels = labels, append = append, cat_prefix = cat_prefix)
}
cat = function(..., file = "", sep = " ", fill = FALSE, labels = NULL, append = FALSE, cat_prefix = NULL) {
if(!is.null(options("cat_verbose")[[1]])) {
if(!options("cat_verbose")[[1]]) {
return(invisible(NULL))
}
}
if(!qq.options("cat_verbose")) {
return(invisible(NULL))
}
if(is.null(cat_prefix)) {
if(!is.null(options("cat_prefix")[[1]])) {
cat_prefix = options("cat_prefix")[[1]]
} else {
cat_prefix = qq.options("cat_prefix")
}
}
if(is.function(cat_prefix)) {
cat_prefix = cat_prefix()
}
base::cat(cat_prefix, file = file, sep = sep, fill = fill, labels = labels, append = append)
base::cat(... , file = file, sep = sep, fill = fill, labels = labels, append = append)
}
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.