diff --git a/DESCRIPTION b/DESCRIPTION index 7cccb92..d46e7cb 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: modules Title: Self Contained Units of Source Code -Version: 0.8.0 +Version: 0.8.1 Authors@R: person("Sebastian", "Warnholz", email = "wahani@gmail.com", role = c("aut", "cre")) Description: Provides modules as an organizational unit for source code. Modules enforce to be more rigorous when defining dependencies and have @@ -24,22 +24,23 @@ Suggests: lintr, rmarkdown, parallel -RoxygenNote: 6.1.0 +RoxygenNote: 6.1.1 Collate: - 'amodule.R' 'NAMESPACE.R' - 'getSearchPath.R' + 'amodule.R' + 'base-override.R' 'class.R' 'depend.R' 'export.R' 'expose.R' 'extend.R' + 'getSearchPath.R' 'import.R' 'module-class.R' 'module-coercion.R' 'module-helper.R' - 'module.R' 'use.R' + 'module.R' + 'module-package.R' 'testModule.R' - 'base-override.R' VignetteBuilder: knitr diff --git a/NAMESPACE b/NAMESPACE index a1ae764..2816647 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -19,8 +19,10 @@ export(getSearchPathContent) export(getSearchPathDuplicates) export(getSearchPathNames) export(import) +export(initModules) export(module) export(use) +export(useModules) importFrom(utils,download.file) importFrom(utils,install.packages) importFrom(utils,installed.packages) diff --git a/R/module-package.R b/R/module-package.R new file mode 100644 index 0000000..fdf561f --- /dev/null +++ b/R/module-package.R @@ -0,0 +1,109 @@ +#' Use modules in package build process +#' +#' This function will create a configuration file, such that sub folders inside +#' the R folder of a package are used as module definition. It will then compile +#' all modules. +#' +#' @param folder (character) the folder where to create the configuration file: +#' the package root. +#' +#' @details Guiding principles: (1) this is R CMD check compliant (2) we do not +#' introduce new logic to the semantics of R code. +#' +#' What does it mean to have sub-folders in packages? Each folder defines a +#' module. The name of the module is the name of the folder. Having several R +#' files in a sub-folder adds no additional logic. All files are sourced into +#' the same environment. As with packages, we can make objects public by +#' exporting them. As with packages, nested sub-folders are currently ignored. +#' +#' Each sub-folder is compiled into one module, represented by a regular R file +#' in the package. The file is auto-generated and managed by the build +#' process. The presence of this file enables to take advantage of R CMD +#' check. Hence checks may refer to the target file, however changes need to +#' happen inside the module/sub-folder itself. +#' +#' @rdname initModules +#' @export +useModules <- function(folder = ".") { + addConfigure(folder) + initModules(folder) +} + +addConfigure <- function(folder = ".") { + folder <- normalizePath(folder) + configureFile <- normalizePath(paste0(folder, "/configure")) + message(sprintf("Creating %s[.win]", configureFile)) + code <- c( + '#!/bin/sh', '', + '$R_HOME/bin/Rscript -e "modules::initModules()"', + '') + writeLines(code, configureFile) + writeLines(code, configureFileWin <- paste0(configureFile, ".win")) + Sys.chmod(configureFile, "0764") + Sys.chmod(configureFileWin, "0764") +} + +#' @rdname initModules +#' @export +initModules <- function(folder = ".") { + message("** compiling modules") + folder <- normalizePath(paste0(folder, "/R"), mustWork = FALSE) + removeOutdatedModules(folder) + processModules(folder) + invisible(NULL) +} + +removeOutdatedModules <- function(folder) { + subFiles <- getSubFiles(folder) + for (file in subFiles) { + moduleFolder <- sub("module-", "", sub("\\.[rR]$", "", file)) + if (!dir.exists(moduleFolder)) { + message(sprintf("*** removing %s: no module folder found", file)) + file.remove(file) + } + } +} + +processModules <- function(folder) { + subFolders <- getSubFolders(folder) + subFiles <- constSubFiles(folder) + subNames <- getSubNames(folder) + mapply(parseAndWrite, subFolders, subNames, subFiles) +} + +parseAndWrite <- function(subFolder, subName, subFile) { + code <- parseModule(subFolder, subName) + if (!file.exists(subFile)) message("*** creating %s", subFile) + writeLines(code, subFile) +} + +parseModule <- function(subFolder, subName) { + code <- lapply(list.files(subFolder, "\\.[rR]$", full.names = TRUE), readLines) + code <- unlist(code) + doc <- code[grepl(" *#+'", code)] + code <- code[!grepl(" *#+'", code)] + code <- ifelse(code == "", code, paste0(" ", code)) + assignConst <- sprintf("new%s <- function() modules::module({", subName) + assignModule <- sprintf("%s <- new%1$s()", subName) + c(disclaimer(), assignConst, code, "})", "\n", doc, assignModule) +} + +disclaimer <- function() "# Generated by modules: do not edit by hand\n" + +getSubFolders <- function(folder) { + list.dirs(folder, recursive = FALSE) +} + +getSubNames <- function(folder) { + basename(getSubFolders(folder)) +} + +getSubFiles <- function(folder) { + list.files(folder, "module-.*\\.[rR]$", full.names = TRUE) +} + +constSubFiles <- function(folder) { + subNames <- getSubNames(folder) + if (length(subNames) == 0) character(0) + else paste0(folder, "/module-", subNames, ".R") +} diff --git a/man/initModules.Rd b/man/initModules.Rd new file mode 100644 index 0000000..433a6e1 --- /dev/null +++ b/man/initModules.Rd @@ -0,0 +1,36 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/module-package.R +\name{useModules} +\alias{useModules} +\alias{initModules} +\title{Use modules in package build process} +\usage{ +useModules(folder = ".") + +initModules(folder = ".") +} +\arguments{ +\item{folder}{(character) the folder where to create the configuration file: +the package root.} +} +\description{ +This function will create a configuration file, such that sub folders inside +the R folder of a package are used as module definition. It will then compile +all modules. +} +\details{ +Guiding principles: (1) this is R CMD check compliant (2) we do not + introduce new logic to the semantics of R code. + +What does it mean to have sub-folders in packages? Each folder defines a + module. The name of the module is the name of the folder. Having several R + files in a sub-folder adds no additional logic. All files are sourced into + the same environment. As with packages, we can make objects public by + exporting them. As with packages, nested sub-folders are currently ignored. + +Each sub-folder is compiled into one module, represented by a regular R file + in the package. The file is auto-generated and managed by the build + process. The presence of this file enables to take advantage of R CMD + check. Hence checks may refer to the target file, however changes need to + happen inside the module/sub-folder itself. +} diff --git a/prepareRepo.R b/prepareRepo.R index 1c88d0d..b2ec29d 100644 --- a/prepareRepo.R +++ b/prepareRepo.R @@ -16,6 +16,9 @@ writeLines(text, "README.md") ## TODO +## - amodule +## - parameters masked by internals +## - dealing with elipses ## - depend ## - on .tar.gz diff --git a/vignettes/.gitignore b/vignettes/.gitignore new file mode 100644 index 0000000..097b241 --- /dev/null +++ b/vignettes/.gitignore @@ -0,0 +1,2 @@ +*.html +*.R diff --git a/vignettes/modulesInPackages.Rmd b/vignettes/modulesInPackages.Rmd new file mode 100644 index 0000000..a1e18c7 --- /dev/null +++ b/vignettes/modulesInPackages.Rmd @@ -0,0 +1,40 @@ +--- +title: "Modules and subfolders inside R packages" +author: "Sebastian Warnholz" +date: "`r Sys.Date()`" +output: rmarkdown::html_vignette +vignette: > + %\VignetteIndexEntry{Modules and subfolders inside R packages} + %\VignetteEngine{knitr::rmarkdown} + %\VignetteEncoding{UTF-8} +--- + +```{r setup, include = FALSE} +knitr::opts_chunk$set( + collapse = TRUE, + comment = "#>" +) +``` + +# Introduction + +In the following you find how you can use modules and subfolders inside of an R +package. This gives you additional options to impose structure on your +code-base. While using modules inside of packages is a viable option already, it +does not affect the way you structure the files. Also, an R package uses the +sub-folder `R` for all R source code, but ignores all sub-folders within. In +small code bases, this is no problem. Quite the contrary, it's simplicity stops +you from over-complicating things. In large code bases, say everything with +more than 20 files, you may long for something more than a flat folder +structure. + +# Example + +The idea is very simple. You add a sub-folder to your package. They contain R +source files, just like your ordinary R package. + +- Copy & Paste aus dem Paket in einen Unterordner muss funktionieren. + - Umgang mit Dokumentation? + - Umgang mit Exports +- Dokumentation von Modulen +-