Learn R Programming

this.path (version 2.5.0)

set.sys.path: Implement 'this.path()' For Arbitrary 'source()'-Like Functions

Description

sys.path() is implemented to work with these functions and packages:

set.sys.path() can be used to implement sys.path() for any other source()-like functions.

set.env.path() and set.src.path() can be used alongside set.sys.path() to implement env.path() and src.path(), thereby fully implementing this.path(). Note that set.env.path() only makes sense if the code is being modularized, see Examples.

unset.sys.path() will undo a call to set.sys.path(). You will need to use this if you wish to call set.sys.path() multiple times within a function.

set.sys.path.function() is a special variant of set.sys.path() to be called within callr::r() on a function with an appropriate srcref.

with_sys.path() is a convenient way to evaluate code within the context of a file. Whereas set.sys.path() can only be used within a function, with_sys.path() can only be used outside a function.

See ?sys.path(local = TRUE) which returns the path of the executing script, confining the search to the local environment in which set.sys.path() was called.

wrap.source() should not be used, save for one specific use-case. See details.

Usage

set.sys.path(file,
    path.only = FALSE,
    character.only = path.only,
    file.only = path.only,
    conv2utf8 = FALSE,
    allow.blank.string = FALSE,
    allow.clipboard = !file.only,
    allow.stdin = !file.only,
    allow.url = !file.only,
    allow.file.uri = !path.only,
    allow.unz = !path.only,
    allow.pipe = !file.only,
    allow.terminal = !file.only,
    allow.textConnection = !file.only,
    allow.rawConnection = !file.only,
    allow.sockconn = !file.only,
    allow.servsockconn = !file.only,
    allow.customConnection = !file.only,
    ignore.all = FALSE,
    ignore.blank.string = ignore.all,
    ignore.clipboard = ignore.all,
    ignore.stdin = ignore.all,
    ignore.url = ignore.all,
    ignore.file.uri = ignore.all,
    Function = NULL, ofile)

set.env.path(envir, matchThisEnv = getOption("topLevelEnvironment"))

set.src.path(srcfile)

unset.sys.path()

set.sys.path.function(fun)

with_sys.path(file, expr, ...)

wrap.source(expr, path.only = FALSE, character.only = path.only, file.only = path.only, conv2utf8 = FALSE, allow.blank.string = FALSE, allow.clipboard = !file.only, allow.stdin = !file.only, allow.url = !file.only, allow.file.uri = !path.only, allow.unz = !path.only, allow.pipe = !file.only, allow.terminal = !file.only, allow.textConnection = !file.only, allow.rawConnection = !file.only, allow.sockconn = !file.only, allow.servsockconn = !file.only, allow.customConnection = !file.only, ignore.all = FALSE, ignore.blank.string = ignore.all, ignore.clipboard = ignore.all, ignore.stdin = ignore.all, ignore.url = ignore.all, ignore.file.uri = ignore.all)

Value

for set.sys.path(), if file is a path, then the normalized path with the same attributes, otherwise file itself. The return value of set.sys.path() should be assigned to a variable before use, something like:

{
    file <- set.sys.path(file, ...)
    sourcelike(file)
}

for wrap.source(), the result of evaluating expr.

Arguments

expr

for with_sys.path(), an expression to evaluate within the context of a file.

for wrap.source(), an (unevaluated) call to a source()-like function.

file

a connection or a character string giving the pathname of the file or URL to read from.

path.only

must file be an existing path? This implies character.only and file.only are TRUE and implies allow.file.uri and allow.unz are FALSE, though these can be manually changed.

character.only

must file be a character string?

file.only

must file refer to an existing file?

conv2utf8

if file is a character string, should it be converted to UTF-8?

allow.blank.string

may file be a blank string, i.e. ""?

allow.clipboard

may file be "clipboard" or a clipboard connection?

allow.stdin

may file be "stdin"? Note that "stdin" refers to the C-level ‘standard input’ of the process, differing from stdin() which refers to the R-level ‘standard input’.

allow.url

may file be a URL pathname or a connection of class "url-libcurl" / / "url-wininet"?

allow.file.uri

may file be a file:// URL?

allow.unz, allow.pipe, allow.terminal, allow.textConnection, allow.rawConnection, allow.sockconn, allow.servsockconn

