From 900af68407932201ff30d96c0cf46b6f8d1259b9 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Thu, 4 Jul 2019 22:49:01 +0200 Subject: [PATCH 1/7] init modules in packages --- DESCRIPTION | 11 +++-- NAMESPACE | 2 + R/module-package.R | 87 +++++++++++++++++++++++++++++++++ vignettes/.gitignore | 2 + vignettes/modulesInPackages.Rmd | 40 +++++++++++++++ 5 files changed, 137 insertions(+), 5 deletions(-) create mode 100644 R/module-package.R create mode 100644 vignettes/.gitignore create mode 100644 vignettes/modulesInPackages.Rmd diff --git a/DESCRIPTION b/DESCRIPTION index 7cccb92..34af71d 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -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..68a8e5c 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -19,8 +19,10 @@ export(getSearchPathContent) export(getSearchPathDuplicates) export(getSearchPathNames) export(import) +export(initModules) export(module) export(use) +export(use_modules) 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..131ace0 --- /dev/null +++ b/R/module-package.R @@ -0,0 +1,87 @@ +#' @export +use_modules <- function(folder = ".") { + addConfigure(folder) + initModules(folder) +} + +addConfigure <- function(folder = ".") { + folder <- normalizePath(folder) + message(sprintf("Creating %s", configureFile <- paste0(folder, "/configure"))) + code <- c( + '#!/bin/sh', '', + '$R_HOME/bin/Rscript -e "modules::initModules()"', + '') + writeLines(code, configureFile) + Sys.chmod(configureFile, "0764") +} + +#' @export +initModules <- function(folder = ".", where = parent.frame()) { + message("** 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("modules-", "", 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) + } + if (file.mtime(subFolder) > file.mtime(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)) + disclaimer() + assignConst <- sprintf("new%s <- function() modules::module({", subName) + assignModule <- sprintf("%s <- new%1$s()", subName) + closingParans <- sprintf("})") + c(disclaimer(), assignConst, code, closingParans, "\n", doc, assignModule) +} + +disclaimer <- function() "# Generated by modules: do not edit by hand\n" + +getSubFolders <- function(folder) { + dirs <- list.dirs(folder, recursive = FALSE) + dirs[basename(dirs) != "modules"] +} + +getSubNames <- function(folder) { + basename(getSubFolders(folder)) +} + +getSubFiles <- function(folder) { + list.files(folder, "modules-.*\\.[rR]$", full.names = TRUE) +} + +constSubFiles <- function(folder) { + subNames <- getSubNames(folder) + if (length(subNames) == 0) character(0) + else paste0(folder, "/modules-", subNames, ".R") +} 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 +- From 101bfa1205c1dd60f20f06348c70cc7019d5d618 Mon Sep 17 00:00:00 2001 From: Sebastian Warnholz Date: Wed, 10 Jul 2019 10:16:51 +0200 Subject: [PATCH 2/7] Update module-package.R --- R/module-package.R | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/R/module-package.R b/R/module-package.R index 131ace0..d3025a2 100644 --- a/R/module-package.R +++ b/R/module-package.R @@ -6,13 +6,15 @@ use_modules <- function(folder = ".") { addConfigure <- function(folder = ".") { folder <- normalizePath(folder) - message(sprintf("Creating %s", configureFile <- paste0(folder, "/configure"))) + message(sprintf("Creating %s[.win]", configureFile <- normalizePath(paste0(folder, "/configure")))) 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") } #' @export @@ -44,12 +46,8 @@ processModules <- function(folder) { parseAndWrite <- function(subFolder, subName, subFile) { code <- parseModule(subFolder, subName) - if (!file.exists(subFile)) { - message("*** creating %s", subFile) - writeLines(code, subFile) - } - if (file.mtime(subFolder) > file.mtime(subFile)) - writeLines(code, subFile) + if (!file.exists(subFile)) message("*** creating %s", subFile) + writeLines(code, subFile) } parseModule <- function(subFolder, subName) { @@ -58,7 +56,6 @@ parseModule <- function(subFolder, subName) { doc <- code[grepl(" *#+'", code)] code <- code[!grepl(" *#+'", code)] code <- ifelse(code == "", code, paste0(" ", code)) - disclaimer() assignConst <- sprintf("new%s <- function() modules::module({", subName) assignModule <- sprintf("%s <- new%1$s()", subName) closingParans <- sprintf("})") From 6fa189f554b19e01a13935a52620c14bdcc10dba Mon Sep 17 00:00:00 2001 From: unknown Date: Thu, 11 Jul 2019 14:59:16 +0200 Subject: [PATCH 3/7] added todos --- prepareRepo.R | 3 +++ 1 file changed, 3 insertions(+) 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 From a4d1e9feeeed620c874a70a1791483e22d26788a Mon Sep 17 00:00:00 2001 From: Sebastian Date: Sat, 27 Jul 2019 10:06:12 +0200 Subject: [PATCH 4/7] misc changes --- R/module-package.R | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/R/module-package.R b/R/module-package.R index 131ace0..0abdf7d 100644 --- a/R/module-package.R +++ b/R/module-package.R @@ -17,7 +17,7 @@ addConfigure <- function(folder = ".") { #' @export initModules <- function(folder = ".", where = parent.frame()) { - message("** modules") + message("** compiling modules") folder <- normalizePath(paste0(folder, "/R"), mustWork = FALSE) removeOutdatedModules(folder) processModules(folder) @@ -27,7 +27,7 @@ initModules <- function(folder = ".", where = parent.frame()) { removeOutdatedModules <- function(folder) { subFiles <- getSubFiles(folder) for (file in subFiles) { - moduleFolder <- sub("modules-", "", sub("\\.[rR]$", "", file)) + moduleFolder <- sub("module-", "", sub("\\.[rR]$", "", file)) if (!dir.exists(moduleFolder)) { message(sprintf("*** removing %s: no module folder found", file)) file.remove(file) @@ -45,11 +45,9 @@ processModules <- function(folder) { parseAndWrite <- function(subFolder, subName, subFile) { code <- parseModule(subFolder, subName) if (!file.exists(subFile)) { - message("*** creating %s", subFile) - writeLines(code, subFile) + message("*** creating ", subFile) } - if (file.mtime(subFolder) > file.mtime(subFile)) - writeLines(code, subFile) + writeLines(code, subFile) } parseModule <- function(subFolder, subName) { @@ -57,19 +55,21 @@ parseModule <- function(subFolder, subName) { code <- unlist(code) doc <- code[grepl(" *#+'", code)] code <- code[!grepl(" *#+'", code)] - code <- ifelse(code == "", code, paste0(" ", code)) + code <- ifelse(code == "", code, paste0(" ", code)) disclaimer() - assignConst <- sprintf("new%s <- function() modules::module({", subName) + assignConst <- sprintf("new%s <- function(...) {", subName) + assignArgs <- " args <- list(...)" + constModule <- " modules::module({" + closingParans <- " })\n}" assignModule <- sprintf("%s <- new%1$s()", subName) - closingParans <- sprintf("})") - c(disclaimer(), assignConst, code, closingParans, "\n", doc, assignModule) + c(disclaimer(), assignConst, assignArgs, constModule, code, + closingParans, "\n", doc, assignModule) } disclaimer <- function() "# Generated by modules: do not edit by hand\n" getSubFolders <- function(folder) { - dirs <- list.dirs(folder, recursive = FALSE) - dirs[basename(dirs) != "modules"] + list.dirs(folder, recursive = FALSE) } getSubNames <- function(folder) { @@ -77,11 +77,11 @@ getSubNames <- function(folder) { } getSubFiles <- function(folder) { - list.files(folder, "modules-.*\\.[rR]$", full.names = TRUE) + list.files(folder, "module-.*\\.[rR]$", full.names = TRUE) } constSubFiles <- function(folder) { subNames <- getSubNames(folder) if (length(subNames) == 0) character(0) - else paste0(folder, "/modules-", subNames, ".R") + else paste0(folder, "/module-", subNames, ".R") } From 1f49ecb74d9c04b5f9c3ae24ec364e04166ccf39 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Sat, 14 Sep 2019 13:16:49 +0200 Subject: [PATCH 5/7] docs --- NAMESPACE | 2 +- R/module-package.R | 33 ++++++++++++++++++++++++++++++--- man/initModules.Rd | 36 ++++++++++++++++++++++++++++++++++++ 3 files changed, 67 insertions(+), 4 deletions(-) create mode 100644 man/initModules.Rd diff --git a/NAMESPACE b/NAMESPACE index 68a8e5c..2816647 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -22,7 +22,7 @@ export(import) export(initModules) export(module) export(use) -export(use_modules) +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 index 4e003f5..35a4307 100644 --- a/R/module-package.R +++ b/R/module-package.R @@ -1,12 +1,38 @@ +#' 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 -use_modules <- function(folder = ".") { +useModules <- function(folder = ".") { addConfigure(folder) initModules(folder) } addConfigure <- function(folder = ".") { folder <- normalizePath(folder) - message(sprintf("Creating %s[.win]", configureFile <- normalizePath(paste0(folder, "/configure")))) + configureFile <- normalizePath(paste0(folder, "/configure")) + message(sprintf("Creating %s[.win]", configureFile)) code <- c( '#!/bin/sh', '', '$R_HOME/bin/Rscript -e "modules::initModules()"', @@ -17,8 +43,9 @@ addConfigure <- function(folder = ".") { Sys.chmod(configureFileWin, "0764") } +#' @rdname initModules #' @export -initModules <- function(folder = ".", where = parent.frame()) { +initModules <- function(folder = ".") { message("** compiling modules") folder <- normalizePath(paste0(folder, "/R"), mustWork = FALSE) removeOutdatedModules(folder) 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. +} From d30b4c012053e94604654d9361aa96fae892975a Mon Sep 17 00:00:00 2001 From: Sebastian Date: Sat, 14 Sep 2019 13:19:42 +0200 Subject: [PATCH 6/7] version buff --- DESCRIPTION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DESCRIPTION b/DESCRIPTION index 34af71d..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 From f1da1e89a7e865b3d7a3c53cbcc855d9f76275d0 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Sat, 14 Sep 2019 13:39:58 +0200 Subject: [PATCH 7/7] fix parsing --- R/module-package.R | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/R/module-package.R b/R/module-package.R index 35a4307..fdf561f 100644 --- a/R/module-package.R +++ b/R/module-package.R @@ -85,8 +85,7 @@ parseModule <- function(subFolder, subName) { code <- ifelse(code == "", code, paste0(" ", code)) assignConst <- sprintf("new%s <- function() modules::module({", subName) assignModule <- sprintf("%s <- new%1$s()", subName) - c(disclaimer(), assignConst, assignArgs, constModule, code, - closingParans, "\n", doc, assignModule) + c(disclaimer(), assignConst, code, "})", "\n", doc, assignModule) } disclaimer <- function() "# Generated by modules: do not edit by hand\n"