Skip to content

Commit

Permalink
Merge branch 'cran' into develop
Browse files Browse the repository at this point in the history
  • Loading branch information
klmr committed Feb 8, 2021
2 parents eb4e17d + db5dfa6 commit 15ebcdb
Show file tree
Hide file tree
Showing 28 changed files with 663 additions and 250 deletions.
2 changes: 2 additions & 0 deletions .Rbuildignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
^LICENSE.md$
^printenv\.r$
^pkgdown$
^cran-comments.md$
^doc$
^docs$
^figures$
Expand All @@ -19,3 +20,4 @@
^renv$
^renv\.lock$
^\.github$
^scripts$
25 changes: 13 additions & 12 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
.Rprofile
man/*.Rd
NAMESPACE
check
doc
docs
Meta
pkgdown/favicon
src/*.o
src/*.so
vignettes/*.html
vignettes/*.R
/.Rprofile
/man/*.Rd
/NAMESPACE
/build/
/check/
/doc/
/docs/
/Meta/
/pkgdown/favicon/
/src/*.o
/src/*.so
/vignettes/*.html
/vignettes/*.R
13 changes: 13 additions & 0 deletions .lintr
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
linters: source('scripts/linters.r'); with_defaults(
assignment_linter = arrow_assignment_linter,
closed_curly_linter = NULL,
commented_code_linter = NULL,
function_left_parentheses_linter = NULL,
line_length_linter(120L),
infix_spaces_linter = NULL,
object_length_linter = s3_object_length_linter(20L),
object_name_linter = NULL,
object_usage_linter = NULL,
open_curly_linter = NULL,
single_quotes_linter = double_quotes_linter
)
46 changes: 40 additions & 6 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,22 @@ deploy_remote ?= origin
deploy_branch ?= master
deploy_source ?= develop

# Helper functions for a recursive wildcard function.
match_files = $(filter $(subst *,%,$2),$1)
filter_out_dirs = $(filter-out %/,$(foreach f,$1,$(wildcard $f/)))
rec_find = $(foreach f,$(wildcard ${1:=/*}),$(call filter_out_dirs,$(call rec_find,$f,$2) $(call match_files,$f,$2)))

r_source_files = $(wildcard R/*.r)

vignette_files = $(call rec_find,vignettes,*)

rmd_files = $(wildcard vignettes/*.rmd)
knit_results = $(patsubst vignettes/%.rmd,doc/%.md,${rmd_files})

pkg_bundle_name := $(shell ${rscript} --vanilla -e 'cat(sprintf("%s.tar.gz\n", paste(read.dcf("DESCRIPTION")[1L, c("Package", "Version")], collapse = "_")))')

cran-tmpdir = tmp.cran

favicons_small = $(addprefix pkgdown/favicon/,$(addprefix favicon-,16x16.png 32x32.png))

favicons_large = $(addprefix pkgdown/favicon/,\
Expand Down Expand Up @@ -54,8 +63,15 @@ test-%: documentation
.PHONY: check
## Run R CMD check
check: documentation
mkdir -p check
${rscript} -e "devtools::check(check_dir = 'check')"
ret=0; \
for rule in scripts/check_rule_*; do \
if ! $$rule .; then ret=1; fi \
done; \
mkdir -p check; \
${rscript} -e "devtools::check(check_dir = 'check')"; \
check=$$?; \
let ret='ret | check'; \
exit $$ret

.PHONY: site
## Create package website
Expand All @@ -79,14 +95,16 @@ reference: documentation
# both `vignettes` and `knit_all` rules?
.PHONY: vignettes
## Compile all vignettes and other R Markdown articles
vignettes:
vignette: Meta/vignette.rds

Meta/vignette.rds: DESCRIPTION NAMESPACE ${r_source_files} ${vignette_files}
${rscript} -e "devtools::build_vignettes(dependencies = TRUE)"

.PHONY: knit_all
## Compile R markdown articles and move files to the documentation directory
knit_all: ${knit_results} | doc

doc/%.md: vignettes/%.rmd | doc
doc/%.md: vignettes/%.rmd DESCRIPTION NAMESPACE ${r_source_files} | doc
${rscript} -e "rmarkdown::render('$<', output_format = 'md_document', output_file = '${@F}', output_dir = '${@D}')"

.PHONY: documentation
Expand All @@ -98,15 +116,26 @@ NAMESPACE: ${r_source_files} DESCRIPTION
${rscript} -e "devtools::document()"

README.md: README.rmd DESCRIPTION
${rscript} -e "knitr::knit('$<')"
${rscript} -e "devtools::load_all(export_all = FALSE); knitr::knit('$<')"

.PHONY: build
## Build the package tar.gz bundle
build: ${pkg_bundle_name}

${pkg_bundle_name}: DESCRIPTION NAMESPACE ${r_source_files} ${rmd_files}
${pkg_bundle_name}: DESCRIPTION NAMESPACE ${r_source_files}
R CMD build .

.PHONY: build-cran
## Bundle the package with static vignette sources for submission to CRAN
build-cran:
mkdir ${cran-tmpdir} && \
git clone . ${cran-tmpdir} && \
${MAKE} -C ${cran-tmpdir} knit_all && \
scripts/precompile-vignettes ${cran-tmpdir} && \
${MAKE} -C ${cran-tmpdir} build && \
mv ${cran-tmpdir}/${pkg_bundle_name} . && \
rm -rf ${cran-tmpdir}

.PHONY: favicons
## Generate the documentation site favicons
favicons: ${favicons}
Expand All @@ -121,6 +150,11 @@ ${favicons_small}: man/figures/box.svg | pkgdown/favicon
${favicons_large}: man/figures/box.svg | pkgdown/favicon
$(call export-favicon,-51:0:711:760)

.PHONY: lint
## Link the package source
lint:
${rscript} -e "lintr::lint_package('.')"

doc pkgdown/favicon:
mkdir -p $@

Expand Down
34 changes: 13 additions & 21 deletions R/S3.r
Original file line number Diff line number Diff line change
Expand Up @@ -3,34 +3,23 @@
#' \code{register_S3_method} makes an S3 method for a given generic and class
#' known inside a module.
#'
#' @param name the name of the generic as a character string
#' @param class the class name
#' @param method the method to register
#' @param name the name of the generic as a character string.
#' @param class the class name.
#' @param method the method to register.
#' @return \code{register_S3_method} is called for its side-effect.
#'
#' @details Methods for generics defined in the same module do not need to be
#' registered explicitly, and indeed \emph{should not} be registered. However,
#' if the user wants to add a method for a known generic (defined outside the
#' module, e.g. \code{\link{print}}), then this needs to be made known
#' explicitly.
#'
#' See the vignette in \code{vignette('box', 'box')} for more information on
#' defining S3 methods inside modules.
#'
#' @note \strong{Do not} call \code{\link[base]{registerS3method}} inside a
#' module. Only use \code{register_S3_method}. This is important for the
#' module’s own book-keeping.
#' @examples
#' \dontrun{
#' # In module mymod/a:
#' print.my_class = function (x) {
#' message(sprintf('My class with field %s\n', x$field))
#' invisible(x)
#' }
#'
#' box::register_S3_method('print', 'my_class', print.my_class)
#'
#' # Globally:
#' box::use(mymod/a)
#' obj = structure(list(field = 42), class = 'my_class')
#' obj # calls `print.my_class`, with output "My class with field 42"
#' }
#' @export
register_S3_method = function (name, class, method) {
module = environment(method)
Expand All @@ -47,8 +36,10 @@ register_S3_method = function (name, class, method) {
#' point, calls \code{UseMethod}.
#'
#' \code{make_S3_methods_known} finds and registers S3 methods inside a module.
#' @param function_name function name as character string
#' @param envir the environment this function is invoked from
#' @param function_name function name as character string.
#' @param envir the environment this function is invoked from.
#' @return \code{is_S3_user_generic} returns \code{TRUE} if the specified
#' function is a user-defined S3 generic, \code{FALSE} otherwise.
#' @keywords internal
#' @name s3
is_S3_user_generic = function (function_name, envir = parent.frame()) {
Expand Down Expand Up @@ -104,7 +95,8 @@ make_S3_methods_known = function (module) {

#' Return a list of function names in an environment
#'
#' @param envir the environment to search in
#' @param envir the environment to search in.
#' @return \code{lsf} returns a vector of function names in the given environment.
#' @keywords internal
lsf = function (envir) {
# We cannot use `eapply` here since that will try to evaluate active
Expand Down
45 changes: 37 additions & 8 deletions R/box-package.r
Original file line number Diff line number Diff line change
Expand Up @@ -35,20 +35,49 @@
#' @docType package
'_PACKAGE'

.onAttach = function (libname, pkgname) {
called_from_devtools = function () {
if (isNamespaceLoaded('devtools')) {
is_devtools_ns = function (x) identical(x, getNamespace('devtools'))
called_from_devtools = length(Filter(is_devtools_ns, lapply(sys.frames(), topenv))) != 0L
if (called_from_devtools) return()
length(Filter(is_devtools_ns, lapply(sys.frames(), topenv))) != 0L
} else {
FALSE
}
}

called_from_pkgdown = function () {
if (isNamespaceLoaded('pkgdown')) {
is_pkgdown_ns = function (x) identical(x, getNamespace('pkgdown'))
called_from_pkgdown = length(Filter(is_pkgdown_ns, lapply(sys.frames(), topenv))) != 0L
if (called_from_pkgdown) return()
length(Filter(is_pkgdown_ns, lapply(sys.frames(), topenv))) != 0L
} else {
FALSE
}
if (Sys.getenv('R_INSTALL_PKG') != '') return()
if (Sys.getenv('_R_CHECK_PACKAGE_NAME_') != '') return()
if (Sys.getenv('R_TESTS', unset = '.') == '') return()
}

called_from_ci = function () {
any(Sys.getenv(c('R_INSTALL_PKG', '_R_CHECK_PACKAGE_NAME_', 'R_TESTS')) != '')
}

called_from_example = function () {
utils_ns = getNamespace('utils')
example = quote(example)
for (frame in seq_along(sys.nframe())) {
call = sys.call(frame)
if (identical(call[[1L]], example) && identical(topenv(sys.frame(frame)), utils_ns)) {
return(TRUE)
}
}
FALSE
}

.onAttach = function (libname, pkgname) {
# Do not permit attaching ‘box’, except during build/check/CI.
if (
called_from_devtools() ||
called_from_pkgdown() ||
called_from_ci() ||
# `utils::example` also attaches the package.
called_from_example()
) return()

template = paste0(
'The %s package is not supposed to be attached.\n\n',
Expand Down
11 changes: 10 additions & 1 deletion R/env.r
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
#' Module namespace handling
#'
#' \code{make_namespace} creates a new module namespace.
#' @param info the module info.
#' @return \code{make_namespace} returns the newly created module namespace for
#' the module described by \code{info}.
#' @details
#' The namespace contains a module’s content. This schema is very much like R
#' package organisation. A good resource for this is:
#' <http://obeautifulcode.com/R/How-R-Searches-And-Finds-Stuff/>
Expand Down Expand Up @@ -35,17 +40,21 @@ make_imports_env = function (info) {

#' \code{is_namespace} checks whether a given environment corresponds to a
#' module namespace.
#' @param env Environment that may be a module namespace.
#' @param env an environment that may be a module namespace.
#' @rdname namespace
is_namespace = function (env) {
exists('.__module__.', env, mode = 'environment', inherits = FALSE)
}

#' @param ns the module namespace environment.
#' @param which the key (as a length 1 character string) of the info to get/set.
#' @param default default value to use if the key is not set.
#' @rdname namespace
namespace_info = function (ns, which, default = NULL) {
get0(which, ns$.__module__., inherits = FALSE, ifnotfound = default)
}

#' @param value the value to assign to the specified key.
#' @rdname namespace
`namespace_info<-` = function (ns, which, value) {
assign(which, value, envir = ns$.__module__.)
Expand Down
9 changes: 8 additions & 1 deletion R/export.r
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,8 @@ block_name = function (block) {
#' Extract comment tags from Roxygen block comments
#'
#' @param exprs The unevaluated expressions to parse.
#' @return \code{parse_export_tags} returns a list of \code{roxy_block}s for all
#' exported declarations.
#' @note The following code performs the same function as roxygen2 with a custom
#' \code{@} tag roclet. Unfortunately roxygen2 itself pulls in many
#' dependencies, making it less suitable for an infrastructure package such as
Expand All @@ -143,6 +145,9 @@ parse_export_tags = function (info, exprs, mod_ns) {
#'
#' @param expr The unevaluated expression represented by the tag.
#' @param ref The code reference \code{srcref} represented by the tag.
#' @return \code{create_export_block} returns an object of type
#' \code{roxy_block} represents an exported declaration expression, along with
#' its source code location.
#' @note This could be represented much simpler but we keep compatibility with
#' roxygen2 — at least for the time being — to make integration with the
#' roxygen2 API easier, should it become necessary.
Expand Down Expand Up @@ -222,7 +227,9 @@ roxygen2_object = function (alias, value, type) {

#' Extend code regions to include leading comments and whitespace
#'
#' @param refs The code region \code{srcref}s to extend.
#' @param refs a list of the code region \code{srcref}s to extend.
#' @return \code{add_comment} returns a list of \code{srcref}s corresponding to
#' \code{srcref}, but extended to include the preceding comment block.
#' @keywords internal
add_comments = function (refs) {
block_end_lines = map_int(`[[`, refs, 3L)
Expand Down
11 changes: 5 additions & 6 deletions R/help.r
Original file line number Diff line number Diff line change
Expand Up @@ -52,13 +52,13 @@ patch_mod_doc = function (docs) {
#' in the format \code{module$function}.
#' @param help_type character string specifying the output format; currently,
#' only \code{'text'} is supported.
#' @return \code{help} is called for its side-effect when called directly from
#' the command prompt.
#' @details
#' See the vignette in \code{vignette('box', 'box')} for more information on
#' displaying help for modules.
#' @rdname help
#' @export
#' @examples
#' \dontrun{
#' box::use(my/mod)
#' box::help(mod$func)
#' }
help = function (topic, help_type = getOption('help_type', 'text')) {
topic = substitute(topic)
target = help_topic_target(parent.frame(), topic)
Expand Down Expand Up @@ -142,7 +142,6 @@ help = function (topic, help_type = getOption('help_type', 'text')) {
#'
#' @param caller the environment from which \code{help} was called.
#' @param topic the unevaluated expression passed to \code{help}.
#'
#' @return \code{help_topic_target} returns a list of two elements containing
#' the innermost module of the \code{help} call, as well as the name of the
#' object that’s the subject of the \code{help} call. For \code{help(a$b$c$d)},
Expand Down
2 changes: 2 additions & 0 deletions R/hooks.r
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ call_hook = function (ns, hook, ...) {
#' Modules can declare functions to be called when a module is first loaded.
#'
#' @param ns the module namespace environment
#' @return \code{.on_load} is called for its side-effect. Any return value is
#' ignored.
#'
#' @note The API for hook functions is still subject to change. In particular,
#' there might in the future be a way to subscribe to module events of other
Expand Down
7 changes: 5 additions & 2 deletions R/info.r
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,11 @@
#'
#' A \code{mod_info} represents an existing, installed module and its runtime
#' physical location (usually in the file system).
#' @param mod_spec a \code{mod_spec}
#' @param source_path character string full path to the physical module location
#' @param mod_spec a \code{mod_spec}.
#' @param source_path character string full path to the physical module
#' location.
#' @return \code{mod_info} and \code{pkg_info} return a structure representing
#' the module/package information for the given specification/source location.
#' @keywords internal
#' @name info
mod_info = function (spec, source_path) {
Expand Down
Loading

0 comments on commit 15ebcdb

Please sign in to comment.