# File src/library/utils/R/package.skeleton.R # Part of the R package, https://www.R-project.org # # Copyright (C) 1995-2017 The R Core Team # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # A copy of the GNU General Public License is available at # https://www.R-project.org/Licenses/ package.skeleton <- function(name = "anRpackage", list = character(), environment = .GlobalEnv, path = ".", force = FALSE, code_files = character(), encoding = "unknown") { if(!grepl(sprintf("^%s$", .standard_regexps()$valid_package_name), name)) stop("Malformed package name.") safe.dir.create <- function(path) { if(!dir.exists(path) && !dir.create(path)) stop(gettextf("cannot create directory '%s'", path), domain = NA) } if(!is.character(code_files)) stop("'code_files' must be a character vector") use_code_files <- length(code_files) > 0L envIsMissing <- missing(environment) # before R clobbers this information if(missing(list)) { if(use_code_files) { environment <- new.env(hash = TRUE, parent = globalenv()) methods::setPackageName(name, environment) for(cf in code_files) sys.source(cf, envir = environment) } ## all.names: crucial for metadata list <- ls(environment, all.names=TRUE) ## Exclude .Random.seed from .GlobalEnv if not asked for ## explicitly. if(identical(environment, .GlobalEnv)) list <- list[list != ".Random.seed"] } if(!is.character(list)) stop("'list' must be a character vector naming R objects") if(use_code_files || !envIsMissing) { classesList <- methods::getClasses(environment) classes0 <- .fixPackageFileNames(classesList) names(classes0) <- classesList methodsList <- methods::getGenerics(environment) methods0 <- .fixPackageFileNames(methodsList) names(methods0) <- methodsList } else { # nobody should specify classes or methods as object names! classesList <- methodsList <- character() } usingS4 <- length(classesList) > 0L || length(methodsList) > 0L ## ## This should no longer be necessary? ## ## ## we need to test in the C locale ## curLocale <- Sys.getlocale("LC_CTYPE") ## on.exit(Sys.setlocale("LC_CTYPE", curLocale), add = TRUE) ## if(Sys.setlocale("LC_CTYPE", "C") != "C") ## warning("cannot turn off locale-specific chars via LC_CTYPE", ## domain = NA) ## ## have <- vapply(list, exists, NA, envir = environment) if(any(!have)) warning(sprintf(ngettext(sum(!have), "object '%s' not found", "objects '%s' not found"), paste(sQuote(list[!have]), collapse=", ")), domain = NA) list <- list[have] if(!length(list)) stop("no R objects specified or available") message("Creating directories ...", domain = NA) ## Make the directories dir <- file.path(path, name) if(file.exists(dir) && !force) stop(gettextf("directory '%s' already exists", dir), domain = NA) safe.dir.create(dir) safe.dir.create(code_dir <- file.path(dir, "R")) safe.dir.create(docs_dir <- file.path(dir, "man")) safe.dir.create(data_dir <- file.path(dir, "data")) ## DESCRIPTION message("Creating DESCRIPTION ...", domain = NA) description <- file(file.path(dir, "DESCRIPTION"), "wt") cat("Package: ", name, "\n", "Type: Package\n", "Title: What the Package Does (Short Line)\n", "Version: 1.0\n", "Date: ", format(Sys.time(), format="%Y-%m-%d"), "\n", "Author: Who wrote it\n", "Maintainer: Who to complain to \n", "Description: More about what it does (maybe more than one line).\n", "License: What license is it under?\n", if(usingS4) "Imports: methods\n", if(nzchar(encoding) && encoding != "unknown") paste0("Encoding: ", encoding, "\n"), file = description, sep = "") close(description) ## NAMESPACE ## ## For the time being, we export all non-internal objects with names ## beginning with alpha ## All S4 methods and classes are exported. ## S3 methods will be exported if the function's name would be ## exported. ## message("Creating NAMESPACE ...", domain = NA) out <- file(file.path(dir, "NAMESPACE"), "wt") list0 <- list[grepl("^[[:alpha:]]", list)] if(length(list0)) writeLines(strwrap(sprintf("export(%s)", paste0("\"", list0, "\"", collapse = ", ")), exdent = 7L), out) if(length(methodsList)) { cat("exportMethods(\n ", file = out) cat(paste0('"', methodsList, '"', collapse = ",\n "), "\n)\n", file = out) } if(length(classesList)) { cat("exportClasses(\n ", file = out) cat(paste0('"', classesList, '"', collapse = ",\n "), "\n)\n", file = out) } close(out) ## Read-and-delete-me message("Creating Read-and-delete-me ...", domain = NA) out <- file(file.path(dir, "Read-and-delete-me"), "wt") msg <- c("* Edit the help file skeletons in 'man', possibly combining help files for multiple functions.", "* Edit the package 'DESCRIPTION'.", "* Edit the exports in 'NAMESPACE', and add necessary imports.", "* Put any C/C++/Fortran code in 'src'.", "* If you have compiled code, add a useDynLib() directive to 'NAMESPACE'.", "* Run R CMD build to build the package tarball.", "* Run R CMD check to check the package tarball.", "", "Read \"Writing R Extensions\" for more information.") writeLines(strwrap(msg, exdent = 2), out) close(out) internalObjInds <- grep("^\\.", list) internalObjs <- list[internalObjInds] if(length(internalObjInds)) list <- list[-internalObjInds] list0 <- .fixPackageFileNames(list) names(list0) <- list ## Dump the items in 'data' or 'R' if(!use_code_files) { message("Saving functions and data ...", domain = NA) if(length(internalObjInds)) dump(internalObjs, file = file.path(code_dir, sprintf("%s-internal.R", name)), envir = environment) for(item in list){ objItem <- get(item, envir = environment) if(is.function(objItem)) { if(isS4(objItem)) stop(gettextf("generic functions and other S4 objects (e.g., '%s') cannot be dumped; use the 'code_files' argument", item), domain = NA) dump(item, file = file.path(code_dir, sprintf("%s.R", list0[item])), envir = environment) } else # we cannot guarantee this is a valid file name try(save(list = item, envir = environment, file = file.path(data_dir, sprintf("%s.rda", item)))) } } else { message("Copying code files ...", domain = NA) file.copy(code_files, code_dir) ## Only "abc.R"-like files are really ok: R_files <- tools::list_files_with_type(code_dir, "code", full.names = FALSE, OS_subdirs = "") code_files <- basename(code_files) wrong <- code_files[is.na(match(code_files, R_files))] if(length(wrong)) { warning("Invalid file name(s) for R code in ", code_dir,":\n", strwrap(paste(sQuote(wrong), collapse = ", "), indent=2), "\n are now renamed to 'z.R'", domain = NA) file.rename(from = file.path(code_dir, wrong), to = file.path(code_dir, paste0("z", sub("(\\.[^.]*)?$", ".R", wrong)))) } } ## Make help file skeletons in 'man' message("Making help files ...", domain = NA) ## Suppress partially inappropriate messages from prompt(). yy <- try(suppressMessages({ promptPackage(name, filename = file.path(docs_dir, sprintf("%s-package.Rd", name)), lib.loc = path) sapply(list, function(item) { prompt(get(item, envir = environment), name = item, filename = file.path(docs_dir, sprintf("%s.Rd", list0[item]))) }) sapply(classesList, function(item) { methods::promptClass(item, filename = file.path(docs_dir, sprintf("%s-class.Rd", classes0[item])), where = environment) }) sapply(methodsList, function(item) { methods::promptMethods(item, filename = file.path(docs_dir, sprintf("%s-methods.Rd", methods0[item])), methods::findMethods(item, where = environment)) }) })) ## don't document generic functions from other packages for(item in methodsList) { if(exists(item, envir = environment, inherits = FALSE)) { ff <- get(item, envir = environment) if(methods::is(ff, "genericFunction") && !identical(ff@package, name)) # don't document file.remove(file.path(docs_dir, sprintf("%s.Rd", list0[item]))) } } if(inherits(yy, "try-error")) stop(yy) ## Now we may have created an empty data or R directory if(length(list.files(code_dir)) == 0L) unlink(code_dir, recursive = TRUE) if(length(list.files(data_dir)) == 0L) unlink(data_dir, recursive = TRUE) message("Done.", domain = NA) message(sprintf("Further steps are described in '%s'.", file.path(dir, "Read-and-delete-me")), domain = NA) } .fixPackageFileNames <- function(list) { ## Some object names may not be valid file names, especially ## replacement function names. And if we start changing them ## they may collide. ## ## If we use given code files, we could still check whether ## these file are valid across platforms ... ## list <- as.character(list) # remove S4 class if any, to add names() later if(length(list) == 0L) return(list) list0 <- gsub("[[:cntrl:]\"*/:<>?\\|]", "_", list) wrong <- grep("^(con|prn|aux|clock\\$|nul|lpt[1-3]|com[1-4])(\\..*|)$", list0) if(length(wrong)) list0[wrong] <- paste0("zz", list0[wrong]) ## using grep was wrong, as could give -integer(0) ok <- grepl("^[[:alnum:]]", list0) if(any(!ok)) list0[!ok] <- paste0("z", list0[!ok]) ## now on Mac/Windows lower/uppercase will collide too list1 <- tolower(list0) list2 <- make.unique(list1, sep = "_") changed <- (list2 != list1) list0[changed] <- list2[changed] list0 }