In R, I am using source()
to load some functions:
source("functions.R")
Is it possible to get the list of all functions defined in this file? As function names. (Maybe source()
itself can somehow return it?).
PS: The last resort would be to call source()
second time like local({ source(); })
and then do ls()
inside and filter functions, but that's too complicated - is there easier and less clumsy solution?
I think the best way would be to source the file into a temporary environment. Query that environment for all the functions, then copy those values to the parent environment.
my_source <- function(..., local=NULL) {
tmp <- new.env(parent=parent.frame())
source(..., local = tmp)
funs <- names(tmp)[unlist(eapply(tmp, is.function))]
for(x in names(tmp)) {
assign(x, tmp[[x]], envir = parent.frame())
}
list(functions=funs)
}
my_source("script.R")
It's a bit clunky but you could look at changes in the objects before and after the source
call like this.
# optionally delete all variables
#rm(list=ls())
before <- ls()
cat("f1 <- function(){}\nf2 <- function(){}\n", file = 'define_function.R')
# defines these
#f1 <- function() {}
#f2 <- function() {}
source('define_function.R')
after <- ls()
changed <- setdiff(after, before)
changed_objects <- mget(changed, inherits = T)
changed_function <- do.call(rbind, lapply(changed_objects, is.function))
new_functions <- changed[changed_function]
new_functions
# [1] "f1" "f2"
I think that this regex catches almost every valid type of function (binary operator, assignment functions) and every valid character in a function name, but I may have missed an edge case.
# lines <- readLines("functions.R")
lines <- c(
"`%in%` <- function",
"foo <- function",
"foo2bar <- function",
"`%in%`<-function",
"foo<-function",
".foo <-function",
"foo2bar<-function",
"`foo2bar<-`<-function",
"`foo3bar<-`=function",
"`foo4bar<-` = function",
"` d d` <- function",
"lapply(x, function)"
)
grep("^`?%?[.a-zA-Z][._a-zA-Z0-9 ]+%?(<-`)?`?\\s*(<-|=)\\s*function", lines)
#> [1] 1 2 3 4 5 6 7 8 9 10
funs <- grep("^`?%?[.a-zA-Z][._a-zA-Z0-9 ]+%?(<-`)?`?\\s*(<-|=)\\s*function", lines, value = TRUE)
gsub("^(`?%?[.a-zA-Z][._a-zA-Z0-9 ]+%?(<-`)?`?).*", "\\1", funs)
#> [1] "`%in%`" "foo " "foo2bar " "`%in%`" "foo"
#> [6] ".foo " "foo2bar" "`foo2bar<-`" "`foo3bar<-`" "`foo4bar<-`"
If this is your own script so that you have control over how it is formatted a simple convention would be sufficient. Just ensure that each function name starts at the first character on its line and that the word function
also appears on that line. Any other use of the word function
should appear on a line that starts with a space or tab. Then a one-line solution is:
sub(" .*", "", grep("^\\S.*function", readLines("myscript.R"), value = TRUE))
The advantages of this approach are that
it is very simple. The rules are simply stated and there is only one simple line of R code needed to extract the function names. Regex is also simple and for an existing file it is very easy to check -- just grep the word function
and check if each occurrence displayed follows the rule.
no need to run the source. It is entirely static.
in many cases you won't need to change the source file at all and in others there will be minimal changes. If you are writing the script from scratch with this in mind it is even easier to arrange.
There are many other alternatives along the idea of conventions. you could have a more sophisticated regex or you could add # FUNCTION
at the end of the first line of any function definition if you are writing the script from scratch and then grep out that phrase and extract the first word on the line but the main suggestion here seems particularly attractive due to its simplicity and the other advantages listed.
# generate test file
cat("f <- function(x) x\nf(23)\n", file = "myscript.R")
sub(" .*", "", grep("^\\S.*function", readLines("myscript.R"), value = TRUE))
## [1] "f"
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With