may file be a connection of class "unz" / / "pipe" / / "terminal" / / "textConnection" / / "rawConnection" / / "sockconn" / / "servsockconn"?

allow.customConnection

may file be a custom connection?

ignore.all, ignore.blank.string, ignore.clipboard, ignore.stdin, ignore.url, ignore.file.uri

ignore the special meaning of these types of strings, treating it as a path instead?

Function

character vector of length 1 or 2; the name of the function and package in which set.sys.path() is called.

ofile

a connection or a character string specifying the original file argument. This overwrites the value returned by sys.path(original = TRUE).

envir, matchThisEnv

arguments passed to topenv() to determine the top level environment in which to assign an associated path.

srcfile

source file in which to assign a pathname.

fun

function with a srcref.

...

further arguments passed to set.sys.path().

Using 'ofile'

ofile can be used when the file argument supplied to set.sys.path() is not the same as the file argument supplied to the source()-like function:

sourcelike <- function (file)
{
    ofile <- file
    if (!is.character(ofile) || length(ofile) != 1)
        stop(gettextf("'%s' must be a character string", "file"))
    ## if the file exists, do nothing
    if (file.exists(file)) {
    }
    ## look for the file in the home directory
    ## if it exists, do nothing
    else if (file.exists(file <- this.path::path.join("~", ofile))) {
    }
    ## you could add other directories to look in,
    ## but this is good enough for an example
    else stop(gettextf("'%s' is not an existing file", ofile))
    file <- this.path::set.sys.path(file, ofile = ofile)
    exprs <- parse(n = -1, file = file)
    for (i in seq_along(exprs)) eval(exprs[i], envir)
    invisible()
}

Details

set.sys.path() should be added to the body of your source()-like function before reading / / evaluating the expressions.

wrap.source(), unlike set.sys.path(), does not accept an argument file. Instead, an attempt is made to extract the file from expr, after which expr is evaluated. It is assumed that the file is the first argument of the function, as is the case with most source()-like functions. The function of the call is evaluated, its formals() are retrieved, and then the arguments of expr are searched for a name matching the name of the first formal argument. If a match cannot be found by name, the first unnamed argument is taken instead. If no such argument exists, the file is assumed missing.

wrap.source() does non-standard evaluation and does some guess work to determine the file. As such, it is less desirable than set.sys.path() when the option is available. I can think of exactly one scenario in which wrap.source() might be preferable: suppose there is a source()-like function sourcelike() in a foreign package (a package for which you do not have write permission). Suppose that you write your own function in which the formals are (...) to wrap sourcelike():

wrapper <- function (...)
{
    ## possibly more args to wrap.source()
    wrap.source(sourcelike(...))
}

This is the only scenario in which wrap.source() is preferable, since extracting the file from the ... list would be a pain. Then again, you could simply change the formals of wrapper() from (...) to (file, ...). If this does not describe your exact scenario, use set.sys.path() instead.

Examples

Run this code
FILE.R <- tempfile(fileext = ".R")
this.path:::.writeCode({
    this.path::sys.path(verbose = TRUE)
    try(this.path::env.path(verbose = TRUE))
    this.path::src.path(verbose = TRUE)
    this.path::this.path(verbose = TRUE)
}, FILE.R)


## here we have a source-like function, suppose this
## function is in a package for which you have write permission
sourcelike <- function (file, envir = parent.frame())
{
    ofile <- file
    file <- set.sys.path(file, Function = "sourcelike")
    lines <- readLines(file, warn = FALSE)
    filename <- sys.path(local = TRUE, for.msg = TRUE)
    isFile <- !is.na(filename)
    if (isFile) {
        timestamp <- file.mtime(filename)[1]
        ## in case 'ofile' is a URL pathname / / 'unz' connection
        if (is.na(timestamp))
            timestamp <- Sys.time()
    }
    else {
        filename <- if (is.character(ofile)) ofile else ""
        timestamp <- Sys.time()
    }
    srcfile <- srcfilecopy(filename, lines, timestamp, isFile)
    set.src.path(srcfile)
    exprs <- parse(text = lines, srcfile = srcfile, keep.source = FALSE)
    invisible(source.exprs(exprs, evaluated = TRUE, envir = envir))
}


sourcelike(FILE.R)
sourcelike(conn <- file(FILE.R)); close(conn)


