R/create_package.R

Defines functions insert_file valid_package_name create_package

Documented in create_package

#' Create an R package according to INBO requirements
#'
#' Creates a package template in a new folder.
#' Use this function when you want to start a new package.
#' Please DO READ `vignette("getting_started")` before running this function.
#'
#' @template checklist_structure
#'
#' @param package Name of the new package.
#' @param path Where to create the package directory.
#' @param title A single sentence with the title of the package.
#' @param description A single paragraph describing the package.
#' @param maintainer When missing, the function interactively lets you add the
#' maintainer and other authors.
#' Otherwise it must be the output of [utils::person()].
#' @param language Language of the project in `xx-YY` format.
#' `xx` is the two letter code for the language.
#' `YY` is the two letter code for the language variant.
#' E.g. `en-GB` for British English, `en-US` for American English, `nl-BE` for
#' Belgian Dutch.
#' @param license What type of license should be used?
#' Choice between GPL-3 and MIT.
#' Default GPL-3.
#' @param keywords A vector of keywords.
#' @param communities An optional vector of Zenodo community id's.
#' @export
#' @importFrom assertthat assert_that is.string
#' @importFrom desc description
#' @importFrom fs dir_create dir_ls file_copy is_dir path
#' @importFrom gert git_add git_init
#' @importFrom tools toTitleCase
#' @importFrom utils installed.packages
#' @family setup
#' @examples
#' # maintainer in `utils::person()` format
#' maintainer <- person(
#'   given = "Thierry",
#'   family = "Onkelinx",
#'   role = c("aut", "cre"),
#'   email = "thierry.onkelinx@inbo.be",
#'   comment = c(ORCID = "0000-0001-8804-4216")
#' )
#'
#' # creating the package
#' path <- tempfile()
#' dir.create(path)
#' create_package(
#'   path = path, package = "packagename", title = "package title",
#'   description = "A short description.", maintainer = maintainer,
#'   language = "en-GB", license = "GPL-3", keywords = "keyword"
#' )
create_package <- function(
  package, path = ".", title, description, keywords, language = "en-GB",
  license = c("GPL-3", "MIT"), communities = character(0), maintainer
) {
  assert_that(
    length(find.package("roxygen2", quiet = TRUE)) > 0,
    msg =
      "Please install the `roxygen2` package. `install.packages(\"roxygen2\")`"
  )
  if (missing(maintainer)) {
    cat("Please select the maintainer")
    maintainer <- author2person(role = c("aut", "cre"))
    while (isTRUE(ask_yes_no("Add another author?", default = FALSE))) {
      maintainer <- c(maintainer, author2person())
    }
  }
  assert_that(inherits(maintainer, "person"))

  assert_that(is_dir(path), msg = sprintf("`%s` is not a directory", path))
  assert_that(is.string(package))
  assert_that(valid_package_name(package))
  assert_that(is.character(keywords), length(keywords) > 0)
  assert_that(is.character(communities))
  org <- read_organisation(path)
  maintainer <- c(maintainer, org$as_person)
  path <- path(path, package)
  assert_that(
    !is_dir(path) || length(dir_ls(path, recurse = TRUE)) == 0,
    msg = sprintf("`%s` is not an empty directory", path)
  )
  assert_that(is.string(title))
  validate_language(language)
  license <- match.arg(license)

  dir_create(path)
  repo <- git_init(path = path)
  dir_create(path(path, "R"))

  # add checklist.yml
  x <- checklist$new(x = path, package = TRUE, language = language)
  x$set_ignore(c(".github", "LICENSE.md"))
  write_checklist(x)
  git_add("checklist.yml", repo = repo)


  # create DESCRIPTION
  desc <- desc::description$new("!new")
  desc$set("Package", package)
  desc$set("Title", toTitleCase(title))
  desc$set_version("0.0.0")
  desc$set_authors(maintainer)
  desc$set("Description", description)
  desc$set("License", ifelse(license == "MIT", "MIT + file LICENSE", license))
  desc$set_urls(sprintf("https://github.com/%s/%s", org$get_github, package))
  desc$set(
    "BugReports",
    sprintf("https://github.com/%s/%s/issues", org$get_github, package)
  )
  if (length(communities)) {
    desc$set(
      "Config/checklist/communities", paste(communities, collapse = "; ")
    )
  }
  desc$set("Config/checklist/keywords", paste(keywords, collapse = "; "))
  desc$set("Encoding", "UTF-8")
  desc$set("Language", language)
  desc$set("Roxygen", "list(markdown = TRUE)")
  desc$set("RoxygenNote", installed.packages()["roxygen2", "Version"])
  desc$del("Maintainer")
  desc$write(path(path, "DESCRIPTION"))
  git_add("DESCRIPTION", repo = repo)

  # create NAMESPACE
  c("# Generated by roxygen2: do not edit by hand", "") |>
    writeLines(path(path, "NAMESPACE"))
  git_add("NAMESPACE", repo = repo)

  # create RStudio project
  insert_file(
    repo = repo, filename = "rproj.template", template = "package_template",
    target = path, new_name = paste0(package, ".Rproj")
  )

  # add .gitignore
  insert_file(
    repo = repo, filename = "gitignore", template = "generic_template",
    target = path, new_name = ".gitignore"
  )

  # add .Rbuildignore
  insert_file(
    repo = repo, filename = "rbuildignore", template = "package_template",
    target = path, new_name = ".Rbuildignore"
  )

  # add codecov.yml
  insert_file(
    repo = repo, filename = "codecov.yml", template = "package_template",
    target = path
  )

  # add NEWS.md
  paste(
    "# %s 0.0.0", "",
    "* Added a `NEWS.md` file to track changes to the package.",
    "* Add [`checklist`](https://inbo.github.io/checklist/) infrastructure.",
    sep = "\n"
  ) |>
    sprintf(package) |>
    writeLines(path(path, "NEWS.md"))
  git_add("NEWS.md", repo = repo)

  # add README.Rmd
  license_batch <- switch(
    license,
    "GPL-3" = "https://img.shields.io/badge/license-GPL--3-blue.svg?style=flat",
    "MIT" = "https://img.shields.io/badge/license-MIT-blue.svg?style=flat"
  )
  license_site <- switch(
    license,
    "GPL-3" = "https://www.gnu.org/licenses/gpl-3.0.html",
    "MIT" = "https://opensource.org/licenses/MIT"
  )
  path("package_template", "README.Rmd") |>
    system.file(package = "checklist") |>
    readLines() |>
    gsub(pattern = "\\{\\{\\{ Package \\}\\}\\}", replacement = package) |>
    gsub(
      pattern = "\\{\\{\\{ license batch \\}\\}\\}", replacement = license_batch
    ) |>
    gsub(
      pattern = "\\{\\{\\{ license site \\}\\}\\}", replacement = license_site
    ) |>
    writeLines(path(path, "README.Rmd"))
  git_add("README.Rmd", repo = repo)

  # add LICENSE.md
  set_license(x)
  git_add("LICENSE.md", repo = repo)

  # Add code of conduct
  target <- path(path, ".github")
  dir_create(target)
  insert_file(
    repo = repo, filename = "CODE_OF_CONDUCT.md",
    template = "generic_template", target = target
  )

  # Add contributing guidelines
  insert_file(
    repo = repo, filename = "CONTRIBUTING.md",
    template = "package_template", target = target
  )

  # Add GitHub actions
  target <- path(path, ".github", "workflows")
  dir_create(target)
  insert_file(
    repo = repo, filename = "check_on_branch.yml",
    template = "package_template", target = target
  )
  insert_file(
    repo = repo, filename = "check_on_main.yml",
    template = "package_template", target = target
  )
  insert_file(
    repo = repo, filename = "check_on_different_r_os.yml",
    template = "package_template", target = target
  )
  insert_file(
    repo = repo, filename = "release.yml",
    template = "package_template", target = target
  )

  # prepare pkgdown
  path("package_template", "_pkgdown.yml") |>
    system.file(package = "checklist") |>
    readLines() |>
    gsub(pattern = "\\{\\{\\{ Package \\}\\}\\}", replacement = package) |>
    writeLines(path(path, "_pkgdown.yml"))
  git_add("_pkgdown.yml", repo = repo)

  target <- path(path, "pkgdown")
  dir_create(target)
  insert_file(
    repo = repo, filename = "pkgdown.css", template = "package_template",
    target = target, new_name = "extra.css"
  )

  target <- path(path, "man", "figures")
  dir_create(target)
  insert_file(
    repo = repo, filename = "logo-en.png", template = "package_template",
    target = target
  )
  insert_file(
    repo = repo, filename = "background-pattern.png",
    template = "package_template", target = target
  )
  insert_file(
    repo = repo, filename = "flanders.woff2", template = "package_template",
    target = target
  )
  insert_file(
    repo = repo, filename = "flanders.woff", template = "package_template",
    target = target
  )

  message("package created at `", path, "`")

  if (
    !interactive() || !requireNamespace("rstudioapi", quietly = TRUE) ||
      !rstudioapi::isAvailable()
  ) {
    return(invisible(NULL))
  }
  rstudioapi::openProject(path, newSession = TRUE)
}

valid_package_name <- function(x) {
  grepl("^[a-zA-Z][a-zA-Z0-9.]+$", x) && !grepl("\\.$", x)
}

#' @importFrom assertthat on_failure<-
on_failure(valid_package_name) <- function(call, env) {
  paste(deparse(call$x), "is not a valid package name.")
}

#' @importFrom fs file_copy path
#' @importFrom gert git_add
insert_file <- function(repo, filename, template, target, new_name) {
  if (missing(new_name)) {
    new_name <- path(target, filename)
  } else {
    new_name <- path(target, new_name)
  }
  path(template, filename) |>
    system.file(package = "checklist") |>
    file_copy(new_name, overwrite = TRUE)
  if (is.null(repo)) {
    return(invisible(NULL))
  }
  git_add(new_name, force = TRUE, repo = repo)
  return(invisible(NULL))
}
inbo/checklist documentation built on June 15, 2025, 12:54 p.m.