diff --git a/.Rbuildignore b/.Rbuildignore index 112ad26..be665f6 100644 --- a/.Rbuildignore +++ b/.Rbuildignore @@ -1,3 +1,6 @@ ^.*\.Rproj$ ^\.Rproj\.user$ ^\.travis\.yml$ +^_pkgdown.yaml +^docs +^update_examples.sh \ No newline at end of file diff --git a/.gitignore b/.gitignore index d1de5fe..beeb841 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,7 @@ +inst/doc .Rproj.user .Rhistory .RData -.DS_Store \ No newline at end of file +.DS_Store +docs +*.RProj diff --git a/.travis.yml b/.travis.yml index ac1471b..c8731d1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,8 +1,12 @@ -# R for travis: see documentation at https://docs.travis-ci.com/user/languages/r - language: R sudo: false cache: packages warnings_are_errors: false -before_install: - - Rscript -e "install.packages('devtools');devtools::install_github(repo = 'pepkit/pepr',ref = 'dev');source('https://bioconductor.org/biocLite.R');biocLite('GenomicRanges');biocLite('BiocFileCache');devtools::install_github(repo = 'databio/simpleCache')" +r_packages: devtools +r_github_packages: + - pepkit/pepr + - databio/simpleCache +bioc_packages: + - GenomicRanges + - BiocStyle + - BiocFileCache \ No newline at end of file diff --git a/DESCRIPTION b/DESCRIPTION index a98d27c..e3ebeb0 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: BiocProject Title: Bioconductor Management with Portable Encapsulated Project (PEP) Objects -Version: 0.1 +Version: 0.1.1 Authors@R: c(person("Michal", "Stolarczyk", email = "mjs5kd@virginia.edu",role = c("aut", "cre")), person("Nathan", "Sheffield", email = "nathan@code.databio.org",role = c("aut"))) Description: A Bioconductor-oriented project management class. It wraps the @@ -9,11 +9,15 @@ Description: A Bioconductor-oriented project management class. It wraps the License: GPL-3 Encoding: UTF-8 LazyData: true -Depends: S4Vectors, pepr +Depends: S4Vectors, pepr, methods Suggests: knitr, - rmarkdown + rmarkdown, + testthat, + yaml Enhances: BiocFileCache, simpleCache, GenomicRanges +biocViews: DataImport, DataRepresentation RoxygenNote: 6.1.1 +URL: https://github.com/pepkit/BiocProject BugReports: https://github.com/pepkit/BiocProject VignetteBuilder: knitr diff --git a/NAMESPACE b/NAMESPACE index b2da0ad..4f16d3a 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -1,7 +1,12 @@ # Generated by roxygen2: do not edit by hand +export(.insertPEP) +export(.updateList) export(BiocProject) exportMethods(config) exportMethods(getProject) exportMethods(is) exportMethods(samples) +import(S4Vectors) +import(methods) +import(pepr) diff --git a/NEWS.md b/NEWS.md index 014860b..e577ea0 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,58 +1,63 @@ -# BiocProject 0.1 +# BiocProject Changelog -## 2019-01-28 +## [0.1.1] - 2019-02-15 -## Added +### Added -* methods: `is` (overwrites this method behavior just for the class `Annotated`), `.is.project`, `samples`, `config` for signiture `Annotated` +* passes `BiocCheck` +* added option to specify additional arguments in the config file using a `funcArgs` subsection within the `bioconductor` section of the PEP config `yaml` file. + +### Changed + +* now, any object can be returned by the data processing function, rather than requiring objects to inherit from `Annotated` +* when errors are encountered, BiocProject now returns a PEP (`pepr::Project`) along with the error message in a `S4Vectors::List` object +* the `bioconductor` section of the config file now follows the Bioconductor coding style (`camelCaps`) + +## [0.1] - 2019-01-28 + +### Added + +* methods: `is` (overwrites this method behavior just for the class `Annotated`), `.is.project`, `samples`, `config` for signature `Annotated` * functions: `.insertPEP` and `BiocProject` (the workhorse of the package) -## Changed +### Changed * **complete concept redesign**: no `BiocProject` class. The objects returned by the custom data reading function have to be of class `Annotated` and the `PEP` is inserted as the first element of its `metadata()` list -# BiocProject 0.0.4 +## [0.0.4] - 2019-01-25 -## 2019-01-25 - -## Changed +### Changed * better custom data loading function error/warning communication * all exceptions are caught with `BiocProject` constructor * fix `.updateSubconfig(.Object@config, sp) : Subproject not found:` warning in `toProject` method -# BiocProject 0.0.3 - -## 2018-12-21 +## [0.0.3] - 2018-12-21 -## Changed +### Changed * the default values for all optional arguments are `NULL` * change `lambda function` to `anonymous function` -# BiocProject 0.0.2 +## [0.0.2] - 2018-12-01 -## 2018-12-01 - -## Changed +### Changed * the object constructor does not fail if the `pepr::Project` object is provided in the `funcArgs` arguments list * if the user-supplied function errors or throws a warning, appropriate messages are nicely displayed * errors and warnings (if any) are returned instead of the data * if the object constructor can't find the function file, the message is more informative -# BiocProject 0.0.1 - -## 2018-11-20 +## [0.0.1] - 2018-11-20 -## Added +### Added * add `BiocProject::BiocProject` constructor function * add `BiocProject::getData` method to extract the data from the object * allow for passing additional arguments for user-provided functions in `BiocProject::BiocProject` * allow to use lambda functions with `func` parameter -## Changed +### Changed * make `BiocProject` class inherit from `pepr::Project` and `base::list` * the `initialize` method can read in the data with the provided `func` diff --git a/R/constants.R b/R/constants.R new file mode 100644 index 0000000..8972cc9 --- /dev/null +++ b/R/constants.R @@ -0,0 +1,14 @@ +# config section names +# +# The YAML file looks like this: +# +# MAIN_SECTION: +# FUNCTION_NAME: +# FUNCTION_PATH: +# FUNCTION_ARGS: +# : +# : +MAIN_SECTION = "bioconductor" +FUNCTION_ARGS = "funcArgs" +FUNCTION_PATH = "readFunPath" +FUNCTION_NAME = "readFunName" diff --git a/R/functions.R b/R/functions.R index b955fb3..0883e7a 100644 --- a/R/functions.R +++ b/R/functions.R @@ -1,8 +1,13 @@ #' Portable Encapsulated Project (PEP) for biological applications #' -#' This function creates a \code{\link[pepr]{Project-class}} object, and executes the user provided function with the created object as a first argument. -#' \cr\cr\emph{The custom data processing function has to return and object of \code{\link[S4Vectors]{Annotated-class}}, otherwise an error will be returned.} +#' This function creates a \code{\link[pepr]{Project-class}} object, +#' and executes the user provided function with the created object +#' as a first argument. +#' \cr\cr\emph{If the custom data processing function returns an object of +#' class other than \code{\link[S4Vectors]{Annotated-class}}, the output +#' will be packaged in a \code{\link[S4Vectors]{List-class}} with a metadata +#' slot populated with the \code{\link[pepr]{Project-class}}.} #' #' This \code{\link{BiocProject}} function provides some degree #' of flexibility in your custom data processing function usage and @@ -10,26 +15,27 @@ #' \itemize{ #' \item use a function loaded into the \code{R} environment and specified in #' the config slot in \code{\link[pepr]{Project-class}} -#' (specifically: \code{config(project)$bioconductor$read_fun_name}). -#' \item use a function \emph{not} loaded into the \code{R} environment and specified in -#' the config slot in \code{\link[pepr]{Project}} -#' (specifically: \code{config(project)$bioconductor$read_fun_path}). +#' (specifically: \code{config(project)$bioconductor$readFunName}). +#' \item use a function \emph{not} loaded into the \code{R} environment and +#' specified in the config slot in \code{\link[pepr]{Project}} +#' (specifically: \code{config(project)$bioconductor$readFunPath}). #' \item use a function from other \code{R} package not loaded into #' the \code{R} environment and specified in the config slot #' in \code{\link[pepr]{Project}} -#' (specifically: \code{config(project)$bioconductor$read_fun_name}), like: +#' (specifically: \code{config(project)$bioconductor$readFunName}), like: #' \code{pkgName::functionName} #' \item use a function implemented in the \code{\link{BiocProject}} -#' call (passed to the \code{func} argument - anonymous function). This option is given the top priority and overrides -#' other arguments if provided. +#' call (passed to the \code{func} argument - anonymous function). +#' This option is given the top priority and overrides other +#' arguments if provided. #' } #' The custom data processing function must take #' the \code{\link[pepr]{Project-class}} as an argument since this object will #' be passed to the function by default. However, if the function requires #' addtional arguments, ones can be provided with the \code{funcArgs} argument #' in the \code{\link{BiocProject}} function call. -#' Besides, the \code{func} argument with the anonymous function may serve similar -#' possibility. +#' Besides, the \code{func} argument with the anonymous +#' function may serve similar possibility. #' #' #' If the \code{autoLoad} is set to \code{FALSE} the data will not be loaded @@ -45,122 +51,174 @@ #' @param file a character vector with a path to the PEP config file #' @param subproject a character vector with a name of the subproject #' to be activated -#' @param func a anonymous function that reads and/or processess the data, it must take +#' @param func a anonymous function that reads and/or processess the data, +#' it must take #' the \code{\link[pepr]{Project-class}} as an argument. #' See \code{Details} for more information -#' @param funcArgs a named list with arguments you want to pass to the \code{func}. +#' @param funcArgs a named list with arguments you want +#' to pass to the \code{func}. #' The PEP will be passed automatically, -#' but if provided regardless, the constructor will disregard it +#' but if provided regardless, the constructor will disregard it. +#' You can also pass the arguments in a \code{funcArgs} section within +#' the \code{bioconductor} section in the config file. #' @param autoLoad a logical indicating whether the data should be loaded #' automatically. See \code{Details} for more information. #' -#' @return an object of \code{\link[S4Vectors]{Annotated-class}} that is returned by the user provided function with the \code{\link[pepr]{Project-class}} object inserted into the first element of the list in its medatada slot +#' @return an object of \code{\link[S4Vectors]{Annotated-class}} that is +#' returned by the user provided function with +#' the \code{\link[pepr]{Project-class}} object inserted into the first +#' element of the list in its medatada slot +#' +#' @examples +#' projectConfig = system.file("extdata", "example_peps-master", +#' "example_BiocProject", "project_config.yaml", package="BiocProject") +#' bp=BiocProject(projectConfig) +#' +#' bp +#' +#' metadata(bp) #' #' @seealso \url{https://pepkit.github.io/} -#' +#' @import pepr #' @export BiocProject -BiocProject = function(file, subproject = NULL, autoLoad = T, func = NULL, funcArgs = NULL) { +BiocProject = function(file, subproject = NULL, autoLoad = TRUE, func = NULL, + funcArgs = NULL) { p = tryCatch( - expr = { - pepr::Project(file = file, subproject = subproject) + expr = { + pepr::Project(file=file, subproject=subproject) },warning = function(w) { message(w) - stop("There are warnings associated with the 'Project' object creation.") + stop("There are warnings + associated with the 'Project' object creation.") } ) # prevent PEP (Project object) input. This prevents BiocProject object # failing when the user provides the Project object if(is.null(funcArgs)){ - funcArgs = list() + funcArgs = list() }else{ - pepArgs = as.logical(lapply(funcArgs, function(x) { - is(x, "Project") - })) - if (any(pepArgs)) - funcArgs = funcArgs[-which(pepArgs)] + if (length(.findProjectInList(funcArgs)) > 0) + funcArgs = funcArgs[-.findProjectInList(funcArgs)] } args = append(list(p), funcArgs) + if(pepr::checkSection(pepr::config(p), c(MAIN_SECTION, FUNCTION_ARGS))){ + args = .updateList(config(p)[[MAIN_SECTION]][[FUNCTION_ARGS]],args) + argsNames = names(args) + project = args[[.findProjectInList(args)]] + argsNames = append("",argsNames[-.findProjectInList(args)]) + args = append(list(p), args[[-.findProjectInList(args)]]) + names(args) = argsNames + } if (!is.null(func)) { - # use the anonymous function if provided - if (is.function(func)) { - readData = .callBiocFun(func, list(p)) - return(.insertPEP(readData, p)) - } else{ - stop("The anonymous function you provided is invalid.") - } - } else{ - # use config to find it - if (autoLoad) { - # check if the config consists of bioconductor section - if(is.null(pepr::config(p)$bioconductor)){ - warning("The config YAML is missing the bioconductor section.") - message("No data was read. Returning a Project object") - return(p) + # use the anonymous function if provided + if (is.function(func)) { + readData = .callBiocFun(func, list(p)) + message("Used function from the 'func' argument") + return(.insertPEP(readData, p)) + }else{ + stop("The anonymous function you provided is invalid.") } - funcName = pepr::config(p)$bioconductor$read_fun_name + }else{ + # use config to find it + if(!is.logical(autoLoad)) stop("'autoLoad' argument has to be a logical, + got '", class(autoLoad),"'") + if (autoLoad) { + # check if the config consists of MAIN_SECTION section + if(!pepr::checkSection(pepr::config(p), MAIN_SECTION)){ + message("No data was read. Returning a Project object") + warning("The config YAML is missing the '", + MAIN_SECTION,"' section.") + return(p) + } + funcName = pepr::config(p)[[MAIN_SECTION]][[FUNCTION_NAME]] # check if the function name was provided # and if it exists in the environment - if (!is.null(funcName) && exists(funcName)) { - # function from config.yaml in environment - readData = .callBiocFun(funcName, args) - message("Used function ", funcName, " from the environment") - return(.insertPEP(readData, p)) - } else{ - if (!is.null(funcName) && length(grep("(\\:){2,3}", funcName)) != 0) { - # trying to access the function from the namespace that - # was specified in the config.yaml read_fun_name - splitted = strsplit(funcName, ":")[[1]] - nonEmpty = splitted[which(splitted != "")] - funcName = getFromNamespace(x=nonEmpty[2], ns=nonEmpty[1]) - readData = .callBiocFun(funcName, args) - message("Used function ", funcName, " from the environment") - return(.insertPEP(readData, p)) - } - # function from config.yaml in read_fun_name not in environment, - # trying to source the file specified in - # the config.yaml read_fun_path - funcPath = - pepr::.expandPath(pepr::config(p)$bioconductor$read_fun_path) - if (!is.null(funcPath)){ - if (!file.exists(funcPath)) - funcPath = .makeAbsPath(funcPath,dirname(p@file)) - if(!file.exists(funcPath)) - stop( - "The function does not exist in the environment and file ", - funcPath, - " does not exist" - ) - readFun = source(funcPath)$value - message("Function read from file: ", funcPath) - readData = .callBiocFun(readFun, args) - return(.insertPEP(readData, p)) - }else{ - warning("Can't find function in the environment and the value for read_fun_path key was not provided in the config YAML.") + if (!is.null(funcName) && exists(funcName)) { + # function from config.yaml in environment + readData = .callBiocFun(funcName, args) + message("Used function ", funcName, " from the environment") + return(.insertPEP(readData, p)) + }else{ + if (!is.null(funcName) && length(grep("(\\:){2,3}", funcName)) != 0) { + # trying to access the function from the namespace that + # was specified in the config.yaml FUNCTION_NAME + splitted = strsplit(funcName, ":")[[1]] + nonEmpty = splitted[which(splitted != "")] + funcName = utils::getFromNamespace(x=nonEmpty[2], ns=nonEmpty[1]) + readData = .callBiocFun(funcName, args) + message("Used function ", funcName, " from the environment") + return(.insertPEP(readData, p)) + } + # function from config.yaml in read_fun_name not in environment, + # trying to source the file specified in + # the config.yaml FUNCTION_PATH + funcPath = pepr::.expandPath( + pepr::config(p)[[MAIN_SECTION]][[FUNCTION_PATH]]) + if (!is.null(funcPath)){ + if (!file.exists(funcPath)) + funcPath = .makeAbsPath(funcPath,dirname(p@file)) + if(!file.exists(funcPath)) + stop( + "The function does not exist in the environment and file ", + funcPath, + " does not exist" + ) + readFun = source(funcPath)$value + message("Function read from file: ", funcPath) + readData = .callBiocFun(readFun, args) + return(.insertPEP(readData, p)) + }else{ + warning("Can't find function in the environment and the value for '" + , FUNCTION_PATH, + "' key was not provided in the config YAML.") + message("No data was read. Returning a Project object") + return(p) + } + } + }else{ message("No data was read. Returning a Project object") return(p) - } } - } else{ - message("No data was read. Returning a Project object") - return(p) - } } } #' Insert a PEP metadata in a metadata slot of Annotated #' -#' This function inserts the PEP (\code{\link[pepr]{Project-class}}) into the metadata slot of objects that extend the \code{\link[S4Vectors]{Annotated-class}} +#' This function inserts the PEP (\code{\link[pepr]{Project-class}}) +#' into the metadata slot of objects that +#' extend the \code{\link[S4Vectors]{Annotated-class}} #' #' @param object an object of \code{\link[S4Vectors]{Annotated-class}} #' @param pep an object of class \code{\link[pepr]{Project-class}} #' -#' @return an object of the same class as the object argument but enriched with the metadata from the pep argument +#' @return an object of the same class as the object argument but enriched +#' with the metadata from the pep argument +#' +#' @examples +#' # If the object is of class Annotated +#' object = S4Vectors::List(result="test") +#' result = .insertPEP(object, pepr::Project()) +#' metadata(result) +#' +#' # If the object is not of class Annotated +#' object1 = "test" +#' result1 = .insertPEP(object1, pepr::Project()) +#' metadata(result1) +#' @import S4Vectors methods +#' @export .insertPEP = function(object, pep) { - if(is(object, "Annotated") & is(pep, "Project")){ + if(!methods::is(pep, "Project")) + stop("the pep argument has to be of class 'Project', + got '", class(pep),"'") + if(methods::is(object, "Annotated")){ S4Vectors::metadata(object) = list(PEP=pep) object }else{ - stop("Type error: The 'object' argument has to be of class 'Annotated', got '", class(object),"'. And the pep argument has to be of class 'Project', got '", class(pep),"'") + warning("The 'object' argument has to be of class 'Annotated', got '", + class(object),"'") + result = S4Vectors::List(result=object) + S4Vectors::metadata(result) = list(PEP=pep) + result } } diff --git a/R/methods_Annotated.R b/R/methods_Annotated.R index 909503c..5b9c825 100644 --- a/R/methods_Annotated.R +++ b/R/methods_Annotated.R @@ -11,14 +11,13 @@ setMethod(".is.project","Annotated",function(.Object){ is(result,"Project") }) -setGeneric("getProject", function(.Object) - standardGeneric("getProject")) - -#' Extract the object of \code{\link[pepr]{Project-class}} from the \code{\link[S4Vectors]{Annotated-class}} +#' Extract the object of \code{\link[pepr]{Project-class}} from +#' the \code{\link[S4Vectors]{Annotated-class}} #' -#' This method can be used to extract the project metadata from objects of \code{\link[S4Vectors]{Annotated-class}} +#' This method can be used to extract the project metadata from objects of +#' \code{\link[S4Vectors]{Annotated-class}} #' -#' @param object an object of \code{\link[S4Vectors]{Annotated-class}} +#' @param .Object an object of \code{\link[S4Vectors]{Annotated-class}} #' #' @return an object of \code{\link[pepr]{Project-class}} #' @@ -26,9 +25,14 @@ setGeneric("getProject", function(.Object) #' projectConfig = system.file("extdata", "example_peps-master", #' "example_BiocProject", "project_config.yaml", package="BiocProject") #' p=BiocProject(projectConfig) -#' samples(p) +#' getProject(p) #' -#' @export +#' @import S4Vectors +#' @exportMethod getProject +setGeneric("getProject", function(.Object) + standardGeneric("getProject")) + +#' @describeIn getProject extracts \code{\link[pepr]{Project-class}} from the \code{\link[S4Vectors]{Annotated-class}} setMethod("getProject","Annotated",function(.Object){ if(.is.project(.Object)) { S4Vectors::metadata(.Object)[[1]] @@ -40,7 +44,8 @@ setMethod("getProject","Annotated",function(.Object){ #' View samples in the objects of \code{\link[pepr]{Project-class}} #' #' This method can be used to view the samples slot -#' of the \code{\link[pepr]{Project-class}} or \code{\link[S4Vectors]{Annotated-class}} +#' of the \code{\link[pepr]{Project-class}} +#' or \code{\link[S4Vectors]{Annotated-class}} #' #' @param object an object of \code{\link[pepr]{Project-class}} #' @@ -50,7 +55,7 @@ setMethod("getProject","Annotated",function(.Object){ #' "example_BiocProject", "project_config.yaml", package="BiocProject") #' p=BiocProject(projectConfig) #' samples(p) -#' +#' @import pepr #' @export setMethod( f = "samples", @@ -63,7 +68,8 @@ setMethod( #' View PEP config of the object of \code{\link[pepr]{Project-class}} #' #' This method can be used to view the config slot of -#' the \code{\link[pepr]{Project-class}} or or \code{\link[S4Vectors]{Annotated-class}} +#' the \code{\link[pepr]{Project-class}} +#' or \code{\link[S4Vectors]{Annotated-class}} #' #' @param object an object of \code{\link[pepr]{Project-class}} #' @@ -75,6 +81,7 @@ setMethod( #' p=BiocProject(projectConfig) #' config(p) #' +#' @import pepr #' @export setMethod( f = "config", @@ -87,16 +94,28 @@ setGeneric("is", package = "methods") #' Is an Object from a Class? #' -#' Functions to test inheritance relationships between an object and a class or between two classes. It uses the generic is function but overrides its behavior for obejcts of class \code{\link[S4Vectors]{Annotated-class}} when testing for inheritance from \code{\link[pepr]{Project-class}} class. +#' Functions to test inheritance relationships between an object and a class +#' or between two classes. It uses the generic is function but overrides its +#' behavior for obejcts of class \code{\link[S4Vectors]{Annotated-class}} when +#' testing for inheritance from \code{\link[pepr]{Project-class}} class. #' #' see the \code{\link[methods]{is}} for more details #' +#' @param object the object to be tested +#' @param class2 the class name to test the object against +#' +#' @return a logical +#' @examples +#' object = S4Vectors::List(test="test") +#' is(object,"Annotated") +#' +#' @import methods #' @export setMethod("is", "Annotated", definition = function(object, class2){ if(class2=="Project" & .is.project(object)){ TRUE } else { - extends(class(object), class2) + methods::extends(class(object), class2) } }) diff --git a/R/utils.R b/R/utils.R index f69ee5b..53d38f4 100644 --- a/R/utils.R +++ b/R/utils.R @@ -1,69 +1,72 @@ # internal function used for wrapping the user-supplied function meessages # in a box .wrapFunMessages = function(messages, type) { - n = options("width")[[1]] - header = ifelse( - length(messages) > 1, - paste0(" Your function ", type, "s (", length(messages), ") "), - paste0(" Your function ", type, " ") - ) - nH = floor(nchar(header) / 2) - nFill = floor(n / 2) - message("\n",rep("-", nFill - nH), header, rep("-", nFill - nH)) - i = 1 - for (i in seq_along(messages)) { - m = trimws(messages[i], which="both") - message("\n", type, " ", i , ": ", m, "\n") - } - message(rep("-", n), "\n") + n = options("width")[[1]] + header = ifelse( + length(messages) > 1, + paste0(" Your function ", type,"s (", length(messages), ") "), + paste0(" Your function ", type, " ") + ) + nH = floor(nchar(header) / 2) + nFill = floor(n / 2) + message("\n",rep("-", nFill - nH), header, rep("-", nFill - nH)) + i = 1 + for (i in seq_along(messages)) { + m = trimws(messages[i], which="both") + message("\n", type, " ", i , ": ", m, "\n") + } + message(rep("-", n), "\n") } # internal function that wraps the external function execution # in tryCatch to indicate problems with the external function execution -.callBiocFun <- function(func, arguments) -{ - .warnings = c() - frameNumber <- sys.nframe() - wHandler <- function(w){ - # warning handler - assign(".warnings", append(.warnings,w$message), - envir = sys.frame(frameNumber)) - invokeRestart("muffleWarning") - } - eHandler <- function(e){ - # error handler - .wrapFunMessages(e$message,"error") - message("No data was read. The error message was returned instead.") - List(e$message) - } - res = withCallingHandlers(tryCatch(do.call(func, arguments), error = eHandler),warning = wHandler) - if(length(.warnings) > 0){ - warning("There were warnings associated with your function execution.") - .wrapFunMessages(.warnings,"warning") - } - return(res) +.callBiocFun <- function(func, arguments) { + if(!is(arguments, "list")) + stop("The 'arguments' argument has to be a list, got '", + class(arguments),"'") + .warnings = c() + frameNumber <- sys.nframe() + wHandler <- function(w){ + # warning handler + assign(".warnings", append(.warnings,w$message), + envir = sys.frame(frameNumber)) + invokeRestart("muffleWarning") + } + eHandler <- function(e){ + # error handler + .wrapFunMessages(e$message,"error") + message("No data was read. The error message was returned instead.") + S4Vectors::List(e$message) + } + res = withCallingHandlers( + tryCatch(do.call(func, arguments), error = eHandler),warning = wHandler) + if(length(.warnings) > 0){ + warning("There were warnings associated with your function execution.") + .wrapFunMessages(.warnings,"warning") + } + return(res) } -#' Create an absolute path from a primary target and a parent candidate. +# Create an absolute path from a primary target and a parent candidate. # -#' @param perhapsRelative: Path to primary target directory. -#' @param parent: Path to parent folder to use if target isn't absolute. +# @param perhapsRelative: Path to primary target directory. +# @param parent a path to parent folder to use if target isn't absolute. # -#' @return Target itself if already absolute, else target nested within parent. +# @return Target itself if already absolute, else target nested within parent. .makeAbsPath = function(perhapsRelative, parent) { - if (!.isDefined(perhapsRelative)) { return(perhapsRelative)} - perhapsRelative = pepr::.expandPath(perhapsRelative) - if (.isAbsolute(perhapsRelative)) { - abspath = perhapsRelative - } else { - abspath = file.path(normalizePath(parent), perhapsRelative) - } - if (!.isAbsolute(abspath)) { - errmsg = sprintf("Relative path '%s' and parent '%s' failed to create - absolute path: '%s'", perhapsRelative, parent, abspath) - stop(errmsg) - } - return(abspath) + if (!.isDefined(perhapsRelative)) return(perhapsRelative) + perhapsRelative = pepr::.expandPath(perhapsRelative) + if (.isAbsolute(perhapsRelative)) { + abspath = perhapsRelative + }else { + abspath = file.path(normalizePath(parent), perhapsRelative) + } + if (!.isAbsolute(abspath)) { + errmsg = sprintf("Relative path '%s' and parent '%s' failed to create + absolute path: '%s'", perhapsRelative, parent, abspath) + stop(errmsg) + } + return(abspath) } # Must test for is.null first, since is.na(NULL) returns a logical(0) which is @@ -71,12 +74,61 @@ .isDefined = function(var) { ! (is.null(var) || is.na(var)) } -#' Determine whether a path is absolute. -#' -#' @param path The path to check for seeming absolute-ness. -#' @return Flag indicating whether the \code{path} appears to be absolute. +# Determine whether a path is absolute. +# +# @param path The path to check for seeming absolute-ness. +# @return Flag indicating whether the \code{path} appears to be absolute. .isAbsolute = function(path) { - if(!is.character(path)) stop("The path must be character.") - firstChar = substr(path, 1, 1) - return(identical("/", firstChar) | identical("~", firstChar)) -} \ No newline at end of file + if(!is.character(path)) stop("The path must be character.") + firstChar = substr(path, 1, 1) + return(identical("/", firstChar) | identical("~", firstChar)) +} + +#' Update list with another list +#' +#' This function performs a union of two lists and updates the elements of the +#' first one if are found in the other one. +#' +#' Both elements have to be lists. If some elements are not named, they are +#' preserved but the order might be lost. +#' +#' @param list1 a list to be updated +#' @param list2 a list to update with +#' +#' @return an updated list +#' +#' @examples +#' list1=list(a=1,b=2) +#' list2=list(a=1,b=1,c=3) +#' .updateList(list1,list2) +#' +#' @export +.updateList = function(list1,list2) { + if((!is.list(list1)) || (!is.list(list2))) + stop("One of the arguments was not a list") + nms1 = names(list1) + nms2 = names(list2) + if(is.null(nms2)) nms2 = "" + counter=1 + for(n in nms2){ + idx = which(nms1 == n) + if(length(idx) > 0){ + list1[[idx]] = list2[[n]] + }else{ + add = list(list2[[counter]]) + names(add) = n + list1 = append(list1,add) + } + counter = counter + 1 + } + return(list1) +} + +# Finds the pepr::Project object in a list and returns its index +# If it is not present, returns integer(0) +.findProjectInList = function(l) { + which(as.logical(lapply(l, function(x) { + is(x, "Project") + }))) +} + diff --git a/README.md b/README.md index a62ffaa..fbd45c5 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,6 @@ [![Travis-CI Build Status](https://travis-ci.org/pepkit/BiocProject.svg?branch=master)](https://travis-ci.org/pepkit/BiocProject) [![PEP compatible](http://pepkit.github.io/img/PEP-compatible-green.svg)](http://pepkit.github.io) - # Description of the BiocProject package The `BiocProject` package is a [Bioconductor](https://www.bioconductor.org/)-oriented project management package. It wraps the generic [pepr](http://code.databio.org/pepr/) R package for project metadata. `BiocProject` allows you to read in project metadata and data for an entire project with a single line of `R` code. @@ -14,7 +13,7 @@ Install from GitHub: devtools::install_github("pepkit/BiocProject") ``` -Read in both the metadata and data by passing your PEP configuration file: +Read in both the metadata and data by passing your [PEP configuration file](http://pepkit.github.io): ``` bp = BiocProject(file=ProjectConfig) ``` diff --git a/_pkgdown.yaml b/_pkgdown.yaml index 70f2285..950ec5c 100644 --- a/_pkgdown.yaml +++ b/_pkgdown.yaml @@ -26,9 +26,20 @@ navbar: articles: - title: Introduction contents: - - 1getStarted + - vignette1getStarted - title: Advanced features contents: - - 2multipleArguments - - 3simpleCache - - 4remoteData + - vignette2multipleArguments + - vignette3simpleCache + - vignette4remoteData + +reference: + - title: "BiocProject API" + desc: "Exported functions of `BiocProject`." + contents: + - -matches("\\.") + - title: "utilities" + desc: "Extra non-exported utility functions." + contents: + - .updateList + - .insertPEP diff --git a/inst/extdata/example_peps-master/example_BiocProject/project_config.yaml b/inst/extdata/example_peps-master/example_BiocProject/project_config.yaml index 7e21bc3..7e4de85 100644 --- a/inst/extdata/example_peps-master/example_BiocProject/project_config.yaml +++ b/inst/extdata/example_peps-master/example_BiocProject/project_config.yaml @@ -2,5 +2,5 @@ metadata: sample_annotation: sample_annotation.csv bioconductor: - read_fun_name: readBedFiles - read_fun_path: readBedFiles.R + readFunName: readBedFiles + readFunPath: readBedFiles.R diff --git a/inst/extdata/example_peps-master/example_BiocProject/project_config_resize.yaml b/inst/extdata/example_peps-master/example_BiocProject/project_config_resize.yaml index 2bf1070..f10bd9e 100644 --- a/inst/extdata/example_peps-master/example_BiocProject/project_config_resize.yaml +++ b/inst/extdata/example_peps-master/example_BiocProject/project_config_resize.yaml @@ -2,5 +2,8 @@ metadata: sample_annotation: sample_annotation.csv bioconductor: - read_fun_name: readBedFiles_resize - read_fun_path: readBedFiles_resize.R + readFunName: readBedFiles_resize + readFunPath: readBedFiles_resize.R + funcArgs: + resize.width: 100 + diff --git a/inst/extdata/example_peps-master/example_BiocProject_exceptions/project_config.yaml b/inst/extdata/example_peps-master/example_BiocProject_exceptions/project_config.yaml index b572fe4..d295359 100644 --- a/inst/extdata/example_peps-master/example_BiocProject_exceptions/project_config.yaml +++ b/inst/extdata/example_peps-master/example_BiocProject_exceptions/project_config.yaml @@ -2,5 +2,5 @@ metadata: sample_annotation: sample_annotation.csv bioconductor: - read_fun_name: readBedFilesExceptions - read_fun_path: readBedFilesExceptions.R + readFunName: readBedFilesExceptions + readFunPath: readBedFilesExceptions.R diff --git a/inst/extdata/example_peps-master/example_BiocProject_remote/project_config.yaml b/inst/extdata/example_peps-master/example_BiocProject_remote/project_config.yaml index acb19b1..1027b21 100644 --- a/inst/extdata/example_peps-master/example_BiocProject_remote/project_config.yaml +++ b/inst/extdata/example_peps-master/example_BiocProject_remote/project_config.yaml @@ -2,5 +2,5 @@ metadata: sample_annotation: sample_annotation.csv bioconductor: - read_fun_name: readRemoteData - read_fun_path: readRemoteData.R + readFunName: readRemoteData + readFunPath: readRemoteData.R diff --git a/inst/test_projects/faulty_project/project_config_no_function.yaml b/inst/test_projects/faulty_project/project_config_no_function.yaml new file mode 100644 index 0000000..60e3d90 --- /dev/null +++ b/inst/test_projects/faulty_project/project_config_no_function.yaml @@ -0,0 +1,8 @@ +# This is a faulty PEP. +# The readFunName and readFunPath values point to function that does not exist +metadata: + sample_annotation: sample_annotation.csv + +bioconductor: + readFunName: readBedFiles_missing + readFunPath: readBedFiles_missing.R diff --git a/inst/test_projects/faulty_project/project_config_no_section.yaml b/inst/test_projects/faulty_project/project_config_no_section.yaml new file mode 100644 index 0000000..7cd0341 --- /dev/null +++ b/inst/test_projects/faulty_project/project_config_no_section.yaml @@ -0,0 +1,4 @@ +# This is a faulty PEP. +# The bioconductor section does not exist +metadata: + sample_annotation: sample_annotation.csv diff --git a/inst/test_projects/faulty_project/readBedFiles.R b/inst/test_projects/faulty_project/readBedFiles.R new file mode 100644 index 0000000..363c5c0 --- /dev/null +++ b/inst/test_projects/faulty_project/readBedFiles.R @@ -0,0 +1,16 @@ +readBedFiles = function(project) { + cwd = getwd() + paths = pepr::samples(project)$file_path + sampleNames = pepr::samples(project)$sample_name + setwd(dirname(project@file)) + result = lapply(paths, function(x){ + df = read.table(x) + colnames(df) = c('chr', 'start', 'end') + gr = GenomicRanges::GRanges(df) + }) + setwd(cwd) + names(result) = sampleNames + return(GenomicRanges::GRangesList(result)) +} + + diff --git a/inst/test_projects/faulty_project/sample_annotation.csv b/inst/test_projects/faulty_project/sample_annotation.csv new file mode 100644 index 0000000..59de6e5 --- /dev/null +++ b/inst/test_projects/faulty_project/sample_annotation.csv @@ -0,0 +1,3 @@ +sample_name,file_path +laminB1Lads,data/laminB1Lads.bed +vistaEnhancers,data/vistaEnhancers.bed diff --git a/man/BiocProject.Rd b/man/BiocProject.Rd index c687196..751a8de 100644 --- a/man/BiocProject.Rd +++ b/man/BiocProject.Rd @@ -4,7 +4,7 @@ \alias{BiocProject} \title{Portable Encapsulated Project (PEP) for biological applications} \usage{ -BiocProject(file, subproject = NULL, autoLoad = T, func = NULL, +BiocProject(file, subproject = NULL, autoLoad = TRUE, func = NULL, funcArgs = NULL) } \arguments{ @@ -16,20 +16,32 @@ to be activated} \item{autoLoad}{a logical indicating whether the data should be loaded automatically. See \code{Details} for more information.} -\item{func}{a anonymous function that reads and/or processess the data, it must take +\item{func}{a anonymous function that reads and/or processess the data, +it must take the \code{\link[pepr]{Project-class}} as an argument. See \code{Details} for more information} -\item{funcArgs}{a named list with arguments you want to pass to the \code{func}. -The PEP will be passed automatically, -but if provided regardless, the constructor will disregard it} +\item{funcArgs}{a named list with arguments you want +to pass to the \code{func}. + The PEP will be passed automatically, + but if provided regardless, the constructor will disregard it. + You can also pass the arguments in a \code{funcArgs} section within + the \code{bioconductor} section in the config file.} } \value{ -an object of \code{\link[S4Vectors]{Annotated-class}} that is returned by the user provided function with the \code{\link[pepr]{Project-class}} object inserted into the first element of the list in its medatada slot +an object of \code{\link[S4Vectors]{Annotated-class}} that is +returned by the user provided function with +the \code{\link[pepr]{Project-class}} object inserted into the first +element of the list in its medatada slot } \description{ -This function creates a \code{\link[pepr]{Project-class}} object, and executes the user provided function with the created object as a first argument. -\cr\cr\emph{The custom data processing function has to return and object of \code{\link[S4Vectors]{Annotated-class}}, otherwise an error will be returned.} +This function creates a \code{\link[pepr]{Project-class}} object, +and executes the user provided function with the created object +as a first argument. +\cr\cr\emph{If the custom data processing function returns an object of +class other than \code{\link[S4Vectors]{Annotated-class}}, the output +will be packaged in a \code{\link[S4Vectors]{List-class}} with a metadata +slot populated with the \code{\link[pepr]{Project-class}}.} } \details{ This \code{\link{BiocProject}} function provides some degree @@ -38,26 +50,27 @@ implementation. Consider the possibilities listed below: \itemize{ \item use a function loaded into the \code{R} environment and specified in the config slot in \code{\link[pepr]{Project-class}} - (specifically: \code{config(project)$bioconductor$read_fun_name}). - \item use a function \emph{not} loaded into the \code{R} environment and specified in - the config slot in \code{\link[pepr]{Project}} - (specifically: \code{config(project)$bioconductor$read_fun_path}). + (specifically: \code{config(project)$bioconductor$readFunName}). + \item use a function \emph{not} loaded into the \code{R} environment and + specified in the config slot in \code{\link[pepr]{Project}} + (specifically: \code{config(project)$bioconductor$readFunPath}). \item use a function from other \code{R} package not loaded into the \code{R} environment and specified in the config slot in \code{\link[pepr]{Project}} - (specifically: \code{config(project)$bioconductor$read_fun_name}), like: + (specifically: \code{config(project)$bioconductor$readFunName}), like: \code{pkgName::functionName} \item use a function implemented in the \code{\link{BiocProject}} - call (passed to the \code{func} argument - anonymous function). This option is given the top priority and overrides - other arguments if provided. + call (passed to the \code{func} argument - anonymous function). + This option is given the top priority and overrides other + arguments if provided. } The custom data processing function must take the \code{\link[pepr]{Project-class}} as an argument since this object will be passed to the function by default. However, if the function requires addtional arguments, ones can be provided with the \code{funcArgs} argument in the \code{\link{BiocProject}} function call. -Besides, the \code{func} argument with the anonymous function may serve similar -possibility. +Besides, the \code{func} argument with the anonymous +function may serve similar possibility. If the \code{autoLoad} is set to \code{FALSE} the data will not be loaded and empty \code{\link[pepr]{Project-class}} object will be returned. @@ -69,6 +82,16 @@ Browse the for more detailed explanation with examples. } +\examples{ +projectConfig = system.file("extdata", "example_peps-master", +"example_BiocProject", "project_config.yaml", package="BiocProject") +bp=BiocProject(projectConfig) + +bp + +metadata(bp) + +} \seealso{ \url{https://pepkit.github.io/} } diff --git a/man/config-Annotated-method.Rd b/man/config-Annotated-method.Rd index 5d54bd3..8ca99c8 100644 --- a/man/config-Annotated-method.Rd +++ b/man/config-Annotated-method.Rd @@ -15,7 +15,8 @@ a list with the config file } \description{ This method can be used to view the config slot of -the \code{\link[pepr]{Project-class}} or or \code{\link[S4Vectors]{Annotated-class}} +the \code{\link[pepr]{Project-class}} + or \code{\link[S4Vectors]{Annotated-class}} } \examples{ projectConfig = system.file("extdata", "example_peps-master", diff --git a/man/dot-insertPEP.Rd b/man/dot-insertPEP.Rd new file mode 100644 index 0000000..27aff49 --- /dev/null +++ b/man/dot-insertPEP.Rd @@ -0,0 +1,33 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/functions.R +\name{.insertPEP} +\alias{.insertPEP} +\title{Insert a PEP metadata in a metadata slot of Annotated} +\usage{ +.insertPEP(object, pep) +} +\arguments{ +\item{object}{an object of \code{\link[S4Vectors]{Annotated-class}}} + +\item{pep}{an object of class \code{\link[pepr]{Project-class}}} +} +\value{ +an object of the same class as the object argument but enriched + with the metadata from the pep argument +} +\description{ +This function inserts the PEP (\code{\link[pepr]{Project-class}}) +into the metadata slot of objects that +extend the \code{\link[S4Vectors]{Annotated-class}} +} +\examples{ +# If the object is of class Annotated +object = S4Vectors::List(result="test") +result = .insertPEP(object, pepr::Project()) +metadata(result) + +# If the object is not of class Annotated +object1 = "test" +result1 = .insertPEP(object1, pepr::Project()) +metadata(result1) +} diff --git a/man/dot-updateList.Rd b/man/dot-updateList.Rd new file mode 100644 index 0000000..4d5b78d --- /dev/null +++ b/man/dot-updateList.Rd @@ -0,0 +1,30 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/utils.R +\name{.updateList} +\alias{.updateList} +\title{Update list with another list} +\usage{ +.updateList(list1, list2) +} +\arguments{ +\item{list1}{a list to be updated} + +\item{list2}{a list to update with} +} +\value{ +an updated list +} +\description{ +This function performs a union of two lists and updates the elements of the +first one if are found in the other one. +} +\details{ +Both elements have to be lists. If some elements are not named, they are +preserved but the order might be lost. +} +\examples{ +list1=list(a=1,b=2) +list2=list(a=1,b=1,c=3) +.updateList(list1,list2) + +} diff --git a/man/getProject-Annotated-method.Rd b/man/getProject.Rd similarity index 59% rename from man/getProject-Annotated-method.Rd rename to man/getProject.Rd index 6551604..2d6abaf 100644 --- a/man/getProject-Annotated-method.Rd +++ b/man/getProject.Rd @@ -1,25 +1,35 @@ % Generated by roxygen2: do not edit by hand % Please edit documentation in R/methods_Annotated.R \docType{methods} -\name{getProject,Annotated-method} +\name{getProject} +\alias{getProject} \alias{getProject,Annotated-method} -\title{Extract the object of \code{\link[pepr]{Project-class}} from the \code{\link[S4Vectors]{Annotated-class}}} +\title{Extract the object of \code{\link[pepr]{Project-class}} from +the \code{\link[S4Vectors]{Annotated-class}}} \usage{ +getProject(.Object) + \S4method{getProject}{Annotated}(.Object) } \arguments{ -\item{object}{an object of \code{\link[S4Vectors]{Annotated-class}}} +\item{.Object}{an object of \code{\link[S4Vectors]{Annotated-class}}} } \value{ an object of \code{\link[pepr]{Project-class}} } \description{ -This method can be used to extract the project metadata from objects of \code{\link[S4Vectors]{Annotated-class}} +This method can be used to extract the project metadata from objects of +\code{\link[S4Vectors]{Annotated-class}} } +\section{Methods (by class)}{ +\itemize{ +\item \code{Annotated}: extracts \code{\link[pepr]{Project-class}} from the \code{\link[S4Vectors]{Annotated-class}} +}} + \examples{ projectConfig = system.file("extdata", "example_peps-master", "example_BiocProject", "project_config.yaml", package="BiocProject") p=BiocProject(projectConfig) -samples(p) +getProject(p) } diff --git a/man/is-Annotated-method.Rd b/man/is-Annotated-method.Rd index 05ee832..0661bc6 100644 --- a/man/is-Annotated-method.Rd +++ b/man/is-Annotated-method.Rd @@ -7,9 +7,25 @@ \usage{ \S4method{is}{Annotated}(object, class2) } +\arguments{ +\item{object}{the object to be tested} + +\item{class2}{the class name to test the object against} +} +\value{ +a logical +} \description{ -Functions to test inheritance relationships between an object and a class or between two classes. It uses the generic is function but overrides its behavior for obejcts of class \code{\link[S4Vectors]{Annotated-class}} when testing for inheritance from \code{\link[pepr]{Project-class}} class. +Functions to test inheritance relationships between an object and a class +or between two classes. It uses the generic is function but overrides its +behavior for obejcts of class \code{\link[S4Vectors]{Annotated-class}} when +testing for inheritance from \code{\link[pepr]{Project-class}} class. } \details{ see the \code{\link[methods]{is}} for more details } +\examples{ +object = S4Vectors::List(test="test") +is(object,"Annotated") + +} diff --git a/man/samples-Annotated-method.Rd b/man/samples-Annotated-method.Rd index f26ac86..3ff83f5 100644 --- a/man/samples-Annotated-method.Rd +++ b/man/samples-Annotated-method.Rd @@ -15,12 +15,12 @@ a data.table with the with metadata about samples } \description{ This method can be used to view the samples slot -of the \code{\link[pepr]{Project-class}} or \code{\link[S4Vectors]{Annotated-class}} +of the \code{\link[pepr]{Project-class}} +or \code{\link[S4Vectors]{Annotated-class}} } \examples{ projectConfig = system.file("extdata", "example_peps-master", "example_BiocProject", "project_config.yaml", package="BiocProject") p=BiocProject(projectConfig) samples(p) - } diff --git a/tests/testthat.R b/tests/testthat.R new file mode 100755 index 0000000..b3c203a --- /dev/null +++ b/tests/testthat.R @@ -0,0 +1,4 @@ +Sys.setenv("R_TESTS" = "") +library(testthat) +library(BiocProject) +test_check("BiocProject") diff --git a/tests/testthat/test_all.R b/tests/testthat/test_all.R new file mode 100644 index 0000000..d124af7 --- /dev/null +++ b/tests/testthat/test_all.R @@ -0,0 +1,224 @@ +library(yaml) +# Prep data --------------------------------------------------------------- + +configFile = system.file( + "extdata", + "example_peps-master", + "example_BiocProject", + "project_config.yaml", + package = "BiocProject" +) + +configFileArgs = system.file( + "extdata", + "example_peps-master", + "example_BiocProject", + "project_config_resize.yaml", + package = "BiocProject" +) + +configFileMissingFun = system.file( + "test_projects", + "faulty_project", + "project_config_no_function.yaml", + package = "BiocProject" +) + +configFileNoSection = system.file( + "test_projects", + "faulty_project", + "project_config_no_section.yaml", + package = "BiocProject" +) + + +bp = BiocProject(configFile) + +a=function(arg) { + stop(arg) +} + +b=function(arg) { + warning(arg) +} + +c=function(arg) { + return(arg) +} + +testChar = "a" + +# Test -------------------------------------------------------------------- +context("Test .updateList utility function") + +test_that(".updateList returns correct object type", { + expect_is(.updateList(list(a=1),list(a=2,b=2)), 'list') +}) + +test_that(".updateList returns list of correct length", { + expect_equal(length(.updateList(list(a=1),list(a=2,b=2))), 2) + expect_equal(length(.updateList(list(a=1,c=3),list(a=2,b=2))), 3) + expect_equal(length(.updateList(list(a=1,b=3),list(c=2,d=2))), 4) +}) + +test_that(".updateList throws errors", { + expect_error(.updateList(list(a=1),2)) +}) + +context("Test .makeAbsPath utility function") + +test_that(".makeAbsPath returns correct object", { + expect_is(.makeAbsPath("~"),"character") +}) + +test_that(".makeAbsPath returns correct value", { + expect_equal(.makeAbsPath("~"),Sys.getenv("HOME")) +}) + +context("Test .isDefined utility function") + +test_that(".isDefined returns correct object", { + expect_is(.isDefined(NA),"logical") +}) + +test_that(".isDefined returns correct value", { + expect_equal(.isDefined(NA),FALSE) + expect_equal(.isDefined(NULL),FALSE) + expect_equal(.isDefined(configFile),TRUE) +}) + +context("Test .isAbsolute utility function") + +test_that(".isAbsolute returns correct object", { + expect_is(.isAbsolute("~"),"logical") +}) + +test_that(".isAbsolute returns correct value", { + expect_equal(.isAbsolute("~"),TRUE) + expect_equal(.isAbsolute("../test"),FALSE) +}) + +context("Test .callBiocFun untility function") + +test_that(".callBiocFun catches errors", { + expect_error(expect_error(.callBiocFun(a,list(testChar)))) + expect_equal(.callBiocFun(a,list(testChar)),S4Vectors::List(testChar)) + expect_warning(.callBiocFun(b,list(testChar))) +}) + +test_that(".callBiocFun returns correct object on success", { + expect_is(.callBiocFun(c,list(testChar)),class(testChar)) +}) + +test_that(".callBiocFun returns correct value on success", { + expect_equal(.callBiocFun(c,list(testChar)),testChar) +}) + +test_that(".callBiocFun throws errors", { + expect_error(.callBiocFun(a,testChar)) +}) + +context("Test .insertPEP function") + +test_that(".insertPEP returns correct object with a warning",{ + expect_warning(expect_is(.insertPEP("a",pepr::Project()),"Annotated")) +}) + +test_that(".insertPEP returns correct object",{ + expect_is(.insertPEP(S4Vectors::List(),pepr::Project()),"Annotated") +}) + +test_that(".insertPEP throws errors",{ + expect_error(.insertPEP(S4Vectors::List(),"test")) +}) + +context("Test BiocProject function") + +test_that("BiocProject function return correct object", { + expect_is(BiocProject(configFile),"Annotated") +}) + +test_that("BiocProject function works with arguments", { + expect_is(BiocProject(configFileArgs),"Annotated") + expect_is(BiocProject(configFileArgs, funcArgs = list(resize.width=200)), "Annotated") +}) + +# test_that("BiocProject function overrides the arguments specified in the config +# file with ones that have the same names in the funcArgs list", { +# expect_failure(expect_identical( +# BiocProject(configFileArgs), +# BiocProject(configFileArgs, funcArgs = list(resize.width = 200)) +# )) +# }) + +test_that("BiocProject function returns Annotated when provided objects of + different class and thorows a warning", { + expect_warning(expect_is(BiocProject(configFile, func = function(x){ + return("test") + }),"Annotated")) +}) + +test_that("BiocProject function returns a Project object + when autoload is set to FALSE", { + expect_is(BiocProject(file=configFile,autoLoad = FALSE),"Project") +}) + +test_that("BiocProject function throws errors/warnings + when the arguments are inappropriate", { + expect_error(BiocProject(file=configFile,func = "2")) + expect_error(BiocProject(file = "test")) + expect_error(BiocProject(file = configFile,autoLoad = "test")) +}) + +test_that("BiocProject function catches errors in the user-provided + function returns the error message as Annotated", { + expect_is(BiocProject(file=configFile,func=function(x) { + stop("test") + }),"Annotated") +}) + +test_that("BiocProject function catches errors when the function specified + does not exist", { + expect_error(BiocProject(configFileMissingFun)) + }) + +test_that("BiocProject function throws a warning and returns a Project object + when no bioconductor section found",{ + expect_warning(expect_is(BiocProject(configFileNoSection),"Project")) +}) + +test_that("BiocProject function throws an error + when nonexistent subproject is provided",{ + expect_error(expect_warning(BiocProject(configFile, subproject = "test"))) +}) + +context("Test Annotated methods") + +test_that("samples returns a correct object", { + expect_is(samples(bp),"data.table") +}) + +test_that("config returns a correct object", { + expect_is(config(bp),"Config") +}) + +test_that(".is.project returns a correct object", { + expect_is(.is.project(bp),"logical") +}) + +test_that(".is.project returns a value", { + expect_equal(.is.project(bp),TRUE) + expect_equal(.is.project(S4Vectors::List(a=1)), FALSE) +}) + +test_that("is method returns correct value when Annotated provided", { + expect_equal(is(bp,"Project"), TRUE) +}) + +test_that("getProject returns a correct object", { + expect_is(getProject(bp),"Project") +}) + +test_that("getProject returns a correct value", { + expect_equal(getProject(bp), pepr::Project(configFile)) +}) \ No newline at end of file diff --git a/vignettes/1getStarted.Rmd b/vignettes/vignette1getStarted.Rmd similarity index 87% rename from vignettes/1getStarted.Rmd rename to vignettes/vignette1getStarted.Rmd index 674c6e1..546da7a 100644 --- a/vignettes/1getStarted.Rmd +++ b/vignettes/vignette1getStarted.Rmd @@ -2,9 +2,9 @@ title: "Getting started with BiocProject" author: "Michal Stolarczyk" date: "`r Sys.Date()`" -output: rmarkdown::html_vignette +output: BiocStyle::html_document vignette: > - %\VignetteIndexEntry{Vignette Title} + %\VignetteIndexEntry{Getting started with BiocProject} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- @@ -98,10 +98,10 @@ knitr::kable(sampleAnnotationDF, format = "html") In this example, our PEP has two samples, which have two attributes: `sample_name`, and `file_path`, which points the location for the data. -The configuration file also points to a third file (``r { basename(config(bp)$bioconductor$read_fun_path) }``). This file holds a single `R` function called ``r { basename(config(bp)$bioconductor$read_fun_name) }``, which has these contents: +The configuration file also points to a third file (``r { basename(config(bp)$bioconductor$readFunPath) }``). This file holds a single `R` function called ``r { basename(config(bp)$bioconductor$readFunName) }``, which has these contents: ```{r echo=FALSE, eval=TRUE, comment=""} -get(config(bp)$bioconductor$read_fun_name) +get(config(bp)$bioconductor$readFunName) ``` And that's all there is to it! **This PEP consists really of 3 components:** @@ -150,15 +150,15 @@ In the basic case the function name (and path to source file, if necessary) is s ``` bioconductor: - read_fun_name: function_name + readFunName: function_name ``` or ``` bioconductor: - read_fun_name: function_name - read_fun_path: /path/to/the/file.R + readFunName: function_name + readFunPath: /path/to/the/file.R ``` The function specified can be a data processing function of any complexity, but has to follow 3 rules listed below. @@ -207,11 +207,11 @@ bpExceptions = BiocProject(configFile) # Further reading -See this [More arguments than just a PEP in your function?](./2multipleArguments.html) vignette if you want to: +See this [More arguments than just a PEP in your function?](./vignette2multipleArguments.html) vignette if you want to: * use an anonymous function instead of one defined *a priori* * use a function that requires more arguments than just a PEP -See the [Working with remote data](./4remoteData.html) vignette to learn how to download the data from the Internet, process it and store it conveniently with related metadata in any object from the Bioconductor project. +See the [Working with remote data](./vignette4remoteData.html) vignette to learn how to download the data from the Internet, process it and store it conveniently with related metadata in any object from the Bioconductor project. -See the [Working with large datasets - simpleCache](./3simpleCache.html) vignette to learn how the `simpleCache` R package can be used to prevent copious and lengthy results recalculations when working with large datasets. \ No newline at end of file +See the [Working with large datasets - simpleCache](./vignette3simpleCache.html) vignette to learn how the `simpleCache` R package can be used to prevent copious and lengthy results recalculations when working with large datasets. \ No newline at end of file diff --git a/vignettes/2multipleArguments.Rmd b/vignettes/vignette2multipleArguments.Rmd similarity index 58% rename from vignettes/2multipleArguments.Rmd rename to vignettes/vignette2multipleArguments.Rmd index a580c06..c92ac64 100644 --- a/vignettes/2multipleArguments.Rmd +++ b/vignettes/vignette2multipleArguments.Rmd @@ -2,9 +2,9 @@ title: "More arguments than just a PEP in your function?" author: "Michal Stolarczyk" date: "`r Sys.Date()`" -output: rmarkdown::html_vignette +output: BiocStyle::html_document vignette: > - %\VignetteIndexEntry{Vignette Title} + %\VignetteIndexEntry{More arguments than just a PEP in your function?} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- @@ -18,9 +18,29 @@ knitr::opts_chunk$set( # Introduction -Before you start see the [Getting started with `BiocProject` vignette](./1getStarted.html) for the basic information and installation instructions. +Before you start see the [Getting started with `BiocProject` vignette](./vignette1getStarted.html) for the basic information and installation instructions. -# Additional arguments in your function +Get paths to the files used in this vignette +```{r echo=T,message=FALSE} +library(BiocProject) +ProjectConfigArgs = system.file( + "extdata", + "example_peps-master", + "example_BiocProject", + "project_config_resize.yaml", + package = "BiocProject" +) + +readBedFiles_resize = system.file( + "extdata", + "example_peps-master", + "example_BiocProject", + "readBedFiles_resize.R", + package = "BiocProject" +) +``` + +# Ways to provide addtional arguments **What if your custom data processing function requires more arguments than just a PEP?** @@ -38,15 +58,44 @@ source(processFunction) ```{r echo=FALSE, comment=""} readBedFiles_resize ``` - There are a few ways to enable your function to get multiple arguments - not just a PEP ([`pepr::Project`](http://code.databio.org/pepr/reference/Project-class.html)) object, which is the basic scenario. +**The options:** + +- additional section in the config file +- using `funcArgs` argument of `BiocProject` function +- using an anonymous function in the `func` argument of `BiocProject` function + +# How to provide addtional section in the config file + +The easiest way to provide addtional arguments to your data reading/processing function is to add addtional section in the config file. See the config file below for reference: + +```{r, warning=FALSE, echo=FALSE, message=FALSE, collapse=TRUE, comment=" "} +library(pepr) +config_resize = configFile = system.file( + "extdata", + "example_peps-master", + "example_BiocProject", + "project_config_resize.yaml", + package = "BiocProject" +) +.printNestedList(yaml::read_yaml(config_resize)) +``` + +The section `funcArgs` was added within the `bioconductor` section. + +```{r} +bp = BiocProject(ProjectConfigArgs) +bp +``` + # How to use the `funcArgs` argument Provide additional `funcArgs` argument to the `BiocProject` function. This argument has to be a named list. The names have to correspond to the argument names of your function. **The PEP (`pepr::Project` object) will be passed to your function by default**. For example: -Get paths to the files used in this vignette -```{r echo=T,message=FALSE} + +Read the function into R environment and run the `BiocProject` function with the `funcArgs` argument +```{r include=F,eval=TRUE} library(BiocProject) ProjectConfigArgs = system.file( "extdata", @@ -64,12 +113,12 @@ readBedFiles_resize = system.file( package = "BiocProject" ) ``` -Read the function into R environment and run the `BiocProject` function ```{r} source(readBedFiles_resize) -bpArgs = BiocProject(file=ProjectConfigArgs, funcArgs=list(resize.width=100)) +bpArgs = BiocProject(file=ProjectConfigArgs, funcArgs=list(resize.width=200)) +bpArgs ``` -The `funcArgs` argument gets a one element list and passes the `resize.width` argument to your custom data processing function. +The `funcArgs` argument gets a one element list and passes the `resize.width` argument to your custom data processing function. If any arguments are present in the config file, they will be overwritten (the width of the ranges has changed from 100 to 200 in the example above). # How to use an anonymous function diff --git a/vignettes/3simpleCache.Rmd b/vignettes/vignette3simpleCache.Rmd similarity index 87% rename from vignettes/3simpleCache.Rmd rename to vignettes/vignette3simpleCache.Rmd index f3a5cc7..fb0edba 100644 --- a/vignettes/3simpleCache.Rmd +++ b/vignettes/vignette3simpleCache.Rmd @@ -2,9 +2,9 @@ title: "Working with large datasets - simpleCache" author: "Michal Stolarczyk" date: "`r Sys.Date()`" -output: rmarkdown::html_vignette +output: BiocStyle::html_document vignette: > - %\VignetteIndexEntry{Vignette Title} + %\VignetteIndexEntry{Working with large datasets - simpleCache} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- @@ -18,7 +18,7 @@ knitr::opts_chunk$set( # Introduction -This vignette assumes you're familiar with the [Getting started with `BiocProject` vignette](./1getStarted.html) for the basic `BiocProject` information and [An introduction to `simpleCache`](http://code.databio.org/simpleCache/articles/simpleCacheIntroduction.html) for the basic `simpleCache` information. +This vignette assumes you're familiar with the [Getting started with `BiocProject` vignette](./vignette1getStarted.html) for the basic `BiocProject` information and [An introduction to `simpleCache`](http://code.databio.org/simpleCache/articles/simpleCacheIntroduction.html) for the basic `simpleCache` information. # Why to use `simpleCache` with `BiocProject` diff --git a/vignettes/4remoteData.Rmd b/vignettes/vignette4remoteData.Rmd similarity index 91% rename from vignettes/4remoteData.Rmd rename to vignettes/vignette4remoteData.Rmd index 119be13..8cdca9a 100644 --- a/vignettes/4remoteData.Rmd +++ b/vignettes/vignette4remoteData.Rmd @@ -2,9 +2,9 @@ title: "Working with remote data" author: "Michal Stolarczyk" date: "`r Sys.Date()`" -output: rmarkdown::html_vignette +output: BiocStyle::html_document vignette: > - %\VignetteIndexEntry{Vignette Title} + %\VignetteIndexEntry{Working with remote data} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- @@ -18,7 +18,7 @@ knitr::opts_chunk$set( # Introduction -Before you start see the [Getting started with `BiocProject` vignette](./1getStarted.html) for the basic information and installation instructions. +Before you start see the [Getting started with `BiocProject` vignette](./vignette1getStarted.html) for the basic information and installation instructions. # How to download the data with your function @@ -72,7 +72,7 @@ library(pepr) ## Execute the `BiocProject` function Get path to the config file -```{r echo=T,message=FALSE} +```{r echo=TRUE,message=FALSE} library(BiocProject) ProjectConfigRemote = system.file( "extdata",