Nothing
#' Retrieve the name of an environment
#'
#' Retrieve the name of an environment as the \code{\link{environmentName}} function of the base package does,
#' but extending its functionality to retrieving the names of user-defined environments and function
#' execution environments.
#'
#' @param env environment whose name is of interest.
#' It can be given as an object of class environment, as a string with the name of the environment,
#' or as a string with the memory address of the environment.
#' The latter is useful to find out if a given memory address is the reference of an environment object.
#' Note that the variable passed may or may \emph{not} exist in the calling environment, as the purpose
#' of this function is also to search for it (and return its name if it is an environment).
#' It defaults to parent.frame(), meaning that the name of the environment that calls this function is retrieved.
#' @param envir environment where \code{env} should be searched for. When \code{NULL}, \code{env} is searched in
#' the whole workspace, including packages and user-defined environments, recursively.
#' @param envmap data frame containing a lookup table with name-address pairs of environment names and
#' addresses to be used when searching for environment \code{env}. It defaults to \code{NULL} which means that the
#' lookup table is constructed on the fly with the environments defined in the \code{envir} environment
#' --if not \code{NULL}--, or in the whole workspace if \code{envir=NULL}.
#' See the details section for more information on its structure.
#' @param matchname flag indicating whether the match for \code{env} is based on its name or on its
#' memory address. In the latter case all environments sharing the same memory address of the given
#' environment are returned. Such scenario happens when, for instance, different
#' environment objects have been defined equal to another environment (as in \code{env1 <- env}).
#' It defaults to \code{FALSE}.
#' @param ignore one or more environment names to ignore if found during the search. These environments
#' are removed from the output. It should be given as a character array if more than one environments
#' should be ignored. See the details section for more information.
#' @param include_functions flag indicating whether to look for user-defined environments inside function
#' execution environments. This should be used with care because in a complicated function chain, some function
#' execution environments may contain environments that point to other environments (e.g. the 'envclos' environment
#' in the \code{eval()} function when running tests using the \code{test_that} package).
#'
#' @details
#' If \code{env} is an environment it is searched for in the \code{envir} environment using its memory address.
#' If \code{env} is a string containing a valid 16-digit memory address (enclosed in < >), the memory address
#' itself is searched for among the defined environments in the \code{envir} environment.
#' In both cases, if \code{envir=NULL} the search is carried out in the whole workspace.
#'
#' It may happen that more than one environment exist with the same memory address (for instance
#' if an environment is a copy of another environment). In such case, if \code{matchname=FALSE},
#' the names of ALL the environments matching \code{env}'s memory address are returned. Otherwise,
#' only the environments matching the given name are returned.
#'
#' If \code{envmap} is passed it should be a data frame providing an address-name pair lookup table
#' of environments and should contain at least the following columns:
#' \itemize{
#' \item{\code{location}} for user-defined environments, the name of the environment where the environment
#' is located; otherwise \code{NA}.
#' \item{\code{pathname}} the full \emph{environment path} to reach the environment separated by \code{$}
#' (e.g. \code{"env1$env$envx"})
#' \item{\code{address}} an 8-digit (32-bit architectures) thru 16-digit (64-bit architectures) memory address
#' of the environment given in \code{pathname} enclosed in < > (e.g. \code{"<0000000007DCFB38>"}
#' (64-bit architectures))
#' Be ware that Linux Debian distributions may have a 12-digit memory address representation.
#' So the best way to know is to check a memory address by calling e.g. `address("x")`.
#' }
#' Passing an \code{envmap} lookup table is useful for speedup purposes, in case several calls to this
#' function will be performed in the context of an unchanged set of defined environments.
#' Such \code{envmap} data frame can be created by calling \link{get_env_names}.
#' Use this parameter with care, as the matrix passed may not correspond to the actual mapping of existing
#' environments to their addresses and in that case results may be different from those expected.
#'
#' The following example illustrates the use of the \code{ignore} parameter:
#'
#' \code{for (e in c(globalenv(), baseenv())) { print(environment_name(e, ignore="e")) }}
#'
#' That is, we iterate on a set of environments and we don't want the loop variable (an environment itself)
#' to show up as part of the output generated by the call to \code{environment_name()}.
#'
#' @return
#' If \code{matchname=FALSE} (the default), an array containing the names of all the environments
#' (defined in the \code{envir} environment if \code{envir} is not \code{NULL}) having the same memory address
#' as the \code{env} environment.
#'
#' If \code{matchname=TRUE}, the environment name contained in \code{env} is used in addition to the memory
#' address to check the matched environments (potentially many if they have the same memory address)
#' so that only the environments having the same name and address as the \code{env} environment are returned.
#' Note that several environments may be found if environments with the same name are defined in
#' different environments.
#' WARNING: in this case, the name is matched exactly as the expression given in \code{env}. So for instance,
#' if \code{env=globalenv()$env1} the name \code{"globalenv()$env1"} is checked and this will not return any
#' environments since no environment can be called like that. For such scenario call the function with
#' parameter \code{env=env1} instead, or optionally with \code{env=env1} and \code{envir=globalenv()}
#' if the \code{env1} environment should be searched for just in the global environment.
#'
#' If \code{env} is not found or it is not an environment, \code{NULL} is returned.
#'
#' @aliases get_env_name
#'
#' @examples
#' # Retrieve name of a user-defined environment
#' env1 <- new.env()
#' environment_name(env1) # "env1"
#'
#' # Retrieve the name of an environment given as a memory address
#' env1_address = get_obj_address(globalenv()$env1)
#' environment_name(env1_address) # "env1"
#'
#' # Create a copy of the above environment
#' env1_copy <- env1
#' environment_name(env1) # "env1" "env1_copy"
#' # Retrieve just the env1 environment name
#' environment_name(env1, matchname=TRUE) # "env1"
#'
#' # Retrieve the name of an environment defined within another environment
#' with(env1, envx <- new.env())
#' environment_name(env1$envx) # "env1$envx" "env1_copy$envx"
#' environment_name(env1$envx, matchname=TRUE)
#' ## NULL, because the environment name is "envx", NOT "env1$envx"
#'
#' # Get a function's execution environment name
#' with(env1, f <- function() { cat("We are inside function", environment_name()) })
#' ## "We are inside function env1$f"
environment_name <- function(env=parent.frame(), envir=NULL, envmap=NULL, matchname=FALSE, ignore=NULL, include_functions=FALSE) {
# Output variable
env_names = NULL
# Flavor of the environment functions... (for reference)
#env_current = environment()
#env_parent = parent.env(env_current) # this is the enclosure of the current execution environment which may be different from parent.frame()! In fact, in general parent.env() of the execution environment is the global environment...
#env_calling = parent.frame()
# Setup the address-name pairs of the environments defined in envir if the envmap variable is not passed
if (is.null(envmap)) {
envmap = get_env_names(envir=envir, include_functions=include_functions)
}
if (!is.null(envmap)) { # This means that parameter 'envir' is a valid environment
# Variable used to store the indices of the envmap lookup table where the environment is found
indfound = numeric(0)
# Look for 'env' as a potential environment object in the address-name lookup table
# only when it is NOT given as a memory address (i.e. a string as in "<0x00000000119dba68>")
# If it's given as a memory address search directly for that memory address in the lookup table
# This special case is needed because get_obj_address() will return the memory address of variable
# 'env' and this is *not* what we want when 'env' is directly a memory address; we just want to
# search for the given memory address in the envmap lookup table... so no need to call get_obj_address()
# in that case.
if (!is_memory_address(env)) {
# We now call get_obj_address() to first look for 'env' in the given 'envir' environment --if not NULL--
# or in the whole workspace when envir=NULL, and then retrieve its memory address (if 'env' is found).
# NOTE that we don't simply call address() to get the memory address of 'env' because:
# - 'env' can be given as a string (i.e. a string containing the environment name)
# - even if 'env' is given as an environment object, it could exist in different environments
# and we would like to retrieve ALL of them. This is also the reason why we are calling the variable
# where the returned value is stored as a plural name ("addresses" instead of "address").
env_addresses = get_obj_address(env, envir=envir, envmap=envmap, n=1, include_functions=TRUE)
# Now look for the addresses returned in the envmap lookup table created above
# But first check if the match should be done by ADDRESS or instead by the NAME of the environment
# (the latter means that only the environments matching both the memory address AND the name
# of the 'env' environment are returned)
# This is important because several environments may exist that point to the same memory address
# (e.g. by doing e = globalenv(), e will have the same memory address as the global environment)
if (!matchname) {
# Look just for the address (the name of the environment doesn't matter)
# Note that this search can return more than one match even if there is only one environment with
# the name of the 'env' environment, and the reason is that there may be several environments
# pointing to the same memory address, as explained above.
indfound = which(envmap[,"address"] %in% env_addresses)
} else {
# Look for the address AND the name of the environment in the envmap data frame
# Note that this search can ALSO return more than one match since an environment with the same
# name may be present in different environments or packages.
# Get the name of the environment given in 'env'
env_name = get_obj_name(env, n=1, silent=TRUE)
indfound = which(envmap[,"address"] %in% env_addresses & envmap[,"name"] == env_name)
}
} else {
# 'env' is given as a memory address
# => search for this memory address in the envmap table
# Note that in this case we don't care about the value of parameter 'matchname' because
# there is no name associated to 'env' so the match will for sure be by address only!!
# First parse the memory address string
env = parse_memory_address(env)
indfound = which(toupper(envmap[,"address"]) == toupper(env))
}
# Clean up the matched environments: in the case both "function" and "proper" environments matched, keep just the "proper" environments
indfound = clean_up_matching_environments(envmap, indfound)
# If any environments are found construct the output variable
if (length(indfound) > 0) {
# Get the locations and names of the environments found
env_locations = ifelse( envmap[indfound,"path"] == "",
as.character(envmap[indfound,"location"]),
ifelse(envmap[indfound,"location"] == "R_GlobalEnv",
as.character(envmap[indfound,"path"]),
paste(as.character(envmap[indfound,"location"]), as.character(envmap[indfound,"path"]), sep="$")
)
)
env_names = as.character(envmap[indfound,"name"])
# Standardize the environment names (in case the global environment or the base environment are present)
env_names = sapply(env_names, FUN=standardize_env_name, USE.NAMES=FALSE)
# Check if the user asked to ignore some of the environments found and if so remove them from the output
# This is useful when we are calling environment_name() from e.g. a loop on a set of environments
# and we want to ignore from the list the loop variable which simply references an environmenet, as in:
# for (e in c(globalenv(), baseenv())) { print(environment_name(e, ignore="e")) }
if (!is.null(ignore)) {
# Standardize ignore in case it's the global environment or the base environment
ignore = sapply(ignore, FUN=standardize_env_name)
indkeep = which( !(env_names %in% ignore) )
env_names = env_names[indkeep]
}
# Store the location of the environments found as the names attribute of array env_name when:
# - envir=NULL (i.e. the search for the environment is in the whole workspace) AND
# - more than one environment was found (so that we use the names of the env_names object to distinguish where
# each environment was found)
# In fact, envir=NULL means that the user doesn't know where the environment is located
# (i.e. in which package) and may want to know where it is.
# In addition, there may be several environments matching 'env' if different variables point to the
# the same environment (that's why we also add the names when length(env_names) > 1).
# This is the same logic implemented in function get_obj_address() when envir=NULL (although implemented
# differently there because of the different information available in that case)
#
# If envir=NULL and just one environment has been found, the location is prefixed to the environment name,
# (as long as the location is NOT the global environment --which is assumed to be the default location of environments)
# so that the user receives just a character output (i.e. NOT a named character array on which
# Gabor Grothendieck complained)
#
# If envir is not NULL, the location information is not returned as part of the result because the
# user already gave the location in the envir= parameter!
if (is.null(envir)) {
if (length(env_names) > 1) {
names(env_names) = env_locations
} else if (!is.na(env_locations) && # env_locations = NA for system and package enviroments
env_locations != "R_GlobalEnv") { # We choose NOT to add R_GlobalEnv to the environment name because this is the DEFAULT location of environments!
env_names = paste(env_locations, env_names, sep="$")
}
}
}
}
# When the env_names array has a names attribute (containing the locations of the environments found),
# sort the array by the names attribute (so that locations appear alphabetically sorted)
if (!is.null(names(env_names))) {
ord = order(names(env_names))
env_names = env_names[ord]
}
return(env_names)
}
get_env_name <- environment_name
Any scripts or data that you put into this service are public.
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.