From 5c2b66dcc723f5a1206a45c5bfe46e65d3bc3970 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 15 May 2026 14:13:26 +0000 Subject: [PATCH 1/9] Initial plan From ca214618dad88f109fc429a7f54b0d9ff556b050 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 15 May 2026 14:23:55 +0000 Subject: [PATCH 2/9] Refactor generate_pkg to accept direct API inputs Agent-Logs-Url: https://github.com/api2r/beekeeper/sessions/f954404e-4a85-438b-8eb8-932b6d18002b Co-authored-by: jonthegeek <33983824+jonthegeek@users.noreply.github.com> --- NAMESPACE | 2 + R/aaa-shared_params.R | 12 ++++++ R/generate_pkg-setup.R | 20 +++++++-- R/generate_pkg.R | 39 +++++++++++++---- R/use_beekeeper.R | 6 +-- man/dot-shared-params.Rd | 18 ++++++++ man/generate_pkg.Rd | 23 ++++++++--- man/read_api_definition.Rd | 21 ++++++++++ man/read_config.Rd | 22 ++++++++++ man/use_beekeeper.Rd | 11 ++--- tests/testthat/_snaps/generate_pkg-setup.md | 2 +- tests/testthat/helper.R | 9 ++++ tests/testthat/test-generate_pkg-paths.R | 24 +++++------ tests/testthat/test-generate_pkg-prepare.R | 2 +- tests/testthat/test-generate_pkg-security.R | 2 +- tests/testthat/test-generate_pkg-setup.R | 11 +++-- tests/testthat/test-generate_pkg-shared.R | 6 +-- tests/testthat/test-generate_pkg.R | 46 ++++++++++----------- 18 files changed, 196 insertions(+), 80 deletions(-) create mode 100644 man/read_api_definition.Rd create mode 100644 man/read_config.Rd diff --git a/NAMESPACE b/NAMESPACE index 6b4ac9a..382b66d 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -1,6 +1,8 @@ # Generated by roxygen2: do not edit by hand export(generate_pkg) +export(read_api_definition) +export(read_config) export(use_beekeeper) if (getRversion() < "4.3.0") importFrom("S7", "@") importFrom(S7,class_any) diff --git a/R/aaa-shared_params.R b/R/aaa-shared_params.R index b578a6c..04d4908 100644 --- a/R/aaa-shared_params.R +++ b/R/aaa-shared_params.R @@ -3,7 +3,19 @@ #' These parameters are used in multiple functions. They are defined here to #' make them easier to import and to find. #' +#' @param api_abbr (`character(1)`) A short (about 2-5 letter) abbreviation for +#' the API, for use in function names and environment variables. +#' @param api_definition (`rapid::class_rapid`) The API definition to generate +#' package code from. +#' @param api_title (`character(1)`) The API title used in generated package +#' files. #' @param call (`environment`) The caller environment for error messages. +#' @param config_file (`character(1)` or `fs_path`) The path to a beekeeper yaml +#' config file. +#' @param pkg_dir (`character(1)` or `fs_path`) The directory containing package +#' files. +#' @param rapid_file (`character(1)` or `fs_path`) The path to the R API +#' definition (rapid) file. #' #' @name .shared-params #' @keywords internal diff --git a/R/generate_pkg-setup.R b/R/generate_pkg-setup.R index 0112b70..7416481 100644 --- a/R/generate_pkg-setup.R +++ b/R/generate_pkg-setup.R @@ -30,8 +30,14 @@ !is.null(root_file) } -.read_config <- function(config_file = "_beekeeper.yml") { - config <- yaml::read_yaml(config_file) +#' Read a beekeeper config file +#' +#' @inheritParams .shared-params +#' @returns A `list` of configuration information, with elements `api_title`, +#' `api_abbr`, `api_version`, `rapid_file`, and `updated_on`. +#' @export +read_config <- function(pkg_dir = ".", config_file = "_beekeeper.yml") { + config <- yaml::read_yaml(path(pkg_dir, config_file)) return(.stabilize_config(config)) } @@ -48,7 +54,15 @@ return(config) } -.read_api_definition <- function(pkg_dir, rapid_file) { +#' Read an API definition file +#' +#' @inheritParams .shared-params +#' @returns A [rapid::class_rapid()] with the definition of the API. +#' @export +read_api_definition <- function( + pkg_dir = ".", + rapid_file = "_beekeeper_rapid.rds" +) { readRDS( path(pkg_dir, rapid_file) ) diff --git a/R/generate_pkg.R b/R/generate_pkg.R index 28fdb6a..92f7900 100644 --- a/R/generate_pkg.R +++ b/R/generate_pkg.R @@ -4,27 +4,48 @@ #' config file (generated by [use_beekeeper()] or manually). The files enforce #' an opinionated framework for API packages. #' -#' @param config_file (`length-1 character` or `fs_path`) The path to a -#' beekeeper yaml file. All package files are created relative to this file. -#' @param pkg_dir (`length-1 character` or `fs_path`) The directory in which the -#' package files will be created. Defaults to the directory of the config -#' file. +#' @inheritParams .shared-params #' #' @return A character vector of paths to files that were added or updated, #' invisibly. #' @export generate_pkg <- function( + api_abbr = NULL, + api_definition = NULL, + api_title = NULL, config_file = "_beekeeper.yml", - pkg_dir = fs::path_dir(config_file) + pkg_dir = "." ) { # TODO: Confirm that they use github & everything is committed. Error or warn # if not, letting them know that this can be destructive. Skip this check in # tests. .assert_is_pkg(pkg_dir) - config <- .read_config(config_file) - api_definition <- .read_api_definition(pkg_dir, config$rapid_file) + if ( + is.null(api_abbr) || + is.null(api_definition) || + is.null(api_title) + ) { + config <- read_config(pkg_dir = pkg_dir, config_file = config_file) + } + if (is.null(api_abbr)) { + api_abbr <- config$api_abbr + } else { + api_abbr <- stbl::stabilize_character_scalar(api_abbr) + } + if (is.null(api_title)) { + api_title <- config$api_title + } else { + api_title <- stbl::stabilize_character_scalar(api_title) + } + if (is.null(api_definition)) { + api_definition <- read_api_definition( + pkg_dir = pkg_dir, + rapid_file = config$rapid_file + ) + } + config <- list(api_abbr = api_abbr, api_title = api_title) security_data <- .generate_security( - config$api_abbr, + api_abbr, api_definition@components@security_schemes ) security_arg_names <- security_data$security_arg_names %|0|% character() diff --git a/R/use_beekeeper.R b/R/use_beekeeper.R index 8d5c5c9..6755b9d 100644 --- a/R/use_beekeeper.R +++ b/R/use_beekeeper.R @@ -9,11 +9,7 @@ #' translatable to a [rapid::class_rapid()] object by [rapid::as_rapid()]. #' Usually this will be a url pointing to an OpenAPI document, or a list #' generated by reading such a document. -#' @param api_abbr A short (about 2-5 letter) abbreviation for the API, for use -#' in function names and environment variables. -#' @param config_file The path to which the configuration should be written. -#' @param rapid_file The path to which the R API definition (rapid) object -#' should be written. +#' @inheritParams .shared-params #' #' @return The path to the configuration file, invisibly. The config file is #' written as a side effect of this function. The rapid object is also diff --git a/man/dot-shared-params.Rd b/man/dot-shared-params.Rd index 381d147..5bf2b64 100644 --- a/man/dot-shared-params.Rd +++ b/man/dot-shared-params.Rd @@ -4,7 +4,25 @@ \alias{.shared-params} \title{Shared parameters} \arguments{ +\item{api_abbr}{(\code{character(1)}) A short (about 2-5 letter) abbreviation for +the API, for use in function names and environment variables.} + +\item{api_definition}{(\code{rapid::class_rapid}) The API definition to generate +package code from.} + +\item{api_title}{(\code{character(1)}) The API title used in generated package +files.} + \item{call}{(\code{environment}) The caller environment for error messages.} + +\item{config_file}{(\code{character(1)} or \code{fs_path}) The path to a beekeeper yaml +config file.} + +\item{pkg_dir}{(\code{character(1)} or \code{fs_path}) The directory containing package +files.} + +\item{rapid_file}{(\code{character(1)} or \code{fs_path}) The path to the R API +definition (rapid) file.} } \description{ These parameters are used in multiple functions. They are defined here to diff --git a/man/generate_pkg.Rd b/man/generate_pkg.Rd index d1c261c..295bae5 100644 --- a/man/generate_pkg.Rd +++ b/man/generate_pkg.Rd @@ -5,17 +5,28 @@ \title{Use a beekeeper config file to generate code} \usage{ generate_pkg( + api_abbr = NULL, + api_definition = NULL, + api_title = NULL, config_file = "_beekeeper.yml", - pkg_dir = fs::path_dir(config_file) + pkg_dir = "." ) } \arguments{ -\item{config_file}{(\verb{length-1 character} or \code{fs_path}) The path to a -beekeeper yaml file. All package files are created relative to this file.} +\item{api_abbr}{(\code{character(1)}) A short (about 2-5 letter) abbreviation for +the API, for use in function names and environment variables.} -\item{pkg_dir}{(\verb{length-1 character} or \code{fs_path}) The directory in which the -package files will be created. Defaults to the directory of the config -file.} +\item{api_definition}{(\code{rapid::class_rapid}) The API definition to generate +package code from.} + +\item{api_title}{(\code{character(1)}) The API title used in generated package +files.} + +\item{config_file}{(\code{character(1)} or \code{fs_path}) The path to a beekeeper yaml +config file.} + +\item{pkg_dir}{(\code{character(1)} or \code{fs_path}) The directory containing package +files.} } \value{ A character vector of paths to files that were added or updated, diff --git a/man/read_api_definition.Rd b/man/read_api_definition.Rd new file mode 100644 index 0000000..76e56fa --- /dev/null +++ b/man/read_api_definition.Rd @@ -0,0 +1,21 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/generate_pkg-setup.R +\name{read_api_definition} +\alias{read_api_definition} +\title{Read an API definition file} +\usage{ +read_api_definition(pkg_dir = ".", rapid_file = "_beekeeper_rapid.rds") +} +\arguments{ +\item{pkg_dir}{(\code{character(1)} or \code{fs_path}) The directory containing package +files.} + +\item{rapid_file}{(\code{character(1)} or \code{fs_path}) The path to the R API +definition (rapid) file.} +} +\value{ +A \code{\link[rapid:class_rapid]{rapid::class_rapid()}} with the definition of the API. +} +\description{ +Read an API definition file +} diff --git a/man/read_config.Rd b/man/read_config.Rd new file mode 100644 index 0000000..4238fb8 --- /dev/null +++ b/man/read_config.Rd @@ -0,0 +1,22 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/generate_pkg-setup.R +\name{read_config} +\alias{read_config} +\title{Read a beekeeper config file} +\usage{ +read_config(pkg_dir = ".", config_file = "_beekeeper.yml") +} +\arguments{ +\item{pkg_dir}{(\code{character(1)} or \code{fs_path}) The directory containing package +files.} + +\item{config_file}{(\code{character(1)} or \code{fs_path}) The path to a beekeeper yaml +config file.} +} +\value{ +A \code{list} of configuration information, with elements \code{api_title}, +\code{api_abbr}, \code{api_version}, \code{rapid_file}, and \code{updated_on}. +} +\description{ +Read a beekeeper config file +} diff --git a/man/use_beekeeper.Rd b/man/use_beekeeper.Rd index a094275..57461e5 100644 --- a/man/use_beekeeper.Rd +++ b/man/use_beekeeper.Rd @@ -18,15 +18,16 @@ translatable to a \code{\link[rapid:class_rapid]{rapid::class_rapid()}} object b Usually this will be a url pointing to an OpenAPI document, or a list generated by reading such a document.} -\item{api_abbr}{A short (about 2-5 letter) abbreviation for the API, for use -in function names and environment variables.} +\item{api_abbr}{(\code{character(1)}) A short (about 2-5 letter) abbreviation for +the API, for use in function names and environment variables.} \item{...}{These dots are for future extensions and must be empty.} -\item{config_file}{The path to which the configuration should be written.} +\item{config_file}{(\code{character(1)} or \code{fs_path}) The path to a beekeeper yaml +config file.} -\item{rapid_file}{The path to which the R API definition (rapid) object -should be written.} +\item{rapid_file}{(\code{character(1)} or \code{fs_path}) The path to the R API +definition (rapid) file.} } \value{ The path to the configuration file, invisibly. The config file is diff --git a/tests/testthat/_snaps/generate_pkg-setup.md b/tests/testthat/_snaps/generate_pkg-setup.md index ad9a2b7..d0318ea 100644 --- a/tests/testthat/_snaps/generate_pkg-setup.md +++ b/tests/testthat/_snaps/generate_pkg-setup.md @@ -7,7 +7,7 @@ ! Can't generate package files outside of a package. TMPDIR is not inside a package. -# .read_config() reads configs +# read_config() reads configs Code config diff --git a/tests/testthat/helper.R b/tests/testthat/helper.R index d9d338a..bba4bf5 100644 --- a/tests/testthat/helper.R +++ b/tests/testthat/helper.R @@ -103,3 +103,12 @@ make_writing_impl <- function(tmp) { out_path } } + +guru_config <- read_config(pkg_dir = test_path("_fixtures", "guru")) +guru_api_definition <- read_api_definition( + pkg_dir = test_path("_fixtures", "guru") +) +trello_config <- read_config(pkg_dir = test_path("_fixtures", "trello")) +trello_api_definition <- read_api_definition( + pkg_dir = test_path("_fixtures", "trello") +) diff --git a/tests/testthat/test-generate_pkg-paths.R b/tests/testthat/test-generate_pkg-paths.R index fd78ba8..2de841a 100644 --- a/tests/testthat/test-generate_pkg-paths.R +++ b/tests/testthat/test-generate_pkg-paths.R @@ -12,7 +12,7 @@ test_that(".generate_paths() returns empty character for empty paths (#65)", { test_that(".generate_paths() calls correct templates for guru (#65)", { # 1 tag, no security skip_on_cran() - config <- .read_config(test_path("_fixtures", "guru", "_beekeeper.yml")) + config <- read_config(pkg_dir = test_path("_fixtures", "guru")) api_definition <- readRDS(test_path( "_fixtures", "guru", @@ -79,7 +79,7 @@ test_that(".generate_paths() calls correct templates for guru (#65)", { test_that(".generate_paths() writes correct templates for guru (#65)", { # Visual confirmation that paths.R, test-paths.R, and setup.R render correctly skip_on_cran() - config <- .read_config(test_path("_fixtures", "guru", "_beekeeper.yml")) + config <- read_config(pkg_dir = test_path("_fixtures", "guru")) api_definition <- readRDS(test_path( "_fixtures", "guru", @@ -117,11 +117,10 @@ test_that(".generate_paths() writes correct templates for guru (#65)", { test_that(".generate_paths() calls correct templates for fec (#65)", { # 3 tags (audit, debts, legal), more complicated security skip_on_cran() - config <- .read_config(test_path( - "_fixtures", - "fec", - "fec_subset_beekeeper.yml" - )) + config <- read_config( + pkg_dir = test_path("_fixtures", "fec"), + config_file = "fec_subset_beekeeper.yml" + ) api_definition <- readRDS(test_path( "_fixtures", "fec", @@ -174,11 +173,10 @@ test_that(".generate_paths() calls correct templates for fec (#65)", { test_that(".generate_paths() writes correct paths.R for fec (#65)", { # Visual confirmation: 3 tags, complicated security skip_on_cran() - config <- .read_config(test_path( - "_fixtures", - "fec", - "fec_subset_beekeeper.yml" - )) + config <- read_config( + pkg_dir = test_path("_fixtures", "fec"), + config_file = "fec_subset_beekeeper.yml" + ) api_definition <- readRDS(test_path( "_fixtures", "fec", @@ -211,7 +209,7 @@ test_that(".generate_paths() writes correct paths.R for fec (#65)", { test_that(".generate_paths() writes correct paths.R for trello (#65)", { # Visual confirmation: more complicated security skip_on_cran() - config <- .read_config(test_path("_fixtures", "trello", "_beekeeper.yml")) + config <- read_config(pkg_dir = test_path("_fixtures", "trello")) api_definition <- readRDS(test_path( "_fixtures", "trello", diff --git a/tests/testthat/test-generate_pkg-prepare.R b/tests/testthat/test-generate_pkg-prepare.R index 123037a..40f188c 100644 --- a/tests/testthat/test-generate_pkg-prepare.R +++ b/tests/testthat/test-generate_pkg-prepare.R @@ -1,6 +1,6 @@ test_that(".generate_prepare() generates prepare file.", { skip_on_cran() - config <- .read_config(test_path("_fixtures", "guru", "_beekeeper.yml")) + config <- read_config(pkg_dir = test_path("_fixtures", "guru")) api_definition <- readRDS(test_path( "_fixtures", "guru", diff --git a/tests/testthat/test-generate_pkg-security.R b/tests/testthat/test-generate_pkg-security.R index e8f8566..ea49471 100644 --- a/tests/testthat/test-generate_pkg-security.R +++ b/tests/testthat/test-generate_pkg-security.R @@ -23,7 +23,7 @@ test_that("as_bk_data() returns empty list for empty api_key_security_scheme", { test_that(".generate_security() generates security file for trello", { skip_on_cran() - config <- .read_config(test_path("_fixtures", "trello", "_beekeeper.yml")) + config <- read_config(pkg_dir = test_path("_fixtures", "trello")) api_definition <- readRDS(test_path( "_fixtures", "trello", diff --git a/tests/testthat/test-generate_pkg-setup.R b/tests/testthat/test-generate_pkg-setup.R index c241281..d87634f 100644 --- a/tests/testthat/test-generate_pkg-setup.R +++ b/tests/testthat/test-generate_pkg-setup.R @@ -13,18 +13,17 @@ test_that(".assert_is_pkg() isn't obtrusive for packages", { }) }) -test_that(".read_config() reads configs", { - config <- .read_config(test_path("_fixtures", "guru", "_beekeeper.yml")) +test_that("read_config() reads configs", { + config <- read_config(pkg_dir = test_path("_fixtures", "guru")) expect_s3_class(config$updated_on, c("POSIXlt", "POSIXt")) expect_snapshot({ config }) }) -test_that(".read_api_definition() reads api_definitions", { - api_definition <- .read_api_definition( - test_path("_fixtures", "guru"), - "_beekeeper_rapid.rds" +test_that("read_api_definition() reads api_definitions", { + api_definition <- read_api_definition( + pkg_dir = test_path("_fixtures", "guru") ) expect_s7_class(api_definition, rapid::class_rapid) }) diff --git a/tests/testthat/test-generate_pkg-shared.R b/tests/testthat/test-generate_pkg-shared.R index d580bb8..249a204 100644 --- a/tests/testthat/test-generate_pkg-shared.R +++ b/tests/testthat/test-generate_pkg-shared.R @@ -31,11 +31,7 @@ test_that(".generate_shared_params() writes security params for API with securit "trello", "_beekeeper_rapid.rds" )) - trello_config <- .read_config(test_path( - "_fixtures", - "trello", - "_beekeeper.yml" - )) + trello_config <- read_config(pkg_dir = test_path("_fixtures", "trello")) security_data <- .generate_security( trello_config$api_abbr, trello_rapid@components@security_schemes diff --git a/tests/testthat/test-generate_pkg.R b/tests/testthat/test-generate_pkg.R index 1ab806c..1f58e80 100644 --- a/tests/testthat/test-generate_pkg.R +++ b/tests/testthat/test-generate_pkg.R @@ -1,17 +1,12 @@ test_that("generate_pkg() returns a vector of created files", { skip_on_cran() - config_text <- readLines(test_path("_fixtures", "guru", "_beekeeper.yml")) - api_definition <- readRDS(test_path( - "_fixtures", - "guru", - "_beekeeper_rapid.rds" - )) - - test_dir <- create_local_package() - writeLines(config_text, "_beekeeper.yml") - saveRDS(api_definition, "_beekeeper_rapid.rds") + create_local_package() - test_result <- generate_pkg() + test_result <- generate_pkg( + api_abbr = guru_config$api_abbr, + api_definition = guru_api_definition, + api_title = guru_config$api_title + ) test_result <- scrub_path(test_result) # 7 guru operations all in "apis" tag: 7 R files + 1 test file + setup expected_result <- c( @@ -39,16 +34,6 @@ test_that("generate_pkg() generates call function with API keys", { character() } ) - config_text <- readLines(test_path( - "_fixtures", - "trello", - "_beekeeper.yml" - )) - api_definition <- readRDS(test_path( - "_fixtures", - "trello", - "_beekeeper_rapid.rds" - )) prepare_expected <- readLines(test_path( "_fixtures", "trello", @@ -56,11 +41,22 @@ test_that("generate_pkg() generates call function with API keys", { )) create_local_package() - writeLines(config_text, "_beekeeper.yml") - saveRDS(api_definition, "_beekeeper_rapid.rds") - - generate_pkg() + generate_pkg( + api_abbr = trello_config$api_abbr, + api_definition = trello_api_definition, + api_title = trello_config$api_title + ) prepare_result <- scrub_testpkg(readLines("R/010-prepare.R")) expect_identical(prepare_result, prepare_expected) }) + +test_that("generate_pkg() can read values from config and rapid files", { + skip_on_cran() + config_text <- readLines(test_path("_fixtures", "guru", "_beekeeper.yml")) + create_local_package() + writeLines(config_text, "_beekeeper.yml") + saveRDS(guru_api_definition, "_beekeeper_rapid.rds") + + expect_no_error(generate_pkg()) +}) From c82aa667337c50a4a1347dc37d368660961feed9 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 15 May 2026 14:24:41 +0000 Subject: [PATCH 3/9] Clarify fallback test name Agent-Logs-Url: https://github.com/api2r/beekeeper/sessions/f954404e-4a85-438b-8eb8-932b6d18002b Co-authored-by: jonthegeek <33983824+jonthegeek@users.noreply.github.com> --- tests/testthat/test-generate_pkg.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/testthat/test-generate_pkg.R b/tests/testthat/test-generate_pkg.R index 1f58e80..5e493f1 100644 --- a/tests/testthat/test-generate_pkg.R +++ b/tests/testthat/test-generate_pkg.R @@ -51,7 +51,7 @@ test_that("generate_pkg() generates call function with API keys", { expect_identical(prepare_result, prepare_expected) }) -test_that("generate_pkg() can read values from config and rapid files", { +test_that("generate_pkg() falls back to config file when arguments are NULL", { skip_on_cran() config_text <- readLines(test_path("_fixtures", "guru", "_beekeeper.yml")) create_local_package() From bcd0c475b1cb2f630733e8a49aa0b4f14f3c1396 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 15 May 2026 14:36:49 +0000 Subject: [PATCH 4/9] Use preloaded fixture objects in generate_pkg tests Agent-Logs-Url: https://github.com/api2r/beekeeper/sessions/ef836e33-d386-4b3c-93c1-5ffc597ddf24 Co-authored-by: jonthegeek <33983824+jonthegeek@users.noreply.github.com> --- tests/testthat/helper.R | 8 ++++ tests/testthat/test-generate_pkg-paths.R | 52 +++++---------------- tests/testthat/test-generate_pkg-prepare.R | 8 +--- tests/testthat/test-generate_pkg-security.R | 15 ++---- tests/testthat/test-generate_pkg-shared.R | 8 +--- 5 files changed, 25 insertions(+), 66 deletions(-) diff --git a/tests/testthat/helper.R b/tests/testthat/helper.R index bba4bf5..fa71c1e 100644 --- a/tests/testthat/helper.R +++ b/tests/testthat/helper.R @@ -112,3 +112,11 @@ trello_config <- read_config(pkg_dir = test_path("_fixtures", "trello")) trello_api_definition <- read_api_definition( pkg_dir = test_path("_fixtures", "trello") ) +fec_config <- read_config( + pkg_dir = test_path("_fixtures", "fec"), + config_file = "fec_subset_beekeeper.yml" +) +fec_api_definition <- read_api_definition( + pkg_dir = test_path("_fixtures", "fec"), + rapid_file = "fec_subset_rapid.rds" +) diff --git a/tests/testthat/test-generate_pkg-paths.R b/tests/testthat/test-generate_pkg-paths.R index 2de841a..989f290 100644 --- a/tests/testthat/test-generate_pkg-paths.R +++ b/tests/testthat/test-generate_pkg-paths.R @@ -12,12 +12,8 @@ test_that(".generate_paths() returns empty character for empty paths (#65)", { test_that(".generate_paths() calls correct templates for guru (#65)", { # 1 tag, no security skip_on_cran() - config <- read_config(pkg_dir = test_path("_fixtures", "guru")) - api_definition <- readRDS(test_path( - "_fixtures", - "guru", - "_beekeeper_rapid.rds" - )) + config <- guru_config + api_definition <- guru_api_definition spy <- make_spy_impl() local_mocked_bindings(.bk_use_template_impl = spy$mock) @@ -79,12 +75,8 @@ test_that(".generate_paths() calls correct templates for guru (#65)", { test_that(".generate_paths() writes correct templates for guru (#65)", { # Visual confirmation that paths.R, test-paths.R, and setup.R render correctly skip_on_cran() - config <- read_config(pkg_dir = test_path("_fixtures", "guru")) - api_definition <- readRDS(test_path( - "_fixtures", - "guru", - "_beekeeper_rapid.rds" - )) + config <- guru_config + api_definition <- guru_api_definition expected_path_contents <- load_expected_files("guru", "/paths-.+\\.R$") expected_test_contents <- load_expected_files("guru", "/test-paths-.+\\.R$") expected_setup_content <- readLines(test_path("_fixtures", "guru", "setup.R")) @@ -117,15 +109,8 @@ test_that(".generate_paths() writes correct templates for guru (#65)", { test_that(".generate_paths() calls correct templates for fec (#65)", { # 3 tags (audit, debts, legal), more complicated security skip_on_cran() - config <- read_config( - pkg_dir = test_path("_fixtures", "fec"), - config_file = "fec_subset_beekeeper.yml" - ) - api_definition <- readRDS(test_path( - "_fixtures", - "fec", - "fec_subset_rapid.rds" - )) + config <- fec_config + api_definition <- fec_api_definition spy <- make_spy_impl() local_mocked_bindings(.bk_use_template_impl = spy$mock) @@ -173,15 +158,8 @@ test_that(".generate_paths() calls correct templates for fec (#65)", { test_that(".generate_paths() writes correct paths.R for fec (#65)", { # Visual confirmation: 3 tags, complicated security skip_on_cran() - config <- read_config( - pkg_dir = test_path("_fixtures", "fec"), - config_file = "fec_subset_beekeeper.yml" - ) - api_definition <- readRDS(test_path( - "_fixtures", - "fec", - "fec_subset_rapid.rds" - )) + config <- fec_config + api_definition <- fec_api_definition expected_file_content <- readLines( test_path("_fixtures", "fec", "paths-audit-get_names_audit_candidates.R") ) @@ -209,12 +187,8 @@ test_that(".generate_paths() writes correct paths.R for fec (#65)", { test_that(".generate_paths() writes correct paths.R for trello (#65)", { # Visual confirmation: more complicated security skip_on_cran() - config <- read_config(pkg_dir = test_path("_fixtures", "trello")) - api_definition <- readRDS(test_path( - "_fixtures", - "trello", - "_beekeeper_rapid.rds" - )) + config <- trello_config + api_definition <- trello_api_definition expected_file_content <- readLines( test_path("_fixtures", "trello", "paths-board-add_boards.R") ) @@ -270,11 +244,7 @@ test_that(".params_to_validations() only includes supported checks (#69)", { }) test_that(".paths_need_stbl() flags actionable validations (#69)", { - api_definition_true <- readRDS(test_path( - "_fixtures", - "guru", - "_beekeeper_rapid.rds" - )) + api_definition_true <- guru_api_definition expect_true( .paths_need_stbl( diff --git a/tests/testthat/test-generate_pkg-prepare.R b/tests/testthat/test-generate_pkg-prepare.R index 40f188c..5563c67 100644 --- a/tests/testthat/test-generate_pkg-prepare.R +++ b/tests/testthat/test-generate_pkg-prepare.R @@ -1,11 +1,7 @@ test_that(".generate_prepare() generates prepare file.", { skip_on_cran() - config <- read_config(pkg_dir = test_path("_fixtures", "guru")) - api_definition <- readRDS(test_path( - "_fixtures", - "guru", - "_beekeeper_rapid.rds" - )) + config <- guru_config + api_definition <- guru_api_definition prepare_expected <- readLines(test_path( "_fixtures", "guru", diff --git a/tests/testthat/test-generate_pkg-security.R b/tests/testthat/test-generate_pkg-security.R index ea49471..a58b593 100644 --- a/tests/testthat/test-generate_pkg-security.R +++ b/tests/testthat/test-generate_pkg-security.R @@ -4,12 +4,7 @@ test_that(".generate_security() returns empty list for no security", { }) test_that("as_bk_data() dispatches correctly for security_scheme_details", { - trello_rapid <- readRDS(test_path( - "_fixtures", - "trello", - "_beekeeper_rapid.rds" - )) - details <- trello_rapid@components@security_schemes@details + details <- trello_api_definition@components@security_schemes@details result <- as_bk_data(details) expect_length(result, 2L) expect_identical(result[[1]]$type, "api_key") @@ -23,12 +18,8 @@ test_that("as_bk_data() returns empty list for empty api_key_security_scheme", { test_that(".generate_security() generates security file for trello", { skip_on_cran() - config <- read_config(pkg_dir = test_path("_fixtures", "trello")) - api_definition <- readRDS(test_path( - "_fixtures", - "trello", - "_beekeeper_rapid.rds" - )) + config <- trello_config + api_definition <- trello_api_definition security_expected <- readLines(test_path("_fixtures", "trello", "020-auth.R")) tmp <- withr::local_tempdir() local_mocked_bindings(.bk_use_template_impl = make_writing_impl(tmp)) diff --git a/tests/testthat/test-generate_pkg-shared.R b/tests/testthat/test-generate_pkg-shared.R index 249a204..8f813d5 100644 --- a/tests/testthat/test-generate_pkg-shared.R +++ b/tests/testthat/test-generate_pkg-shared.R @@ -26,15 +26,9 @@ test_that(".generate_shared_params() writes security params for API with securit skip_on_cran() tmp <- withr::local_tempdir() local_mocked_bindings(.bk_use_template_impl = make_writing_impl(tmp)) - trello_rapid <- readRDS(test_path( - "_fixtures", - "trello", - "_beekeeper_rapid.rds" - )) - trello_config <- read_config(pkg_dir = test_path("_fixtures", "trello")) security_data <- .generate_security( trello_config$api_abbr, - trello_rapid@components@security_schemes + trello_api_definition@components@security_schemes ) shared_expected <- readLines(test_path("_fixtures", "trello", "000-shared.R")) .generate_shared_params(security_data) From 94a560e4eec5e4447859ea24910209552015f785 Mon Sep 17 00:00:00 2001 From: Jon Harmon Date: Sat, 16 May 2026 06:41:24 -0500 Subject: [PATCH 5/9] Use pre-read configs and definitions in more places. --- tests/testthat/test-use_beekeeper.R | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/tests/testthat/test-use_beekeeper.R b/tests/testthat/test-use_beekeeper.R index 5718e95..e0e9db5 100644 --- a/tests/testthat/test-use_beekeeper.R +++ b/tests/testthat/test-use_beekeeper.R @@ -4,25 +4,20 @@ test_that("config writes a yml", { invisible(TRUE) } ) - rapid_path <- test_path("_fixtures/guru/_beekeeper_rapid.rds") - guru_rapid <- readRDS(rapid_path) config_path <- withr::local_tempfile(fileext = ".yml") rapid_write_path <- withr::local_tempfile(fileext = ".rds") test_result <- use_beekeeper( - guru_rapid, + guru_api_definition, api_abbr = "guru", config_file = config_path, rapid_file = rapid_write_path ) expect_identical(test_result, config_path) reread_rapid <- readRDS(rapid_write_path) - expect_identical(guru_rapid, reread_rapid) + expect_identical(guru_api_definition, reread_rapid) test_result_file <- scrub_config(readLines(config_path)) expected_result_file <- scrub_config( readLines(test_path("_fixtures", "guru", "_beekeeper.yml")) ) - expect_identical( - test_result_file, - expected_result_file - ) + expect_identical(test_result_file, expected_result_file) }) From 5eb9ec7e64bf1432214743a09830c2360696ee7f Mon Sep 17 00:00:00 2001 From: Jon Harmon Date: Sat, 16 May 2026 07:22:38 -0500 Subject: [PATCH 6/9] Formatting, documentation, imports, and refactors --- NAMESPACE | 28 --------- R/as_bk_data.R | 9 +-- R/beekeeper-package.R | 36 ++---------- R/generate_pkg-paths.R | 31 +++++----- R/generate_pkg-security.R | 28 +++++---- R/generate_pkg-setup.R | 46 ++++++++++----- R/generate_pkg-template.R | 12 ++-- R/generate_pkg.R | 31 ++-------- R/use_beekeeper.R | 8 +-- R/utils.R | 64 +++++---------------- man/dot-is_pkg.Rd | 2 +- man/read_api_definition.Rd | 4 +- man/read_config.Rd | 20 ++++++- tests/testthat/helper.R | 14 ++--- tests/testthat/test-generate_pkg-paths.R | 12 ++-- tests/testthat/test-generate_pkg-security.R | 2 +- tests/testthat/test-generate_pkg-shared.R | 6 +- tests/testthat/test-use_beekeeper.R | 3 +- 18 files changed, 140 insertions(+), 216 deletions(-) diff --git a/NAMESPACE b/NAMESPACE index 382b66d..9b0680f 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -8,38 +8,10 @@ if (getRversion() < "4.3.0") importFrom("S7", "@") importFrom(S7,class_any) importFrom(S7,class_data.frame) importFrom(S7,class_list) -importFrom(cli,cli_abort) -importFrom(cli,cli_warn) -importFrom(fs,file_delete) -importFrom(fs,file_exists) -importFrom(fs,path) -importFrom(fs,path_dir) -importFrom(fs,path_rel) -importFrom(glue,glue) -importFrom(glue,glue_collapse) -importFrom(httptest2,use_httptest2) importFrom(nectar,req_prepare) -importFrom(purrr,discard) -importFrom(purrr,imap) -importFrom(purrr,map) -importFrom(purrr,map2) -importFrom(purrr,map_chr) -importFrom(purrr,pmap) -importFrom(rapid,as_rapid) importFrom(rapid,class_api_key_security_scheme) importFrom(rapid,class_paths) importFrom(rapid,class_security_scheme_details) importFrom(rapid,class_security_schemes) importFrom(rlang,.data) -importFrom(rlang,check_dots_empty) -importFrom(rlang,set_names) -importFrom(rlang,try_fetch) importFrom(testthat,test_that) -importFrom(usethis,proj_get) -importFrom(usethis,proj_path) -importFrom(usethis,use_build_ignore) -importFrom(usethis,use_directory) -importFrom(usethis,use_package) -importFrom(usethis,use_template) -importFrom(usethis,use_testthat) -importFrom(utils,capture.output) diff --git a/R/as_bk_data.R b/R/as_bk_data.R index b34a68d..3c42069 100644 --- a/R/as_bk_data.R +++ b/R/as_bk_data.R @@ -8,14 +8,9 @@ #' #' @return A list. #' @keywords internal -as_bk_data <- S7::new_generic( - "as_bk_data", - dispatch_args = "x" -) +as_bk_data <- S7::new_generic("as_bk_data", dispatch_args = "x") S7::method(as_bk_data, class_any) <- function(x) { - cli_warn( - "No method for as_bk_data() for class {.cls {class(x)}}." - ) + cli::cli_warn("No method for as_bk_data() for class {.cls {class(x)}}.") return(list()) } diff --git a/R/beekeeper-package.R b/R/beekeeper-package.R index a146a86..b29f37b 100644 --- a/R/beekeeper-package.R +++ b/R/beekeeper-package.R @@ -2,44 +2,18 @@ "_PACKAGE" ## usethis namespace: start -# Needed for installation: nectar (likely others, TBD) -#' @importFrom cli cli_abort -#' @importFrom cli cli_warn -#' @importFrom fs file_delete -#' @importFrom fs file_exists -#' @importFrom fs path -#' @importFrom fs path_dir -#' @importFrom fs path_rel -#' @importFrom glue glue -#' @importFrom glue glue_collapse -#' @importFrom httptest2 use_httptest2 +## Force installation #' @importFrom nectar req_prepare -#' @importFrom purrr discard -#' @importFrom purrr imap -#' @importFrom purrr map -#' @importFrom purrr map_chr -#' @importFrom purrr map2 -#' @importFrom purrr pmap -#' @importFrom rapid as_rapid +#' @importFrom testthat test_that +## S7 classes #' @importFrom rapid class_api_key_security_scheme #' @importFrom rapid class_paths #' @importFrom rapid class_security_scheme_details #' @importFrom rapid class_security_schemes -#' @importFrom rlang .data -#' @importFrom rlang check_dots_empty -#' @importFrom rlang set_names -#' @importFrom rlang try_fetch #' @importFrom S7 class_any #' @importFrom S7 class_data.frame #' @importFrom S7 class_list -#' @importFrom testthat test_that -#' @importFrom usethis proj_get -#' @importFrom usethis proj_path -#' @importFrom usethis use_build_ignore -#' @importFrom usethis use_directory -#' @importFrom usethis use_package -#' @importFrom usethis use_template -#' @importFrom usethis use_testthat -#' @importFrom utils capture.output +## Use as pronoun +#' @importFrom rlang .data ## usethis namespace: end NULL diff --git a/R/generate_pkg-paths.R b/R/generate_pkg-paths.R index 9e683ea..e5f041b 100644 --- a/R/generate_pkg-paths.R +++ b/R/generate_pkg-paths.R @@ -69,14 +69,17 @@ S7::method(as_bk_data, class_paths) <- function(x) { } .paths_fill_operation_id <- function(operation_id, endpoint, method) { - .coalesce(.to_snake(operation_id), glue("{method}_{.to_snake(endpoint)}")) + .coalesce( + .to_snake(operation_id), + glue::glue("{method}_{.to_snake(endpoint)}") + ) } .paths_fill_summary <- function(summary, endpoint, method) { endpoint_spaced <- stringr::str_replace_all(.to_snake(endpoint), "_", " ") .coalesce( stringr::str_squish(summary), - stringr::str_to_sentence(glue("{method} {endpoint_spaced}")) + stringr::str_to_sentence(glue::glue("{method} {endpoint_spaced}")) ) } @@ -209,15 +212,15 @@ S7::method(as_bk_data, class_paths) <- function(x) { .path_as_arg <- function(path, params_df) { if (!nrow(params_df) || !any(params_df$`in` == "path")) { - return(glue('"{path}"')) + return(glue::glue('"{path}"')) } params_path <- params_df$name[params_df$`in` == "path"] params <- .collapse_comma_self_equal(params_path) - return(glue('c("{path}", {params})')) + return(glue::glue('c("{path}", {params})')) } .collapse_comma_self_equal <- function(x) { - .collapse_comma(glue("{x} = {x}")) + .collapse_comma(glue::glue("{x} = {x}")) } # generate files --------------------------------------------------------------- @@ -231,7 +234,7 @@ S7::method(as_bk_data, class_paths) <- function(x) { security_arg_names <- security_data$security_arg_names %|0|% character() # Prep each operation: remove security args, compile args strings - prepped <- imap(paths_by_operation, function(op, op_id) { + prepped <- purrr::imap(paths_by_operation, function(op, op_id) { params <- .remove_security_args(op$params, security_arg_names) params_query <- .prep_param_args(op$params_query_raw, security_arg_names) params_header <- .prep_param_args(op$params_header_raw, security_arg_names) @@ -255,13 +258,13 @@ S7::method(as_bk_data, class_paths) <- function(x) { }) # One R file per operation - r_files <- unname(unlist(imap(prepped, function(op, op_id) { + r_files <- unname(unlist(purrr::imap(prepped, function(op, op_id) { .generate_paths_file(op, op_id, api_abbr, security_data) }))) # One test file per tag (operations grouped by tag, preserving encounter # order) - tags <- map_chr(prepped, "tag") + tags <- purrr::map_chr(prepped, "tag") unique_tags <- unique(tags) test_files <- unname(unlist(lapply(unique_tags, function(tag_name) { tag_ops <- prepped[tags == tag_name] @@ -272,15 +275,15 @@ S7::method(as_bk_data, class_paths) <- function(x) { } .params_to_args <- function(params) { - .collapse_comma(map_chr(params, "name")) %|"|% character() + .collapse_comma(purrr::map_chr(params, "name")) %|"|% character() } .params_to_named_args <- function(params) { - .collapse_comma_self_equal(map_chr(params, "name")) %|"|% character() + .collapse_comma_self_equal(purrr::map_chr(params, "name")) %|"|% character() } .remove_security_args <- function(params, security_args) { - discard( + purrr::discard( params, function(param) { param$name %in% security_args @@ -339,12 +342,12 @@ S7::method(as_bk_data, class_paths) <- function(x) { pagination_fn = "" ) ), - target = glue("paths-{path_operation$tag}-{operation_id}.R") + target = glue::glue("paths-{path_operation$tag}-{operation_id}.R") ) } .generate_paths_test_file <- function(tag_operations, tag_name, api_abbr) { - paths_list <- unname(imap(tag_operations, function(op, op_id) { + paths_list <- unname(purrr::imap(tag_operations, function(op, op_id) { list( operation_id = op_id, test_args = op$test_args %|0|% "" @@ -358,6 +361,6 @@ S7::method(as_bk_data, class_paths) <- function(x) { api_abbr = api_abbr ), dir = "tests/testthat", - target = glue("test-paths-{tag_name}.R") + target = glue::glue("test-paths-{tag_name}.R") ) } diff --git a/R/generate_pkg-security.R b/R/generate_pkg-security.R index 765b0b7..bb461b6 100644 --- a/R/generate_pkg-security.R +++ b/R/generate_pkg-security.R @@ -14,9 +14,9 @@ } .generate_security_signature <- function(security_arg_names, api_abbr) { - env_vars <- toupper(glue("{api_abbr}_{security_arg_names}")) + env_vars <- toupper(glue::glue("{api_abbr}_{security_arg_names}")) return( - .collapse_comma_newline(glue( + .collapse_comma_newline(glue::glue( "{security_arg_names} = Sys.getenv(\"{env_vars}\")" )) ) @@ -31,7 +31,7 @@ S7::method(as_bk_data, class_security_schemes) <- function(x) { } .security_schemes_collect <- function(x) { - pmap( + purrr::pmap( list( x@name, x@details, @@ -87,30 +87,28 @@ S7::method(as_bk_data, class_security_schemes) <- function(x) { } .security_args_compile <- function(security_schemes) { - security_args <- sort(unique(map_chr(security_schemes, "arg_name"))) + security_args <- sort(unique(purrr::map_chr(security_schemes, "arg_name"))) return(list( security_arg_names = security_args, - security_arg_list = .collapse_comma( - glue("{security_args} = {security_args}") - ), + security_arg_list = .collapse_comma(glue::glue( + "{security_args} = {security_args}" + )), security_arg_helps = .generate_security_arg_help( security_schemes, security_args ), - security_arg_nulls = .collapse_comma( - glue("{security_args} = NULL") - ) + security_arg_nulls = .collapse_comma(glue::glue("{security_args} = NULL")) )) } .generate_security_arg_help <- function(security_schemes, security_args) { - security_arg_description <- set_names( - map_chr(security_schemes, "description"), - map_chr(security_schemes, "arg_name") + security_arg_description <- rlang::set_names( + purrr::map_chr(security_schemes, "description"), + purrr::map_chr(security_schemes, "arg_name") ) security_arg_description <- unname(security_arg_description[security_args]) return( - map2( + purrr::map2( security_arg_description, security_args, .security_arg_description_clean @@ -123,7 +121,7 @@ S7::method(as_bk_data, class_security_schemes) <- function(x) { } S7::method(as_bk_data, class_security_scheme_details) <- function(x) { - map(x, as_bk_data) + purrr::map(x, as_bk_data) } S7::method(as_bk_data, class_api_key_security_scheme) <- function(x) { diff --git a/R/generate_pkg-setup.R b/R/generate_pkg-setup.R index 7416481..d93bded 100644 --- a/R/generate_pkg-setup.R +++ b/R/generate_pkg-setup.R @@ -8,7 +8,7 @@ if (.is_pkg(base_path)) { return(invisible(NULL)) } - cli_abort(c( + cli::cli_abort(c( "Can't generate package files outside of a package.", x = "{.path {base_path}} is not inside a package." )) @@ -22,8 +22,8 @@ #' #' @return `TRUE` if the project is a package, `FALSE` if not. #' @keywords internal -.is_pkg <- function(base_path = proj_get()) { - root_file <- try_fetch( +.is_pkg <- function(base_path = usethis::proj_get()) { + root_file <- rlang::try_fetch( rprojroot::find_package_root_file(path = base_path), error = function(cnd) NULL ) @@ -32,12 +32,30 @@ #' Read a beekeeper config file #' +#' Reads a YAML file (default name `_beekeeper.yml`) with configuration +#' information about the API and the package. This file is generated by +#' [use_beekeeper()], or can be created manually. +#' +#' The configuration information must contain these fields: +#' +#' - `api_title` (`character(1)`): The title of the API, used in documentation +#' and messages. +#' - `api_abbr` (`character(1)`): An abbreviation for the API, used in function +#' names and other identifiers. +#' - `api_version` (`character(1)`): The version of the API. +#' - `rapid_file` (`character(1)`): The name of the file (relative to the +#' package root) where the API definition is stored as an RDS file. By default, +#' this is `_beekeeper_rapid.rds`, and is generated by [use_beekeeper()] based +#' on an OpenAPI definition file. +#' - `updated_on` (`character(1)`): A timestamp of when the API definition was +#' last updated, in the format "YYYY-MM-DD HH:MM:SS" (UTC). +#' #' @inheritParams .shared-params #' @returns A `list` of configuration information, with elements `api_title`, #' `api_abbr`, `api_version`, `rapid_file`, and `updated_on`. #' @export read_config <- function(pkg_dir = ".", config_file = "_beekeeper.yml") { - config <- yaml::read_yaml(path(pkg_dir, config_file)) + config <- yaml::read_yaml(fs::path(pkg_dir, config_file)) return(.stabilize_config(config)) } @@ -56,6 +74,10 @@ read_config <- function(pkg_dir = ".", config_file = "_beekeeper.yml") { #' Read an API definition file #' +#' Reads an RDS file (default name `_beekeeper_rapid.rds`) with the API +#' definition stored as a [rapid::class_rapid()] object. This file is generated +#' by [use_beekeeper()] based on an OpenAPI definition file. +#' #' @inheritParams .shared-params #' @returns A [rapid::class_rapid()] with the definition of the API. #' @export @@ -63,21 +85,19 @@ read_api_definition <- function( pkg_dir = ".", rapid_file = "_beekeeper_rapid.rds" ) { - readRDS( - path(pkg_dir, rapid_file) - ) + readRDS(fs::path(pkg_dir, rapid_file)) } .setup_r <- function(pkg_dir, include_stbl = FALSE) { if (as.character(pkg_dir) != ".") { usethis::local_project(pkg_dir, quiet = TRUE) # nocov } - use_directory("R") - use_testthat() - purrr::quietly(use_httptest2)() - use_package("nectar") + usethis::use_directory("R") + usethis::use_testthat() + purrr::quietly(httptest2::use_httptest2)() + usethis::use_package("nectar") if (include_stbl) { - use_package("stbl") + usethis::use_package("stbl") } - use_package("beekeeper", type = "Suggests") + usethis::use_package("beekeeper", type = "Suggests") } diff --git a/R/generate_pkg-template.R b/R/generate_pkg-template.R index 555355d..585db0a 100644 --- a/R/generate_pkg-template.R +++ b/R/generate_pkg-template.R @@ -15,20 +15,20 @@ target = template, dir = c("R", "tests/testthat") ) { - check_dots_empty() + rlang::check_dots_empty() dir <- match.arg(dir) target <- .bk_use_template_impl(template, data, target, dir) return(invisible(target)) } .bk_use_template_impl <- function(template, data, target, dir) { - target <- proj_path(dir, target) - save_as <- path_rel(target, proj_path()) - if (file_exists(target)) { + target <- usethis::proj_path(dir, target) + save_as <- fs::path_rel(target, usethis::proj_path()) + if (fs::file_exists(target)) { # TODO: Intelligently prompt about this. - file_delete(target) + fs::file_delete(target) } - use_template( + usethis::use_template( template = template, save_as = save_as, data = data, diff --git a/R/generate_pkg.R b/R/generate_pkg.R index 92f7900..d758e6d 100644 --- a/R/generate_pkg.R +++ b/R/generate_pkg.R @@ -20,29 +20,13 @@ generate_pkg <- function( # if not, letting them know that this can be destructive. Skip this check in # tests. .assert_is_pkg(pkg_dir) - if ( - is.null(api_abbr) || - is.null(api_definition) || - is.null(api_title) - ) { + if (purrr::some(list(api_abbr, api_definition, api_title), is.null)) { config <- read_config(pkg_dir = pkg_dir, config_file = config_file) } - if (is.null(api_abbr)) { - api_abbr <- config$api_abbr - } else { - api_abbr <- stbl::stabilize_character_scalar(api_abbr) - } - if (is.null(api_title)) { - api_title <- config$api_title - } else { - api_title <- stbl::stabilize_character_scalar(api_title) - } - if (is.null(api_definition)) { - api_definition <- read_api_definition( - pkg_dir = pkg_dir, - rapid_file = config$rapid_file - ) - } + api_abbr <- stbl::stabilize_character_scalar(api_abbr %||% config$api_abbr) + api_title <- stbl::stabilize_character_scalar(api_title %||% config$api_title) + api_definition <- api_definition %||% + read_api_definition(pkg_dir, config$rapid_file) config <- list(api_abbr = api_abbr, api_title = api_title) security_data <- .generate_security( api_abbr, @@ -51,10 +35,7 @@ generate_pkg <- function( security_arg_names <- security_data$security_arg_names %|0|% character() .setup_r( pkg_dir, - include_stbl = .paths_need_stbl( - api_definition@paths, - security_arg_names - ) + include_stbl = .paths_need_stbl(api_definition@paths, security_arg_names) ) touched_files <- .generate_pkg_impl(config, api_definition, security_data) return(invisible(touched_files)) diff --git a/R/use_beekeeper.R b/R/use_beekeeper.R index 6755b9d..5747522 100644 --- a/R/use_beekeeper.R +++ b/R/use_beekeeper.R @@ -22,7 +22,7 @@ use_beekeeper <- function( config_file = "_beekeeper.yml", rapid_file = "_beekeeper_rapid.rds" ) { - x <- as_rapid(x) + x <- rapid::as_rapid(x) rapid_file <- .write_rapid(x, rapid_file) config_file <- .write_config(x, api_abbr, rapid_file, config_file) @@ -32,7 +32,7 @@ use_beekeeper <- function( .write_rapid <- function(x, rapid_file) { rapid_file <- stbl::stabilize_character_scalar(rapid_file) saveRDS(x, rapid_file) - use_build_ignore(rapid_file) + usethis::use_build_ignore(rapid_file) return(rapid_file) } @@ -44,11 +44,11 @@ use_beekeeper <- function( api_title = x@info@title, api_abbr = stbl::stabilize_character_scalar(api_abbr), api_version = x@info@version, - rapid_file = path_rel(rapid_file, path_dir(config_file)), + rapid_file = fs::path_rel(rapid_file, fs::path_dir(config_file)), updated_on = as.character(update_time) ), file = config_file ) - use_build_ignore(config_file) + usethis::use_build_ignore(config_file) return(config_file) } diff --git a/R/utils.R b/R/utils.R index 87a7cd2..7d20f57 100644 --- a/R/utils.R +++ b/R/utils.R @@ -1,69 +1,33 @@ -`%|0|%` <- function(x, y) { - if (!length(x)) { - y - } else { - x - } -} +`%||%` <- function(x, y) if (is.null(x)) y else x -`%|"|%` <- function(x, y) { - if (!nzchar(x)) { - y - } else { - x - } -} +`%|0|%` <- function(x, y) if (!length(x)) y else x -.coalesce <- function(x, y) { - ifelse(is.na(x), y, x) -} +`%|"|%` <- function(x, y) if (!nzchar(x)) y else x -.collapse_comma <- function(x) { - glue_collapse(x, sep = ", ") -} +.coalesce <- function(x, y) ifelse(is.na(x), y, x) -.collapse_comma_newline <- function(x) { - glue_collapse(x, sep = ",\n") -} +.collapse_comma <- function(x) glue::glue_collapse(x, sep = ", ") + +.collapse_comma_newline <- function(x) glue::glue_collapse(x, sep = ",\n") .collapse_quote_comma <- function(x) { stringr::str_flatten_comma(paste0('"', x, '"')) } .paste0_if <- function(original, test, addition) { - ifelse( - test, - paste0(original, addition), - original - ) + ifelse(test, paste0(original, addition), original) } .glue_pipe_brace <- function(..., .envir = rlang::caller_env()) { - glue::glue( - ..., - .open = "|{", - .close = "}|", - .envir = .envir - ) + glue::glue(..., .open = "|{", .close = "}|", .envir = .envir) } -.to_snake <- function(x) { - snakecase::to_snake_case(x, parsing_option = 3) -} +.to_snake <- function(x) snakecase::to_snake_case(x, parsing_option = 3) -.flatten_df <- S7::new_generic( - ".flatten_df", - dispatch_args = "x" -) +.flatten_df <- S7::new_generic(".flatten_df", dispatch_args = "x") -S7::method(.flatten_df, class_data.frame) <- function(x) { - return(x) -} +S7::method(.flatten_df, class_data.frame) <- function(x) x -S7::method(.flatten_df, class_list) <- function(x) { - return(purrr::list_rbind(x)) -} +S7::method(.flatten_df, class_list) <- function(x) purrr::list_rbind(x) -S7::method(.flatten_df, NULL) <- function(x) { - return(data.frame()) -} +S7::method(.flatten_df, NULL) <- function(x) data.frame() diff --git a/man/dot-is_pkg.Rd b/man/dot-is_pkg.Rd index ae0dce2..c9eaccc 100644 --- a/man/dot-is_pkg.Rd +++ b/man/dot-is_pkg.Rd @@ -4,7 +4,7 @@ \alias{.is_pkg} \title{Check whether we're in a package} \usage{ -.is_pkg(base_path = proj_get()) +.is_pkg(base_path = usethis::proj_get()) } \arguments{ \item{base_path}{The root URL of the current project.} diff --git a/man/read_api_definition.Rd b/man/read_api_definition.Rd index 76e56fa..aa4ddf0 100644 --- a/man/read_api_definition.Rd +++ b/man/read_api_definition.Rd @@ -17,5 +17,7 @@ definition (rapid) file.} A \code{\link[rapid:class_rapid]{rapid::class_rapid()}} with the definition of the API. } \description{ -Read an API definition file +Reads an RDS file (default name \verb{_beekeeper_rapid.rds}) with the API +definition stored as a \code{\link[rapid:class_rapid]{rapid::class_rapid()}} object. This file is generated +by \code{\link[=use_beekeeper]{use_beekeeper()}} based on an OpenAPI definition file. } diff --git a/man/read_config.Rd b/man/read_config.Rd index 4238fb8..1360857 100644 --- a/man/read_config.Rd +++ b/man/read_config.Rd @@ -18,5 +18,23 @@ A \code{list} of configuration information, with elements \code{api_title}, \code{api_abbr}, \code{api_version}, \code{rapid_file}, and \code{updated_on}. } \description{ -Read a beekeeper config file +Reads a YAML file (default name \verb{_beekeeper.yml}) with configuration +information about the API and the package. This file is generated by +\code{\link[=use_beekeeper]{use_beekeeper()}}, or can be created manually. +} +\details{ +The configuration information must contain these fields: +\itemize{ +\item \code{api_title} (\code{character(1)}): The title of the API, used in documentation +and messages. +\item \code{api_abbr} (\code{character(1)}): An abbreviation for the API, used in function +names and other identifiers. +\item \code{api_version} (\code{character(1)}): The version of the API. +\item \code{rapid_file} (\code{character(1)}): The name of the file (relative to the +package root) where the API definition is stored as an RDS file. By default, +this is \verb{_beekeeper_rapid.rds}, and is generated by \code{\link[=use_beekeeper]{use_beekeeper()}} based +on an OpenAPI definition file. +\item \code{updated_on} (\code{character(1)}): A timestamp of when the API definition was +last updated, in the format "YYYY-MM-DD HH:MM:SS" (UTC). +} } diff --git a/tests/testthat/helper.R b/tests/testthat/helper.R index fa71c1e..60bc6d2 100644 --- a/tests/testthat/helper.R +++ b/tests/testthat/helper.R @@ -51,12 +51,8 @@ scrub_tempdir <- function(input) { scrub_path <- function(input, keep_dirs = c("R", "tests")) { dirs_string <- paste0(keep_dirs, collapse = "|") - search <- glue("^.*(/({dirs_string})/)") - stringr::str_replace( - input, - search, - "\\1" - ) + search <- glue::glue("^.*(/({dirs_string})/)") + stringr::str_replace(input, search, "\\1") } # Find all fixture files matching a regexp and read their contents. @@ -81,7 +77,7 @@ make_spy_impl <- function() { target = target, dir = dir ) - file.path(dir, target) + fs::path(dir, target) }, calls = function() calls ) @@ -96,9 +92,9 @@ make_writing_impl <- function(tmp) { readLines(template_path, warn = FALSE), data ) - out_dir <- file.path(tmp, dir) + out_dir <- fs::path(tmp, dir) fs::dir_create(out_dir) - out_path <- file.path(out_dir, target) + out_path <- fs::path(out_dir, target) writeLines(strsplit(rendered, "\n", fixed = TRUE)[[1]], out_path) out_path } diff --git a/tests/testthat/test-generate_pkg-paths.R b/tests/testthat/test-generate_pkg-paths.R index 989f290..0757ed7 100644 --- a/tests/testthat/test-generate_pkg-paths.R +++ b/tests/testthat/test-generate_pkg-paths.R @@ -92,16 +92,16 @@ test_that(".generate_paths() writes correct templates for guru (#65)", { ) purrr::iwalk(expected_path_contents, \(expected, name) { - expect_identical(readLines(file.path(tmp, "R", name)), expected) + expect_identical(readLines(fs::path(tmp, "R", name)), expected) }) purrr::iwalk(expected_test_contents, \(expected, name) { expect_identical( - readLines(file.path(tmp, "tests", "testthat", name)), + readLines(fs::path(tmp, "tests", "testthat", name)), expected ) }) expect_identical( - readLines(file.path(tmp, "tests", "testthat", "setup.R")), + readLines(fs::path(tmp, "tests", "testthat", "setup.R")), expected_setup_content ) }) @@ -179,7 +179,7 @@ test_that(".generate_paths() writes correct paths.R for fec (#65)", { ) expect_identical( - readLines(file.path(tmp, "R", "paths-audit-get_names_audit_candidates.R")), + readLines(fs::path(tmp, "R", "paths-audit-get_names_audit_candidates.R")), expected_file_content ) }) @@ -208,7 +208,7 @@ test_that(".generate_paths() writes correct paths.R for trello (#65)", { ) expect_identical( - readLines(file.path(tmp, "R", "paths-board-add_boards.R")), + readLines(fs::path(tmp, "R", "paths-board-add_boards.R")), expected_file_content ) }) @@ -311,7 +311,7 @@ test_that(".generate_paths_file() renders header and cookie params correctly (#8 .generate_paths_file(op, "search_things", "test", list()) expect_identical( - readLines(file.path(tmp, "R", "paths-things-search_things.R")), + readLines(fs::path(tmp, "R", "paths-things-search_things.R")), expected_content ) }) diff --git a/tests/testthat/test-generate_pkg-security.R b/tests/testthat/test-generate_pkg-security.R index a58b593..1f26d75 100644 --- a/tests/testthat/test-generate_pkg-security.R +++ b/tests/testthat/test-generate_pkg-security.R @@ -41,7 +41,7 @@ test_that(".generate_security() generates security file for trello", { ) ) expect_identical( - readLines(file.path(tmp, "R", "020-auth.R")), + readLines(fs::path(tmp, "R", "020-auth.R")), security_expected ) }) diff --git a/tests/testthat/test-generate_pkg-shared.R b/tests/testthat/test-generate_pkg-shared.R index 8f813d5..85dfc12 100644 --- a/tests/testthat/test-generate_pkg-shared.R +++ b/tests/testthat/test-generate_pkg-shared.R @@ -5,7 +5,7 @@ test_that(".generate_shared_params() returns file path for no-security API (#65) result <- .generate_shared_params(list()) - expect_identical(result, file.path(tmp, "R", "000-shared.R")) + expect_identical(result, fs::path(tmp, "R", "000-shared.R")) }) test_that(".generate_shared_params() writes correct content for no-security API (#65)", { @@ -17,7 +17,7 @@ test_that(".generate_shared_params() writes correct content for no-security API .generate_shared_params(list()) expect_identical( - readLines(file.path(tmp, "R", "000-shared.R")), + readLines(fs::path(tmp, "R", "000-shared.R")), shared_expected ) }) @@ -33,7 +33,7 @@ test_that(".generate_shared_params() writes security params for API with securit shared_expected <- readLines(test_path("_fixtures", "trello", "000-shared.R")) .generate_shared_params(security_data) expect_identical( - readLines(file.path(tmp, "R", "000-shared.R")), + readLines(fs::path(tmp, "R", "000-shared.R")), shared_expected ) }) diff --git a/tests/testthat/test-use_beekeeper.R b/tests/testthat/test-use_beekeeper.R index e0e9db5..9182b53 100644 --- a/tests/testthat/test-use_beekeeper.R +++ b/tests/testthat/test-use_beekeeper.R @@ -2,7 +2,8 @@ test_that("config writes a yml", { local_mocked_bindings( use_build_ignore = function(...) { invisible(TRUE) - } + }, + .package = "usethis" ) config_path <- withr::local_tempfile(fileext = ".yml") rapid_write_path <- withr::local_tempfile(fileext = ".rds") From dc28073ad34584fb676a4d9707948d60cffb8a2b Mon Sep 17 00:00:00 2001 From: Jon Harmon Date: Sat, 16 May 2026 08:13:49 -0500 Subject: [PATCH 7/9] Add tests for other conditions --- tests/testthat/_snaps/aaa-conditions.md | 20 ++++++++++++++++++++ tests/testthat/test-aaa-conditions.R | 18 ++++++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/tests/testthat/_snaps/aaa-conditions.md b/tests/testthat/_snaps/aaa-conditions.md index df12762..5c7b207 100644 --- a/tests/testthat/_snaps/aaa-conditions.md +++ b/tests/testthat/_snaps/aaa-conditions.md @@ -8,3 +8,23 @@ Error: ! This is a test error +# .pkg_warn works + + Code + (expect_pkg_warning_classes(.pkg_warn("This is a test warning", c("subclass", + "test_warning")), "beekeeper", "subclass", "test_warning")) + Output + + Warning: + This is a test warning + +# .pkg_inform works + + Code + (expect_pkg_message_classes(.pkg_inform("This is a test message", c("subclass", + "test_message")), "beekeeper", "subclass", "test_message")) + Output + + Message: + This is a test message + diff --git a/tests/testthat/test-aaa-conditions.R b/tests/testthat/test-aaa-conditions.R index 3772d0a..ef7c48c 100644 --- a/tests/testthat/test-aaa-conditions.R +++ b/tests/testthat/test-aaa-conditions.R @@ -6,3 +6,21 @@ test_that(".pkg_abort works", { "test_error" ) }) + +test_that(".pkg_warn works", { + stbl::expect_pkg_warning_snapshot( + .pkg_warn("This is a test warning", c("subclass", "test_warning")), + "beekeeper", + "subclass", + "test_warning" + ) +}) + +test_that(".pkg_inform works", { + stbl::expect_pkg_message_snapshot( + .pkg_inform("This is a test message", c("subclass", "test_message")), + "beekeeper", + "subclass", + "test_message" + ) +}) From 3688f7e24dea6a39053a3c754ecb0fd3f7414858 Mon Sep 17 00:00:00 2001 From: Jon Harmon Date: Sat, 16 May 2026 08:20:23 -0500 Subject: [PATCH 8/9] Tag tests --- tests/testthat/test-generate_pkg.R | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/testthat/test-generate_pkg.R b/tests/testthat/test-generate_pkg.R index 5e493f1..4ec664d 100644 --- a/tests/testthat/test-generate_pkg.R +++ b/tests/testthat/test-generate_pkg.R @@ -1,4 +1,4 @@ -test_that("generate_pkg() returns a vector of created files", { +test_that("generate_pkg() returns a vector of created files (#15, #82)", { skip_on_cran() create_local_package() @@ -27,7 +27,7 @@ test_that("generate_pkg() returns a vector of created files", { expect_identical(test_result, expected_result) }) -test_that("generate_pkg() generates call function with API keys", { +test_that("generate_pkg() generates call function with API keys (#17, #82)", { skip_on_cran() local_mocked_bindings( .generate_paths = function(...) { @@ -51,7 +51,7 @@ test_that("generate_pkg() generates call function with API keys", { expect_identical(prepare_result, prepare_expected) }) -test_that("generate_pkg() falls back to config file when arguments are NULL", { +test_that("generate_pkg() falls back to config file when arguments are NULL (#82)", { skip_on_cran() config_text <- readLines(test_path("_fixtures", "guru", "_beekeeper.yml")) create_local_package() From 98d9866ac3b49069fede0a6baf75bce1e7e23b4e Mon Sep 17 00:00:00 2001 From: Jon Harmon Date: Sat, 16 May 2026 08:34:14 -0500 Subject: [PATCH 9/9] Tag all issues --- tests/testthat/test-generate_pkg-setup.R | 2 +- tests/testthat/test-generate_pkg.R | 2 +- tests/testthat/test-use_beekeeper.R | 2 +- tests/testthat/test-utils.R | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/testthat/test-generate_pkg-setup.R b/tests/testthat/test-generate_pkg-setup.R index d87634f..07c570a 100644 --- a/tests/testthat/test-generate_pkg-setup.R +++ b/tests/testthat/test-generate_pkg-setup.R @@ -28,7 +28,7 @@ test_that("read_api_definition() reads api_definitions", { expect_s7_class(api_definition, rapid::class_rapid) }) -test_that(".setup_r() sets up dependencies", { +test_that(".setup_r() sets up dependencies (#16)", { skip_on_cran() create_local_package() diff --git a/tests/testthat/test-generate_pkg.R b/tests/testthat/test-generate_pkg.R index 4ec664d..e887163 100644 --- a/tests/testthat/test-generate_pkg.R +++ b/tests/testthat/test-generate_pkg.R @@ -1,4 +1,4 @@ -test_that("generate_pkg() returns a vector of created files (#15, #82)", { +test_that("generate_pkg() returns a vector of created files (#15, #16, #35, #82)", { skip_on_cran() create_local_package() diff --git a/tests/testthat/test-use_beekeeper.R b/tests/testthat/test-use_beekeeper.R index 9182b53..0550003 100644 --- a/tests/testthat/test-use_beekeeper.R +++ b/tests/testthat/test-use_beekeeper.R @@ -1,4 +1,4 @@ -test_that("config writes a yml", { +test_that("use_beekeeper writes a yml config (#10)", { local_mocked_bindings( use_build_ignore = function(...) { invisible(TRUE) diff --git a/tests/testthat/test-utils.R b/tests/testthat/test-utils.R index df3f86d..dc30f62 100644 --- a/tests/testthat/test-utils.R +++ b/tests/testthat/test-utils.R @@ -8,7 +8,7 @@ test_that("%|\"|% works (#noissue)", { expect_identical("foo" %|"|% "bar", "foo") }) -test_that(".coalesce() works (#noissue)", { +test_that(".coalesce() works (#52)", { expect_identical(.coalesce(c("a", NA), c("x", "y")), c("a", "y")) })