## here we have another source-like function, suppose this function
## is in a foreign package for which you do not have write permission
sourcelike2 <- function (pathname, envir = globalenv())
{
    if (!(is.character(pathname) && file.exists(pathname)))
        stop(gettextf("'%s' is not an existing file",
             pathname, domain = "R-base"))
    envir <- as.environment(envir)
    lines <- readLines(pathname, warn = FALSE)
    srcfile <- srcfilecopy(pathname, lines, isFile = TRUE)
    exprs <- parse(text = lines, srcfile = srcfile, keep.source = FALSE)
    invisible(source.exprs(exprs, evaluated = TRUE, envir = envir))
}


## the above function is similar to sys.source(), and it
## expects a character string referring to an existing file
##
## with the following, you should be able
## to use 'sys.path()' within 'FILE.R':
wrap.source(sourcelike2(FILE.R), path.only = TRUE)


# ## with R >= 4.1.0, use the forward pipe operator '|>' to
# ## make calls to 'wrap.source' more intuitive:
# sourcelike2(FILE.R) |> wrap.source(path.only = TRUE)


## 'wrap.source' can recognize arguments by name, so they
## do not need to appear in the same order as the formals
wrap.source(sourcelike2(envir = new.env(), pathname = FILE.R),
    path.only = TRUE)


## it it much easier to define a new function to do this
sourcelike3 <- function (...)
wrap.source(sourcelike2(...), path.only = TRUE)


## the same as before
sourcelike3(FILE.R)


## however, this is preferable:
sourcelike4 <- function (pathname, ...)
{
    ## pathname is now normalized
    pathname <- set.sys.path(pathname, path.only = TRUE)
    sourcelike2(pathname = pathname, ...)
}
sourcelike4(FILE.R)


## perhaps you wish to run several scripts in the same function
fun <- function (paths, ...)
{
    for (pathname in paths) {
        pathname <- set.sys.path(pathname, path.only = TRUE)
        sourcelike2(pathname = pathname, ...)
        unset.sys.path(pathname)
    }
}


## here we have a source-like function which modularizes its code
sourcelike5 <- function (file)
{
    ofile <- file
    file <- set.sys.path(file, Function = "sourcelike5")
    lines <- readLines(file, warn = FALSE)
    filename <- sys.path(local = TRUE, for.msg = TRUE)
    isFile <- !is.na(filename)
    if (isFile) {
        timestamp <- file.mtime(filename)[1]
        ## in case 'ofile' is a URL pathname / / 'unz' connection
        if (is.na(timestamp))
            timestamp <- Sys.time()
    }
    else {
        filename <- if (is.character(ofile)) ofile else ""
        timestamp <- Sys.time()
    }
    srcfile <- srcfilecopy(filename, lines, timestamp, isFile)
    set.src.path(srcfile)
    envir <- new.env(hash = TRUE, parent = .BaseNamespaceEnv)
    envir$.packageName <- filename
    oopt <- options(topLevelEnvironment = envir)
    on.exit(options(oopt))
    set.env.path(envir)
    exprs <- parse(text = lines, srcfile = srcfile, keep.source = FALSE)
    source.exprs(exprs, evaluated = TRUE, envir = envir)
    envir
}


sourcelike5(FILE.R)


## the code can be made much simpler in some cases
sourcelike6 <- function (file)
{
    ## we expect a character string refering to a file
    ofile <- file
    filename <- set.sys.path(file, path.only = TRUE, ignore.all = TRUE,
        Function = "sourcelike6")
    lines <- readLines(filename, warn = FALSE)
    timestamp <- file.mtime(filename)[1]
    srcfile <- srcfilecopy(filename, lines, timestamp, isFile = TRUE)
    set.src.path(srcfile)
    envir <- new.env(hash = TRUE, parent = .BaseNamespaceEnv)
    envir$.packageName <- filename
    oopt <- options(topLevelEnvironment = envir)
    on.exit(options(oopt))
    set.env.path(envir)
    exprs <- parse(text = lines, srcfile = srcfile, keep.source = FALSE)
    source.exprs(exprs, evaluated = TRUE, envir = envir)
    envir
}


sourcelike6(FILE.R)


unlink(FILE.R)

Run the code above in your browser using DataLab