From 278d9fd3cfd585e3780dac0c18f3497376e68fb8 Mon Sep 17 00:00:00 2001 From: Jon Harmon Date: Mon, 16 Dec 2024 09:09:26 -0600 Subject: [PATCH 01/33] Deal with tags better. I started this PR quite a while ago. I'm creating this draft to make it easier to visually compare this to the existing code. --- R/generate_pkg-paths.R | 4 ++-- beekeeper.Rproj | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/R/generate_pkg-paths.R b/R/generate_pkg-paths.R index d6d9615..80885db 100644 --- a/R/generate_pkg-paths.R +++ b/R/generate_pkg-paths.R @@ -1,6 +1,4 @@ .generate_paths <- function(paths, api_abbr, security_data, base_url) { - # TODO: Do any APIDs lack tags? - # TODO: Do any APIDs have multiple tags? paths_by_tag <- as_bk_data(paths) paths_file_paths <- character() if (length(paths_by_tag)) { @@ -32,6 +30,8 @@ S7::method(as_bk_data, class_paths) <- function(x) { .paths_to_tags_df <- function(x) { x <- unnest(x, "operations") x <- x[!x$deprecated, ] + x$tags[lengths(x$tags) == 0] <- "general" + x$tags <- purrr::map_chr(x$tags, 1) nest( x, .by = "tags", .key = "endpoints" diff --git a/beekeeper.Rproj b/beekeeper.Rproj index 69fafd4..2307ac2 100644 --- a/beekeeper.Rproj +++ b/beekeeper.Rproj @@ -1,4 +1,5 @@ Version: 1.0 +ProjectId: becf4a99-cd70-4096-862c-21c74eb2c5bc RestoreWorkspace: No SaveWorkspace: No From 8159d36a9d025f887d5a57198bbc1c7571592728 Mon Sep 17 00:00:00 2001 From: Jon Harmon Date: Mon, 27 Jan 2025 06:48:36 -0600 Subject: [PATCH 02/33] Handle user agent purely in nectar. --- .Rbuildignore | 1 + .gitignore | 1 + DESCRIPTION | 2 +- NAMESPACE | 1 - R/generate_pkg-agent.R | 31 ------------------------------- R/generate_pkg-call.R | 7 +++---- R/generate_pkg_main.R | 11 ++++------- man/generate_pkg.Rd | 8 +------- man/generate_pkg_agent.Rd | 19 ------------------- 9 files changed, 11 insertions(+), 70 deletions(-) delete mode 100644 R/generate_pkg-agent.R delete mode 100644 man/generate_pkg_agent.Rd diff --git a/.Rbuildignore b/.Rbuildignore index 1e85aba..61019bf 100644 --- a/.Rbuildignore +++ b/.Rbuildignore @@ -11,3 +11,4 @@ ^_beekeeper\.yml$ ^_beekeeper_rapid\.rds$ ^exploration$ +^_slackapi_rapid\.rds$ diff --git a/.gitignore b/.gitignore index 2e563c9..7c05924 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,4 @@ inst/doc _beekeeper.yml exploration _beekeeper_rapid.rds +_slackapi_rapid.rds diff --git a/DESCRIPTION b/DESCRIPTION index a869324..9056e5e 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -49,4 +49,4 @@ Remotes: Config/testthat/edition: 3 Encoding: UTF-8 Roxygen: list(markdown = TRUE) -RoxygenNote: 7.3.1 +RoxygenNote: 7.3.2 diff --git a/NAMESPACE b/NAMESPACE index 1f235b4..d9ba0b2 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -1,7 +1,6 @@ # Generated by roxygen2: do not edit by hand export(generate_pkg) -export(generate_pkg_agent) export(use_beekeeper) if (getRversion() < "4.3.0") importFrom("S7", "@") importFrom(S7,class_any) diff --git a/R/generate_pkg-agent.R b/R/generate_pkg-agent.R deleted file mode 100644 index 56fbf19..0000000 --- a/R/generate_pkg-agent.R +++ /dev/null @@ -1,31 +0,0 @@ -#' Create a user agent for the active package -#' -#' @param path The path to the DESCRIPTION file, or to a directory within a -#' package. -#' -#' @return A string with the name of the package and (if available) the first -#' URL associated with the package. -#' -#' @export -generate_pkg_agent <- function(path = ".") { - if (!is_dir(path) && path_file(path) != "DESCRIPTION") { - path <- path_dir(path) # nocov - } - pkg_desc <- desc(file = path) - pkg_name <- pkg_desc$get_field("Package") - pkg_url <- .get_pkg_url(pkg_desc) - return( - glue("{pkg_name}{pkg_url}") - ) -} - -.get_pkg_url <- function(pkg_desc) { - pkg_url_glue <- "" - pkg_url <- pkg_desc$get_urls() - if (length(pkg_url)) { - pkg_url_glue <- glue( - " ({pkg_url[[1]]})" - ) - } - return(pkg_url_glue) -} diff --git a/R/generate_pkg-call.R b/R/generate_pkg-call.R index 8e0e04e..385e455 100644 --- a/R/generate_pkg-call.R +++ b/R/generate_pkg-call.R @@ -1,19 +1,18 @@ -.generate_call <- function(config, api_definition, pkg_agent, security_data) { +.generate_call <- function(config, api_definition, security_data) { touched_files <- c( - .generate_call_r(config, api_definition, pkg_agent, security_data), + .generate_call_r(config, api_definition, security_data), .generate_call_test(config$api_abbr) ) return(touched_files) } -.generate_call_r <- function(config, api_definition, pkg_agent, security_data) { +.generate_call_r <- function(config, api_definition, security_data) { .bk_use_template( template = "010-call.R", data = list( api_title = config$api_title, api_abbr = config$api_abbr, base_url = api_definition@servers@url, - pkg_agent = pkg_agent, has_security = security_data$has_security, security_arg_helps = security_data$security_arg_helps, security_signature = security_data$security_signature, diff --git a/R/generate_pkg_main.R b/R/generate_pkg_main.R index 01256c6..affe9b9 100644 --- a/R/generate_pkg_main.R +++ b/R/generate_pkg_main.R @@ -5,14 +5,11 @@ #' an opinionated framework for API packages. #' #' @param config_file The path to a beekeeper yaml file. -#' @param pkg_agent A string to identify this package, for use in the -#' `user_agent` argument of [nectar::req_setup()]. #' #' @return A character vector of paths to files that were added or updated, #' invisibly. #' @export -generate_pkg <- function(config_file = "_beekeeper.yml", - pkg_agent = generate_pkg_agent(config_file)) { +generate_pkg <- function(config_file = "_beekeeper.yml") { # 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. @@ -20,16 +17,16 @@ generate_pkg <- function(config_file = "_beekeeper.yml", config <- .read_config(config_file) api_definition <- .read_api_definition(config_file, config$rapid_file) .prepare_r() - touched_files <- .generate_pkg_impl(config, api_definition, pkg_agent) + touched_files <- .generate_pkg_impl(config, api_definition) return(invisible(touched_files)) } -.generate_pkg_impl <- function(config, api_definition, pkg_agent) { +.generate_pkg_impl <- function(config, api_definition) { security_data <- .generate_security( config$api_abbr, api_definition@components@security_schemes ) - call_files <- .generate_call(config, api_definition, pkg_agent, security_data) + call_files <- .generate_call(config, api_definition, security_data) path_files <- .generate_paths( api_definition@paths, config$api_abbr, diff --git a/man/generate_pkg.Rd b/man/generate_pkg.Rd index 0487d99..05ec5b5 100644 --- a/man/generate_pkg.Rd +++ b/man/generate_pkg.Rd @@ -4,16 +4,10 @@ \alias{generate_pkg} \title{Use a beekeeper config file to generate code} \usage{ -generate_pkg( - config_file = "_beekeeper.yml", - pkg_agent = generate_pkg_agent(config_file) -) +generate_pkg(config_file = "_beekeeper.yml") } \arguments{ \item{config_file}{The path to a beekeeper yaml file.} - -\item{pkg_agent}{A string to identify this package, for use in the -\code{user_agent} argument of \code{\link[nectar:req_setup]{nectar::req_setup()}}.} } \value{ A character vector of paths to files that were added or updated, diff --git a/man/generate_pkg_agent.Rd b/man/generate_pkg_agent.Rd deleted file mode 100644 index 1ed369d..0000000 --- a/man/generate_pkg_agent.Rd +++ /dev/null @@ -1,19 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/generate_pkg-agent.R -\name{generate_pkg_agent} -\alias{generate_pkg_agent} -\title{Create a user agent for the active package} -\usage{ -generate_pkg_agent(path = ".") -} -\arguments{ -\item{path}{The path to the DESCRIPTION file, or to a directory within a -package.} -} -\value{ -A string with the name of the package and (if available) the first -URL associated with the package. -} -\description{ -Create a user agent for the active package -} From fcff2358c0cc93b7dddcc2045fa48f6cd82ee642 Mon Sep 17 00:00:00 2001 From: Jon Harmon Date: Mon, 27 Jan 2025 12:31:39 -0600 Subject: [PATCH 03/33] Clean up. --- R/generate_pkg-prepare.R | 9 ++++++--- R/generate_pkg_main.R | 15 ++++++++++----- README.Rmd | 20 ++++++++++---------- README.md | 25 ++++++++++--------------- tests/testthat/helper.R | 2 -- 5 files changed, 36 insertions(+), 35 deletions(-) diff --git a/R/generate_pkg-prepare.R b/R/generate_pkg-prepare.R index 6a256cd..52f909f 100644 --- a/R/generate_pkg-prepare.R +++ b/R/generate_pkg-prepare.R @@ -49,13 +49,16 @@ return(config) } -.read_api_definition <- function(config_file, rapid_file) { +.read_api_definition <- function(pkg_dir, rapid_file) { readRDS( - path(path_dir(config_file), rapid_file) + path(pkg_dir, rapid_file) ) } -.prepare_r <- function() { +.prepare_r <- function(pkg_dir) { + if (as.character(pkg_dir) != ".") { + usethis::local_project(pkg_dir, quiet = TRUE) + } use_directory("R") use_testthat() quietly(use_httptest2)() diff --git a/R/generate_pkg_main.R b/R/generate_pkg_main.R index affe9b9..1d7e606 100644 --- a/R/generate_pkg_main.R +++ b/R/generate_pkg_main.R @@ -4,19 +4,24 @@ #' config file (generated by [use_beekeeper()] or manually). The files enforce #' an opinionated framework for API packages. #' -#' @param config_file The path to a beekeeper yaml file. +#' @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. #' #' @return A character vector of paths to files that were added or updated, #' invisibly. #' @export -generate_pkg <- function(config_file = "_beekeeper.yml") { +generate_pkg <- function(config_file = "_beekeeper.yml", + pkg_dir = fs::path_dir(config_file)) { # 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() + .assert_is_pkg(pkg_dir) config <- .read_config(config_file) - api_definition <- .read_api_definition(config_file, config$rapid_file) - .prepare_r() + api_definition <- .read_api_definition(pkg_dir, config$rapid_file) + .prepare_r(pkg_dir) touched_files <- .generate_pkg_impl(config, api_definition) return(invisible(touched_files)) } diff --git a/README.Rmd b/README.Rmd index 95b8a38..251d1b1 100644 --- a/README.Rmd +++ b/README.Rmd @@ -59,16 +59,16 @@ Most of the outline was included in the grant proposal. - **Potential challenges:** This step will involve more reading and documenting than code, to gather examples of how different APIs implement limits and batching. It's possible systems will be so different that it will be difficult to summarize them. For example, Slack has two separate batching systems in its API, with some functions moved to the newer system, and others not. - **UPDATE:** The [development version of {httr2}](https://github.com/r-lib/httr2/) has functionality to help with this quite a lot, thankfully! I'm skipping this milestone while that functionality stabilizes (this was previously 0.3.0). - [ ] **0.5.0: More robust scaffolding.** - - [ ] Add parameter documentation. - - [ ] Also add parameter type checking. - - [ ] **Potential challenges:** By this point I'll need an OAS definition document to use for testing that includes all of the possible parameter types. I'll likely need to generate a fake API specification that goes beyond a typical individual example. -- **0.6.0: Expected results.** - - Add response (return value) documentation. - - Use expected responses to generate better test scaffolds. - - **Potential challenges:** Testing the generation of tests might present unique challenges. I'll need to look into how testthat tests itself. -- **0.7.0: Error messaging.** - - Add more robust error messaging for non-standard responses. - - **Potential challenges:** Mocking cases where things fail can be tricky. Ideally this step will involve pushing the package to a stable 1.0.0, but that will require enough usage to feel confident that the core function definitions are stable. + - [x] Add parameter documentation. + - [ ] Deal with pagination semi-automatically. +- [ ] **0.6.0: Expected results.** + - [ ] Add response (return value) documentation. + - [ ] Use response (return value) schemas to parse responses. + - [ ] Add parameter type checking. + - [ ] Use expected responses to generate better test scaffolds. +- [ ] **0.7.0: Error messaging.** + - [ ] Add more robust error messaging for non-standard responses. + - [ ] **Potential challenges:** Mocking cases where things fail can be tricky. Ideally this step will involve pushing the package to a stable 1.0.0, but that will require enough usage to feel confident that the core function definitions are stable. ## Installation diff --git a/README.md b/README.md index 106ba14..746ff2b 100644 --- a/README.md +++ b/README.md @@ -87,21 +87,16 @@ included in the grant proposal. milestone while that functionality stabilizes (this was previously 0.3.0). - [ ] **0.5.0: More robust scaffolding.** - - [ ] Add parameter documentation. - - [ ] Also add parameter type checking. - - [ ] **Potential challenges:** By this point I’ll need an OAS - definition document to use for testing that includes all of the - possible parameter types. I’ll likely need to generate a fake API - specification that goes beyond a typical individual example. -- **0.6.0: Expected results.** - - Add response (return value) documentation. - - Use expected responses to generate better test scaffolds. - - **Potential challenges:** Testing the generation of tests might - present unique challenges. I’ll need to look into how testthat tests - itself. -- **0.7.0: Error messaging.** - - Add more robust error messaging for non-standard responses. - - **Potential challenges:** Mocking cases where things fail can be + - [x] Add parameter documentation. + - [ ] Deal with pagination semi-automatically. +- [ ] **0.6.0: Expected results.** + - [ ] Add response (return value) documentation. + - [ ] Use response (return value) schemas to parse responses. + - [ ] Add parameter type checking. + - [ ] Use expected responses to generate better test scaffolds. +- [ ] **0.7.0: Error messaging.** + - [ ] Add more robust error messaging for non-standard responses. + - [ ] **Potential challenges:** Mocking cases where things fail can be tricky. Ideally this step will involve pushing the package to a stable 1.0.0, but that will require enough usage to feel confident that the core function definitions are stable. diff --git a/tests/testthat/helper.R b/tests/testthat/helper.R index ba2afa5..4591f11 100644 --- a/tests/testthat/helper.R +++ b/tests/testthat/helper.R @@ -9,9 +9,7 @@ create_local_package <- function(pkgname = "testpkg", usethis::create_package( dir, - # I need a url to check for it in user agent. fields = list( - "URL" = "https://example.com", "Config/testthat/edition" = "3" ), rstudio = TRUE, From d347d7dbb9d87eec9bf32dcb56a2b77fc0e50495 Mon Sep 17 00:00:00 2001 From: Jon Harmon Date: Mon, 27 Jan 2025 16:08:14 -0600 Subject: [PATCH 04/33] New templates (in progress). Update templates to match current slackapi package. --- R/beekeeper-package.R | 3 - R/generate_pkg-prepare.R | 80 +++++------------- R/generate_pkg-setup.R | 69 +++++++++++++++ R/{generate_pkg_main.R => generate_pkg.R} | 8 +- R/utils.R | 6 +- inst/templates/000-shared.R | 16 ++++ inst/templates/010-call.R | 30 ------- inst/templates/010-prepare.R | 31 +++++++ inst/templates/030-pagination.R | 24 ++++++ inst/templates/paths.R | 40 ++++++--- inst/templates/test-010-call.R | 10 --- inst/templates/test-010-prepare.R | 12 +++ man/dot-assert_is_pkg.Rd | 2 +- man/dot-is_pkg.Rd | 2 +- man/generate_pkg.Rd | 12 ++- tests/testthat/_fixtures/guru-010-call.R | 29 ------- tests/testthat/_fixtures/guru-010-prepare.R | 30 +++++++ tests/testthat/_fixtures/guru-test-010-call.R | 10 --- .../_fixtures/guru-test-010-prepare.R | 12 +++ tests/testthat/_fixtures/trello-010-call.R | 32 ------- tests/testthat/_fixtures/trello-010-prepare.R | 32 +++++++ tests/testthat/_snaps/generate_pkg-prepare.md | 9 -- tests/testthat/_snaps/generate_pkg-setup.md | 30 +++++++ tests/testthat/test-generate_pkg-call.R | 31 ------- tests/testthat/test-generate_pkg-paths.R | 36 +++++--- tests/testthat/test-generate_pkg-prepare.R | 84 +++---------------- tests/testthat/test-generate_pkg-security.R | 53 +++++------- tests/testthat/test-generate_pkg-setup.R | 52 ++++++++++++ tests/testthat/test-generate_pkg.R | 42 ++++++++++ tests/testthat/test-generate_pkg_main.R | 21 ----- 30 files changed, 477 insertions(+), 371 deletions(-) create mode 100644 R/generate_pkg-setup.R rename R/{generate_pkg_main.R => generate_pkg.R} (83%) create mode 100644 inst/templates/000-shared.R delete mode 100644 inst/templates/010-call.R create mode 100644 inst/templates/010-prepare.R create mode 100644 inst/templates/030-pagination.R delete mode 100644 inst/templates/test-010-call.R create mode 100644 inst/templates/test-010-prepare.R delete mode 100644 tests/testthat/_fixtures/guru-010-call.R create mode 100644 tests/testthat/_fixtures/guru-010-prepare.R delete mode 100644 tests/testthat/_fixtures/guru-test-010-call.R create mode 100644 tests/testthat/_fixtures/guru-test-010-prepare.R delete mode 100644 tests/testthat/_fixtures/trello-010-call.R create mode 100644 tests/testthat/_fixtures/trello-010-prepare.R delete mode 100644 tests/testthat/_snaps/generate_pkg-prepare.md create mode 100644 tests/testthat/_snaps/generate_pkg-setup.md delete mode 100644 tests/testthat/test-generate_pkg-call.R create mode 100644 tests/testthat/test-generate_pkg-setup.R create mode 100644 tests/testthat/test-generate_pkg.R delete mode 100644 tests/testthat/test-generate_pkg_main.R diff --git a/R/beekeeper-package.R b/R/beekeeper-package.R index 9005276..0dea3e5 100644 --- a/R/beekeeper-package.R +++ b/R/beekeeper-package.R @@ -18,7 +18,6 @@ #' @importFrom nectar stabilize_string #' @importFrom purrr discard #' @importFrom purrr imap -#' @importFrom purrr list_rbind #' @importFrom purrr map #' @importFrom purrr map_chr #' @importFrom purrr map2 @@ -44,9 +43,7 @@ #' @importFrom stringr str_to_sentence #' @importFrom styler style_file #' @importFrom testthat test_that -#' @importFrom tibble as_tibble #' @importFrom tidyr nest -#' @importFrom tidyr unnest #' @importFrom usethis proj_get #' @importFrom usethis proj_path #' @importFrom usethis use_build_ignore diff --git a/R/generate_pkg-prepare.R b/R/generate_pkg-prepare.R index 52f909f..8ad4e80 100644 --- a/R/generate_pkg-prepare.R +++ b/R/generate_pkg-prepare.R @@ -1,67 +1,29 @@ -#' Error if not in package -#' -#' @inheritParams .is_pkg -#' -#' @return `NULL`, invisibly. -#' @keywords internal -.assert_is_pkg <- function(base_path = usethis::proj_get()) { - if (.is_pkg(base_path)) { - return(invisible(NULL)) - } - cli_abort(c( - "Can't generate package files outside of a package.", - x = "{.path {base_path}} is not inside a package." - )) -} - -#' Check whether we're in a package -#' -#' Inspired by usethis:::is_package. -#' -#' @param base_path The root URL of the current project. -#' -#' @return `TRUE` if the project is a package, `FALSE` if not. -#' @keywords internal -.is_pkg <- function(base_path = proj_get()) { - root_file <- try_fetch( - find_package_root_file(path = base_path), - error = function(cnd) NULL +.generate_prepare <- function(config, api_definition, security_data) { + c( + .generate_prepare_r(config, api_definition, security_data), + .generate_prepare_test(config$api_abbr) ) - !is.null(root_file) -} - -.read_config <- function(config_file = "_beekeeper.yml") { - config <- read_yaml(config_file) - config <- .stabilize_config(config) - return(config) } -.stabilize_config <- function(config) { - config$api_title <- stabilize_string(config$api_title) - config$api_abbr <- stabilize_string(config$api_abbr) - config$api_version <- stabilize_string(config$api_version) - config$rapid_file <- stabilize_string(config$rapid_file) - config$updated_on <- strptime( - config$updated_on, - format = "%Y-%m-%d %H:%M:%S", - tz = "UTC" +.generate_prepare_r <- function(config, api_definition, security_data) { + .bk_use_template( + template = "010-prepare.R", + data = list( + api_title = config$api_title, + api_abbr = config$api_abbr, + base_url = api_definition@servers@url, + has_security = security_data$has_security, + security_arg_helps = security_data$security_arg_helps, + security_signature = security_data$security_signature, + security_arg_list = security_data$security_arg_list + ) ) - return(config) } -.read_api_definition <- function(pkg_dir, rapid_file) { - readRDS( - path(pkg_dir, rapid_file) +.generate_prepare_test <- function(api_abbr) { + .bk_use_template( + template = "test-010-prepare.R", + dir = "tests/testthat", + data = list(api_abbr = api_abbr) ) } - -.prepare_r <- function(pkg_dir) { - if (as.character(pkg_dir) != ".") { - usethis::local_project(pkg_dir, quiet = TRUE) - } - use_directory("R") - use_testthat() - quietly(use_httptest2)() - use_package("nectar") - use_package("beekeeper", type = "Suggests") -} diff --git a/R/generate_pkg-setup.R b/R/generate_pkg-setup.R new file mode 100644 index 0000000..b716f1c --- /dev/null +++ b/R/generate_pkg-setup.R @@ -0,0 +1,69 @@ +#' Error if not in package +#' +#' @inheritParams .is_pkg +#' +#' @return `NULL`, invisibly. +#' @keywords internal +.assert_is_pkg <- function(base_path = usethis::proj_get()) { + if (.is_pkg(base_path)) { + return(invisible(NULL)) + } + cli_abort(c( + "Can't generate package files outside of a package.", + x = "{.path {base_path}} is not inside a package." + )) +} + +#' Check whether we're in a package +#' +#' Inspired by usethis:::is_package. +#' +#' @param base_path The root URL of the current project. +#' +#' @return `TRUE` if the project is a package, `FALSE` if not. +#' @keywords internal +.is_pkg <- function(base_path = proj_get()) { + root_file <- try_fetch( + find_package_root_file(path = base_path), + error = function(cnd) NULL + ) + !is.null(root_file) +} + +.read_config <- function(config_file = "_beekeeper.yml") { + config <- read_yaml(config_file) + return(.stabilize_config(config)) # nocov +} + +# covr doesn't see the line above and a bunch below for some reason. +# nocov start +.stabilize_config <- function(config) { + config$api_title <- stabilize_string(config$api_title) + config$api_abbr <- stabilize_string(config$api_abbr) + config$api_version <- stabilize_string(config$api_version) + config$rapid_file <- stabilize_string(config$rapid_file) + config$updated_on <- strptime( + config$updated_on, + format = "%Y-%m-%d %H:%M:%S", + tz = "UTC" + ) + return(config) +} +# nocov end + +.read_api_definition <- function(pkg_dir, rapid_file) { + readRDS( + path(pkg_dir, rapid_file) + ) +} + +.setup_r <- function(pkg_dir) { + if (as.character(pkg_dir) != ".") { + usethis::local_project(pkg_dir, quiet = TRUE) # nocov + } + use_directory("R") + use_testthat() + quietly(use_httptest2)() + use_package("nectar") + use_package("beekeeper", type = "Suggests") +} diff --git a/R/generate_pkg_main.R b/R/generate_pkg.R similarity index 83% rename from R/generate_pkg_main.R rename to R/generate_pkg.R index 1d7e606..ec8d817 100644 --- a/R/generate_pkg_main.R +++ b/R/generate_pkg.R @@ -21,7 +21,7 @@ generate_pkg <- function(config_file = "_beekeeper.yml", .assert_is_pkg(pkg_dir) config <- .read_config(config_file) api_definition <- .read_api_definition(pkg_dir, config$rapid_file) - .prepare_r(pkg_dir) + .setup_r(pkg_dir) touched_files <- .generate_pkg_impl(config, api_definition) return(invisible(touched_files)) } @@ -31,13 +31,15 @@ generate_pkg <- function(config_file = "_beekeeper.yml", config$api_abbr, api_definition@components@security_schemes ) - call_files <- .generate_call(config, api_definition, security_data) + prep_files <- .generate_prepare(config, api_definition, security_data) + # stop("Note: We do NOT need pagination before prepare; pagination is used in paths.") + # stop("But we DO need to sort out '030-pagination.R' before paths.") path_files <- .generate_paths( api_definition@paths, config$api_abbr, security_data, api_definition@servers@url ) - touched_files <- c(call_files, security_data$security_file_path, path_files) + touched_files <- c(prep_files, security_data$security_file_path, path_files) return(invisible(touched_files)) } diff --git a/R/utils.R b/R/utils.R index 0d0ad64..f8d1347 100644 --- a/R/utils.R +++ b/R/utils.R @@ -26,6 +26,10 @@ glue_collapse(x, sep = ",\n") } +.collapse_quote_comma <- function(x) { + stringr::str_flatten_comma(paste0('"', x, '"')) +} + .to_snake <- function(x) { to_snake_case(x, parsing_option = 3) } @@ -40,7 +44,7 @@ S7::method(.flatten_df, class_data.frame) <- function(x) { } S7::method(.flatten_df, class_list) <- function(x) { - return(list_rbind(x)) + return(purrr::list_rbind(x)) } S7::method(.flatten_df, NULL) <- function(x) { diff --git a/inst/templates/000-shared.R b/inst/templates/000-shared.R new file mode 100644 index 0000000..f38b9ec --- /dev/null +++ b/inst/templates/000-shared.R @@ -0,0 +1,16 @@ +#' Parameters used in multiple functions +#' +#' Reused parameter definitions are gathered here for easier editing. +#' +#' @param max_reqs (`integer`) The maximum number of separate requests to +#' perform. Passed on to [nectar::req_perform_opinionated()]. +#' @param max_tries_per_req (`integer`) The maximum number of times to attempt +#' each individual request. Passed on to [nectar::req_perform_opinionated()]. +#' @param req (`httr2_request`) The request object to modify. +#' @param ... These dots are for future extensions and must be empty.{{#shared_arg_helps}} +#' @param {{name}} {{{description}}}{{/shared_arg_helps}}{{#security_arg_helps}} +#' @param {{name}} {{{description}}}{{/security_arg_helps}} +#' +#' @name .shared-params +#' @keywords internal +NULL diff --git a/inst/templates/010-call.R b/inst/templates/010-call.R deleted file mode 100644 index 2172fbe..0000000 --- a/inst/templates/010-call.R +++ /dev/null @@ -1,30 +0,0 @@ -# Set up the basic call once at package build. -{{api_abbr}}_req_base <- nectar::req_setup( - "{{base_url}}", - user_agent = "{{pkg_agent}}" -) - -#' Call the {{api_title}} API -#' -#' Generate a request to an {{api_title}} endpoint. -#' -#' @inheritParams nectar::req_modify{{#security_arg_helps}} -#' @param {{name}} {{{description}}}{{/security_arg_helps}} -#' -#' @return The response from the endpoint. -#' @export -{{api_abbr}}_call_api <- function(path, - query = NULL, - body = NULL, - method = NULL{{#has_security}},{{{security_signature}}}{{/has_security}}) { - req <- nectar::req_modify( - {{api_abbr}}_req_base, - path = path, - query = query, - body = body, - method = method - ) - {{#has_security}}req <- .{{api_abbr}}_req_auth(req, {{security_arg_list}}){{/has_security}} - resp <- nectar::req_perform_opinionated(req) - nectar::resp_parse(resp, response_parser = .{{api_abbr}}_response_parser) -} diff --git a/inst/templates/010-prepare.R b/inst/templates/010-prepare.R new file mode 100644 index 0000000..d4b2792 --- /dev/null +++ b/inst/templates/010-prepare.R @@ -0,0 +1,31 @@ +#' Generate a request for the {{api_title}} API +#' +#' Prepare a request for the Slack API, using the opinionated framework defined +#' in [nectar::req_init()], [nectar::req_modify()], [nectar::req_tidy_policy()], +#' and [nectar::req_pagination_policy()]. +#' +#' You may wish to export this function (if the API changes often or you do not +#' fully implement the API, for example). +#'{{#has_security}} +#' @inheritParams .shared-params{{/has_security}} +#' @inheritParams nectar::req_prepare +#' @inherit nectar::req_prepare return +#' @keywords internal +{{api_abbr}}_req_prepare <- function(path, + query = list(), + body = NULL, + method = NULL, + tidy_fn = nectar::resp_tidy_unknown,{{#has_security}}{{{security_signature}}},{{/has_security}} + call = rlang::caller_env()) { + req <- nectar::req_prepare( + "{{base_url}}", + path = path, + query = query, + body = body, + method = method, + tidy_fn = tidy_fn, + call = call + ) + {{#has_security}}req <- .{{api_abbr}}_req_auth(req, {{security_arg_list}}){{/has_security}} + return(req) +} diff --git a/inst/templates/030-pagination.R b/inst/templates/030-pagination.R new file mode 100644 index 0000000..b637cb1 --- /dev/null +++ b/inst/templates/030-pagination.R @@ -0,0 +1,24 @@ +# These functions were generated by the {beekeeper} package, based on +# developer inputs. You may need to manually rename these functions or tweak +# their arguments. +# +# We currently only support JSON response bodies with cursor pagination. + +{{#pagination_schemes}} +#' Apply pagination +#' +#' Process a `resp` object to determine how and whether to continue iteration, +#' then build the next `req`. Use as the `pagination_fn` argument to +#' [{{api_abbr}}_req_prepare()]. +#' +#' @inheritParams .shared-params +#' @inherit nectar::req_prepare return +#' @keywords internal +{{api_abbr}}_pagination_{{family}} <- function(resp, req) { + nectar::iterate_with_json_cursor( + "{{param_name}}"{{#next_cursor_path}}, + c({{{.}}}){{/next_cursor_path}} + )(resp, req) +} + +{{/pagination_schemes}} diff --git a/inst/templates/paths.R b/inst/templates/paths.R index e6d4571..416e2c7 100644 --- a/inst/templates/paths.R +++ b/inst/templates/paths.R @@ -1,25 +1,39 @@ # These functions were generated by the {beekeeper} package, based on the paths # element from the source API description. You should carefully review these -# functions. Missing documentation is tagged with "BKTODO" to make it easier for -# you to search for issues. +# functions. -{{#paths}} #' {{summary}} #' #' {{description}} -#' {{#has_security}} -#' @inheritParams {{api_abbr}}_call_api{{/has_security}}{{#params}} -#' @param {{name}} {{{description}}}{{/params}} -#' @return BKTODO: Return descriptions are not yet implemented in beekeeper +#'{{#has_security}} +#' @inheritParams .shared-params{{/has_security}} +#' {{#params}} +#' @param {{name}} ({{{class}}}) {{{description}}}{{/params}} +#' +#' @returns `{{operation_id}}()`: The API response. #' @export -{{api_abbr}}_{{operation_id}} <- function({{{args}}}{{#has_security}}{{#args}},{{/args}}{{{security_signature}}}{{/has_security}}) { - {{api_abbr}}_call_api( - path = {{{path}}}, +{{operation_id}} <- function({{{args}}}{{#has_security}}{{#args}},{{/args}}{{{security_signature}}}{{/has_security}}) { + req <- req_{{operation_id}}( + # TODO: Add params when I have an example that has params + ) + resps <- nectar::req_perform_opinionated( + req, + max_reqs = max_reqs, # Should only include this with pagination. + max_tries_per_req = max_tries_per_req + ) + return(nectar::resp_tidy(resps)) +} + +#' @rdname {{operation_id}} +#' @returns `req_{{operation_id}}()`: A `httr2_request` request object. +req_{{operation_id}} <- function({{{args}}}{{#has_security}}{{#args}},{{/args}}{{{security_signature}}}{{/has_security}}) { + slack_req_prepare( + path = "{{{path}}}", method = "{{method}}"{{#has_security}}, {{security_arg_list}}{{/has_security}}{{#params_query}}, query = list({{params_query}}){{/params_query}}{{#params_header}}, - body = list({{params_header}}){{/params_header}} + body = list({{params_header}}){{/params_header}}{{#pagination}}, + pagination_fn = {{pagination_fn}}{{/pagination}}{{#tidy}}, + tidy_fn = {{tidy_fn}}{{/tidy}} ) } - -{{/paths}} diff --git a/inst/templates/test-010-call.R b/inst/templates/test-010-call.R deleted file mode 100644 index 67a0c5a..0000000 --- a/inst/templates/test-010-call.R +++ /dev/null @@ -1,10 +0,0 @@ -httptest2::with_mock_dir("api/01-call/valid", { - test_that("Can call an endpoint without errors", { - # A path will be auto-filled in a future version of beekeeper. - fail( - "Provide any path for this API in PROVIDED_PATH, then delete this fail." - ) - PROVIDED_PATH <- "path/to/endpoint" - expect_no_error({{api_abbr}}_call_api(PROVIDED_PATH)) - }) -}) diff --git a/inst/templates/test-010-prepare.R b/inst/templates/test-010-prepare.R new file mode 100644 index 0000000..198a855 --- /dev/null +++ b/inst/templates/test-010-prepare.R @@ -0,0 +1,12 @@ +test_that("Can prepare a request without errors", { + test_result <- expect_no_error({{api_abbr}}_req_prepare("testing")) + expect_s3_class(test_result, c("nectar_request", "httr2_request")) + expect_named( + test_result, + c("url", "method", "headers", "body", "fields", "options", "policies") + ) + expect_contains( + names(test_result$policies), + "resp_tidy" + ) +}) diff --git a/man/dot-assert_is_pkg.Rd b/man/dot-assert_is_pkg.Rd index a486924..31c1b6c 100644 --- a/man/dot-assert_is_pkg.Rd +++ b/man/dot-assert_is_pkg.Rd @@ -1,5 +1,5 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/generate_pkg-prepare.R +% Please edit documentation in R/generate_pkg-setup.R \name{.assert_is_pkg} \alias{.assert_is_pkg} \title{Error if not in package} diff --git a/man/dot-is_pkg.Rd b/man/dot-is_pkg.Rd index 4ab171a..ae0dce2 100644 --- a/man/dot-is_pkg.Rd +++ b/man/dot-is_pkg.Rd @@ -1,5 +1,5 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/generate_pkg-prepare.R +% Please edit documentation in R/generate_pkg-setup.R \name{.is_pkg} \alias{.is_pkg} \title{Check whether we're in a package} diff --git a/man/generate_pkg.Rd b/man/generate_pkg.Rd index 05ec5b5..90164be 100644 --- a/man/generate_pkg.Rd +++ b/man/generate_pkg.Rd @@ -4,10 +4,18 @@ \alias{generate_pkg} \title{Use a beekeeper config file to generate code} \usage{ -generate_pkg(config_file = "_beekeeper.yml") +generate_pkg( + config_file = "_beekeeper.yml", + pkg_dir = fs::path_dir(config_file) +) } \arguments{ -\item{config_file}{The path to a beekeeper yaml file.} +\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{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.} } \value{ A character vector of paths to files that were added or updated, diff --git a/tests/testthat/_fixtures/guru-010-call.R b/tests/testthat/_fixtures/guru-010-call.R deleted file mode 100644 index 61ca27b..0000000 --- a/tests/testthat/_fixtures/guru-010-call.R +++ /dev/null @@ -1,29 +0,0 @@ -# Set up the basic call once at package build. -guru_req_base <- nectar::req_setup( - "https://api.apis.guru/v2", - user_agent = "TESTPKG (https://example.com)" -) - -#' Call the APIs.guru API -#' -#' Generate a request to an APIs.guru endpoint. -#' -#' @inheritParams nectar::req_modify -#' -#' @return The response from the endpoint. -#' @export -guru_call_api <- function(path, - query = NULL, - body = NULL, - method = NULL) { - req <- nectar::req_modify( - guru_req_base, - path = path, - query = query, - body = body, - method = method - ) - - resp <- nectar::req_perform_opinionated(req) - nectar::resp_parse(resp, response_parser = .guru_response_parser) -} diff --git a/tests/testthat/_fixtures/guru-010-prepare.R b/tests/testthat/_fixtures/guru-010-prepare.R new file mode 100644 index 0000000..0463041 --- /dev/null +++ b/tests/testthat/_fixtures/guru-010-prepare.R @@ -0,0 +1,30 @@ +#' Generate a request for the APIs.guru API +#' +#' Prepare a request for the Slack API, using the opinionated framework defined +#' in [nectar::req_init()], [nectar::req_modify()], [nectar::req_tidy_policy()], +#' and [nectar::req_pagination_policy()]. +#' +#' You may wish to export this function (if the API changes often or you do not +#' fully implement the API, for example). +#' +#' @inheritParams nectar::req_prepare +#' @inherit nectar::req_prepare return +#' @keywords internal +guru_req_prepare <- function(path, + query = list(), + body = NULL, + method = NULL, + tidy_fn = nectar::resp_tidy_unknown, + call = rlang::caller_env()) { + req <- nectar::req_prepare( + "https://api.apis.guru/v2", + path = path, + query = query, + body = body, + method = method, + tidy_fn = tidy_fn, + call = call + ) + + return(req) +} diff --git a/tests/testthat/_fixtures/guru-test-010-call.R b/tests/testthat/_fixtures/guru-test-010-call.R deleted file mode 100644 index 53dc0bb..0000000 --- a/tests/testthat/_fixtures/guru-test-010-call.R +++ /dev/null @@ -1,10 +0,0 @@ -httptest2::with_mock_dir("api/01-call/valid", { - test_that("Can call an endpoint without errors", { - # A path will be auto-filled in a future version of beekeeper. - fail( - "Provide any path for this API in PROVIDED_PATH, then delete this fail." - ) - PROVIDED_PATH <- "path/to/endpoint" - expect_no_error(guru_call_api(PROVIDED_PATH)) - }) -}) diff --git a/tests/testthat/_fixtures/guru-test-010-prepare.R b/tests/testthat/_fixtures/guru-test-010-prepare.R new file mode 100644 index 0000000..06b5bc5 --- /dev/null +++ b/tests/testthat/_fixtures/guru-test-010-prepare.R @@ -0,0 +1,12 @@ +test_that("Can prepare a request without errors", { + test_result <- expect_no_error(guru_req_prepare("testing")) + expect_s3_class(test_result, c("nectar_request", "httr2_request")) + expect_named( + test_result, + c("url", "method", "headers", "body", "fields", "options", "policies") + ) + expect_contains( + names(test_result$policies), + "resp_tidy" + ) +}) diff --git a/tests/testthat/_fixtures/trello-010-call.R b/tests/testthat/_fixtures/trello-010-call.R deleted file mode 100644 index 54ae1ec..0000000 --- a/tests/testthat/_fixtures/trello-010-call.R +++ /dev/null @@ -1,32 +0,0 @@ -# Set up the basic call once at package build. -trello_req_base <- nectar::req_setup( - "https://trello.com/1", - user_agent = "TESTPKG (https://example.com)" -) - -#' Call the Trello API -#' -#' Generate a request to an Trello endpoint. -#' -#' @inheritParams nectar::req_modify -#' @param key An API key provided by the API provider. This key is not clearly documented in the API description. Check the API documentation for details. -#' @param token An API key provided by the API provider. This key is not clearly documented in the API description. Check the API documentation for details. -#' -#' @return The response from the endpoint. -#' @export -trello_call_api <- function(path, - query = NULL, - body = NULL, - method = NULL, key = Sys.getenv("TRELLO_KEY"), - token = Sys.getenv("TRELLO_TOKEN")) { - req <- nectar::req_modify( - trello_req_base, - path = path, - query = query, - body = body, - method = method - ) - req <- .trello_req_auth(req, key = key, token = token) - resp <- nectar::req_perform_opinionated(req) - nectar::resp_parse(resp, response_parser = .trello_response_parser) -} diff --git a/tests/testthat/_fixtures/trello-010-prepare.R b/tests/testthat/_fixtures/trello-010-prepare.R new file mode 100644 index 0000000..91816e8 --- /dev/null +++ b/tests/testthat/_fixtures/trello-010-prepare.R @@ -0,0 +1,32 @@ +#' Generate a request for the Trello API +#' +#' Prepare a request for the Slack API, using the opinionated framework defined +#' in [nectar::req_init()], [nectar::req_modify()], [nectar::req_tidy_policy()], +#' and [nectar::req_pagination_policy()]. +#' +#' You may wish to export this function (if the API changes often or you do not +#' fully implement the API, for example). +#' +#' @inheritParams .shared-params +#' @inheritParams nectar::req_prepare +#' @inherit nectar::req_prepare return +#' @keywords internal +trello_req_prepare <- function(path, + query = list(), + body = NULL, + method = NULL, + tidy_fn = nectar::resp_tidy_unknown, key = Sys.getenv("TRELLO_KEY"), + token = Sys.getenv("TRELLO_TOKEN"), + call = rlang::caller_env()) { + req <- nectar::req_prepare( + "https://trello.com/1", + path = path, + query = query, + body = body, + method = method, + tidy_fn = tidy_fn, + call = call + ) + req <- .trello_req_auth(req, key = key, token = token) + return(req) +} diff --git a/tests/testthat/_snaps/generate_pkg-prepare.md b/tests/testthat/_snaps/generate_pkg-prepare.md deleted file mode 100644 index dd14438..0000000 --- a/tests/testthat/_snaps/generate_pkg-prepare.md +++ /dev/null @@ -1,9 +0,0 @@ -# .assert_is_pkg() errors informatively for non-packages - - Code - .assert_is_pkg(tempdir()) - Condition - Error in `.assert_is_pkg()`: - ! Can't generate package files outside of a package. - TMPDIR is not inside a package. - diff --git a/tests/testthat/_snaps/generate_pkg-setup.md b/tests/testthat/_snaps/generate_pkg-setup.md new file mode 100644 index 0000000..1b8f40c --- /dev/null +++ b/tests/testthat/_snaps/generate_pkg-setup.md @@ -0,0 +1,30 @@ +# .assert_is_pkg() errors informatively for non-packages + + Code + .assert_is_pkg(tempdir()) + Condition + Error in `.assert_is_pkg()`: + ! Can't generate package files outside of a package. + TMPDIR is not inside a package. + +# .read_config() reads configs + + Code + config + Output + $api_title + [1] "APIs.guru" + + $api_abbr + [1] "guru" + + $api_version + [1] "2.2.0" + + $rapid_file + [1] "guru_rapid.rds" + + $updated_on + [1] "2024-03-27 19:14:00 UTC" + + diff --git a/tests/testthat/test-generate_pkg-call.R b/tests/testthat/test-generate_pkg-call.R deleted file mode 100644 index 2295186..0000000 --- a/tests/testthat/test-generate_pkg-call.R +++ /dev/null @@ -1,31 +0,0 @@ -test_that("generate_pkg() generates call function.", { - skip_on_cran() - config <- readLines(test_path("_fixtures", "guru_beekeeper.yml")) - guru_rapid <- readRDS(test_path("_fixtures", "guru_rapid.rds")) - call_expected <- readLines(test_path("_fixtures", "guru-010-call.R")) - - create_local_package() - writeLines(config, "_beekeeper.yml") - saveRDS(guru_rapid, "guru_rapid.rds") - - generate_pkg(pkg_agent = "TESTPKG (https://example.com)") - - call_result <- scrub_testpkg(readLines("R/010-call.R")) - expect_identical(call_result, call_expected) -}) - -test_that("generate_pkg() generates call function test.", { - skip_on_cran() - config <- readLines(test_path("_fixtures", "guru_beekeeper.yml")) - guru_rapid <- readRDS(test_path("_fixtures", "guru_rapid.rds")) - t_call_expected <- readLines(test_path("_fixtures", "guru-test-010-call.R")) - - create_local_package() - writeLines(config, "_beekeeper.yml") - saveRDS(guru_rapid, "guru_rapid.rds") - - generate_pkg(pkg_agent = "TESTPKG (https://example.com)") - - t_call_result <- readLines("tests/testthat/test-010-call.R") - expect_identical(t_call_result, t_call_expected) -}) diff --git a/tests/testthat/test-generate_pkg-paths.R b/tests/testthat/test-generate_pkg-paths.R index 312d2fd..d5e31b7 100644 --- a/tests/testthat/test-generate_pkg-paths.R +++ b/tests/testthat/test-generate_pkg-paths.R @@ -1,20 +1,32 @@ -test_that("generate_pkg() generates path functions for guru", { +test_that(".generate_paths() generates path files", { # 1 tag, no security skip_on_cran() - config <- readLines(test_path("_fixtures", "guru_beekeeper.yml")) - guru_rapid <- readRDS(test_path("_fixtures", "guru_rapid.rds")) - expected_file_content <- readLines( + config <- .read_config(test_path("_fixtures", "guru_beekeeper.yml")) + api_definition <- readRDS(test_path("_fixtures", "guru_rapid.rds")) + r_expected <- readLines( test_path("_fixtures", "guru-paths-apis.R") ) - + tests_expected <- readLines( + test_path("_fixtures", "guru-test-paths-apis.R") + ) create_local_package() - writeLines(config, "_beekeeper.yml") - saveRDS(guru_rapid, "guru_rapid.rds") + usethis::use_testthat() - generate_pkg(pkg_agent = "TESTPKG (https://example.com)") + test_result <- .generate_paths( + paths = api_definition@paths, + api_abbr = config$api_abbr, + security_data = list(), + base_url = api_definition@servers@url + ) + expect_identical( + basename(test_result), + c("paths-apis.R", "test-paths-apis.R", "setup.R") + ) - generated_file_content <- readLines("R/paths-apis.R") - expect_identical(generated_file_content, expected_file_content) + r_result <- readLines("R/paths-apis.R") + expect_identical(r_result, r_expected) + tests_result <- readLines("tests/testthat/test-paths-apis.R") + expect_identical(tests_result, tests_expected) }) test_that("generate_pkg() generates path tests for guru", { @@ -30,7 +42,7 @@ test_that("generate_pkg() generates path tests for guru", { writeLines(config, "_beekeeper.yml") saveRDS(guru_rapid, "guru_rapid.rds") - generate_pkg(pkg_agent = "TESTPKG (https://example.com)") + generate_pkg() generated_file_content <- readLines("tests/testthat/test-paths-apis.R") expect_identical(generated_file_content, expected_file_content) @@ -49,7 +61,7 @@ test_that("generate_pkg() generates test setup file for guru", { writeLines(config, "_beekeeper.yml") saveRDS(guru_rapid, "guru_rapid.rds") - generate_pkg(pkg_agent = "TESTPKG (https://example.com)") + generate_pkg() generated_file_content <- readLines("tests/testthat/setup.R") expect_identical(generated_file_content, expected_file_content) diff --git a/tests/testthat/test-generate_pkg-prepare.R b/tests/testthat/test-generate_pkg-prepare.R index 06e3851..adedbad 100644 --- a/tests/testthat/test-generate_pkg-prepare.R +++ b/tests/testthat/test-generate_pkg-prepare.R @@ -1,79 +1,21 @@ -test_that(".assert_is_pkg() errors informatively for non-packages", { - expect_snapshot( - .assert_is_pkg(tempdir()), - error = TRUE, - transform = scrub_tempdir - ) -}) - -test_that("generate_pkg() adds nectar to import.", { +test_that(".generate_prepare() generates prepare file.", { skip_on_cran() - config <- readLines(test_path("_fixtures", "guru_beekeeper.yml")) - guru_rapid <- readRDS(test_path("_fixtures", "guru_rapid.rds")) + config <- .read_config(test_path("_fixtures", "guru_beekeeper.yml")) + api_definition <- readRDS(test_path("_fixtures", "guru_rapid.rds")) + prepare_expected <- readLines(test_path("_fixtures", "guru-010-prepare.R")) + t_prepare_expected <- readLines(test_path("_fixtures", "guru-test-010-prepare.R")) create_local_package() - writeLines(config, "_beekeeper.yml") - saveRDS(guru_rapid, "guru_rapid.rds") - - generate_pkg(pkg_agent = "TESTPKG (https://example.com)") + usethis::use_testthat() + test_result <- .generate_prepare(config, api_definition, list()) - dependencies <- desc::desc()$get_deps() expect_identical( - dependencies$package[dependencies$type == "Imports"], - "nectar" - ) -}) - -test_that("generate_pkg() adds beekeeper to suggests.", { - skip_on_cran() - config <- readLines(test_path("_fixtures", "guru_beekeeper.yml")) - guru_rapid <- readRDS(test_path("_fixtures", "guru_rapid.rds")) - - create_local_package() - writeLines(config, "_beekeeper.yml") - saveRDS(guru_rapid, "guru_rapid.rds") - - generate_pkg(pkg_agent = "TESTPKG (https://example.com)") - - dependencies <- desc::desc()$get_deps() - expect_contains( - dependencies$package[dependencies$type == "Suggests"], - "beekeeper" + basename(test_result), + c("010-prepare.R", "test-010-prepare.R") ) -}) -test_that("generate_pkg() adds httptest2 to suggests.", { - skip_on_cran() - config <- readLines(test_path("_fixtures", "guru_beekeeper.yml")) - guru_rapid <- readRDS(test_path("_fixtures", "guru_rapid.rds")) - - create_local_package() - writeLines(config, "_beekeeper.yml") - saveRDS(guru_rapid, "guru_rapid.rds") - - generate_pkg(pkg_agent = "TESTPKG (https://example.com)") - - dependencies <- desc::desc()$get_deps() - expect_contains( - dependencies$package[dependencies$type == "Suggests"], - "httptest2" - ) -}) - -test_that("generate_pkg() adds testthat to suggests.", { - skip_on_cran() - config <- readLines(test_path("_fixtures", "guru_beekeeper.yml")) - guru_rapid <- readRDS(test_path("_fixtures", "guru_rapid.rds")) - - create_local_package() - writeLines(config, "_beekeeper.yml") - saveRDS(guru_rapid, "guru_rapid.rds") - - generate_pkg(pkg_agent = "TESTPKG (https://example.com)") - - dependencies <- desc::desc()$get_deps() - expect_contains( - dependencies$package[dependencies$type == "Suggests"], - "testthat" - ) + prepare_result <- scrub_testpkg(readLines("R/010-prepare.R")) + expect_identical(prepare_result, prepare_expected) + t_prepare_result <- scrub_testpkg(readLines("tests/testthat/test-010-prepare.R")) + expect_identical(t_prepare_result, t_prepare_expected) }) diff --git a/tests/testthat/test-generate_pkg-security.R b/tests/testthat/test-generate_pkg-security.R index c3bff50..f8519d7 100644 --- a/tests/testthat/test-generate_pkg-security.R +++ b/tests/testthat/test-generate_pkg-security.R @@ -1,40 +1,27 @@ -test_that("generate_pkg() generates call function with API keys", { +test_that(".generate_security() generates security file", { skip_on_cran() - local_mocked_bindings( - .generate_paths = function(...) { - character() - } - ) - config <- readLines(test_path("_fixtures", "trello_beekeeper.yml")) - trello_rapid <- readRDS(test_path("_fixtures", "trello_rapid.rds")) - call_expected <- readLines(test_path("_fixtures", "trello-010-call.R")) - - create_local_package() - writeLines(config, "_beekeeper.yml") - saveRDS(trello_rapid, "trello_rapid.rds") - - generate_pkg(pkg_agent = "TESTPKG (https://example.com)") - - call_result <- scrub_testpkg(readLines("R/010-call.R")) - expect_identical(call_result, call_expected) -}) - -test_that("generate_pkg() generates security functions", { - skip_on_cran() - local_mocked_bindings( - .generate_paths = function(...) { - character() - } - ) - config <- readLines(test_path("_fixtures", "trello_beekeeper.yml")) - trello_rapid <- readRDS(test_path("_fixtures", "trello_rapid.rds")) + config <- .read_config(test_path("_fixtures", "trello_beekeeper.yml")) + api_definition <- readRDS(test_path("_fixtures", "trello_rapid.rds")) security_expected <- readLines(test_path("_fixtures", "trello-020-auth.R")) - create_local_package() - writeLines(config, "_beekeeper.yml") - saveRDS(trello_rapid, "trello_rapid.rds") - generate_pkg(pkg_agent = "TESTPKG (https://example.com)") + test_result <- .generate_security( + config$api_abbr, + api_definition@components@security_schemes + ) + expect_named( + test_result, + c( + "has_security", + "security_schemes", + "security_arg_names", + "security_arg_list", + "security_arg_helps", + "security_arg_nulls", + "security_file_path", + "security_signature" + ) + ) security_result <- scrub_testpkg(readLines("R/020-auth.R")) expect_identical(security_result, security_expected) diff --git a/tests/testthat/test-generate_pkg-setup.R b/tests/testthat/test-generate_pkg-setup.R new file mode 100644 index 0000000..c22b4ea --- /dev/null +++ b/tests/testthat/test-generate_pkg-setup.R @@ -0,0 +1,52 @@ +test_that(".assert_is_pkg() errors informatively for non-packages", { + expect_snapshot( + .assert_is_pkg(tempdir()), + error = TRUE, + transform = scrub_tempdir + ) +}) + +test_that(".assert_is_pkg() isn't obtrusive for packages", { + create_local_package() + expect_null({ + .assert_is_pkg() + }) +}) + +test_that(".read_config() reads configs", { + config <- .read_config(test_path("_fixtures", "guru_beekeeper.yml")) + 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_rapid.rds") + expect_s7_class(api_definition, rapid::class_rapid) +}) + +test_that(".setup_r() sets up dependencies", { + skip_on_cran() + + create_local_package() + .setup_r(".") + + dependencies <- desc::desc()$get_deps() + expect_identical( + dependencies$package[dependencies$type == "Imports"], + "nectar" + ) + expect_contains( + dependencies$package[dependencies$type == "Suggests"], + "beekeeper" + ) + expect_contains( + dependencies$package[dependencies$type == "Suggests"], + "httptest2" + ) + expect_contains( + dependencies$package[dependencies$type == "Suggests"], + "testthat" + ) +}) diff --git a/tests/testthat/test-generate_pkg.R b/tests/testthat/test-generate_pkg.R new file mode 100644 index 0000000..ad5b259 --- /dev/null +++ b/tests/testthat/test-generate_pkg.R @@ -0,0 +1,42 @@ +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_rapid.rds")) + + test_dir <- create_local_package() + writeLines(config_text, "_beekeeper.yml") + saveRDS(api_definition, "guru_rapid.rds") + + test_result <- generate_pkg() + test_result <- scrub_path(test_result) + expected_result <- c( + "/R/010-prepare.R", + "/tests/testthat/test-010-prepare.R", + "/R/paths-apis.R", + "/tests/testthat/test-paths-apis.R", + "/tests/testthat/setup.R" + ) + + expect_identical(test_result, expected_result) +}) + +test_that("generate_pkg() generates call function with API keys", { + skip_on_cran() + local_mocked_bindings( + .generate_paths = function(...) { + character() + } + ) + config_text <- readLines(test_path("_fixtures", "trello_beekeeper.yml")) + api_definition <- readRDS(test_path("_fixtures", "trello_rapid.rds")) + prepare_expected <- readLines(test_path("_fixtures", "trello-010-prepare.R")) + + create_local_package() + writeLines(config_text, "_beekeeper.yml") + saveRDS(api_definition, "trello_rapid.rds") + + generate_pkg() + + prepare_result <- scrub_testpkg(readLines("R/010-prepare.R")) + expect_identical(prepare_result, prepare_expected) +}) diff --git a/tests/testthat/test-generate_pkg_main.R b/tests/testthat/test-generate_pkg_main.R deleted file mode 100644 index 0c305a6..0000000 --- a/tests/testthat/test-generate_pkg_main.R +++ /dev/null @@ -1,21 +0,0 @@ -test_that("generate_pkg() returns a vector of created files", { - skip_on_cran() - config <- readLines(test_path("_fixtures", "guru_beekeeper.yml")) - guru_rapid <- readRDS(test_path("_fixtures", "guru_rapid.rds")) - - test_dir <- create_local_package() - writeLines(config, "_beekeeper.yml") - saveRDS(guru_rapid, "guru_rapid.rds") - - test_result <- generate_pkg(pkg_agent = "TESTPKG (https://example.com)") - test_result <- scrub_path(test_result) - expected_result <- c( - "/R/010-call.R", - "/tests/testthat/test-010-call.R", - "/R/paths-apis.R", - "/tests/testthat/test-paths-apis.R", - "/tests/testthat/setup.R" - ) - - expect_identical(test_result, expected_result) -}) From a2c6643b18de750e47c62c57333c530d45303ce3 Mon Sep 17 00:00:00 2001 From: Jon Harmon Date: Wed, 14 May 2025 06:01:20 -0500 Subject: [PATCH 05/33] Push to finish this eternal PR. --- .Rbuildignore | 1 + DESCRIPTION | 7 +- NAMESPACE | 5 - R/beekeeper-package.R | 4 +- R/generate_pkg-pagination.R | 3 + R/generate_pkg-paths.R | 207 ++++++++++++++++------- R/generate_pkg.R | 19 ++- R/utils.R | 17 ++ data-raw/oas_format_registry.R | 99 +++++++++++ data/oas_format_registry.rda | Bin 0 -> 861 bytes inst/talks/.gitignore | 2 + inst/talks/recruit.qmd | 125 ++++++++++++++ inst/talks/recruit_generation.R | 83 +++++++++ inst/talks/style.css | 33 ++++ man/generate_pkg.Rd | 2 +- tests/testthat/test-generate_pkg-agent.R | 6 - tests/testthat/test-generate_pkg-paths.R | 4 +- 17 files changed, 534 insertions(+), 83 deletions(-) create mode 100644 R/generate_pkg-pagination.R create mode 100644 data-raw/oas_format_registry.R create mode 100644 data/oas_format_registry.rda create mode 100644 inst/talks/.gitignore create mode 100644 inst/talks/recruit.qmd create mode 100644 inst/talks/recruit_generation.R create mode 100644 inst/talks/style.css delete mode 100644 tests/testthat/test-generate_pkg-agent.R diff --git a/.Rbuildignore b/.Rbuildignore index 61019bf..60b1b8c 100644 --- a/.Rbuildignore +++ b/.Rbuildignore @@ -12,3 +12,4 @@ ^_beekeeper_rapid\.rds$ ^exploration$ ^_slackapi_rapid\.rds$ +^data-raw$ diff --git a/DESCRIPTION b/DESCRIPTION index 9056e5e..8e0910e 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -14,10 +14,9 @@ License: MIT + file LICENSE URL: https://beekeeper.api2r.org, https://github.com/jonthegeek/beekeeper BugReports: https://github.com/jonthegeek/beekeeper/issues Depends: - R (>= 3.5.0) + R (>= 4.1.0) Imports: cli, - desc, fs, glue, httptest2, @@ -38,8 +37,11 @@ Imports: yaml Suggests: covr, + desc, + janitor, knitr, rmarkdown, + rvest, withr VignetteBuilder: knitr @@ -50,3 +52,4 @@ Config/testthat/edition: 3 Encoding: UTF-8 Roxygen: list(markdown = TRUE) RoxygenNote: 7.3.2 +LazyData: true diff --git a/NAMESPACE b/NAMESPACE index d9ba0b2..84cc768 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -8,13 +8,10 @@ importFrom(S7,class_data.frame) importFrom(S7,class_list) importFrom(cli,cli_abort) importFrom(cli,cli_warn) -importFrom(desc,desc) importFrom(fs,file_delete) importFrom(fs,file_exists) -importFrom(fs,is_dir) importFrom(fs,path) importFrom(fs,path_dir) -importFrom(fs,path_file) importFrom(fs,path_rel) importFrom(glue,glue) importFrom(glue,glue_collapse) @@ -22,7 +19,6 @@ importFrom(httptest2,use_httptest2) importFrom(nectar,stabilize_string) importFrom(purrr,discard) importFrom(purrr,imap) -importFrom(purrr,list_rbind) importFrom(purrr,map) importFrom(purrr,map2) importFrom(purrr,map_chr) @@ -45,7 +41,6 @@ importFrom(stringr,str_squish) importFrom(stringr,str_to_sentence) importFrom(styler,style_file) importFrom(testthat,test_that) -importFrom(tibble,as_tibble) importFrom(tidyr,nest) importFrom(tidyr,unnest) importFrom(usethis,proj_get) diff --git a/R/beekeeper-package.R b/R/beekeeper-package.R index 0dea3e5..5c82f68 100644 --- a/R/beekeeper-package.R +++ b/R/beekeeper-package.R @@ -4,13 +4,10 @@ ## usethis namespace: start #' @importFrom cli cli_abort #' @importFrom cli cli_warn -#' @importFrom desc desc #' @importFrom fs file_delete #' @importFrom fs file_exists -#' @importFrom fs is_dir #' @importFrom fs path #' @importFrom fs path_dir -#' @importFrom fs path_file #' @importFrom fs path_rel #' @importFrom glue glue #' @importFrom glue glue_collapse @@ -44,6 +41,7 @@ #' @importFrom styler style_file #' @importFrom testthat test_that #' @importFrom tidyr nest +#' @importFrom tidyr unnest #' @importFrom usethis proj_get #' @importFrom usethis proj_path #' @importFrom usethis use_build_ignore diff --git a/R/generate_pkg-pagination.R b/R/generate_pkg-pagination.R new file mode 100644 index 0000000..9de231e --- /dev/null +++ b/R/generate_pkg-pagination.R @@ -0,0 +1,3 @@ +.generate_pagination <- function() { + stop("TODO: FIGURE THIS OUT") +} diff --git a/R/generate_pkg-paths.R b/R/generate_pkg-paths.R index 80885db..ad067cb 100644 --- a/R/generate_pkg-paths.R +++ b/R/generate_pkg-paths.R @@ -1,11 +1,12 @@ -.generate_paths <- function(paths, api_abbr, security_data, base_url) { - paths_by_tag <- as_bk_data(paths) +.generate_paths <- function(paths, api_abbr, security_data, pagination_data, base_url) { + paths_by_operation <- as_bk_data(paths) paths_file_paths <- character() - if (length(paths_by_tag)) { + if (length(paths_by_operation)) { paths_file_paths <- .generate_paths_files( - paths_by_tag, + paths_by_operation, api_abbr, - security_data + security_data, + pagination_data ) setup_file <- .bk_use_template( template = "setup.R", @@ -23,32 +24,34 @@ S7::method(as_bk_data, class_paths) <- function(x) { if (!length(x)) { return(list()) } - paths_tags_df <- .paths_to_tags_df(as_tibble(x)) - return(.paths_to_tag_list(paths_tags_df)) + paths_df <- .paths_to_clean_df(x) + return(tibble::deframe(paths_df)) } -.paths_to_tags_df <- function(x) { - x <- unnest(x, "operations") +.paths_to_clean_df <- function(x) { + x <- tibble::as_tibble(x) |> + tidyr::unnest("operations") x <- x[!x$deprecated, ] - x$tags[lengths(x$tags) == 0] <- "general" - x$tags <- purrr::map_chr(x$tags, 1) - nest( - x, - .by = "tags", .key = "endpoints" + x$deprecated <- NULL + x$tags <- .paths_fill_tags(x$tags) + x$operation_id <- .paths_fill_operation_id( + x$operation_id, + x$endpoint, + x$operation ) -} - -## to tag list ----------------------------------------------------------------- -.paths_to_tag_list <- function(paths_tags_df) { - set_names( - map( - paths_tags_df$endpoints, - .paths_endpoints_to_lists - ), - .to_snake(paths_tags_df$tags) + x$summary <- .paths_fill_summary( + x$summary, + x$endpoint, + x$operation ) + x$description = .paths_fill_descriptions(x$description, x$summary) + # TODO: Deal with x$global_parameters if present + x$parameters <- purrr::map(x$parameters, .prepare_params_df) + # TODO: add breakdown by location + return(tidyr::nest(x, .by = "operation_id")) } +## to list --------------------------------------------------------------------- .paths_endpoints_to_lists <- function(endpoints) { pmap( list( @@ -73,6 +76,12 @@ S7::method(as_bk_data, class_paths) <- function(x) { ### fill data ------------------------------------------------------------------ +.paths_fill_tags <- function(tags) { + tags[lengths(tags) == 0] <- "general" + tags <- purrr::map_chr(tags, 1) + return(.to_snake(tags)) +} + .paths_fill_operation_id <- function(operation_id, endpoint, method) { .coalesce(.to_snake(operation_id), glue("{method}_{.to_snake(endpoint)}")) } @@ -93,7 +102,7 @@ S7::method(as_bk_data, class_paths) <- function(x) { description, params_df, method) { - params_df <- .prepare_paths_df(params_df) + params_df <- .prepare_params_df(params_df) return( list( operation_id = operation_id, @@ -102,18 +111,32 @@ S7::method(as_bk_data, class_paths) <- function(x) { summary = summary, description = description, params = .params_to_list(params_df), - params_query = .extract_params_type(params_df, "query"), - params_header = .extract_params_type(params_df, "header"), - params_cookie = .extract_params_type(params_df, "cookie") + params_query = .extract_params_by_location(params_df, "query"), + params_header = .extract_params_by_location(params_df, "header"), + params_cookie = .extract_params_by_location(params_df, "cookie") ) ) } -.prepare_paths_df <- function(params_df) { +.prepare_params_df <- function(params_df) { + params_df <- .flatten_params_df(params_df) + if (nrow(params_df)) { + params_df$description <- .paths_complete_param_descriptions( + descriptions = params_df$description, + params_schema = params_df$schema, + required = params_df$required, + allow_empty = params_df$allowEmptyValue + ) + } + params_df$schema <- NULL + params_df$style <- NULL + return(params_df) +} + +.flatten_params_df <- function(params_df) { params_df <- .flatten_df(params_df) if (nrow(params_df)) { params_df <- params_df[!params_df$deprecated, ] - params_df$description <- .paths_fill_descriptions(params_df$description) } return(params_df) } @@ -133,18 +156,75 @@ S7::method(as_bk_data, class_paths) <- function(x) { return(params) } -.extract_params_type <- function(params_df, filter_in) { +.extract_params_by_location <- function(params_df, filter_in) { if (!nrow(params_df)) { return(character()) } return(params_df$name[params_df$`in` == filter_in]) } -.paths_fill_descriptions <- function(descriptions) { - descriptions[is.na(descriptions)] <- "BKTODO: No description provided." +.paths_fill_descriptions <- function(descriptions, summaries) { + descriptions[is.na(descriptions)] <- summaries[is.na(descriptions)] + descriptions[is.na(descriptions)] <- "" return(str_squish(descriptions)) } +.describe_param_classes <- function(params_schema, + allow_empty, + required) { + # TODO: Use enum and/or description when available. + # + # TODO: What should we do for `object` and `array`? + type <- dplyr::left_join( + dplyr::select(params_schema, "type", "format"), + oas_format_registry, + by = c("type", "format") + ) + type$r_class_name_display <- stringr::str_remove( + glue::glue("{type$r_class_package}::{type$r_class_name}"), + "^base::" + ) + return(.compile_param_class_descriptions(type, allow_empty, required)) +} + +.compile_param_class_descriptions <- function(type, allow_empty, required) { + r_class_descriptions <- .glue_pipe_brace( + "length-1 \\code{\\link[|{type$r_class_package}|:|{type$r_class_link}|]{|{type$r_class_name_display}|}}" + ) |> + .paste0_if( + allow_empty, + " or \\code{\\link[base:NULL]{NULL}}" + ) |> + .paste0_if( + !required, + ", optional" + ) + + return(r_class_descriptions) +} + +.paths_complete_param_descriptions <- function(descriptions, + params_schema, + allow_empty, + required) { + r_class_descriptions <- .describe_param_classes( + params_schema, + allow_empty, + required + ) + + descriptions <- .paths_fill_descriptions( + descriptions, + params_schema$description + ) + + return( + stringr::str_trim( + .glue_pipe_brace("(|{r_class_descriptions}|) |{descriptions}|") + ) + ) +} + .paths_param_to_list <- function(name, description) { list( name = name, @@ -167,41 +247,54 @@ S7::method(as_bk_data, class_paths) <- function(x) { # generate files ---------------------------------------------------------- -.generate_paths_files <- function(paths_by_tag, api_abbr, security_data) { +.generate_paths_files <- function(paths_by_operation, + api_abbr, + security_data, + pagination_data) { unlist(imap( - paths_by_tag, - function(path_tag, path_tag_name) { - .generate_paths_tag_files( - path_tag, - path_tag_name, + paths_by_operation, + function(path_operation, path_operation_id) { + .generate_paths_operation_files( + path_operation, + path_operation_id, api_abbr, - security_data + security_data, + pagination_data ) } )) } -.generate_paths_tag_files <- function(path_tag, - path_tag_name, - api_abbr, - security_data) { - path_tag <- .prepare_path_tag( - path_tag, +.generate_paths_operation_files <- function(path_operation, + path_operation_id, + api_abbr, + security_data, + pagination_data) { + stop("Everything should be prepped before this, I think.") + path_operation <- .prepare_path_operation( + path_operation, security_data$security_arg_names ) file_path <- .generate_paths_file( - path_tag, - path_tag_name, + path_operation, + path_operation_id, api_abbr, - security_data + security_data, + pagination_data + ) + test_path <- .generate_paths_test_file( + path_operation, + path_operation_id, + pagination_data, + api_abbr ) - test_path <- .generate_paths_test_file(path_tag, path_tag_name, api_abbr) return(c(unname(file_path), unname(test_path))) } -.prepare_path_tag <- function(path_tag, security_args) { - path_tag <- map( - path_tag, +.prepare_path_operation <- function(path_operation, security_args) { + stop("Do this all in initial parsing? Everything is by operation now, which means 1 path per file; no need to map.") + path_operation <- map( + path_operation, function(path) { path$params <- .remove_security_args(path$params, security_args) path$params_cookie <- .prep_param_args(path$params_cookie, security_args) @@ -231,20 +324,20 @@ S7::method(as_bk_data, class_paths) <- function(x) { .collapse_comma_self_equal(setdiff(params, security_args)) %|"|% character() } -.generate_paths_file <- function(path_tag, +.generate_paths_file <- function(path, path_tag_name, api_abbr, security_data) { .bk_use_template( template = "paths.R", - data = list( - paths = path_tag, + data = c( + path, api_abbr = api_abbr, has_security = security_data$has_security, security_signature = security_data$security_signature, security_arg_list = security_data$security_arg_list ), - target = glue("paths-{path_tag_name}.R") + target = glue("paths-{path_tag_name}-{path$operation_id}.R") ) } diff --git a/R/generate_pkg.R b/R/generate_pkg.R index ec8d817..fec5884 100644 --- a/R/generate_pkg.R +++ b/R/generate_pkg.R @@ -32,14 +32,19 @@ generate_pkg <- function(config_file = "_beekeeper.yml", api_definition@components@security_schemes ) prep_files <- .generate_prepare(config, api_definition, security_data) - # stop("Note: We do NOT need pagination before prepare; pagination is used in paths.") - # stop("But we DO need to sort out '030-pagination.R' before paths.") + pagination_data <- .generate_pagination() path_files <- .generate_paths( - api_definition@paths, - config$api_abbr, - security_data, - api_definition@servers@url + paths = api_definition@paths, + api_abbr = config$api_abbr, + security_data = security_data, + pagination_data = pagination_data, + base_url = api_definition@servers@url + ) + touched_files <- c( + prep_files, + security_data$security_file_path, + pagination_data$pagination_file_path, + path_files ) - touched_files <- c(prep_files, security_data$security_file_path, path_files) return(invisible(touched_files)) } diff --git a/R/utils.R b/R/utils.R index f8d1347..d640a69 100644 --- a/R/utils.R +++ b/R/utils.R @@ -30,6 +30,23 @@ stringr::str_flatten_comma(paste0('"', x, '"')) } +.paste0_if <- function(original, test, addition) { + ifelse( + test, + paste0(original, addition), + original + ) +} + +.glue_pipe_brace <- function(..., .envir = rlang::caller_env()) { + glue::glue( + ..., + .open = "|{", + .close = "}|", + .envir = .envir + ) +} + .to_snake <- function(x) { to_snake_case(x, parsing_option = 3) } diff --git a/data-raw/oas_format_registry.R b/data-raw/oas_format_registry.R new file mode 100644 index 0000000..7d163f7 --- /dev/null +++ b/data-raw/oas_format_registry.R @@ -0,0 +1,99 @@ +oas_format_registry_raw <- rvest::read_html("https://spec.openapis.org/registry/format/") + +oas_format_registry <- + oas_format_registry_raw |> + rvest::html_table() |> + _[[1]] |> + janitor::clean_names() |> + dplyr::mutate( + json_data_type = stringr::str_split(.data$json_data_type, ", "), + source = dplyr::na_if(.data$source, ""), + deprecated = .data$deprecated == "Yes" + ) |> + tidyr::unnest_longer("json_data_type") |> + dplyr::select( + type = "json_data_type", + format = "value" #, UNCOMMENT THESE TO DIG INTO FORMATS + # "description", + # "source", + # "deprecated" + ) |> + dplyr::arrange(.data$type, .data$format) |> + dplyr::bind_rows( + data.frame( + type = c( + "boolean", + "integer", + "null", + "number", + "string" + ) + ) + ) |> + dplyr::mutate( + # TODO: Sort out exactly how this works. tibblify will process things. Can + # I make it give these things a class, and then stabilize back and forth + # via that? + # + # nectar re-exports from stbl, and adds HTTP-specific wrappers + to_r = dplyr::case_when( + ## Null + .data$type == "null" ~ "stabilize_null", + ## Numbers + .data$format == "int64" ~ "stabilize_int64", + stringr::str_detect(.data$format, "int") ~ "stabilize_int", + stringr::str_detect(.data$type, "int") ~ "stabilize_int", + .data$type == "number" ~ "stabilize_dbl", + stringr::str_detect(.data$format, "decimal") ~ "stabilize_dbl", + ## Dates and times + .data$format %in% c("date", "http-date") ~ "stabilize_date", + .data$format == "date-time" ~ "stabilize_datetime", + .data$format == "duration" ~ "stabilize_duration", + .data$format == "time" ~ "stabilize_time", + ## Binary + .data$format %in% c("byte", "sf-binary") ~ "stabilize_base64_to_chr", + .data$format == "binary" ~ "stabilize_binary_to_raw", + .data$format == "base64url" ~ "stabilize_base64url_to_chr", + ## Logical + .data$format == "sf-boolean" ~ "stabilize_structured_lgl", + .data$type == "boolean" ~ "stabilize_lgl", + ## Specific Strings + .data$format == "uuid" ~ "stabilize_uuid", + # TODO: Add more specific string formats. + .default = "stabilize_chr" + ), + r_class_name = dplyr::case_match( + .data$to_r, + "stabilize_base64_to_chr" ~ "character", + "stabilize_base64url_to_chr" ~ "character", + "stabilize_binary_to_raw" ~ "raw", + "stabilize_chr" ~ "character", + "stabilize_date" ~ "Date", + "stabilize_datetime" ~ "POSIXct", + "stabilize_dbl" ~ "double", + "stabilize_duration" ~ "Duration", + "stabilize_int" ~ "integer", + "stabilize_int64" ~ "integer64", + "stabilize_lgl" ~ "logical", + "stabilize_null" ~ "NULL", + "stabilize_structured_lgl" ~ "logical", + "stabilize_time" ~ "hms", + "stabilize_uuid" ~ "UUID" + ), + r_class_package = dplyr::case_match( + .data$r_class_name, + "Duration" ~ "lubridate", + "integer64" ~ "bit64", + "hms" ~ "hms", + "UUID" ~ "uuid", + .default = "base" + ), + r_class_link = dplyr::case_match( + .data$r_class_name, + "Duration" ~ "Duration-class", + .default = .data$r_class_name + ) + ) + +usethis::use_data(oas_format_registry, overwrite = TRUE) +rm(oas_format_registry_raw, oas_format_registry) diff --git a/data/oas_format_registry.rda b/data/oas_format_registry.rda new file mode 100644 index 0000000000000000000000000000000000000000..dd1bd86d6abf42c2df15b4e60edada4486e399a0 GIT binary patch literal 861 zcmV-j1ETywT4*^jL0KkKS)}ZJg#ZPA|HS|Q+Ry+4JwOE|-axH00aO5&;yWm`}Xvv9y35dXrFd)QWnJ@^$nlfTw0%9;DOb9U; zCQJe_=8Tw_0GNyj(*g`e36lVf5+oGBQ#2-1XcY7(sp&FkX|*&M)HXFrJ}8)Fs~kZf zs|Bub03-95K$p>=6F!H-PsX)DV5!H>iFq(M-Sh661cYYLTSK{@%%BDarU$TSY({M( z2YZ^YhyAjF%{iEUG^Q4KTu`uN!TyGDQerCB@d499DHJi0Ayf}|$g;7bP)q`25#(8| zNG&Qr>fjzxS2aC$%5G#o30Xc%Do^TL`(G-|w48S}3 zNgpG7LneMM99$$14m6mYOHMVuX~W`FLHxv%2B`@h58Bc@oDI<3BiUE&m#&CH?YK!E z|0C=T680j(JW@!U);=i$$rn;@Nj$;@Xi2S!BzT|0(1q>p$1rML9`iuT}q4t5Y}E4Gy3YtrXYoO zt-5t#70l~rHSnbvQUwlUt%&O1F$Koea3O6u`9qe%h|__g3mLJe{xfD7+v-kKZJfg@ za&z##STrE9=wOkI572a_YzMeS{=TVv#UH;bgj)iU6&!Nf1l#o!jP)yUD7$^Gzfawz&vBeVq%hB$ub& n*+0syyrlvUk|J{1|{`#CI2RML1B9r0ji#v+;mS literal 0 HcmV?d00001 diff --git a/inst/talks/.gitignore b/inst/talks/.gitignore new file mode 100644 index 0000000..5a73e2c --- /dev/null +++ b/inst/talks/.gitignore @@ -0,0 +1,2 @@ +recruit_files +*.html diff --git a/inst/talks/recruit.qmd b/inst/talks/recruit.qmd new file mode 100644 index 0000000..1bc9a6d --- /dev/null +++ b/inst/talks/recruit.qmd @@ -0,0 +1,125 @@ +--- +title: | + Building the Hive: Collaborating on API Packages with {beekeeper} +css: style.css +format: + revealjs: + theme: dark + # logo: images/robodeck-logo.png + footer: api2r.org/ghana2025 | Jon Harmon | @jonthegeek + link-external-newwindow: true + transition: slide + incremental: true +--- + +# Introduction and Motivation + +## How I Use APIs + +- 📊 Web APIs are powerful tools in R +- 🤔 But wouldn't it be great to have them more accessible in R code? + +![Illustration of a person using a computer](person-computer.png) + +## API Developer Docs: Where's R? + +- 📚 Always referring back to the API documentation +- 💡 Imagine if it was all in one neat R package + +![Stack of developer papers](developer-docs.png) + +## The Vision for {beekeeper} + +- 🐝 {beekeeper} simplifies API package creation +- 🌟 Helps maintainers follow best practices +- 🚀 Let's create user-friendly, reliable packages together + +![Illustration of bees buzzing around a hive](bees-hive.png) + +# Creating an API Package with {beekeeper} + +## Finding the API Spec + +- 👀 Locating the API documentation +- 📝 Understanding the required endpoints and parameters + +![API documentation screenshot](api-docs.png) + +## usethis::create_package() + +- #install.packages("usethis") +- 🧩 Setting up the R package structure +- 🏗️ Organizing files and directories effortlessly + +## beekeeper::use_beekeeper() + +- #install.packages("beekeeper") +- 🐝 Initializing {beekeeper} in our package +- 🚦 Kickstarting the API package creation process + +![Illustration of a beekeeper creating a package](beekeeper-package.png) + +## beekeeper::generate_pkg() + +- 📦 Generating the initial package scaffolding +- 🔍 Ensuring a consistent layout and structure + +# What Works Today + +## Raw generate_pkg() Output + +- 🚀 Initial package structure created +- 🕵️‍♀️ Exploring the generated files and directories + +## Improving Parameter Documentation + +- 📋 Enhancing documentation for API parameters +- 🌈 Making it easier for users to understand and interact + +![Document with highlighted text](parameter-docs.png) + +## Response Parsing with {tibblify} + +- #install.packages("tibblify") +- 📊 Converting API responses to tidy data frames +- 📈 Simplifying data manipulation and analysis tasks + +## Testing & Iterating + +- 🧪 Testing our package functionality +- 🔁 Iterating on improvements and bug fixes + +# What's Next + +## What I Have Planned + +- 📅 Roadmap for {beekeeper} enhancements +- 💡 Exciting features in the pipeline + +## What I Don't Know + +- 🤔 Seeking input and feedback from the community +- 🤝 Collaborating to address unknown challenges + +# Collaboration and Community + +## Promoting Your Package + +- 📣 Showcasing your API package +- 🎯 Reaching out to potential users and contributors + +## Feature Requests and Testing + +- 📝 Gathering feedback and feature requests +- 🛠 Collaborating on testing and improving {beekeeper} + +## How You Can Contribute to {beekeeper} + +- ❓ Ways to get involved +- 🐝 Help shape the future of API package development in R + +# Get In Touch Right Now! + +- [{beekeeper}: beekeeper.api2r.org/](https://beekeeper.api2r.org/) +- [LinkedIn: @jonthegeek](https://www.linkedin.com/in/jonthegeek/) +- [Data Science Learning Community](https://DSLC.io) diff --git a/inst/talks/recruit_generation.R b/inst/talks/recruit_generation.R new file mode 100644 index 0000000..71f6e38 --- /dev/null +++ b/inst/talks/recruit_generation.R @@ -0,0 +1,83 @@ +# pak::pak("jonthegeek/robodeck") +# +# Use {robodeck} to help generate an initial version of a slide deck. + +library(robodeck) +title <- "Building the Hive: Collaborating on API Packages with {beekeeper}" +description <- + "Do you use a web API in R, or rely on a particular tool on the web and wish that you could access it in your R code? Do you often find yourself consulting the documentation to remember how to access that tool? Do you wish there was an R package to make all of that easier? Let's work together to create that package! +{beekeeper} is a new package to help you create and maintain an R package to wrap your favorite web API. It takes care of the \"drone work\" of API package creation, so you can quickly generate a package and make sure it's easy-to-use for you and others in the R community. It applies an opinionated framework to help you follow best practices, like consistently documenting parameters, and testing your package to make sure it works how you think it works. By working together and sharing experiences, we can make {beekeeper} even better and ensure it addresses real-world challenges developers face. +This talk is not just about introducing {beekeeper}, but also about building a supportive community of developers who can help each other succeed. Let’s collaborate to create reliable, user-friendly, API-wrapping R packages." |> + stringr::str_squish() + +section_titles <- robodeck::gen_deck_section_titles( + title = "Building the Hive: Collaborating on API Packages with {beekeeper}", + description = description, + minutes = 30, + model = "gpt-4o" +) + +section_titles <- list( + list(title = "Introduction and Motivation", minutes = 6), + list(title = "Creating an API Package with {beekeeper}", minutes = 6), + list(title = "What Works Today", minutes = 6), + list(title = "What's Next", minutes = 6), + list(title = "Collaboration and Community", minutes = 6) +) + +outline <- robodeck::gen_deck_outline( + title = title, + description = description, + minutes = 30, + model = "gpt-4o", + section_titles = section_titles +) + +outline <- list( + `Introduction and Motivation` = c( + "How I Use APIs", + "API Developer Docs: Where's R?", + "The Vision for {beekeeper}" + ), + `Creating an API Package with {beekeeper}` = c( + "Finding the API Spec", + "usethis::create_package()", + "beekeeper::use_beekeeper()", + "beekeeper::generate_pkg()" + ), + `What Works Today` = c( + "Raw generate_pkg() Output", + "Improving Parameter Documentation", + "Response Parsing with {tibblify}", + "Testing & Iterating" + ), + `What's Next` = c( + "What I Have Planned", + "What I Don't Know" + ), + `Collaboration and Community` = c( + "Promoting Your Package", + "Feature Requests and Testing", + "How You Can Contribute to {beekeeper}" + ) +) + +talk <- robodeck::gen_deck( + title = title, + description = description, + minutes = 30, + model = "gpt-4o", + section_titles = section_titles, + outline = outline, + additional_information = "The goal of the talk is to recruit early adopters to develop their own API packages with {beekeeper}. The tone of the talk should be fun and upbeat. Use an emoji at the start of every bullet in bulleted lists." +) + +talk2 <- robodeck::gen_deck( + title = title, + description = description, + minutes = 30, + model = "gpt-4o", + section_titles = section_titles, + outline = outline, + additional_information = "The goal of the talk is to recruit early adopters to develop their own API packages with {beekeeper}. The tone of the talk should be fun and upbeat. Use an emoji at the start of every bullet in bulleted lists. Minimize use of code blocks when possible." +) diff --git a/inst/talks/style.css b/inst/talks/style.css new file mode 100644 index 0000000..ae03a89 --- /dev/null +++ b/inst/talks/style.css @@ -0,0 +1,33 @@ +/* +h1.title { + font-size: 2em !important; +} +*/ +.reveal .footer { + font-size: 30px !important; +} + +.reveal .slide-logo { + max-height: unset !important; + height: 70px !important; +} + +.reveal .title-slide { + display: flex; + align-items: center; /* Vertically center the content */ + justify-content: space-between; /* Horizontally space out content */ +} + +.title-slide img { + width: 40%; + float: right; /* Keeps the image to the right */ +} + +.title-slide .title-text { + width: 50%; /* Adjust width as necessary */ + margin-left: 5%; /* Add some spacing between text and image */ +} + +strong { + color: #A0E9FF; +} diff --git a/man/generate_pkg.Rd b/man/generate_pkg.Rd index 90164be..d1c261c 100644 --- a/man/generate_pkg.Rd +++ b/man/generate_pkg.Rd @@ -1,5 +1,5 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/generate_pkg_main.R +% Please edit documentation in R/generate_pkg.R \name{generate_pkg} \alias{generate_pkg} \title{Use a beekeeper config file to generate code} diff --git a/tests/testthat/test-generate_pkg-agent.R b/tests/testthat/test-generate_pkg-agent.R deleted file mode 100644 index 443c5d6..0000000 --- a/tests/testthat/test-generate_pkg-agent.R +++ /dev/null @@ -1,6 +0,0 @@ -test_that("generate_pkg_agent() generates package agents", { - expect_identical( - generate_pkg_agent(test_path("_fixtures", "DESCRIPTION")), - "beekeeper (https://beekeeper.api2r.org)" - ) -}) diff --git a/tests/testthat/test-generate_pkg-paths.R b/tests/testthat/test-generate_pkg-paths.R index d5e31b7..d3b3243 100644 --- a/tests/testthat/test-generate_pkg-paths.R +++ b/tests/testthat/test-generate_pkg-paths.R @@ -80,7 +80,7 @@ test_that("generate_pkg() generates path functions for fec", { writeLines(config, "_beekeeper.yml") saveRDS(fec_rapid, "fec_subset_rapid.rds") - changed_files <- generate_pkg(pkg_agent = "TESTPKG (https://example.com)") + changed_files <- generate_pkg() expect_snapshot(scrub_path(changed_files)) generated_file_content <- readLines("R/paths-audit.R") @@ -100,7 +100,7 @@ test_that("generate_pkg() generates path functions for trello", { writeLines(config, "_beekeeper.yml") saveRDS(trello_rapid, "trello_rapid.rds") - generate_pkg(pkg_agent = "TESTPKG (https://example.com)") + generate_pkg() generated_file_content <- readLines("R/paths-board.R") expect_identical(generated_file_content, expected_file_content) From dec0dcd0de501f880ce4aad2a85d7357a09d3433 Mon Sep 17 00:00:00 2001 From: Jon Harmon Date: Wed, 18 Jun 2025 05:51:22 -0500 Subject: [PATCH 06/33] Commit everything. I want to move the repo. --- inst/talks/{ => recruit}/.gitignore | 0 inst/talks/recruit/_publish.yml | 4 ++ .../talks/recruit/images/Apple_logo_white.svg | 4 ++ inst/talks/recruit/images/Google__G__Logo.svg | 1 + inst/talks/recruit/images/Zoom_Logo_2022.svg | 1 + inst/talks/{ => recruit}/recruit.qmd | 41 +++++++++++++++++++ inst/talks/{ => recruit}/recruit_generation.R | 0 inst/talks/{ => recruit}/style.css | 0 inst/templates/030-pagination.R | 9 ++-- 9 files changed, 56 insertions(+), 4 deletions(-) rename inst/talks/{ => recruit}/.gitignore (100%) create mode 100644 inst/talks/recruit/_publish.yml create mode 100644 inst/talks/recruit/images/Apple_logo_white.svg create mode 100644 inst/talks/recruit/images/Google__G__Logo.svg create mode 100644 inst/talks/recruit/images/Zoom_Logo_2022.svg rename inst/talks/{ => recruit}/recruit.qmd (68%) rename inst/talks/{ => recruit}/recruit_generation.R (100%) rename inst/talks/{ => recruit}/style.css (100%) diff --git a/inst/talks/.gitignore b/inst/talks/recruit/.gitignore similarity index 100% rename from inst/talks/.gitignore rename to inst/talks/recruit/.gitignore diff --git a/inst/talks/recruit/_publish.yml b/inst/talks/recruit/_publish.yml new file mode 100644 index 0000000..f873076 --- /dev/null +++ b/inst/talks/recruit/_publish.yml @@ -0,0 +1,4 @@ +- source: recruit.qmd + quarto-pub: + - id: 8f439c7b-183f-42e3-b9ed-c04c332f2607 + url: https://jonthegeek.quarto.pub/building-the-hive diff --git a/inst/talks/recruit/images/Apple_logo_white.svg b/inst/talks/recruit/images/Apple_logo_white.svg new file mode 100644 index 0000000..133f976 --- /dev/null +++ b/inst/talks/recruit/images/Apple_logo_white.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/inst/talks/recruit/images/Google__G__Logo.svg b/inst/talks/recruit/images/Google__G__Logo.svg new file mode 100644 index 0000000..4cf163b --- /dev/null +++ b/inst/talks/recruit/images/Google__G__Logo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/inst/talks/recruit/images/Zoom_Logo_2022.svg b/inst/talks/recruit/images/Zoom_Logo_2022.svg new file mode 100644 index 0000000..3990852 --- /dev/null +++ b/inst/talks/recruit/images/Zoom_Logo_2022.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/inst/talks/recruit.qmd b/inst/talks/recruit/recruit.qmd similarity index 68% rename from inst/talks/recruit.qmd rename to inst/talks/recruit/recruit.qmd index 1bc9a6d..dc48cf2 100644 --- a/inst/talks/recruit.qmd +++ b/inst/talks/recruit/recruit.qmd @@ -12,6 +12,47 @@ format: incremental: true --- +# 🎯 What is a web API? + +## 📘 APIs are: {transition="slide-in none-out"} + +🤖↔️🤖 Services that enable communication between computer programs. + +::: notes +- "Application programming interface." (but almost always API) +- You might hear the arguments to a function referred to as the "API" of that function. +::: + +## 📘 Web APIs are: {transition="none-in slide-out"} + +🤖↔️🤖 Services that enable communication between computer programs *on the internet.* + +::: notes +- For the rest of this talk, "API" = "web API". +::: + +## 📆 You use web APIs every day! + +::: incremental +- "Login with ![](images/Google__G__Logo.svg){height="30"} \| ![](images/Apple_logo_white.svg){height="30" style="text-align: top;"}" +- "Create ![](images/Zoom_logo_2022.svg){height="20"} meeting" in work messenger +- "📅 Add to Calendar" +- *Technically: Load any web page == `GET`* +- Learn more about [Web APIs with R (DSLC.io/wapir)](https://DSLC.io/wapir) +::: + +::: notes +- (don't comment on logo alignment) +- Zoom: Or similar app integrations. +- Button on a web page, goes to Google or Apple calendar, etc. +- For "technically": + - When you fetch a web page, you're using the GET method to ask the server to send you the page. + - There are 5 main methods for web apis. + - GET, POST = create something new usually, PUT = replace, PATCH = edit, DELETE; often just GET and/or POST +- I'm writing a book! Slowly, but join DSLC.io to join the next cohort! +::: + + # Introduction and Motivation ## How I Use APIs diff --git a/inst/talks/recruit_generation.R b/inst/talks/recruit/recruit_generation.R similarity index 100% rename from inst/talks/recruit_generation.R rename to inst/talks/recruit/recruit_generation.R diff --git a/inst/talks/style.css b/inst/talks/recruit/style.css similarity index 100% rename from inst/talks/style.css rename to inst/talks/recruit/style.css diff --git a/inst/templates/030-pagination.R b/inst/templates/030-pagination.R index b637cb1..8fb47f5 100644 --- a/inst/templates/030-pagination.R +++ b/inst/templates/030-pagination.R @@ -1,8 +1,9 @@ -# These functions were generated by the {beekeeper} package, based on -# developer inputs. You may need to manually rename these functions or tweak -# their arguments. +# These functions were generated by the {beekeeper} package, based on developer +# inputs. You may need to manually rename these functions or tweak their +# arguments. # -# We currently only support JSON response bodies with cursor pagination. +# {beekeeper} currently only supports JSON response bodies with cursor +# pagination. {{#pagination_schemes}} #' Apply pagination From 5f98fb14574c8bf0af09ffc87fcc5ef01f245f99 Mon Sep 17 00:00:00 2001 From: Jon Harmon Date: Thu, 19 Jun 2025 08:49:55 -0500 Subject: [PATCH 07/33] Tweaks to talk. --- inst/talks/recruit/images/beekeeper.svg | 863 ++++++++++++++++++++++++ inst/talks/recruit/recruit.qmd | 314 ++++++--- inst/talks/recruit/style.css | 2 +- 3 files changed, 1082 insertions(+), 97 deletions(-) create mode 100644 inst/talks/recruit/images/beekeeper.svg diff --git a/inst/talks/recruit/images/beekeeper.svg b/inst/talks/recruit/images/beekeeper.svg new file mode 100644 index 0000000..9bf7e10 --- /dev/null +++ b/inst/talks/recruit/images/beekeeper.svg @@ -0,0 +1,863 @@ + +image/svg+xmlbeekeeperJon HarmonCC0 beekeeper diff --git a/inst/talks/recruit/recruit.qmd b/inst/talks/recruit/recruit.qmd index dc48cf2..ee7b9dc 100644 --- a/inst/talks/recruit/recruit.qmd +++ b/inst/talks/recruit/recruit.qmd @@ -1,166 +1,288 @@ --- -title: | - Building the Hive: Collaborating on API Packages with {beekeeper} css: style.css format: revealjs: theme: dark - # logo: images/robodeck-logo.png - footer: api2r.org/ghana2025 | Jon Harmon | @jonthegeek + logo: images/beekeeper.svg + footer: api2r.org/ghana2025 | Jon Harmon | @jonthegeek link-external-newwindow: true transition: slide incremental: true --- -# 🎯 What is a web API? - -## 📘 APIs are: {transition="slide-in none-out"} +# qr code pointing to DSLC.io/nyr2024Building the Hive: -🤖↔️🤖 Services that enable communication between computer programs. +[Collaborating on API Packages with {beekeeper}]{style="font-size: 1.5em"} ::: notes -- "Application programming interface." (but almost always API) -- You might hear the arguments to a function referred to as the "API" of that function. +- Thank Francis and the Ghana R Users Community +- Introduce myself +- Data Science Learning Community (DSLC.io) +- @jonthegeek (mostly BlueSky, LinkedIn, GitHub) +- How you can help me make APIs easier to use in R +- Slides at api2r.org/ghana2025 +- Old talk introducing APIs: DSLC.io/ghana202306 +- This talk about what I've been up to since ::: -## 📘 Web APIs are: {transition="none-in slide-out"} +# 🎯 What is a web API? -🤖↔️🤖 Services that enable communication between computer programs *on the internet.* +## 📘 APIs are: {transition="slide-in none-out"} + +🤖↔️🤖 Services that + +- Enable communication +- Between computer programs ::: notes -- For the rest of this talk, "API" = "web API". +- "Application programming interface." (but almost always API) +- "Services that enable communication between computer programs." +- You might hear the arguments to a function referred to as the "API" of that function. ::: -## 📆 You use web APIs every day! +## 📘 Web APIs are: {transition="none-in slide-out"} -::: incremental -- "Login with ![](images/Google__G__Logo.svg){height="30"} \| ![](images/Apple_logo_white.svg){height="30" style="text-align: top;"}" -- "Create ![](images/Zoom_logo_2022.svg){height="20"} meeting" in work messenger -- "📅 Add to Calendar" -- *Technically: Load any web page == `GET`* -- Learn more about [Web APIs with R (DSLC.io/wapir)](https://DSLC.io/wapir) +🤖↔️🤖 Services that + +::: nonincremental +- Enable communication +- Between computer programs +- *On the internet* ::: ::: notes -- (don't comment on logo alignment) -- Zoom: Or similar app integrations. -- Button on a web page, goes to Google or Apple calendar, etc. -- For "technically": - - When you fetch a web page, you're using the GET method to ask the server to send you the page. - - There are 5 main methods for web apis. - - GET, POST = create something new usually, PUT = replace, PATCH = edit, DELETE; often just GET and/or POST -- I'm writing a book! Slowly, but join DSLC.io to join the next cohort! +- For the rest of this talk, "API" = "web API". ::: +## 📆 You use web APIs every day! -# Introduction and Motivation - -## How I Use APIs +- "Login with ![](images/Google__G__Logo.svg){height="30"} | ![](images/Apple_logo_white.svg){height="30" style="text-align: top;"}" +- "Create ![](images/Zoom_logo_2022.svg){height="20"} meeting" in work messenger +- "📅 Add to Calendar" +- *Loading any web page == `GET`* -- 📊 Web APIs are powerful tools in R -- 🤔 But wouldn't it be great to have them more accessible in R code? +::: notes +- (don't comment on logo alignment) +- Zoom: Or similar app integrations. +- Button on a web page, goes to Google or Apple calendar, etc. +- For "Loading any": + - When you fetch a web page, you're using the GET method to ask the server to send you the page. + - There are 5 main methods for web apis. + - GET, POST = create something new usually, PUT = replace, PATCH = edit, DELETE; often just GET and/or POST -![Illustration of a person using a computer](person-computer.png) +::: -## API Developer Docs: Where's R? +## 🔍 How do you find APIs? -- 📚 Always referring back to the API documentation -- 💡 Imagine if it was all in one neat R package +- Use browser network tab + - Look for `fetch()` or `XHR` + - {apisniffer} experiment at [jonthegeek.github.io/apisniffer](https://jonthegeek.github.io/apisniffer/) +- [Find APIs](https://dslc-io.github.io/club-wapir/slides/apis-find.html) chapter + - Learn more about [Web APIs with R (DSLC.io/wapir)](https://DSLC.io/wapir) -![Stack of developer papers](developer-docs.png) +::: notes +- Network tab can reveal what the browser is calling behind the scenes. +- {apisniffer} is very experimental and I haven't updated it in a while, but hope to get back to it soon +- Lots more about finding APIs in my book! +- I'm writing a book! Join DSLC.io to join the next book club cohort! -## The Vision for {beekeeper} +::: -- 🐝 {beekeeper} simplifies API package creation -- 🌟 Helps maintainers follow best practices -- 🚀 Let's create user-friendly, reliable packages together -![Illustration of bees buzzing around a hive](bees-hive.png) +# 📄 How are APIs documented? -# Creating an API Package with {beekeeper} +## 👩️ Human vs 🤖 Machine -## Finding the API Spec +- 👩 : prose & examples +- 🤖 : structured specs +- **Swagger** = structured API specification +- **OpenAPI** = new name of Swagger -- 👀 Locating the API documentation -- 📝 Understanding the required endpoints and parameters +::: notes +- Humans and sometimes genAI now, but mostly humans. +- As a programmer, I also like structure, so I can look for standard things! +- Swagger was made for a dictionary site, to describe APIs in a standard way. I *think* they were working with multiple APIs and wanted to standardize them. +- Donated to the Linux Foundation in 2015. +- Renamed to the "OpenAPI Specification". +- Swagger 2.0 = OpenAPI 2.0 (there isn't really OpenAPI 1.0). +- Currently on v3.1.1. +::: -![API documentation screenshot](api-docs.png) +## 🔎 A sample OpenAPI spec (YAML/JSON) + +```yaml +openapi: 3.0.1 +info: + title: Sample API + version: 1.0.0 +paths: + /users: + get: + summary: Get all users + responses: + '200': + description: OK +``` -## usethis::create_package() +::: notes +- JSON = JavaScript Object Notation +- YAML = superset of JSON, more human-readable +- This is a tiny sample of what a full API spec might include. +- Version of the specification on line 1. Sometimes this will be `swagger:` instead of `openapi:` in old specs. +- `info` contains basic information about the API. Can include contact info, license, etc. +- `paths` section is where the API endpoints live — this one has `/users`. +- I mentioned API methods before. This path uses `get`. +- `200` means OK/success. +- Specs can also describe `servers`, `security`, reusable `components` for things that recur in the spec, and more. +- (If connection stable, tab to full spec) +- Key point: tools can parse this to talk to the API. +::: -- #install.packages("usethis") -- 🧩 Setting up the R package structure -- 🏗️ Organizing files and directories effortlessly +# 🐝 The api2r project -## beekeeper::use_beekeeper() +## 🚀 Goal: *Easier* API →️ R 📦 -- #install.packages("beekeeper") -- 🐝 Initializing {beekeeper} in our package -- 🚦 Kickstarting the API package creation process +- Common patterns in API R packages +- The system should: + - Parse the spec + - Scaffold reusable code + - Add docs and tests *by default* -![Illustration of a beekeeper creating a package](beekeeper-package.png) +::: notes +- I've wrapped APIs manually—it’s not fun. +- My goal: reduce effort, make it repeatable. +- We should start with the spec and generate most of the rest. +- Think of this as the {usethis} for API packages. +- R Consortium grant to create {api2r}. +- Ended up with three packages, each handling part of the process. +::: -## beekeeper::generate_pkg() +## 📦 Package 1: `{rapid}` -- 📦 Generating the initial package scaffolding -- 🔍 Ensuring a consistent layout and structure +- [rapid.api2r.org](https://rapid.api2r.org) +- Input: OpenAPI (JSON/YAML) +- Output: structured **R object** ([{S7}](https://rconsortium.github.io/S7/)) +- Can inspect/edit before pkg step -# What Works Today +::: notes +- R API Description +- `{rapid}` is the parser — it takes your OpenAPI spec and builds a structured R object. +- Can work with URLs to JSON or YAML specs, local spec files, or even in-memory lists. +- Uses the new S7 object system, which makes the output strict and composable. +- These objects are nested — one for the whole API, one per path, one per method, etc. +- You can look at or manipulate them before building the actual package. +- That’s useful if the API is too big or you only want a few endpoints, or if you want to edit the authentication. +::: -## Raw generate_pkg() Output +## 📦 Package 2: `{beekeeper}` -- 🚀 Initial package structure created -- 🕵️‍♀️ Exploring the generated files and directories +- [beekeeper.api2r.org](https://beekeeper.api2r.org) +- Input: a `rapid` object +- Output: Full R **package skeleton** + - Standardized functions for every API path + method + - Full {roxygen2} docs + - Starter {testthat} / {httptest2} tests -## Improving Parameter Documentation +::: notes +- apis = Latin name for "bee", so beekeepers work with "apis". Also fits with package hex logos. +- `{beekeeper}` takes the parsed API and scaffolds a complete package. +- That includes function files, tests, docs, and a DESCRIPTION file. +- Mostly uses the next package we'll talk about for function calls. +- You'll need to refine manually, but it gives a solid foundation. +- I'm actively working on a big update, watch the repo! +::: -- 📋 Enhancing documentation for API parameters -- 🌈 Making it easier for users to understand and interact +## 📦 Package 3: `{nectar}` -![Document with highlighted text](parameter-docs.png) +- [nectar.api2r.org](https://nectar.api2r.org) +- Used inside generated packages +- Wraps {httr2}, {tibblify}, {stbl} +- Helper functions for: + - Making requests + - Handling pagination/retries + - Tidying responses -## Response Parsing with {tibblify} +::: notes +- `{nectar}` is an *opinionated* helper package. + - Opinionated = Do things the way I think you should (slightly lower flexibility vs httr2). +- Wraps my {stbl} package to "stabilize" inputs to expected formats, or error before hitting the API if inputs won't work. +- Wraps {httr2} for common API tasks. +- Optionally wraps {tibblify} to make it easier to get tidy outputs. +- Also includes helpers for common pain points like pagination and retries. +- Keeps generated packages clean and consistent. +::: -- #install.packages("tibblify") -- 📊 Converting API responses to tidy data frames -- 📈 Simplifying data manipulation and analysis tasks +## 🔁 Full process -## Testing & Iterating +```r +spec <- rapid::as_rapid("https://my.api/openapi.yaml") +beekeeper::use_beekeeper(spec, "myapi") +beekeeper::generate_pkg() +``` -- 🧪 Testing our package functionality -- 🔁 Iterating on improvements and bug fixes +- Generated packages are *R packages* +- Edit and maintain as usual! -# What's Next +::: notes +- This is the whole flow in 3 lines! +- `as_rapid()` reads & parses the OpenAPI spec. +- `use_beekeeper()` sets up the package configuration and logs info about the spec. Gives opportunity to tweak before generation. +- `generate_pkg()` writes all the package files, including docs and tests. +- After that, it’s just like any other R package. +- The goal is not to avoid editing — it’s to give you a head start. +::: -## What I Have Planned +# 🤝 How can you help? -- 📅 Roadmap for {beekeeper} enhancements -- 💡 Exciting features in the pipeline +## 🧩 Do you work with a weird API? -## What I Don't Know +- Data science, gov, research, etc +- Lots of APIs aren't R-friendly +- Let's fix that! -- 🤔 Seeking input and feedback from the community -- 🤝 Collaborating to address unknown challenges +::: notes +- I *want* to collaborate. +- If you’ve wrestled with an obscure or homegrown API, that’s exactly what I want to hear about. +- Right now the API needs to have an OpenAPI or Swagger spec. +- Right now I need people who want to dig in and create their own package. +::: -# Collaboration and Community +## 🔍 Find an OpenAPI spec -## Promoting Your Package +- Start with the docs! +- Try `/swagger.json`, `/openapi.json` + - `/swagger.yaml`, `/swagger.yml`, `/openapi.yaml`, `/openapi.yml` +- ChatGPT (etc) pretty good at spotting specs -- 📣 Showcasing your API package -- 🎯 Reaching out to potential users and contributors +::: notes +- If specs have a common structure & place to test the code, it’s probably using OpenAPI under the hood. +- Also try .yaml, .yml. +- Even if the spec isn’t linked, it might be discoverable. +::: -## Feature Requests and Testing +## 🐝 What to do -- 📝 Gathering feedback and feature requests -- 🛠 Collaborating on testing and improving {beekeeper} +- Visit [beekeeper.api2r.org](https://beekeeper.api2r.org/) +- Click "Report a bug" +- Open a new issue for your API -## How You Can Contribute to {beekeeper} +::: notes +- You can find all these links at api2r.org/ghana2025. +- I'd love to know what kinds of APIs you want in R. +- The more real users I hear from, the better I can shape the tools. +::: -- ❓ Ways to get involved -- 🐝 Help shape the future of API package development in R +# 🙏 Thanks! -# Get In Touch Right Now! +::: nonincremental +- 🐙 GitHub: [github.com/jonthegeek/beekeeper](https://github.com/jonthegeek/beekeeper) +- 🐝 Website: [beekeeper.api2r.org](https://beekeeper.api2r.org) +- 📬 Contact: [@jonthegeek](https://bsky.app/profile/jonthegeek.com), [DSLC.io](https://DSLC.io) +::: -- [{beekeeper}: beekeeper.api2r.org/](https://beekeeper.api2r.org/) -- [LinkedIn: @jonthegeek](https://www.linkedin.com/in/jonthegeek/) -- [Data Science Learning Community](https://DSLC.io) +::: notes +- Thanks again to Francis and the Ghana R community. +- Please reach out with questions, ideas, or weird APIs! +- Hope to see some collaboration soon. +- For Q&A: I'd love to hear about any APIs people use or want to use! +::: diff --git a/inst/talks/recruit/style.css b/inst/talks/recruit/style.css index ae03a89..dfae54e 100644 --- a/inst/talks/recruit/style.css +++ b/inst/talks/recruit/style.css @@ -19,7 +19,7 @@ h1.title { } .title-slide img { - width: 40%; + width: 25%; float: right; /* Keeps the image to the right */ } From 1939d7c860efa8785c164a912a41917b56da4e3a Mon Sep 17 00:00:00 2001 From: Jon Harmon Date: Tue, 28 Apr 2026 09:04:41 -0500 Subject: [PATCH 08/33] nectar::stabilize_string() no longer exists This is in a really bad state, ugh! --- .Rbuildignore | 2 ++ .gitignore | 1 + DESCRIPTION | 12 ++++++------ NAMESPACE | 1 - R/beekeeper-package.R | 1 - R/generate_pkg-setup.R | 8 ++++---- R/use_beekeeper.R | 18 ++++++++++-------- 7 files changed, 23 insertions(+), 20 deletions(-) diff --git a/.Rbuildignore b/.Rbuildignore index 60b1b8c..752d0f7 100644 --- a/.Rbuildignore +++ b/.Rbuildignore @@ -13,3 +13,5 @@ ^exploration$ ^_slackapi_rapid\.rds$ ^data-raw$ +^\.positai$ +^\.claude$ diff --git a/.gitignore b/.gitignore index 7c05924..b1a5b7f 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,4 @@ _beekeeper.yml exploration _beekeeper_rapid.rds _slackapi_rapid.rds +.positai diff --git a/DESCRIPTION b/DESCRIPTION index 8e0910e..eaf0fce 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -13,9 +13,9 @@ Description: Automatically generate R package skeletons from 'application License: MIT + file LICENSE URL: https://beekeeper.api2r.org, https://github.com/jonthegeek/beekeeper BugReports: https://github.com/jonthegeek/beekeeper/issues -Depends: +Depends: R (>= 4.1.0) -Imports: +Imports: cli, fs, glue, @@ -35,7 +35,7 @@ Imports: usethis, utils, yaml -Suggests: +Suggests: covr, desc, janitor, @@ -43,7 +43,7 @@ Suggests: rmarkdown, rvest, withr -VignetteBuilder: +VignetteBuilder: knitr Remotes: jonthegeek/nectar, @@ -51,5 +51,5 @@ Remotes: Config/testthat/edition: 3 Encoding: UTF-8 Roxygen: list(markdown = TRUE) -RoxygenNote: 7.3.2 -LazyData: true +RoxygenNote: 7.3.3 +LazyData: true \ No newline at end of file diff --git a/NAMESPACE b/NAMESPACE index 84cc768..467eb17 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -16,7 +16,6 @@ importFrom(fs,path_rel) importFrom(glue,glue) importFrom(glue,glue_collapse) importFrom(httptest2,use_httptest2) -importFrom(nectar,stabilize_string) importFrom(purrr,discard) importFrom(purrr,imap) importFrom(purrr,map) diff --git a/R/beekeeper-package.R b/R/beekeeper-package.R index 5c82f68..da1d288 100644 --- a/R/beekeeper-package.R +++ b/R/beekeeper-package.R @@ -12,7 +12,6 @@ #' @importFrom glue glue #' @importFrom glue glue_collapse #' @importFrom httptest2 use_httptest2 -#' @importFrom nectar stabilize_string #' @importFrom purrr discard #' @importFrom purrr imap #' @importFrom purrr map diff --git a/R/generate_pkg-setup.R b/R/generate_pkg-setup.R index b716f1c..56325ef 100644 --- a/R/generate_pkg-setup.R +++ b/R/generate_pkg-setup.R @@ -38,10 +38,10 @@ # covr doesn't see the line above and a bunch below for some reason. # nocov start .stabilize_config <- function(config) { - config$api_title <- stabilize_string(config$api_title) - config$api_abbr <- stabilize_string(config$api_abbr) - config$api_version <- stabilize_string(config$api_version) - config$rapid_file <- stabilize_string(config$rapid_file) + config$api_title <- stbl::stabilize_character_scalar(config$api_title) + config$api_abbr <- stbl::stabilize_character_scalar(config$api_abbr) + config$api_version <- stbl::stabilize_character_scalar(config$api_version) + config$rapid_file <- stbl::stabilize_character_scalar(config$rapid_file) config$updated_on <- strptime( config$updated_on, format = "%Y-%m-%d %H:%M:%S", diff --git a/R/use_beekeeper.R b/R/use_beekeeper.R index 49df626..057aa8d 100644 --- a/R/use_beekeeper.R +++ b/R/use_beekeeper.R @@ -19,11 +19,13 @@ #' written as a side effect of this function. The rapid object is also #' written, and the path to that file is saved in the config file. #' @export -use_beekeeper <- function(x, - api_abbr, - ..., - config_file = "_beekeeper.yml", - rapid_file = "_beekeeper_rapid.rds") { +use_beekeeper <- function( + x, + api_abbr, + ..., + config_file = "_beekeeper.yml", + rapid_file = "_beekeeper_rapid.rds" +) { x <- as_rapid(x) rapid_file <- .write_rapid(x, rapid_file) config_file <- .write_config(x, api_abbr, rapid_file, config_file) @@ -32,19 +34,19 @@ use_beekeeper <- function(x, } .write_rapid <- function(x, rapid_file) { - rapid_file <- stabilize_string(rapid_file) + rapid_file <- stbl::stabilize_character_scalar(rapid_file) saveRDS(x, rapid_file) use_build_ignore(rapid_file) return(rapid_file) } .write_config <- function(x, api_abbr, rapid_file, config_file) { - config_file <- stabilize_string(config_file) + config_file <- stbl::stabilize_character_scalar(config_file) update_time <- strptime(Sys.time(), format = "%Y-%m-%d %H:%M:%S", tz = "UTC") write_yaml( list( api_title = x@info@title, - api_abbr = stabilize_string(api_abbr), + api_abbr = stbl::stabilize_character_scalar(api_abbr), api_version = x@info@version, rapid_file = path_rel(rapid_file, path_dir(config_file)), updated_on = as.character(update_time) From 9ba433d225a7b7bd50afb099b42822cb1cc1b31e Mon Sep 17 00:00:00 2001 From: Jon Harmon Date: Fri, 8 May 2026 14:31:16 -0500 Subject: [PATCH 09/33] roxygen2 8.0 --- DESCRIPTION | 4 ++-- man/beekeeper-package.Rd | 7 +++++++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index eaf0fce..535c97c 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -51,5 +51,5 @@ Remotes: Config/testthat/edition: 3 Encoding: UTF-8 Roxygen: list(markdown = TRUE) -RoxygenNote: 7.3.3 -LazyData: true \ No newline at end of file +LazyData: true +Config/roxygen2/version: 8.0.0 diff --git a/man/beekeeper-package.Rd b/man/beekeeper-package.Rd index 43e1ed9..8014463 100644 --- a/man/beekeeper-package.Rd +++ b/man/beekeeper-package.Rd @@ -6,6 +6,8 @@ \alias{beekeeper-package} \title{beekeeper: Rapidly Scaffold API Client Packages} \description{ +\if{html}{\figure{logo.svg}{options: style='float: right' alt='logo' width='120'}} + Automatically generate R package skeletons from 'application programming interfaces (APIs)' that follow the 'OpenAPI Specification (OAS)'. The skeletons implement best practices to streamline package development. } \seealso{ @@ -20,6 +22,11 @@ Useful links: \author{ \strong{Maintainer}: Jon Harmon \email{jonthegeek@gmail.com} (\href{https://orcid.org/0000-0003-4781-4346}{ORCID}) [copyright holder] +Authors: +\itemize{ + \item Jon Harmon \email{jonthegeek@gmail.com} (\href{https://orcid.org/0000-0003-4781-4346}{ORCID}) [copyright holder] +} + Other contributors: \itemize{ \item R Consortium [funder] From 9d9d3680c15cbca6b8286a5eea5aa1fb1bff547a Mon Sep 17 00:00:00 2001 From: Jon Harmon Date: Fri, 8 May 2026 14:35:39 -0500 Subject: [PATCH 10/33] Actions and air --- .Rbuildignore | 2 + .github/workflows/R-CMD-check.yaml | 26 ++--- .github/workflows/format-suggest.yaml | 46 ++++++++ .github/workflows/pkgdown-cleanup.yaml | 33 ++++++ .github/workflows/pkgdown.yaml | 51 ++++++--- .github/workflows/pr-commands.yaml | 52 ++------- .github/workflows/test-coverage.yaml | 36 ++++--- .vscode/extensions.json | 5 + .vscode/settings.json | 10 ++ R/generate_pkg-paths.R | 67 +++++++----- R/generate_pkg-security.R | 6 +- R/generate_pkg-template.R | 12 ++- R/generate_pkg.R | 6 +- air.toml | 0 .../testthat/_fixtures/000-create_fixtures.R | 8 +- tests/testthat/_fixtures/fec-paths-audit.R | 101 ++++++++++++++++-- tests/testthat/_fixtures/guru-010-prepare.R | 14 +-- tests/testthat/_fixtures/guru-paths-apis.R | 7 +- tests/testthat/_fixtures/trello-010-prepare.R | 17 +-- tests/testthat/_fixtures/trello-paths-board.R | 8 +- tests/testthat/helper.R | 3 +- tests/testthat/test-generate_pkg-prepare.R | 9 +- tests/testthat/test-generate_pkg-setup.R | 5 +- 23 files changed, 370 insertions(+), 154 deletions(-) create mode 100644 .github/workflows/format-suggest.yaml create mode 100644 .github/workflows/pkgdown-cleanup.yaml create mode 100644 .vscode/extensions.json create mode 100644 .vscode/settings.json create mode 100644 air.toml diff --git a/.Rbuildignore b/.Rbuildignore index 752d0f7..67f5c6f 100644 --- a/.Rbuildignore +++ b/.Rbuildignore @@ -15,3 +15,5 @@ ^data-raw$ ^\.positai$ ^\.claude$ +^[.]?air[.]toml$ +^\.vscode$ diff --git a/.github/workflows/R-CMD-check.yaml b/.github/workflows/R-CMD-check.yaml index 8127dc4..efb3807 100644 --- a/.github/workflows/R-CMD-check.yaml +++ b/.github/workflows/R-CMD-check.yaml @@ -11,6 +11,7 @@ name: R-CMD-check jobs: R-CMD-check: runs-on: ${{ matrix.config.os }} + container: ${{ matrix.config.container }} name: ${{ matrix.config.os }} (${{ matrix.config.r }}) @@ -18,31 +19,24 @@ jobs: fail-fast: false matrix: config: - - {os: ubuntu-latest, r: 'release'} - {os: macos-latest, r: 'release'} - {os: windows-latest, r: 'release'} - - {os: ubuntu-latest, r: 'devel', http-user-agent: 'release'} - - {os: ubuntu-latest, r: 'oldrel-1'} - - env: - GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} - R_KEEP_PKG_SOURCE: yes + - {os: ubuntu-latest, r: 'devel', http-user-agent: 'release', container: 'ghcr.io/api2r/pkgskills-ci:devel'} + - {os: ubuntu-latest, r: 'release', container: 'ghcr.io/api2r/pkgskills-ci:release'} + - {os: ubuntu-latest, r: 'oldrel-1', container: 'ghcr.io/api2r/pkgskills-ci:oldrel-1'} steps: - - uses: actions/checkout@v3 - - - uses: r-lib/actions/setup-pandoc@v2 + - uses: actions/checkout@v6 - - uses: r-lib/actions/setup-r@v2 + - uses: api2r/actions/install@v1 with: + use-container: "${{ matrix.config.container != '' }}" + token: ${{ secrets.GITHUB_TOKEN }} r-version: ${{ matrix.config.r }} http-user-agent: ${{ matrix.config.http-user-agent }} - use-public-rspm: true - - - uses: r-lib/actions/setup-r-dependencies@v2 - with: - extra-packages: any::rcmdcheck needs: check + extra-packages: any::rcmdcheck + cache-version: "1" - uses: r-lib/actions/check-r-package@v2 with: diff --git a/.github/workflows/format-suggest.yaml b/.github/workflows/format-suggest.yaml new file mode 100644 index 0000000..af50210 --- /dev/null +++ b/.github/workflows/format-suggest.yaml @@ -0,0 +1,46 @@ +# Workflow derived from https://github.com/posit-dev/setup-air/tree/main/examples + +on: + # Using `pull_request_target` over `pull_request` for elevated `GITHUB_TOKEN` + # privileges, otherwise we can't set `pull-requests: write` when the pull + # request comes from a fork, which is our main use case (external contributors). + # + # `pull_request_target` runs in the context of the target branch (`main`, usually), + # rather than in the context of the pull request like `pull_request` does. Due + # to this, we must explicitly checkout `ref: ${{ github.event.pull_request.head.sha }}`. + # This is typically frowned upon by GitHub, as it exposes you to potentially running + # untrusted code in a context where you have elevated privileges, but they explicitly + # call out the use case of reformatting and committing back / commenting on the PR + # as a situation that should be safe (because we aren't actually running the untrusted + # code, we are just treating it as passive data). + # https://securitylab.github.com/resources/github-actions-preventing-pwn-requests/ + pull_request_target: + +name: format-suggest.yaml + +jobs: + format-suggest: + name: format-suggest + runs-on: ubuntu-latest + + permissions: + # Required to push suggestion comments to the PR + pull-requests: write + + steps: + - uses: actions/checkout@v6 + with: + ref: ${{ github.event.pull_request.head.sha }} + + - name: Install + uses: posit-dev/setup-air@v1 + + - name: Format + run: air format . + + - name: Suggest + uses: reviewdog/action-suggester@v1 + with: + level: error + fail_level: error + tool_name: air diff --git a/.github/workflows/pkgdown-cleanup.yaml b/.github/workflows/pkgdown-cleanup.yaml new file mode 100644 index 0000000..d8ea7c2 --- /dev/null +++ b/.github/workflows/pkgdown-cleanup.yaml @@ -0,0 +1,33 @@ +# This workflow removes the pkgdown preview directory when a PR is closed. +name: Clean up pkgdown preview + +on: + pull_request: + types: [closed] + +permissions: + contents: write + +jobs: + clean-pr-preview: + runs-on: ubuntu-latest + steps: + - name: Checkout gh-pages branch + uses: actions/checkout@v6 + with: + repository: ${{ github.repository }} + token: ${{ secrets.GITHUB_TOKEN }} + ref: gh-pages + + - name: Remove PR preview directory + run: | + pr_dir="pr/${{ github.event.number }}" + if [ -d "$pr_dir" ]; then + git config --local user.name "$GITHUB_ACTOR" + git config --local user.email "$GITHUB_ACTOR@users.noreply.github.com" + git rm -rf "$pr_dir" + git commit -m "Remove preview for PR #${{ github.event.number }}" + git push + else + echo "Directory $pr_dir does not exist, skipping cleanup." + fi diff --git a/.github/workflows/pkgdown.yaml b/.github/workflows/pkgdown.yaml index 087f0b0..7f4f411 100644 --- a/.github/workflows/pkgdown.yaml +++ b/.github/workflows/pkgdown.yaml @@ -2,9 +2,9 @@ # Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help on: push: - branches: [main, master] + branches: [main] pull_request: - branches: [main, master] + branches: [main] release: types: [published] workflow_dispatch: @@ -14,32 +14,59 @@ name: pkgdown jobs: pkgdown: runs-on: ubuntu-latest + container: + image: ghcr.io/api2r/pkgskills-ci:release # Only restrict concurrency for non-PR jobs concurrency: group: pkgdown-${{ github.event_name != 'pull_request' || github.run_id }} env: GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} + permissions: + contents: write + pull-requests: write steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v6 - - uses: r-lib/actions/setup-pandoc@v2 - - - uses: r-lib/actions/setup-r@v2 - with: - use-public-rspm: true - - - uses: r-lib/actions/setup-r-dependencies@v2 + - uses: api2r/actions/install@v1 with: - extra-packages: any::pkgdown, local::. + use-container: "true" + token: ${{ secrets.GITHUB_TOKEN }} needs: website + extra-packages: any::pkgdown gilead-biostats/qcthat local::. any::glue - name: Build site run: pkgdown::build_site_github_pages(new_process = FALSE, install = FALSE) shell: Rscript {0} + - name: Deploy PR preview 🧪 + if: github.event_name == 'pull_request' + uses: JamesIves/github-pages-deploy-action@v4.8.0 + with: + clean: false + branch: gh-pages + folder: docs + target-folder: pr/${{ github.event.number }} + + - name: Comment with PR site URL 🌐 + if: github.event_name == 'pull_request' + run: | + intPRNumber <- ${{ github.event.pull_request.number }} + strOwner <- tolower(qcthat::GetGHOwner()) + strRepo <- qcthat::GetGHRepo() + strURL <- glue::glue( + "https://{strOwner}.github.io/{strRepo}/pr/{intPRNumber}" + ) + print(paste("🌐 URL:", strURL)) + qcthat::CommentIssue( + intPRNumber, + glue::glue("🌐 [PR pkgdown deployed]({strURL})"), + NULL + ) + shell: Rscript {0} + - name: Deploy to GitHub pages 🚀 if: github.event_name != 'pull_request' - uses: JamesIves/github-pages-deploy-action@v4.4.1 + uses: JamesIves/github-pages-deploy-action@v4.8.0 with: clean: false branch: gh-pages diff --git a/.github/workflows/pr-commands.yaml b/.github/workflows/pr-commands.yaml index 71f335b..8c2f575 100644 --- a/.github/workflows/pr-commands.yaml +++ b/.github/workflows/pr-commands.yaml @@ -11,23 +11,23 @@ jobs: if: ${{ github.event.issue.pull_request && (github.event.comment.author_association == 'MEMBER' || github.event.comment.author_association == 'OWNER') && startsWith(github.event.comment.body, '/document') }} name: document runs-on: ubuntu-latest - env: - GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} + container: + image: ghcr.io/api2r/pkgskills-ci:release + permissions: + contents: write steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v6 - uses: r-lib/actions/pr-fetch@v2 with: repo-token: ${{ secrets.GITHUB_TOKEN }} - - uses: r-lib/actions/setup-r@v2 + - uses: api2r/actions/install@v1 with: - use-public-rspm: true - - - uses: r-lib/actions/setup-r-dependencies@v2 - with: - extra-packages: any::roxygen2 + use-container: "true" + token: ${{ secrets.GITHUB_TOKEN }} needs: pr-document + extra-packages: any::roxygen2 - name: Document run: roxygen2::roxygenise() @@ -43,37 +43,3 @@ jobs: - uses: r-lib/actions/pr-push@v2 with: repo-token: ${{ secrets.GITHUB_TOKEN }} - - style: - if: ${{ github.event.issue.pull_request && (github.event.comment.author_association == 'MEMBER' || github.event.comment.author_association == 'OWNER') && startsWith(github.event.comment.body, '/style') }} - name: style - runs-on: ubuntu-latest - env: - GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} - steps: - - uses: actions/checkout@v3 - - - uses: r-lib/actions/pr-fetch@v2 - with: - repo-token: ${{ secrets.GITHUB_TOKEN }} - - - uses: r-lib/actions/setup-r@v2 - - - name: Install dependencies - run: install.packages("styler") - shell: Rscript {0} - - - name: Style - run: styler::style_pkg() - shell: Rscript {0} - - - name: commit - run: | - git config --local user.name "$GITHUB_ACTOR" - git config --local user.email "$GITHUB_ACTOR@users.noreply.github.com" - git add \*.R - git commit -m 'Style' - - - uses: r-lib/actions/pr-push@v2 - with: - repo-token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/test-coverage.yaml b/.github/workflows/test-coverage.yaml index 2c5bb50..8b7a5f3 100644 --- a/.github/workflows/test-coverage.yaml +++ b/.github/workflows/test-coverage.yaml @@ -11,40 +11,48 @@ name: test-coverage jobs: test-coverage: runs-on: ubuntu-latest - env: - GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} - + container: + image: ghcr.io/api2r/pkgskills-ci:release steps: - - uses: actions/checkout@v3 - - - uses: r-lib/actions/setup-r@v2 - with: - use-public-rspm: true + - uses: actions/checkout@v6 - - uses: r-lib/actions/setup-r-dependencies@v2 + - uses: api2r/actions/install@v1 with: - extra-packages: any::covr + use-container: "true" + token: ${{ secrets.GITHUB_TOKEN }} needs: coverage + extra-packages: any::covr any::xml2 - name: Test coverage run: | - covr::codecov( + cov <- covr::package_coverage( quiet = FALSE, clean = FALSE, - install_path = file.path(Sys.getenv("RUNNER_TEMP"), "package") + install_path = file.path(normalizePath(Sys.getenv("RUNNER_TEMP"), winslash = "/"), "package") ) + print(cov) + covr::to_cobertura(cov) shell: Rscript {0} + - uses: codecov/codecov-action@v5 + with: + # Fail if error if not on PR, or if on PR and token is given + fail_ci_if_error: ${{ github.event_name != 'pull_request' || secrets.CODECOV_TOKEN }} + files: ./cobertura.xml + plugins: noop + disable_search: true + token: ${{ secrets.CODECOV_TOKEN }} + - name: Show testthat output if: always() run: | ## -------------------------------------------------------------------- - find ${{ runner.temp }}/package -name 'testthat.Rout*' -exec cat '{}' \; || true + find '${{ runner.temp }}/package' -name 'testthat.Rout*' -exec cat '{}' \; || true shell: bash - name: Upload test results if: failure() - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v6 with: name: coverage-test-failures path: ${{ runner.temp }}/package diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..344f76e --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,5 @@ +{ + "recommendations": [ + "Posit.air-vscode" + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..a9f69fe --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,10 @@ +{ + "[r]": { + "editor.formatOnSave": true, + "editor.defaultFormatter": "Posit.air-vscode" + }, + "[quarto]": { + "editor.formatOnSave": true, + "editor.defaultFormatter": "quarto.quarto" + } +} diff --git a/R/generate_pkg-paths.R b/R/generate_pkg-paths.R index ad067cb..d560091 100644 --- a/R/generate_pkg-paths.R +++ b/R/generate_pkg-paths.R @@ -1,4 +1,10 @@ -.generate_paths <- function(paths, api_abbr, security_data, pagination_data, base_url) { +.generate_paths <- function( + paths, + api_abbr, + security_data, + pagination_data, + base_url +) { paths_by_operation <- as_bk_data(paths) paths_file_paths <- character() if (length(paths_by_operation)) { @@ -96,12 +102,14 @@ S7::method(as_bk_data, class_paths) <- function(x) { ### create whisker data -------------------------------------------------------- -.paths_endpoint_to_list <- function(operation_id, - path, - summary, - description, - params_df, - method) { +.paths_endpoint_to_list <- function( + operation_id, + path, + summary, + description, + params_df, + method +) { params_df <- .prepare_params_df(params_df) return( list( @@ -169,9 +177,7 @@ S7::method(as_bk_data, class_paths) <- function(x) { return(str_squish(descriptions)) } -.describe_param_classes <- function(params_schema, - allow_empty, - required) { +.describe_param_classes <- function(params_schema, allow_empty, required) { # TODO: Use enum and/or description when available. # # TODO: What should we do for `object` and `array`? @@ -203,10 +209,12 @@ S7::method(as_bk_data, class_paths) <- function(x) { return(r_class_descriptions) } -.paths_complete_param_descriptions <- function(descriptions, - params_schema, - allow_empty, - required) { +.paths_complete_param_descriptions <- function( + descriptions, + params_schema, + allow_empty, + required +) { r_class_descriptions <- .describe_param_classes( params_schema, allow_empty, @@ -247,10 +255,12 @@ S7::method(as_bk_data, class_paths) <- function(x) { # generate files ---------------------------------------------------------- -.generate_paths_files <- function(paths_by_operation, - api_abbr, - security_data, - pagination_data) { +.generate_paths_files <- function( + paths_by_operation, + api_abbr, + security_data, + pagination_data +) { unlist(imap( paths_by_operation, function(path_operation, path_operation_id) { @@ -265,11 +275,13 @@ S7::method(as_bk_data, class_paths) <- function(x) { )) } -.generate_paths_operation_files <- function(path_operation, - path_operation_id, - api_abbr, - security_data, - pagination_data) { +.generate_paths_operation_files <- function( + path_operation, + path_operation_id, + api_abbr, + security_data, + pagination_data +) { stop("Everything should be prepped before this, I think.") path_operation <- .prepare_path_operation( path_operation, @@ -292,7 +304,9 @@ S7::method(as_bk_data, class_paths) <- function(x) { } .prepare_path_operation <- function(path_operation, security_args) { - stop("Do this all in initial parsing? Everything is by operation now, which means 1 path per file; no need to map.") + stop( + "Do this all in initial parsing? Everything is by operation now, which means 1 path per file; no need to map." + ) path_operation <- map( path_operation, function(path) { @@ -324,10 +338,7 @@ S7::method(as_bk_data, class_paths) <- function(x) { .collapse_comma_self_equal(setdiff(params, security_args)) %|"|% character() } -.generate_paths_file <- function(path, - path_tag_name, - api_abbr, - security_data) { +.generate_paths_file <- function(path, path_tag_name, api_abbr, security_data) { .bk_use_template( template = "paths.R", data = c( diff --git a/R/generate_pkg-security.R b/R/generate_pkg-security.R index 93ed27a..f1b8325 100644 --- a/R/generate_pkg-security.R +++ b/R/generate_pkg-security.R @@ -6,7 +6,8 @@ data = c(security_data, api_abbr = api_abbr) ) security_data$security_signature <- .generate_security_signature( - security_data$security_arg_names, api_abbr + security_data$security_arg_names, + api_abbr ) } return(security_data) @@ -58,7 +59,8 @@ S7::method(as_bk_data, class_security_schemes) <- function(x) { .security_scheme_description_fill <- function(description, type) { if (is.na(description)) { return( - switch(type, + switch( + type, api_key = .security_scheme_description_api_key, NA_character_ ) diff --git a/R/generate_pkg-template.R b/R/generate_pkg-template.R index 8d4e2cf..fe901ae 100644 --- a/R/generate_pkg-template.R +++ b/R/generate_pkg-template.R @@ -8,11 +8,13 @@ #' #' @return The path to the generated or updated file, invisibly. #' @keywords internal -.bk_use_template <- function(template, - data, - ..., - target = template, - dir = c("R", "tests/testthat")) { +.bk_use_template <- function( + template, + data, + ..., + target = template, + dir = c("R", "tests/testthat") +) { check_dots_empty() dir <- match.arg(dir) target <- .bk_use_template_impl(template, data, target, dir) diff --git a/R/generate_pkg.R b/R/generate_pkg.R index fec5884..6d6e9c2 100644 --- a/R/generate_pkg.R +++ b/R/generate_pkg.R @@ -13,8 +13,10 @@ #' @return A character vector of paths to files that were added or updated, #' invisibly. #' @export -generate_pkg <- function(config_file = "_beekeeper.yml", - pkg_dir = fs::path_dir(config_file)) { +generate_pkg <- function( + config_file = "_beekeeper.yml", + pkg_dir = fs::path_dir(config_file) +) { # 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. diff --git a/air.toml b/air.toml new file mode 100644 index 0000000..e69de29 diff --git a/tests/testthat/_fixtures/000-create_fixtures.R b/tests/testthat/_fixtures/000-create_fixtures.R index 21a3ef6..c2c74d7 100644 --- a/tests/testthat/_fixtures/000-create_fixtures.R +++ b/tests/testthat/_fixtures/000-create_fixtures.R @@ -40,8 +40,12 @@ fec_rapid@paths <- rapid::as_paths({ x$tags <- NULL x }) -rapid_write_path <- test_path(glue::glue("_fixtures/{api_abbr}_subset_rapid.rds")) -config_path <- test_path(glue::glue("_fixtures/{api_abbr}_subset_beekeeper.yml")) +rapid_write_path <- test_path(glue::glue( + "_fixtures/{api_abbr}_subset_rapid.rds" +)) +config_path <- test_path(glue::glue( + "_fixtures/{api_abbr}_subset_beekeeper.yml" +)) fec_rapid |> use_beekeeper( api_abbr = api_abbr, diff --git a/tests/testthat/_fixtures/fec-paths-audit.R b/tests/testthat/_fixtures/fec-paths-audit.R index ef3233e..3f1d0b1 100644 --- a/tests/testthat/_fixtures/fec-paths-audit.R +++ b/tests/testthat/_fixtures/fec-paths-audit.R @@ -29,12 +29,53 @@ #' @param sort Provide a field to sort by. Use `-` for descending order. ex: `-case_no` #' @return BKTODO: Return descriptions are not yet implemented in beekeeper #' @export -fec_get_audit_case <- function(audit_case_id, cycle, sub_category_id, sort_nulls_last, sort_hide_null, min_election_cycle, audit_id, q, per_page, max_election_cycle, candidate_id, committee_type, qq, page, committee_id, committee_designation, primary_category_id, sort_null_only, sort, api_key = Sys.getenv("FEC_API_KEY")) { +fec_get_audit_case <- function( + audit_case_id, + cycle, + sub_category_id, + sort_nulls_last, + sort_hide_null, + min_election_cycle, + audit_id, + q, + per_page, + max_election_cycle, + candidate_id, + committee_type, + qq, + page, + committee_id, + committee_designation, + primary_category_id, + sort_null_only, + sort, + api_key = Sys.getenv("FEC_API_KEY") +) { fec_call_api( path = "/audit-case/", method = "get", api_key = api_key, - query = list(audit_case_id = audit_case_id, cycle = cycle, sub_category_id = sub_category_id, sort_nulls_last = sort_nulls_last, sort_hide_null = sort_hide_null, min_election_cycle = min_election_cycle, audit_id = audit_id, q = q, per_page = per_page, max_election_cycle = max_election_cycle, candidate_id = candidate_id, committee_type = committee_type, qq = qq, page = page, committee_id = committee_id, committee_designation = committee_designation, primary_category_id = primary_category_id, sort_null_only = sort_null_only, sort = sort) + query = list( + audit_case_id = audit_case_id, + cycle = cycle, + sub_category_id = sub_category_id, + sort_nulls_last = sort_nulls_last, + sort_hide_null = sort_hide_null, + min_election_cycle = min_election_cycle, + audit_id = audit_id, + q = q, + per_page = per_page, + max_election_cycle = max_election_cycle, + candidate_id = candidate_id, + committee_type = committee_type, + qq = qq, + page = page, + committee_id = committee_id, + committee_designation = committee_designation, + primary_category_id = primary_category_id, + sort_null_only = sort_null_only, + sort = sort + ) ) } @@ -53,12 +94,31 @@ fec_get_audit_case <- function(audit_case_id, cycle, sub_category_id, sort_nulls #' @param sort Provide a field to sort by. Use `-` for descending order. #' @return BKTODO: Return descriptions are not yet implemented in beekeeper #' @export -fec_get_audit_category <- function(sort_nulls_last, page, primary_category_name, sort_hide_null, primary_category_id, sort_null_only, per_page, sort, api_key = Sys.getenv("FEC_API_KEY")) { +fec_get_audit_category <- function( + sort_nulls_last, + page, + primary_category_name, + sort_hide_null, + primary_category_id, + sort_null_only, + per_page, + sort, + api_key = Sys.getenv("FEC_API_KEY") +) { fec_call_api( path = "/audit-category/", method = "get", api_key = api_key, - query = list(sort_nulls_last = sort_nulls_last, page = page, primary_category_name = primary_category_name, sort_hide_null = sort_hide_null, primary_category_id = primary_category_id, sort_null_only = sort_null_only, per_page = per_page, sort = sort) + query = list( + sort_nulls_last = sort_nulls_last, + page = page, + primary_category_name = primary_category_name, + sort_hide_null = sort_hide_null, + primary_category_id = primary_category_id, + sort_null_only = sort_null_only, + per_page = per_page, + sort = sort + ) ) } @@ -77,12 +137,31 @@ fec_get_audit_category <- function(sort_nulls_last, page, primary_category_name, #' @param sort Provide a field to sort by. Use `-` for descending order. #' @return BKTODO: Return descriptions are not yet implemented in beekeeper #' @export -fec_get_audit_primary_category <- function(sort_nulls_last, page, primary_category_name, sort_hide_null, primary_category_id, sort_null_only, per_page, sort, api_key = Sys.getenv("FEC_API_KEY")) { +fec_get_audit_primary_category <- function( + sort_nulls_last, + page, + primary_category_name, + sort_hide_null, + primary_category_id, + sort_null_only, + per_page, + sort, + api_key = Sys.getenv("FEC_API_KEY") +) { fec_call_api( path = "/audit-primary-category/", method = "get", api_key = api_key, - query = list(sort_nulls_last = sort_nulls_last, page = page, primary_category_name = primary_category_name, sort_hide_null = sort_hide_null, primary_category_id = primary_category_id, sort_null_only = sort_null_only, per_page = per_page, sort = sort) + query = list( + sort_nulls_last = sort_nulls_last, + page = page, + primary_category_name = primary_category_name, + sort_hide_null = sort_hide_null, + primary_category_id = primary_category_id, + sort_null_only = sort_null_only, + per_page = per_page, + sort = sort + ) ) } @@ -94,7 +173,10 @@ fec_get_audit_primary_category <- function(sort_nulls_last, page, primary_catego #' @param q Name (candidate or committee) to search for #' @return BKTODO: Return descriptions are not yet implemented in beekeeper #' @export -fec_get_names_audit_candidates <- function(q, api_key = Sys.getenv("FEC_API_KEY")) { +fec_get_names_audit_candidates <- function( + q, + api_key = Sys.getenv("FEC_API_KEY") +) { fec_call_api( path = "/names/audit_candidates/", method = "get", @@ -111,7 +193,10 @@ fec_get_names_audit_candidates <- function(q, api_key = Sys.getenv("FEC_API_KEY" #' @param q Name (candidate or committee) to search for #' @return BKTODO: Return descriptions are not yet implemented in beekeeper #' @export -fec_get_names_audit_committees <- function(q, api_key = Sys.getenv("FEC_API_KEY")) { +fec_get_names_audit_committees <- function( + q, + api_key = Sys.getenv("FEC_API_KEY") +) { fec_call_api( path = "/names/audit_committees/", method = "get", diff --git a/tests/testthat/_fixtures/guru-010-prepare.R b/tests/testthat/_fixtures/guru-010-prepare.R index 0463041..3325e88 100644 --- a/tests/testthat/_fixtures/guru-010-prepare.R +++ b/tests/testthat/_fixtures/guru-010-prepare.R @@ -10,12 +10,14 @@ #' @inheritParams nectar::req_prepare #' @inherit nectar::req_prepare return #' @keywords internal -guru_req_prepare <- function(path, - query = list(), - body = NULL, - method = NULL, - tidy_fn = nectar::resp_tidy_unknown, - call = rlang::caller_env()) { +guru_req_prepare <- function( + path, + query = list(), + body = NULL, + method = NULL, + tidy_fn = nectar::resp_tidy_unknown, + call = rlang::caller_env() +) { req <- nectar::req_prepare( "https://api.apis.guru/v2", path = path, diff --git a/tests/testthat/_fixtures/guru-paths-apis.R b/tests/testthat/_fixtures/guru-paths-apis.R index 237d607..ad56c10 100644 --- a/tests/testthat/_fixtures/guru-paths-apis.R +++ b/tests/testthat/_fixtures/guru-paths-apis.R @@ -68,7 +68,12 @@ guru_get_api <- function(provider, api) { #' @export guru_get_service_api <- function(provider, service, api) { guru_call_api( - path = c("/specs/{provider}/{service}/{api}.json", provider = provider, service = service, api = api), + path = c( + "/specs/{provider}/{service}/{api}.json", + provider = provider, + service = service, + api = api + ), method = "get" ) } diff --git a/tests/testthat/_fixtures/trello-010-prepare.R b/tests/testthat/_fixtures/trello-010-prepare.R index 91816e8..ec0fc3a 100644 --- a/tests/testthat/_fixtures/trello-010-prepare.R +++ b/tests/testthat/_fixtures/trello-010-prepare.R @@ -11,13 +11,16 @@ #' @inheritParams nectar::req_prepare #' @inherit nectar::req_prepare return #' @keywords internal -trello_req_prepare <- function(path, - query = list(), - body = NULL, - method = NULL, - tidy_fn = nectar::resp_tidy_unknown, key = Sys.getenv("TRELLO_KEY"), - token = Sys.getenv("TRELLO_TOKEN"), - call = rlang::caller_env()) { +trello_req_prepare <- function( + path, + query = list(), + body = NULL, + method = NULL, + tidy_fn = nectar::resp_tidy_unknown, + key = Sys.getenv("TRELLO_KEY"), + token = Sys.getenv("TRELLO_TOKEN"), + call = rlang::caller_env() +) { req <- nectar::req_prepare( "https://trello.com/1", path = path, diff --git a/tests/testthat/_fixtures/trello-paths-board.R b/tests/testthat/_fixtures/trello-paths-board.R index 1146ff3..f6e0023 100644 --- a/tests/testthat/_fixtures/trello-paths-board.R +++ b/tests/testthat/_fixtures/trello-paths-board.R @@ -11,11 +11,13 @@ #' @return BKTODO: Return descriptions are not yet implemented in beekeeper #' @export trello_add_boards <- function( - key = Sys.getenv("TRELLO_KEY"), - token = Sys.getenv("TRELLO_TOKEN")) { + key = Sys.getenv("TRELLO_KEY"), + token = Sys.getenv("TRELLO_TOKEN") +) { trello_call_api( path = "/boards", method = "post", - key = key, token = token + key = key, + token = token ) } diff --git a/tests/testthat/helper.R b/tests/testthat/helper.R index 4591f11..e8b8f22 100644 --- a/tests/testthat/helper.R +++ b/tests/testthat/helper.R @@ -1,7 +1,6 @@ # Inspired by https://github.com/r-lib/usethis tests/testthat/helper.R -create_local_package <- function(pkgname = "testpkg", - env = parent.frame()) { +create_local_package <- function(pkgname = "testpkg", env = parent.frame()) { withr::local_options(usethis.quiet = TRUE, .local_envir = env) dir <- withr::local_tempdir(pattern = pkgname, .local_envir = env) diff --git a/tests/testthat/test-generate_pkg-prepare.R b/tests/testthat/test-generate_pkg-prepare.R index adedbad..142b335 100644 --- a/tests/testthat/test-generate_pkg-prepare.R +++ b/tests/testthat/test-generate_pkg-prepare.R @@ -3,7 +3,10 @@ test_that(".generate_prepare() generates prepare file.", { config <- .read_config(test_path("_fixtures", "guru_beekeeper.yml")) api_definition <- readRDS(test_path("_fixtures", "guru_rapid.rds")) prepare_expected <- readLines(test_path("_fixtures", "guru-010-prepare.R")) - t_prepare_expected <- readLines(test_path("_fixtures", "guru-test-010-prepare.R")) + t_prepare_expected <- readLines(test_path( + "_fixtures", + "guru-test-010-prepare.R" + )) create_local_package() usethis::use_testthat() @@ -16,6 +19,8 @@ test_that(".generate_prepare() generates prepare file.", { prepare_result <- scrub_testpkg(readLines("R/010-prepare.R")) expect_identical(prepare_result, prepare_expected) - t_prepare_result <- scrub_testpkg(readLines("tests/testthat/test-010-prepare.R")) + t_prepare_result <- scrub_testpkg(readLines( + "tests/testthat/test-010-prepare.R" + )) expect_identical(t_prepare_result, t_prepare_expected) }) diff --git a/tests/testthat/test-generate_pkg-setup.R b/tests/testthat/test-generate_pkg-setup.R index c22b4ea..5e7ec78 100644 --- a/tests/testthat/test-generate_pkg-setup.R +++ b/tests/testthat/test-generate_pkg-setup.R @@ -22,7 +22,10 @@ test_that(".read_config() reads configs", { }) test_that(".read_api_definition() reads api_definitions", { - api_definition <- .read_api_definition(test_path("_fixtures"), "guru_rapid.rds") + api_definition <- .read_api_definition( + test_path("_fixtures"), + "guru_rapid.rds" + ) expect_s7_class(api_definition, rapid::class_rapid) }) From e357e718e7ae4fcaa5f507f4b93fc5150df76890 Mon Sep 17 00:00:00 2001 From: Jon Harmon Date: Fri, 8 May 2026 14:37:57 -0500 Subject: [PATCH 11/33] Tidy DESC --- DESCRIPTION | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index 535c97c..b94db8e 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -48,8 +48,8 @@ VignetteBuilder: Remotes: jonthegeek/nectar, jonthegeek/rapid +Config/roxygen2/version: 8.0.0 Config/testthat/edition: 3 Encoding: UTF-8 -Roxygen: list(markdown = TRUE) LazyData: true -Config/roxygen2/version: 8.0.0 +Roxygen: list(markdown = TRUE) From cad75880088171e1e80483d3311632d13fc8df6e Mon Sep 17 00:00:00 2001 From: Jon Harmon Date: Fri, 8 May 2026 14:38:45 -0500 Subject: [PATCH 12/33] api2r targets for remotes --- DESCRIPTION | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index b94db8e..49eca26 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -46,8 +46,8 @@ Suggests: VignetteBuilder: knitr Remotes: - jonthegeek/nectar, - jonthegeek/rapid + api2r/nectar, + api2r/rapid Config/roxygen2/version: 8.0.0 Config/testthat/edition: 3 Encoding: UTF-8 From 2919a226233bec354dc98d0d2c5fa74d6a780e70 Mon Sep 17 00:00:00 2001 From: Jon Harmon Date: Mon, 11 May 2026 16:37:23 -0500 Subject: [PATCH 13/33] Update workflows. Use AI. --- .Rbuildignore | 1 + .github/copilot-instructions.md | 1 + .github/skills/create-issue/SKILL.md | 117 ++++++++++ .github/skills/document/SKILL.md | 172 +++++++++++++++ .github/skills/github/SKILL.md | 22 ++ .github/skills/implement-issue/SKILL.md | 53 +++++ .github/skills/r-code/SKILL.md | 228 ++++++++++++++++++++ .github/skills/search-code/SKILL.md | 84 ++++++++ .github/skills/tdd-workflow/SKILL.md | 250 ++++++++++++++++++++++ .github/workflows/copilot-setup-steps.yml | 52 +++++ .github/workflows/qcthat.yaml | 157 ++++++++++++++ AGENTS.md | 73 +++++++ DESCRIPTION | 1 + R/aaa-conditions.R | 22 ++ R/aaa-shared_params.R | 10 + man/dot-pkg_abort.Rd | 36 ++++ man/dot-shared-params.Rd | 13 ++ 17 files changed, 1292 insertions(+) create mode 100644 .github/copilot-instructions.md create mode 100644 .github/skills/create-issue/SKILL.md create mode 100644 .github/skills/document/SKILL.md create mode 100644 .github/skills/github/SKILL.md create mode 100644 .github/skills/implement-issue/SKILL.md create mode 100644 .github/skills/r-code/SKILL.md create mode 100644 .github/skills/search-code/SKILL.md create mode 100644 .github/skills/tdd-workflow/SKILL.md create mode 100644 .github/workflows/copilot-setup-steps.yml create mode 100644 .github/workflows/qcthat.yaml create mode 100644 AGENTS.md create mode 100644 R/aaa-conditions.R create mode 100644 R/aaa-shared_params.R create mode 100644 man/dot-pkg_abort.Rd create mode 100644 man/dot-shared-params.Rd diff --git a/.Rbuildignore b/.Rbuildignore index 67f5c6f..f637143 100644 --- a/.Rbuildignore +++ b/.Rbuildignore @@ -17,3 +17,4 @@ ^\.claude$ ^[.]?air[.]toml$ ^\.vscode$ +^AGENTS\.md$ diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 0000000..977de95 --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1 @@ +When reviewing pull requests, do not review `man/*.Rd`. diff --git a/.github/skills/create-issue/SKILL.md b/.github/skills/create-issue/SKILL.md new file mode 100644 index 0000000..d5a19b8 --- /dev/null +++ b/.github/skills/create-issue/SKILL.md @@ -0,0 +1,117 @@ +--- +name: create-issue +trigger: create GitHub issues +description: Creates GitHub issues for the package repository. Use when asked to create, file, or open a GitHub issue, or when planning new features or functions that need to be tracked. +compatibility: Requires the `gh` CLI and an authenticated GitHub session. +--- + +# Create a GitHub issue + +Use `gh api graphql` with the `createIssue` mutation to create issues. This sets the issue type in a single step. Write the body to a temp file first, then pass it via `$(cat ...)`. + +If `gh` is not authenticated, stop and ask the user to authenticate before continuing. + +## Looking up IDs + +The hardcoded IDs below are correct for this repo as of 2026-05-11 21:33:53 UTC. If they ever change, or if you're working in a fork, re-run these queries to get fresh values: + +```bash +# Repository node ID +gh api graphql -f query='{ repository(owner: "jonthegeek", name: "beekeeper") { id } }' + +# Available issue type IDs +gh api graphql -f query='{ repository(owner: "jonthegeek", name: "beekeeper") { issueTypes(first: 20) { nodes { id name description } } } }' +``` + +## Issue type + +Choose the type that best fits the issue: + +| Type | ID | Use for | +|---|---|---| + +## Issue title + +Titles use conventional commit prefixes: + +- `feat: my_function()` — new exported function or feature +- `fix: short description` — bug fix +- `docs: short description` — documentation +- `chore: short description` — maintenance or task + +## Issue body structure + +Which sections to include depends on the issue type: + +| Section | Feature | Bug | Documentation | Task | +|---|---|---|---|---| +| `## Summary` | ✓ | ✓ | ✓ | ✓ | +| `## Details` | optional | optional | optional | optional | +| `## Proposed signature` | ✓ | — | — | — | +| `## Behavior` | ✓ | ✓ | — | — | +| `## References` | optional | optional | optional | optional | + +### `## Summary` (all types) + +A single user story sentence (no other content in this section): + +```markdown +> As a [role], in order to [goal], I would like to [feature]. +``` + +Example: + +```markdown +## Summary + +> As a package developer, in order to set up agent skills quickly, I would like to generate a skill template from a single function call. +``` + +### `## Details` (optional, all types) + +For information that's important to capture but doesn't fit naturally into any other section, including implementation details such as packages to add to `Imports` in `DESCRIPTION` or files to add to `inst`. Use sparingly — if the content belongs in `## Behavior`, `## Proposed signature`, or `## References`, put it there instead. + +### `## Proposed signature` (Feature only) + +The proposed R function signature, arguments table, and return value description: + +````markdown +## Proposed signature + +```r +function_name(arg1, arg2) +``` + +**Arguments** + +- `arg1` (`TYPE`) — Description. +- `arg2` (`TYPE`) — Description. + +**Returns** a `TYPE` with description. +```` + +### `## Behavior` (Feature and Bug) + +- **Feature**: bullet points describing expected behavior, edge cases, and any internal helpers to implement as part of this issue. +- **Bug**: describe the current (broken) behavior, the expected behavior, and steps to reproduce if known. + +### `## References` (optional, all types) + +Only include when there are specific reference implementations, external URLs, or related code to link to. Omit it entirely when there are none. + +## Creating the issue + +Use the `repoId` and the `typeId` for the chosen issue type from the table above. + +```bash +gh api graphql \ + -f query='mutation($repoId:ID!, $title:String!, $body:String!, $typeId:ID!) { + createIssue(input:{repositoryId:$repoId, title:$title, body:$body, issueTypeId:$typeId}) { + issue { url } + } + }' \ + -f repoId="R_kgDOJRLJFQ" \ + -f title="feat: my_function()" \ + -f body="$(cat /tmp/issue_body.md)" \ + -f typeId="{typeId}" +``` diff --git a/.github/skills/document/SKILL.md b/.github/skills/document/SKILL.md new file mode 100644 index 0000000..9914cdd --- /dev/null +++ b/.github/skills/document/SKILL.md @@ -0,0 +1,172 @@ +--- +name: document +trigger: document functions +description: Document package functions. Use when asked to document functions. +--- + +# Document functions + +*All* R functions in `R/` should be documented in roxygen2 `#'` style, including internal/unexported functions. + +- Run `air format .` then `devtools::document()` after changing any roxygen2 docs. +- Use sentence case for all headings. +- Wrap roxygen comments at 80 characters. +- Files matching `R/import-standalone-*.R` are imported from other packages and have their own conventions. Do not modify their documentation. +- After documenting functions, run `devtools::document(roclets = c('rd', 'collate', 'namespace'))`. +- If `_pkgdown.yml` exists and contains a `reference` section: + - Whenever you add a new (non-internal) documentation topic, also add the topic to `_pkgdown.yml`. + - Use `pkgdown::check_pkgdown()` to check that all topics are included in the reference index. + +## Shared parameters + +**Parameters used in more than one function** go in `R/aaa-shared_params.R` under `@name .shared-params`. Functions inherit them with `@inheritParams .shared-params`. See `R/aaa-shared_params.R` for current definitions (if it exists). + +Shared params blocks: alphabetize parameters, use `@name .shared-params` (with leading dot), include `@keywords internal`, end with `NULL`. + +Multiple shared-params groups (e.g. `.shared-params-io`, `.shared-params-parsing`) are appropriate when parameters are only shared within a file and closely related files. + +## Parameter documentation format + +```r +#' @param param_name (`TYPE`) Brief description (usually 1-3 sentences. Can +#' include [cross_references()]. Additional details on continuation lines if +#' needed. +``` + +Function-specific `@param` definitions always appear *before* any `@inheritParams` lines. If all parameters are defined locally, omit `@inheritParams` entirely. + +### Type notation + +| Notation | Meaning | +|----------|---------| +| ``(`character`)`` | Character vector | +| ``(`character(1)`)`` | Single string | +| ``(`logical(1)`)`` | Single logical | +| ``(`integer`)`` | Integer vector | +| ``(`integer(1)`)`` | Single integer | +| ``(`double`)`` | Double vector | +| ``(`vector(0)`)`` | A prototype (zero-length vector) | +| ``(`vector`)`` | A vector of unspecified type | +| ``(`list`)`` | List | +| ``(`data.frame`)`` | Data frame or tibble | +| ``(`function` or `NULL`)`` | A function or NULL | +| ``(`my_class`)`` | A class-specific type (use the actual class name) | +| ``(`any`)`` | Any type | + +### Enumerated values + +When a parameter takes one of a fixed set of values, document them with a bullet list: + +```r +#' @param method (`character(1)`) The aggregation method. Can be one of: +#' * `"mean"`: Arithmetic mean. +#' * `"median"`: Median value. +#' * `"sum"`: Total sum. +``` + +## Returns + +Use `@returns` (not `@return`). Include a type when it's informative: + +```r +#' @returns A summary tibble. +#' @returns (`logical(1)`) `TRUE` if `x` is a valid record. +#' @returns Either a tibble or a list, depending on the input. +#' @returns `NULL` (invisibly). +``` + +**Structured returns with columns:** + +```r +#' @returns A [tibble::tibble()] with columns: +#' - `name`: Record name. +#' - `value`: Numeric value. +#' - `status`: Status (`"active"` or `"inactive"`). +``` + +## Exported functions + +```r +#' Title in sentence case +#' +#' Description paragraph providing context and details. +#' +#' @param param_name (`TYPE`) Description. +#' @inheritParams .shared-params +#' +#' @returns Description of return value. +#' @seealso [related_function()] +#' @export +#' +#' @examples +#' example_code() +``` + +- Blank `#'` lines separate: title/description, description/params, and `@export`/`@examples`. +- `@seealso` (optional) goes between `@returns` and `@export`. +- `@details` can supplement the description when needed. + +## Internal (unexported) functions + +Internal helpers (identified by a dot prefix, e.g. `.parse_response()`) use abbreviated documentation. Mark them with `@keywords internal` and omit `@export`: + +```r +#' Title in sentence case +#' +#' @param one_off_param (`TYPE`) Description. +#' @inheritParams .shared-params +#' @returns (`TYPE`) What it returns. +#' @keywords internal +``` + +Description paragraph is optional (only include when usage isn't obvious), fewer blank `#'` lines, and no `@examples`. + +## S3 methods and `@rdname` grouping + +Use `@rdname` to group related functions under one help page. This applies to: +- **S3 methods we own** (generic defined in this package): generic gets full docs, methods get `@rdname` + `@export`. +- **Related exported functions** (e.g. multiple variants of the same operation): primary function gets full docs, variants get `@rdname` + `@export`. + +```r +#' Format a summary object +#' +#' @param x (`any`) The object to format. +#' @param ... Additional arguments passed to methods. +#' @returns A formatted character string. +#' @keywords internal +.format_summary <- function(x, ...) { + UseMethod(".format_summary") +} + +#' @rdname .format_summary +#' @export +.format_summary.data_summary <- function(x, ...) { + # method implementation +} +``` + +**S3 methods we don't own** (generic from another package) need standalone documentation: + +```r +#' Title describing the method +#' +#' @param x (`TYPE`) Description. +#' @param ... Additional arguments (ignored). +#' @returns Description. +#' @exportS3Method pkg::generic +method.class <- function(x, ...) { ... } +``` + +## Style notes + +**Cross-references:** Use square brackets — `[fetch_records()]` (internal), `[tibble::tibble()]` (external), `[topic_name]` (topics). + +**Section comment headers** optionally organize code within a file, lowercase with dashes to column 80: + +```r +# helpers ---------------------------------------------------------------------- +``` + +Only use such headers in complex files. The need for section comment headers might indicate that the file should be split into multiple files. + +**Examples:** Exported functions include `@examples`. Use `@examplesIf interactive()` for network-dependent or slow functions. Use section-style comments (`# Section ---`) to organize longer example blocks. Internal functions do not get examples. diff --git a/.github/skills/github/SKILL.md b/.github/skills/github/SKILL.md new file mode 100644 index 0000000..8fa04d5 --- /dev/null +++ b/.github/skills/github/SKILL.md @@ -0,0 +1,22 @@ +--- +name: github +trigger: from github +description: GitHub workflows using the `gh` CLI, including viewing issues/PRs and commit message conventions. Use when interacting with GitHub in any way, such as viewing, creating, or editing issues and pull requests, making commits, or running any `gh` command. +compatibility: Requires the `gh` CLI and an authenticated GitHub session. +--- + +# GitHub + +Use `gh` CLI, not web URLs: `gh issue view 123`, `gh issue list`, `gh pr view 456`, `gh pr list`. + +## Commit messages + +Conventional commits; backtick-quote function names; close issues in body with `- Closes #N`. + +``` +feat: add `create_skill()` + +Generates a new skill directory with a SKILL.md template. + +- Closes #3 +``` diff --git a/.github/skills/implement-issue/SKILL.md b/.github/skills/implement-issue/SKILL.md new file mode 100644 index 0000000..f17891d --- /dev/null +++ b/.github/skills/implement-issue/SKILL.md @@ -0,0 +1,53 @@ +--- +name: implement-issue +trigger: implement issue / work on #NNN +description: Implements a GitHub issue end-to-end. Use when asked to implement, work on, or fix a specific issue number. +compatibility: Requires the `gh` CLI and an authenticated GitHub session. +--- + +# Implement a GitHub issue + +This skill wraps the Standard Workflow defined in `AGENTS.md`. Run the steps below before and after that workflow. + +## Before the standard workflow + +**A. Read the issue in full:** + +```bash +gh issue view {number} +``` + +If `gh` is not authenticated, stop and ask the user to authenticate before continuing. + +**B. Check/create the branch:** + +- If on `main`: `usethis::pr_init("fix-{number}-{description}")` +- Branch format: `fix-{number}-{description}` + - Parts separated by hyphens; `{description}` uses snake_case + - Example: `fix-42-validate_input` +- If a branch already exists for this issue, check it out instead + +## Run the Standard Workflow from AGENTS.md + +Steps 1–9 of the Standard Workflow are the core development loop. + +## After the standard workflow + +**C. Commit and push:** + +1. Review commits already on this branch (not on `main`) — these are all part of the same PR and should inform the PR description: + ```bash + git log main..HEAD --oneline + ``` +2. Stage and commit all changes: + ```bash + git add -A + git commit -m "{short imperative summary}" + ``` +3. Push and open the PR: + ```bash + gh pr create --fill + ``` + Use `--title` and `--body` explicitly if `--fill` produces an inadequate description. + +This step may be overridden — the user may ask you to stop before committing, handle the push themselves, or complete only part of the workflow. Always follow explicit user instructions over these defaults. diff --git a/.github/skills/r-code/SKILL.md b/.github/skills/r-code/SKILL.md new file mode 100644 index 0000000..70350d8 --- /dev/null +++ b/.github/skills/r-code/SKILL.md @@ -0,0 +1,228 @@ +--- +name: r-code +trigger: writing R functions / API design / error handling +description: Guide for writing R code. Use when writing new functions, designing APIs, or reviewing/modifying existing R code. +--- + +# R code + +This skill covers how to design and write R functions — including naming conventions, signatures, API conventions, input validation, error handling, and common pitfalls. For documenting functions, use the `document` skill. For tests, use the `tdd-workflow` skill. + +## Naming conventions + +### Functions + +Functions use `snake_case` and should be **verbs or verb phrases** that describe what the function does: + +```r +fetch_records() +build_summary() +validate_input() +``` + +A function name should be descriptive enough to make its purpose clear without a comment. Prefer clarity over brevity — don't abbreviate unless there is a widely understood convention (e.g. `df` for data frame, `dir` for directory). + +Internal helpers use a dot prefix: + +```r +.parse_response() +.validate_columns() +``` + +### Parameters + +Parameters use `snake_case` and should generally be **nouns**, occasionally adjectives. The same rule applies: clarity over brevity. + +```r +# Good +fetch_records(file_path, page_size, overwrite) + +# Bad — unclear abbreviations +fetch_records(fp, ps, ow) +``` + +## File organization + +One exported function per file: `R/{function_name}.R` (e.g. `fetch_records()` → `R/fetch_records.R`). Internal helpers used exclusively by that function live in the same file. Shared helpers go in `R/utils.R` or `R/utils-{topic}.R` (e.g. `R/utils-parsing.R`). + +## Coding style + +- Always run `air format .` after generating code. +- Use the base pipe operator (`|>`) not the magrittr pipe (`%>%`). +- Use `\() ...` for single-line anonymous functions. For all other cases, use `function() {...}`. + +## Function design + +**Functional core, imperative shell** — pure, testable functions that accept data and return data form the core. The imperative shell orchestrates program flow, manages state, and calls the functional core. + +Functions should be **small and single-purpose**. Each function should operate at a **single level of abstraction**: it either orchestrates calls to other functions, or performs a direct operation on data, but does not mix the two. + +```r +# Orchestrator — delegates to focused helpers +build_report <- function(data, output_path) { + data <- .clean_data(data) + summary <- .compute_summary(data) + .write_report(summary, output_path) +} + +# Worker — performs one direct operation +.clean_data <- function(data) { + data |> + dplyr::filter(!is.na(value)) |> + dplyr::mutate(value = round(value, 2)) +} +``` + +Name functions well enough that their purpose is obvious from the call site. When reading the orchestrator above, each step is self-documenting — no comments needed. + +**Simplify control flow** — prefer guard clauses and returning early over complex if/else structures. + +**Pure conditionals** — the expression inside a conditional check should not cause side effects. Extract the pure check from the impure action into separate functions if needed. + +## General API design patterns + +**Enum-like arguments** — declare choices as the default vector; resolve with `rlang::arg_match()` at the top of the function: + +```r +summarize_data <- function(x, method = c("mean", "median")) { + method <- rlang::arg_match(method) + # method is now guaranteed to be "mean" or "median" +} +``` + +**`NULL` as "not provided"** — use `NULL` as the default for optional arguments where there is no sensible scalar fallback; check with `is.null()`: + +```r +fetch_records <- function(x, output_column = NULL) { + if (!is.null(output_column)) { ... } +} +``` + +**S3 object construction** — build as a named list, set class explicitly: + +```r +.new_summary <- function(values, method) { + out <- list(values = values, method = method) + class(out) <- c(paste0("summary_", method), "data_summary") + out +} +``` + +**`call` propagation in internal validators** — helpers that validate arguments and may throw errors should accept and forward `call`: + +```r +.check_non_empty <- function(x, call = rlang::caller_env()) { + if (length(x) == 0L) { + .pkg_abort("Input {.arg x} cannot be empty.", "empty_input", call = call) + } +} + +process_data <- function(x, call = rlang::caller_env()) { + .check_non_empty(x, call = call) + ... +} +``` + +**Return tibbles, not data frames:** + +```r +summarize_data <- function(x) { + result |> tibble::as_tibble() +} +``` + +## Input validation + +Use `stbl::to_*()` and `stbl::stabilize_*()` to validate parameters. These functions coerce when safe and fail with clear error messages when not. + +- **`to_*()`** — simple type coercion. Use when you need to ensure a parameter is the right type but don't need additional constraints. +- **`stabilize_*()`** — coercion plus content validation (regex, ranges, etc.). Use when simple type coercion isn't enough. + +**Validate in the function that uses the parameter**, not in a caller that passes it through. This preserves R's lazy evaluation — if a parameter is never used on a code path, it is never evaluated or validated. + +```r +# Good — validation happens where the parameter is used +build_report <- function(data, title, page_size) { + data <- .clean_data(data) + summary <- .compute_summary(data, page_size) + .write_report(summary, title) +} + +.compute_summary <- function(data, page_size, call = rlang::caller_env()) { + page_size <- stbl::to_int_scalar(page_size, call = call) + ... +} + +.write_report <- function(summary, title, call = rlang::caller_env()) { + title <- stbl::to_chr_scalar(title, call = call) + ... +} +``` + +```r +# Bad — validates everything eagerly, breaking lazy evaluation +build_report <- function(data, title, page_size) { + title <- stbl::to_chr_scalar(title) + page_size <- stbl::to_int_scalar(page_size) + ... +} +``` + +When `call` is available (because the function accepts it), always pass it to `stbl` calls so error messages point to the user's call frame. + +## Internal vs. exported functions + +Export a function when: +- Users will call it directly +- Other packages may want to extend it +- It is a stable, intentional part of the API + +Keep a function internal when: +- It is an implementation detail that may change +- It is only used within the package +- Exporting it would clutter the user-facing API + +Internal helpers use a dot prefix (e.g. `.parse_response()`). + +## Error handling + +Use `.pkg_abort()` (defined in `R/aaa-conditions.R`) rather than calling `cli::cli_abort()` directly. This wraps `stbl::pkg_abort()` and ensures consistent error class formatting: + +```r +.pkg_abort( + "Column {.field {name}} not found in {.arg data}.", + "column_not_found", + call = call +) +``` + +Always pass `call = call` (or `call = rlang::caller_env()`) so errors point to the user's call frame, not an internal helper. + +## Common package mistakes + +```r +# Never use library() inside package code +library(dplyr) # Wrong +dplyr::filter(...) # Right +# or `@importFrom dplyr filter` if used extensively + +# Never modify global state without restoring it +options(my_option = TRUE) # Wrong +withr::local_options(list(my_option = TRUE)) # Right + +# Use system.file() for package data, not hardcoded paths +read.csv("/home/user/data.csv") # Wrong +system.file("extdata", "data.csv", package = "mypkg") # Right +``` + +## Dependencies + +### Use existing imports first + +Packages already in `Imports` in `DESCRIPTION` should be preferred over base R equivalents: `purrr::map()` over `lapply()`, `rlang::is_*()` predicates over `is.*()`, and `withr::local_*()` over manual `on.exit()` state management. + +### When to add a new dependency + +Add a dependency when it provides significant functionality that would be complex or brittle to reimplement — date parsing, web requests, complex string manipulation. Stick with base R or existing imports when the solution is straightforward. + +**Adding a new dependency requires explicit discussion with the developer.** diff --git a/.github/skills/search-code/SKILL.md b/.github/skills/search-code/SKILL.md new file mode 100644 index 0000000..c5cf978 --- /dev/null +++ b/.github/skills/search-code/SKILL.md @@ -0,0 +1,84 @@ +--- +name: search-code +trigger: search / rewrite code +description: Search and rewrite R source code by syntax using astgrepr. Use when asked to find patterns in code, search for function calls, identify usage of specific arguments, locate structural patterns across R files, or perform find-and-replace on code structure. +--- + +# Search and rewrite code with astgrepr + +`{astgrepr}` enables AST-based code search — structural queries that regex can't express cleanly. If not installed: `install.packages("astgrepr")` + +```r +library(astgrepr) + +src <- " +add <- function(x, y) x + y +greet <- function(name, greeting, sep) paste0(greeting, sep, name) +square <- function(x) x^2 +" +root <- src |> tree_new() |> tree_root() # or tree_new(file = "R/my_file.R") + +# µNAME/µA/µB capture matched nodes; only `add` matches (2 params) +matches <- node_find_all(root, + ast_rule(id = "two_arg", pattern = "µNAME <- function(µA, µB) µBODY") +) +matches #> |--two_arg: 1 nodes +node_text_all(matches) # source text of each match +lapply(matches$two_arg, \(n) node_get_match(n, "NAME") |> node_text()) #> "add" +``` + +## Reference + +Metavariables: `µVAR` captures one node (uppercase only); `µµµ` captures zero or more (ellipsis). In *replacement* strings, refer to captures as `~~VAR~~`. + +`ast_rule()` — see `?ast_rule`. Key args: + +- `pattern` — code pattern with metavariables +- `kind` — tree-sitter node kind; see the [R grammar](https://github.com/r-lib/tree-sitter-r/blob/main/src/grammar.json) +- `regex` — match node text with a Rust regex +- `id` — names the rule; results accessible as `matches$id` +- `inside`, `has`, `precedes`, `follows` — relational rules, each takes another `ast_rule()` +- `all`, `any`, `not` — boolean combinators, each takes a list of `ast_rule()`s + +For advanced pattern syntax: [ast-grep pattern docs](https://ast-grep.github.io/guide/pattern-syntax.html). + +## Searching across files + +```r +lapply(list.files("R", pattern = "\\.R$", full.names = TRUE), \(f) { + root <- tree_root(tree_new(file = f)) + texts <- node_find_all(root, ast_rule(id = "r", pattern = "YOUR_PATTERN")) |> + node_text_all() |> _$r + if (length(texts) > 0) list(file = basename(f), matches = texts) +}) |> Filter(Negate(is.null), x = _) +``` + +## Patterns + +```r +ast_rule(pattern = "if (µµµ) { return(µµµ) } else µµµ") # if-else with return() +ast_rule(pattern = "util_fun(debug = µµµ)") # named argument +ast_rule(kind = "while_statement") # by node kind +ast_rule(pattern = "df$µCOL") # df$col, any column +ast_rule(pattern = "print(µA)", # relational: inside loop + inside = ast_rule(any = ast_rule(kind = c("for_statement", "while_statement")))) +ast_rule(any = list(ast_rule(pattern = "any(is.na(µµµ))"), # boolean OR + ast_rule(pattern = "any(duplicated(µµµ))"))) +``` + +## Find and replace + +See `?node_replace_all`, `?tree_rewrite`. + +```r +root <- tree_root(tree_new(file = "R/my_file.R")) +fixes <- root |> + node_find_all( + ast_rule(id = "any_na", pattern = "any(is.na(µVAR))"), + ast_rule(id = "any_dup", pattern = "any(duplicated(µVAR))") + ) |> + node_replace_all(any_na = "anyNA(~~VAR~~)", + any_dup = "anyDuplicated(~~VAR~~) > 0") +tree_rewrite(root, fixes) # preview +writeLines(tree_rewrite(root, fixes), con = "R/my_file.R") # write back +``` diff --git a/.github/skills/tdd-workflow/SKILL.md b/.github/skills/tdd-workflow/SKILL.md new file mode 100644 index 0000000..a480565 --- /dev/null +++ b/.github/skills/tdd-workflow/SKILL.md @@ -0,0 +1,250 @@ +--- +name: tdd-workflow +trigger: writing or reviewing tests +description: Test-driven development workflow. Use when writing any R code (writing new features, fixing bugs, refactoring, or reviewing tests). +--- + +# TDD workflow + +## Core principle + +Write a failing test first, then implement the minimal code to make it pass, then refactor. Never write implementation code without a failing test driving it. + +## File naming + +Tests for `R/{name}.R` go in `tests/testthat/test-{name}.R`. Place new tests next to similar existing ones. + +## Running tests + +```r +# Full suite +devtools::test(reporter = "check") + +# Single file +devtools::test(filter = "name", reporter = "check") +``` + +Testing functions load code automatically. You do not need to call `library()` or `devtools::load_all()` separately. + +## Coverage + +Goal: **100%** for every edited file. After editing `R/file_name.R`, verify: + +```r +covr_res <- devtools:::test_coverage_active_file("R/file_name.R") +which(purrr::map_int(covr_res, "value") == 0) +``` + +Files excluded from the coverage requirement: +- `R/*-package.R` +- `R/aaa-shared_params.R` +- Files matching `R/import-standalone-*.R` + +## Test types + +### Unit tests + +Test individual functions in isolation: + +```r +test_that("fetch_records() returns a tibble (#2)", { + result <- fetch_records(sample_input) + expect_s3_class(result, "tbl_df") +}) +``` + +### Integration tests + +Test end-to-end pipelines through multiple functions: + +```r +test_that("build_report() produces expected output (#15)", { + input <- data.frame(value = c(1.123, 2.456, NA)) + result <- build_report(input, tempfile()) + expect_equal(nrow(result), 2L) +}) +``` + +### Snapshot tests + +For complex outputs that are hard to specify with equality assertions: + +```r +test_that("build_summary print method is stable (#123)", { + expect_snapshot(print(build_summary(sample_data))) +}) +``` + +When snapshots change intentionally, check the content of the file corresponding to the edited test file, then accept: + +```r +testthat::snapshot_accept("test_name") +``` + +Snapshots are stored in `tests/testthat/_snaps/`. The filename corresponds to the R file being tested, ending with `.md`. + +## Test design principles + +- **Self-sufficient:** each test contains its own setup, execution, and assertion. Tests must be runnable in isolation. +- **Duplication over factoring:** repeat setup code rather than extracting it. Clarity beats DRY in tests. +- **One concept per test:** a failing test should tell you exactly what broke. +- **Minimal with few comments:** keep tests lean. Avoid over-commenting. +- **Issue reference in description:** the `desc` of every new `test_that()` call should end with one or more parenthetical issue references for the issue(s) *verified by those tests* — typically the issue currently being solved. **Never guess or invent issue numbers.** Determine the number from the user's prompt, the branch name (`git branch --show-current`), or `gh issue list`. Before writing a number, verify you can trace it to one of these sources. If no tracked issue applies, use `#noissue`. The numbers in the examples below are illustrative placeholders — do not copy them: + ```r + test_that("fetch_records() returns correct columns (#1)", { ... }) + test_that("build_summary() returns correct columns (#2, #3)", { ... }) + test_that(".check_record() errors on empty input (#noissue)", { ... }) + ``` + +## testthat Edition 3 — deprecated patterns + +```r +# Deprecated → Modern +context("Data validation") # Remove — filename serves this purpose +expect_equivalent(x, y) # expect_equal(x, y, ignore_attr = TRUE) +with_mock(...) # local_mocked_bindings(...) +expect_is(x, "data.frame") # expect_s3_class(x, "data.frame") +``` + +## Essential expectations + +### Equality & identity + +```r +expect_equal(x, y) # with numeric tolerance +expect_equal(x, y, tolerance = 0.001) +expect_equal(x, y, ignore_attr = TRUE) +expect_identical(x, y) # exact match +``` + +### Conditions + +**Errors thrown by this package** (via `.pkg_abort()`) should always be tested +with `stbl::expect_pkg_error_snapshot()`, which captures both the error class +hierarchy and the user-facing message in one snapshot: + +```r +test_that("process_data() errors on empty input (#42)", { + stbl::expect_pkg_error_snapshot( + process_data(data.frame()), + "beekeeper", + "empty_input" + ) +}) +``` + +Pass `transform = stbl::.transform_path(path)` to scrub volatile values (e.g. temp +paths) from the snapshot before comparison. + +**Errors thrown by `stbl`** (via `stbl::to_*()` / `stbl::stabilize_*()`) +should be tested with `stbl::expect_pkg_error_classes()`. Since the message +text is controlled by `stbl`, only the class hierarchy needs to be asserted: + +```r +test_that("process_data() errors on non-integer page_size (#43)", { + stbl::expect_pkg_error_classes( + process_data(sample_data, page_size = "abc"), + "stbl", + "incompatible_type" + ) +}) +``` + +For **composite** stbl error classes (where the class name contains dashes, +e.g. `stbl-error-coerce-character`), pass each dash-separated component as a +separate argument. Underscores within a component are kept as-is: + +```r +test_that("process_data() errors on non-coercible input (#43)", { + stbl::expect_pkg_error_classes( + process_data(sample_data, value = list(bad = "input")), + "stbl", + "coerce", + "character" + ) +}) +``` + +**Errors from other packages** can be tested with `expect_error()`, optionally +wrapped in `expect_snapshot()` to lock down the message text: + +```r +expect_error(code, "pattern") +expect_error(code, class = "some-error-class") + +# Lock down both class and message text: +test_that("fetch_records errors on invalid input (#456)", { + expect_snapshot( + (expect_error( + fetch_records("not valid input"), + class = "pkg-error" + )) + ) +}) +``` + +```r +expect_warning(code) +expect_no_warning(code) +expect_message(code) +expect_no_message(code) +``` + +### Collections + +```r +expect_setequal(x, y) # same elements, any order +expect_in(element, set) +expect_named(x, c("a", "b")) +``` + +### Type & structure + +```r +expect_type(x, "double") +expect_s3_class(x, "tbl_df") +expect_length(x, 10) +expect_null(x) +``` + +### Logical + +These expectations are a last resort when more-specific checks aren't available. + +```r +expect_true(x) +expect_false(x) +``` + +## `withr` patterns for temporary state + +```r +withr::local_options(list(beekeeper.verbose = TRUE)) +withr::local_envvar(MY_VAR = "value") +withr::local_tempfile(lines = c("a", "b")) +``` + +## Fixtures + +Store static test data in `tests/testthat/fixtures/` and access via: + +```r +test_path("fixtures", "sample.rds") +``` + +## Mocking + +Mock functions that might have unstable output, hit external servers, etc. + +```r +local_mocked_bindings( + .other_fn = function(...) "mocked_result" +) +result <- my_function_that_calls_other_fn() +``` + +## Common mistakes + +- **Do not modify tests to make them pass.** Fix the implementation. +- **Do not write tests that depend on other tests' state.** Each test must be independently runnable. +- **Ask for help if test is bad.** If you think a test might be invalid, do not loop through trying to make impossible tests pass. Ask for help if possible. diff --git a/.github/workflows/copilot-setup-steps.yml b/.github/workflows/copilot-setup-steps.yml new file mode 100644 index 0000000..e1907e1 --- /dev/null +++ b/.github/workflows/copilot-setup-steps.yml @@ -0,0 +1,52 @@ +name: "Copilot Setup Steps" + +# Automatically run when the file changes to allow for easy validation, +# and allow manual testing through the repository's "Actions" tab. +on: + workflow_dispatch: + push: + paths: + - .github/workflows/copilot-setup-steps.yml + pull_request: + paths: + - .github/workflows/copilot-setup-steps.yml + +jobs: + # The job MUST be called `copilot-setup-steps` or it will not be picked up by Copilot. + copilot-setup-steps: + runs-on: ubuntu-latest + + permissions: + contents: read + + steps: + - uses: actions/checkout@v6 + + - uses: api2r/actions/install@v1 + with: + token: ${{ secrets.GITHUB_TOKEN }} + cache-version: copilot + needs: build, check, website + # Extra packages include things referenced in skills, to make sure the + # agent has them available. + optional-packages: any::astgrepr + extra-packages: >- + any::cli + any::covr + any::devtools + any::magick + any::pak + any::pkgdown + any::purrr + any::rcmdcheck + any::rlang + any::roxygen2 + any::stbl + any::testthat + any::usethis + any::withr + Gilead-BioStats/qcthat + local::. + + - name: Install air + uses: posit-dev/setup-air@v1 diff --git a/.github/workflows/qcthat.yaml b/.github/workflows/qcthat.yaml new file mode 100644 index 0000000..2adec84 --- /dev/null +++ b/.github/workflows/qcthat.yaml @@ -0,0 +1,157 @@ +# Workflow derived from +# https://github.com/Gilead-BioStats/qcthat/tree/v1.1.1/inst/workflows/qcthat.yaml. +on: + pull_request: + types: [opened, edited, reopened, synchronize, milestoned] + release: + types: [released] + issues: + types: [closed] + workflow_dispatch: + inputs: + pr: + description: PR number to which reports should be added (leave blank for none). + required: false + milestone: + description: Milestone name to use for the milestone report (leave blank for none). + required: false + tag: + description: Release tag to which the report should be attached (leave blank for none). + required: false + issueNumber: + description: The closed issue number to process to update user acceptance testing information. + required: false + +name: qcthat Quality Control + +permissions: + # read: Required for generating reports and updating UAT status. + # write: Required for initiating the UAT process. + issues: write + # read: Required for updating UAT status. + # write: Required for adding reports to pull requests. + pull-requests: write + # write: Required for attaching reports to releases. + contents: write + # write: Required for updating UAT status. + actions: write + +# Configuration variables for controlling workflow behavior +env: + qcthat_UAT: true + qcthat_PR_REPORT: true + qcthat_COMPLETED_REPORT: true + qcthat_MILESTONE_REPORT: true + qcthat_RELEASE_REPORT: true + qcthat_FAIL_FOR_TEST_FAILURES: true + +jobs: + qcthat: + runs-on: ubuntu-latest + container: + image: ghcr.io/api2r/pkgskills-ci:release + if: >- + (github.event_name == 'issues' && contains(github.event.issue.labels.*.name, 'qcthat-uat')) || + github.event_name != 'issues' + env: + GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} + steps: + - uses: actions/checkout@v6 + + - uses: api2r/actions/install@v1 + with: + use-container: "true" + token: ${{ secrets.GITHUB_TOKEN }} + extra-packages: Gilead-BioStats/qcthat@main local::. + + - name: Manage User Acceptance Testing + if: >- + env.qcthat_UAT == 'true' && ( + (github.event_name == 'issues' && contains(github.event.issue.labels.*.name, 'qcthat-uat')) || + (github.event_name == 'workflow_dispatch' && inputs.issueNumber != '') + ) + run: | + Rscript -e "qcthat::TriggerUAT()" + + - name: Generate Issue-Test Matrix + if: >- + (env.qcthat_PR_REPORT == 'true' || env.qcthat_RELEASE_REPORT == 'true' || env.qcthat_FAIL_FOR_TEST_FAILURES == 'true') && ( + github.event_name == 'pull_request' || + github.event_name == 'release' || + (github.event_name == 'workflow_dispatch' && inputs.issueNumber == '') + ) + run: | + # Generate the full matrix for the package + IssueTestMatrix <- qcthat::QCPackage() + print(IssueTestMatrix) + + # Save the matrix and UAT data for subsequent steps + saveRDS(IssueTestMatrix, "ITM.rds") + qcthat::SaveUATIssues() + shell: Rscript {0} + + - name: Update PR Reports + if: >- + env.qcthat_PR_REPORT == 'true' && ( + github.event_name == 'pull_request' || + (github.event_name == 'workflow_dispatch' && inputs.pr != '') + ) + run: | + issueTestMatrix <- readRDS("ITM.rds") + qcthat::LoadUATIssues() + qcthat::CommentAllReports( + dfITM = issueTestMatrix, + lglPR = as.logical("${{ env.qcthat_PR_REPORT }}"), + lglMilestone = as.logical("${{ env.qcthat_MILESTONE_REPORT }}"), + lglCompleted = as.logical("${{ env.qcthat_COMPLETED_REPORT }}"), + lglUAT = as.logical("${{ env.qcthat_UAT }}") + ) + shell: Rscript {0} + + - name: Update Release Reports + if: >- + env.qcthat_RELEASE_REPORT == 'true' && ( + github.event_name == 'release' || inputs.tag != '' + ) + run: | + issueTestMatrix <- readRDS("ITM.rds") + qcthat::LoadUATIssues() + qcthat::AttachReleaseReports( + dfITM = issueTestMatrix, + lglCompleted = as.logical("${{ env.qcthat_COMPLETED_REPORT }}"), + lglMilestone = as.logical("${{ env.qcthat_MILESTONE_REPORT }}") + ) + shell: Rscript {0} + + - name: Flag failure for PR + if: >- + env.qcthat_FAIL_FOR_TEST_FAILURES == 'true' && ( + github.event_name == 'pull_request' || + (github.event_name == 'workflow_dispatch' && inputs.pr != '') + ) + run: | + issueTestMatrix <- readRDS("ITM.rds") + dfPR <- qcthat::QCPR(dfITM = issueTestMatrix) + if (any(dfPR$Disposition == "fail", na.rm = TRUE)) { + cli::cli_abort( + "One or more tests failed or were skipped for PR-associated issues." + ) + } + shell: Rscript {0} + + - name: Flag failure for completed + if: >- + env.qcthat_FAIL_FOR_TEST_FAILURES == 'true' && ( + github.event_name == 'pull_request' || + github.event_name == 'release' || + (github.event_name == 'workflow_dispatch' && inputs.issueNumber == '') + ) + run: | + issueTestMatrix <- readRDS("ITM.rds") + dfCompleted = qcthat::QCCompletedIssues(dfITM = issueTestMatrix) + if (any(dfCompleted$Disposition == "fail", na.rm = TRUE)) { + cli::cli_abort( + "One or more tests failed or were skipped for completed issues." + ) + } + shell: Rscript {0} diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..4d44d53 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,73 @@ +# AGENTS.md + +## Repository overview + +**beekeeper** — Rapidly Scaffold API Client Packages + +Automatically generate R package skeletons from 'application + programming interfaces (APIs)' that follow the 'OpenAPI Specification + (OAS)'. The skeletons implement best practices to streamline package + development. + +https://beekeeper.api2r.org, https://github.com/jonthegeek/beekeeper + +### Overall structure + +The project follows standard R package conventions with these key directories: + +beekeeper/ +├── R/ # R source code +│ ├── beekeeper-package.R # Auto-generated package docs +│ └── *.R # Function definitions, 1 file ~= 1 exported function +├── .github/ +│ ├── ISSUE_TEMPLATE/ # GitHub issue templates +│ ├── skills/ # Agent skill definitions +│ └── workflows/ # CI/CD configurations +├── tests/testthat/ # Test suite +├── man/ # Generated documentation +├── AGENTS.md # Main agent setup file +├── DESCRIPTION # Package metadata +├── NAMESPACE # Auto-generated export information +├── NEWS.md # Changelog +└── Various config files # .gitignore, codecov.yml, etc. + +--- + +## Standard workflow + +For any feature, fix, or refactor: + +1. **Update packages**: `pak::pak()` +2. **Run tests** — confirm passing before changes: `devtools::test(reporter = "check")`. If any fail, stop and ask. +3. **Plan** — identify affected R files; check if new exports are needed. +4. **Test first** — write failing test, then implement: `devtools::test(filter = "name", reporter = "check")`. +5. **Implement** — minimal code to pass tests. +6. **Refactor** — clean up, keep tests green. +7. **Document** — document any new or changed exports. +8. **Verify**: Run `devtools::test(reporter = "check")`, then `devtools::check(error_on = "warning")`. Resolve warnings, errors, and NOTEs. +9. **News** — add bullet at top of `NEWS.md` (under dev heading): + - User-facing changes only. 1 line, end with `.` + - Present tense, positive framing, function names (backticks + `()`) near start: `` * `fn()` now accepts ... `` not `* Fixed ...` + - Issue/contributor before final period: `` * `fn()` now accepts ... (@user, #N). `` where `#N` is the GitHub issue number being implemented (e.g. `#42`). + - Get username: `gh api user --jq .login`; get issue number from the user's prompt, the branch name (`git branch --show-current`), or `gh issue list`. + - **Never guess or invent an issue number.** Before writing it, verify: (1) you received it from the user or the branch name, OR (2) you looked it up with `gh`. If you cannot trace the number to a concrete source, use `#noissue`. + +--- + +## General + +- R console: use `--quiet --vanilla`. +- Always run `air format .` after generating R code. +- Comments explain *why*, not *what*. + +## Skills + +| Triggers | Path | +|----------|------| +| create GitHub issues | @.github/skills/create-issue/SKILL.md | +| document functions | @.github/skills/document/SKILL.md | +| from github | @.github/skills/github/SKILL.md | +| implement issue / work on #NNN | @.github/skills/implement-issue/SKILL.md | +| writing R functions / API design / error handling | @.github/skills/r-code/SKILL.md | +| search / rewrite code | @.github/skills/search-code/SKILL.md | +| writing or reviewing tests | @.github/skills/tdd-workflow/SKILL.md | diff --git a/DESCRIPTION b/DESCRIPTION index 49eca26..296f502 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -27,6 +27,7 @@ Imports: rprojroot, S7, snakecase, + stbl (>= 0.3.0), stringr, styler, testthat, diff --git a/R/aaa-conditions.R b/R/aaa-conditions.R new file mode 100644 index 0000000..1e416a3 --- /dev/null +++ b/R/aaa-conditions.R @@ -0,0 +1,22 @@ +#' Raise a package-scoped error +#' +#' @inheritParams .shared-params +#' @inheritParams stbl::pkg_abort +#' @returns Does not return. +#' @keywords internal +.pkg_abort <- function( + message, + subclass, + call = rlang::caller_env(), + message_env = rlang::caller_env(), + ... +) { + stbl::pkg_abort( + "beekeeper", + message, + subclass, + call = call, + message_env = message_env, + ... + ) +} diff --git a/R/aaa-shared_params.R b/R/aaa-shared_params.R new file mode 100644 index 0000000..b578a6c --- /dev/null +++ b/R/aaa-shared_params.R @@ -0,0 +1,10 @@ +#' Shared parameters +#' +#' These parameters are used in multiple functions. They are defined here to +#' make them easier to import and to find. +#' +#' @param call (`environment`) The caller environment for error messages. +#' +#' @name .shared-params +#' @keywords internal +NULL diff --git a/man/dot-pkg_abort.Rd b/man/dot-pkg_abort.Rd new file mode 100644 index 0000000..a9b46ce --- /dev/null +++ b/man/dot-pkg_abort.Rd @@ -0,0 +1,36 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/aaa-conditions.R +\name{.pkg_abort} +\alias{.pkg_abort} +\title{Raise a package-scoped error} +\usage{ +.pkg_abort( + message, + subclass, + call = rlang::caller_env(), + message_env = rlang::caller_env(), + ... +) +} +\arguments{ +\item{message}{(\code{character}) The message for the new error. Messages will be +formatted with \code{\link[cli:cli_bullets]{cli::cli_bullets()}}.} + +\item{subclass}{(\code{character}) Class(es) to assign to the error. Will be +prefixed by "\{package\}-error-".} + +\item{call}{(\code{environment}) The caller environment for error messages.} + +\item{message_env}{(\code{environment}) The execution environment to use to +evaluate variables in error messages.} + +\item{...}{Additional parameters passed to \code{\link[cli:cli_abort]{cli::cli_abort()}} and on to +\code{\link[rlang:abort]{rlang::abort()}}.} +} +\value{ +Does not return. +} +\description{ +Raise a package-scoped error +} +\keyword{internal} diff --git a/man/dot-shared-params.Rd b/man/dot-shared-params.Rd new file mode 100644 index 0000000..381d147 --- /dev/null +++ b/man/dot-shared-params.Rd @@ -0,0 +1,13 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/aaa-shared_params.R +\name{.shared-params} +\alias{.shared-params} +\title{Shared parameters} +\arguments{ +\item{call}{(\code{environment}) The caller environment for error messages.} +} +\description{ +These parameters are used in multiple functions. They are defined here to +make them easier to import and to find. +} +\keyword{internal} From 9dd0c4d5f236a9e5ad2f0fc7a071bb9570ba1311 Mon Sep 17 00:00:00 2001 From: Jon Harmon Date: Mon, 11 May 2026 16:45:21 -0500 Subject: [PATCH 14/33] Update templates --- inst/templates/010-prepare.R | 4 ++-- inst/templates/paths.R | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/inst/templates/010-prepare.R b/inst/templates/010-prepare.R index d4b2792..3177b5a 100644 --- a/inst/templates/010-prepare.R +++ b/inst/templates/010-prepare.R @@ -15,7 +15,7 @@ query = list(), body = NULL, method = NULL, - tidy_fn = nectar::resp_tidy_unknown,{{#has_security}}{{{security_signature}}},{{/has_security}} + tidy_policy = nectar::resp_tidy_unknown,{{#has_security}}{{{security_signature}}},{{/has_security}} call = rlang::caller_env()) { req <- nectar::req_prepare( "{{base_url}}", @@ -23,7 +23,7 @@ query = query, body = body, method = method, - tidy_fn = tidy_fn, + tidy_policy = tidy_policy, call = call ) {{#has_security}}req <- .{{api_abbr}}_req_auth(req, {{security_arg_list}}){{/has_security}} diff --git a/inst/templates/paths.R b/inst/templates/paths.R index 416e2c7..f20a29d 100644 --- a/inst/templates/paths.R +++ b/inst/templates/paths.R @@ -21,19 +21,19 @@ max_reqs = max_reqs, # Should only include this with pagination. max_tries_per_req = max_tries_per_req ) - return(nectar::resp_tidy(resps)) + return(nectar::resp_parse(resps)) } #' @rdname {{operation_id}} #' @returns `req_{{operation_id}}()`: A `httr2_request` request object. req_{{operation_id}} <- function({{{args}}}{{#has_security}}{{#args}},{{/args}}{{{security_signature}}}{{/has_security}}) { - slack_req_prepare( + {{api_abbr}}_req_prepare( path = "{{{path}}}", method = "{{method}}"{{#has_security}}, {{security_arg_list}}{{/has_security}}{{#params_query}}, query = list({{params_query}}){{/params_query}}{{#params_header}}, body = list({{params_header}}){{/params_header}}{{#pagination}}, pagination_fn = {{pagination_fn}}{{/pagination}}{{#tidy}}, - tidy_fn = {{tidy_fn}}{{/tidy}} + tidy_policy = {{tidy_policy}}{{/tidy}} ) } From b66c95d4e776cf02d00cba7b21b46cbb79ce73fb Mon Sep 17 00:00:00 2001 From: Jon Harmon Date: Mon, 11 May 2026 16:54:23 -0500 Subject: [PATCH 15/33] Don't import so much. --- NAMESPACE | 12 ------------ R/beekeeper-package.R | 12 ------------ R/generate_pkg-paths.R | 8 ++++---- R/generate_pkg-security.R | 2 +- R/generate_pkg-setup.R | 6 +++--- R/generate_pkg-template.R | 2 +- R/use_beekeeper.R | 2 +- R/utils.R | 2 +- 8 files changed, 11 insertions(+), 35 deletions(-) diff --git a/NAMESPACE b/NAMESPACE index 467eb17..154d612 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -22,7 +22,6 @@ importFrom(purrr,map) importFrom(purrr,map2) importFrom(purrr,map_chr) importFrom(purrr,pmap) -importFrom(purrr,quietly) importFrom(rapid,as_rapid) importFrom(rapid,class_api_key_security_scheme) importFrom(rapid,class_paths) @@ -32,16 +31,7 @@ importFrom(rlang,.data) importFrom(rlang,check_dots_empty) importFrom(rlang,set_names) importFrom(rlang,try_fetch) -importFrom(rprojroot,find_package_root_file) -importFrom(snakecase,to_snake_case) -importFrom(stringr,str_remove) -importFrom(stringr,str_replace_all) -importFrom(stringr,str_squish) -importFrom(stringr,str_to_sentence) -importFrom(styler,style_file) importFrom(testthat,test_that) -importFrom(tidyr,nest) -importFrom(tidyr,unnest) importFrom(usethis,proj_get) importFrom(usethis,proj_path) importFrom(usethis,use_build_ignore) @@ -50,5 +40,3 @@ importFrom(usethis,use_package) importFrom(usethis,use_template) importFrom(usethis,use_testthat) importFrom(utils,capture.output) -importFrom(yaml,read_yaml) -importFrom(yaml,write_yaml) diff --git a/R/beekeeper-package.R b/R/beekeeper-package.R index da1d288..41c3999 100644 --- a/R/beekeeper-package.R +++ b/R/beekeeper-package.R @@ -18,7 +18,6 @@ #' @importFrom purrr map_chr #' @importFrom purrr map2 #' @importFrom purrr pmap -#' @importFrom purrr quietly #' @importFrom rapid as_rapid #' @importFrom rapid class_api_key_security_scheme #' @importFrom rapid class_paths @@ -28,19 +27,10 @@ #' @importFrom rlang check_dots_empty #' @importFrom rlang set_names #' @importFrom rlang try_fetch -#' @importFrom rprojroot find_package_root_file #' @importFrom S7 class_any #' @importFrom S7 class_data.frame #' @importFrom S7 class_list -#' @importFrom snakecase to_snake_case -#' @importFrom stringr str_remove -#' @importFrom stringr str_replace_all -#' @importFrom stringr str_squish -#' @importFrom stringr str_to_sentence -#' @importFrom styler style_file #' @importFrom testthat test_that -#' @importFrom tidyr nest -#' @importFrom tidyr unnest #' @importFrom usethis proj_get #' @importFrom usethis proj_path #' @importFrom usethis use_build_ignore @@ -49,7 +39,5 @@ #' @importFrom usethis use_template #' @importFrom usethis use_testthat #' @importFrom utils capture.output -#' @importFrom yaml read_yaml -#' @importFrom yaml write_yaml ## usethis namespace: end NULL diff --git a/R/generate_pkg-paths.R b/R/generate_pkg-paths.R index d560091..d6c39a6 100644 --- a/R/generate_pkg-paths.R +++ b/R/generate_pkg-paths.R @@ -93,10 +93,10 @@ S7::method(as_bk_data, class_paths) <- function(x) { } .paths_fill_summary <- function(summary, endpoint, method) { - endpoint_spaced <- str_replace_all(.to_snake(endpoint), "_", " ") + endpoint_spaced <- stringr::str_replace_all(.to_snake(endpoint), "_", " ") .coalesce( - str_squish(summary), - str_to_sentence(glue("{method} {endpoint_spaced}")) + stringr::str_squish(summary), + stringr::str_to_sentence(glue("{method} {endpoint_spaced}")) ) } @@ -174,7 +174,7 @@ S7::method(as_bk_data, class_paths) <- function(x) { .paths_fill_descriptions <- function(descriptions, summaries) { descriptions[is.na(descriptions)] <- summaries[is.na(descriptions)] descriptions[is.na(descriptions)] <- "" - return(str_squish(descriptions)) + return(stringr::str_squish(descriptions)) } .describe_param_classes <- function(params_schema, allow_empty, required) { diff --git a/R/generate_pkg-security.R b/R/generate_pkg-security.R index f1b8325..765b0b7 100644 --- a/R/generate_pkg-security.R +++ b/R/generate_pkg-security.R @@ -131,7 +131,7 @@ S7::method(as_bk_data, class_api_key_security_scheme) <- function(x) { return( list( parameter_name = x@parameter_name, - arg_name = str_remove(.to_snake(x@parameter_name), "^x_"), + arg_name = stringr::str_remove(.to_snake(x@parameter_name), "^x_"), location = x@location, type = "api_key", api_key = TRUE diff --git a/R/generate_pkg-setup.R b/R/generate_pkg-setup.R index 56325ef..7354a2a 100644 --- a/R/generate_pkg-setup.R +++ b/R/generate_pkg-setup.R @@ -24,14 +24,14 @@ #' @keywords internal .is_pkg <- function(base_path = proj_get()) { root_file <- try_fetch( - find_package_root_file(path = base_path), + rprojroot::find_package_root_file(path = base_path), error = function(cnd) NULL ) !is.null(root_file) } .read_config <- function(config_file = "_beekeeper.yml") { - config <- read_yaml(config_file) + config <- yaml::read_yaml(config_file) return(.stabilize_config(config)) # nocov } @@ -63,7 +63,7 @@ } use_directory("R") use_testthat() - quietly(use_httptest2)() + purrr::quietly(use_httptest2)() use_package("nectar") use_package("beekeeper", type = "Suggests") } diff --git a/R/generate_pkg-template.R b/R/generate_pkg-template.R index fe901ae..92fcbdf 100644 --- a/R/generate_pkg-template.R +++ b/R/generate_pkg-template.R @@ -18,7 +18,7 @@ check_dots_empty() dir <- match.arg(dir) target <- .bk_use_template_impl(template, data, target, dir) - capture.output(style_file(target)) + capture.output(styler::style_file(target)) return(invisible(target)) } diff --git a/R/use_beekeeper.R b/R/use_beekeeper.R index 057aa8d..8d5c5c9 100644 --- a/R/use_beekeeper.R +++ b/R/use_beekeeper.R @@ -43,7 +43,7 @@ use_beekeeper <- function( .write_config <- function(x, api_abbr, rapid_file, config_file) { config_file <- stbl::stabilize_character_scalar(config_file) update_time <- strptime(Sys.time(), format = "%Y-%m-%d %H:%M:%S", tz = "UTC") - write_yaml( + yaml::write_yaml( list( api_title = x@info@title, api_abbr = stbl::stabilize_character_scalar(api_abbr), diff --git a/R/utils.R b/R/utils.R index d640a69..87a7cd2 100644 --- a/R/utils.R +++ b/R/utils.R @@ -48,7 +48,7 @@ } .to_snake <- function(x) { - to_snake_case(x, parsing_option = 3) + snakecase::to_snake_case(x, parsing_option = 3) } .flatten_df <- S7::new_generic( From 093378036a5caf8fd9e0cf924272b287580a0d4b Mon Sep 17 00:00:00 2001 From: Jon Harmon Date: Mon, 11 May 2026 17:00:33 -0500 Subject: [PATCH 16/33] Update expected results per nectar updates --- tests/testthat/_fixtures/guru-010-prepare.R | 4 ++-- tests/testthat/_fixtures/trello-010-prepare.R | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/testthat/_fixtures/guru-010-prepare.R b/tests/testthat/_fixtures/guru-010-prepare.R index 3325e88..515a67a 100644 --- a/tests/testthat/_fixtures/guru-010-prepare.R +++ b/tests/testthat/_fixtures/guru-010-prepare.R @@ -15,7 +15,7 @@ guru_req_prepare <- function( query = list(), body = NULL, method = NULL, - tidy_fn = nectar::resp_tidy_unknown, + tidy_policy = nectar::tidy_policy_unknown(), call = rlang::caller_env() ) { req <- nectar::req_prepare( @@ -24,7 +24,7 @@ guru_req_prepare <- function( query = query, body = body, method = method, - tidy_fn = tidy_fn, + tidy_policy = tidy_policy, call = call ) diff --git a/tests/testthat/_fixtures/trello-010-prepare.R b/tests/testthat/_fixtures/trello-010-prepare.R index ec0fc3a..491fad6 100644 --- a/tests/testthat/_fixtures/trello-010-prepare.R +++ b/tests/testthat/_fixtures/trello-010-prepare.R @@ -16,7 +16,7 @@ trello_req_prepare <- function( query = list(), body = NULL, method = NULL, - tidy_fn = nectar::resp_tidy_unknown, + tidy_policy = nectar::tidy_policy_unknown(), key = Sys.getenv("TRELLO_KEY"), token = Sys.getenv("TRELLO_TOKEN"), call = rlang::caller_env() @@ -27,7 +27,7 @@ trello_req_prepare <- function( query = query, body = body, method = method, - tidy_fn = tidy_fn, + tidy_policy = tidy_policy, call = call ) req <- .trello_req_auth(req, key = key, token = token) From dad0ff4abc7b0865091b38b12e1b8ab88862b365 Mon Sep 17 00:00:00 2001 From: Jon Harmon Date: Mon, 11 May 2026 17:19:28 -0500 Subject: [PATCH 17/33] Get tests working. --- inst/templates/010-prepare.R | 13 +++++++------ tests/testthat/.gitignore | 1 + 2 files changed, 8 insertions(+), 6 deletions(-) create mode 100644 tests/testthat/.gitignore diff --git a/inst/templates/010-prepare.R b/inst/templates/010-prepare.R index 3177b5a..9f3a592 100644 --- a/inst/templates/010-prepare.R +++ b/inst/templates/010-prepare.R @@ -11,12 +11,13 @@ #' @inheritParams nectar::req_prepare #' @inherit nectar::req_prepare return #' @keywords internal -{{api_abbr}}_req_prepare <- function(path, - query = list(), - body = NULL, - method = NULL, - tidy_policy = nectar::resp_tidy_unknown,{{#has_security}}{{{security_signature}}},{{/has_security}} - call = rlang::caller_env()) { +{{api_abbr}}_req_prepare <- function( + path, + query = list(), + body = NULL, + method = NULL, + tidy_policy = nectar::tidy_policy_unknown(),{{#has_security}}{{{security_signature}}},{{/has_security}} + call = rlang::caller_env()) { req <- nectar::req_prepare( "{{base_url}}", path = path, diff --git a/tests/testthat/.gitignore b/tests/testthat/.gitignore new file mode 100644 index 0000000..66e54ae --- /dev/null +++ b/tests/testthat/.gitignore @@ -0,0 +1 @@ +testthat-problems.rds From ffa95de4be1f3a5a6b5da65b903ab5a7ab801197 Mon Sep 17 00:00:00 2001 From: Jon Harmon Date: Mon, 11 May 2026 17:30:35 -0500 Subject: [PATCH 18/33] Stub pagination. --- R/generate_pkg-pagination.R | 3 +- data-raw/oas_format_registry.R | 184 +++++++++++++++++---------------- inst/templates/010-prepare.R | 3 +- 3 files changed, 97 insertions(+), 93 deletions(-) diff --git a/R/generate_pkg-pagination.R b/R/generate_pkg-pagination.R index 9de231e..8e70eaa 100644 --- a/R/generate_pkg-pagination.R +++ b/R/generate_pkg-pagination.R @@ -1,3 +1,4 @@ .generate_pagination <- function() { - stop("TODO: FIGURE THIS OUT") + # TODO: Accept pagination config and generate 030-pagination.R (#63) + list() } diff --git a/data-raw/oas_format_registry.R b/data-raw/oas_format_registry.R index 7d163f7..228a74c 100644 --- a/data-raw/oas_format_registry.R +++ b/data-raw/oas_format_registry.R @@ -1,99 +1,101 @@ -oas_format_registry_raw <- rvest::read_html("https://spec.openapis.org/registry/format/") +oas_format_registry_raw <- rvest::read_html( + "https://spec.openapis.org/registry/format/" +) oas_format_registry <- oas_format_registry_raw |> - rvest::html_table() |> - _[[1]] |> - janitor::clean_names() |> - dplyr::mutate( - json_data_type = stringr::str_split(.data$json_data_type, ", "), - source = dplyr::na_if(.data$source, ""), - deprecated = .data$deprecated == "Yes" - ) |> - tidyr::unnest_longer("json_data_type") |> - dplyr::select( - type = "json_data_type", - format = "value" #, UNCOMMENT THESE TO DIG INTO FORMATS - # "description", - # "source", - # "deprecated" - ) |> - dplyr::arrange(.data$type, .data$format) |> - dplyr::bind_rows( - data.frame( - type = c( - "boolean", - "integer", - "null", - "number", - "string" - ) - ) - ) |> - dplyr::mutate( - # TODO: Sort out exactly how this works. tibblify will process things. Can - # I make it give these things a class, and then stabilize back and forth - # via that? - # - # nectar re-exports from stbl, and adds HTTP-specific wrappers - to_r = dplyr::case_when( - ## Null - .data$type == "null" ~ "stabilize_null", - ## Numbers - .data$format == "int64" ~ "stabilize_int64", - stringr::str_detect(.data$format, "int") ~ "stabilize_int", - stringr::str_detect(.data$type, "int") ~ "stabilize_int", - .data$type == "number" ~ "stabilize_dbl", - stringr::str_detect(.data$format, "decimal") ~ "stabilize_dbl", - ## Dates and times - .data$format %in% c("date", "http-date") ~ "stabilize_date", - .data$format == "date-time" ~ "stabilize_datetime", - .data$format == "duration" ~ "stabilize_duration", - .data$format == "time" ~ "stabilize_time", - ## Binary - .data$format %in% c("byte", "sf-binary") ~ "stabilize_base64_to_chr", - .data$format == "binary" ~ "stabilize_binary_to_raw", - .data$format == "base64url" ~ "stabilize_base64url_to_chr", - ## Logical - .data$format == "sf-boolean" ~ "stabilize_structured_lgl", - .data$type == "boolean" ~ "stabilize_lgl", - ## Specific Strings - .data$format == "uuid" ~ "stabilize_uuid", - # TODO: Add more specific string formats. - .default = "stabilize_chr" - ), - r_class_name = dplyr::case_match( - .data$to_r, - "stabilize_base64_to_chr" ~ "character", - "stabilize_base64url_to_chr" ~ "character", - "stabilize_binary_to_raw" ~ "raw", - "stabilize_chr" ~ "character", - "stabilize_date" ~ "Date", - "stabilize_datetime" ~ "POSIXct", - "stabilize_dbl" ~ "double", - "stabilize_duration" ~ "Duration", - "stabilize_int" ~ "integer", - "stabilize_int64" ~ "integer64", - "stabilize_lgl" ~ "logical", - "stabilize_null" ~ "NULL", - "stabilize_structured_lgl" ~ "logical", - "stabilize_time" ~ "hms", - "stabilize_uuid" ~ "UUID" - ), - r_class_package = dplyr::case_match( - .data$r_class_name, - "Duration" ~ "lubridate", - "integer64" ~ "bit64", - "hms" ~ "hms", - "UUID" ~ "uuid", - .default = "base" - ), - r_class_link = dplyr::case_match( - .data$r_class_name, - "Duration" ~ "Duration-class", - .default = .data$r_class_name + rvest::html_table() |> + _[[1]] |> + janitor::clean_names() |> + dplyr::mutate( + json_data_type = stringr::str_split(.data$json_data_type, ", "), + source = dplyr::na_if(.data$source, ""), + deprecated = .data$deprecated == "Yes" + ) |> + tidyr::unnest_longer("json_data_type") |> + dplyr::select( + type = "json_data_type", + format = "value" #, UNCOMMENT THESE TO DIG INTO FORMATS + # "description", + # "source", + # "deprecated" + ) |> + dplyr::arrange(.data$type, .data$format) |> + dplyr::bind_rows( + data.frame( + type = c( + "boolean", + "integer", + "null", + "number", + "string" ) ) + ) |> + dplyr::mutate( + # TODO: Sort out exactly how this works. tibblify will process things. Can + # I make it give these things a class, and then stabilize back and forth + # via that? + # + # nectar re-exports from stbl, and adds HTTP-specific wrappers + to_r = dplyr::case_when( + ## Null + .data$type == "null" ~ "stabilize_null", + ## Numbers + .data$format == "int64" ~ "stabilize_int64", + stringr::str_detect(.data$format, "int") ~ "stabilize_int", + stringr::str_detect(.data$type, "int") ~ "stabilize_int", + .data$type == "number" ~ "stabilize_dbl", + stringr::str_detect(.data$format, "decimal") ~ "stabilize_dbl", + ## Dates and times + .data$format %in% c("date", "http-date") ~ "stabilize_date", + .data$format == "date-time" ~ "stabilize_datetime", + .data$format == "duration" ~ "stabilize_duration", + .data$format == "time" ~ "stabilize_time", + ## Binary + .data$format %in% c("byte", "sf-binary") ~ "stabilize_base64_to_chr", + .data$format == "binary" ~ "stabilize_binary_to_raw", + .data$format == "base64url" ~ "stabilize_base64url_to_chr", + ## Logical + .data$format == "sf-boolean" ~ "stabilize_structured_lgl", + .data$type == "boolean" ~ "stabilize_lgl", + ## Specific Strings + .data$format == "uuid" ~ "stabilize_uuid", + # TODO: Add more specific string formats. + .default = "stabilize_chr" + ), + r_class_name = dplyr::case_match( + .data$to_r, + "stabilize_base64_to_chr" ~ "character", + "stabilize_base64url_to_chr" ~ "character", + "stabilize_binary_to_raw" ~ "raw", + "stabilize_chr" ~ "character", + "stabilize_date" ~ "Date", + "stabilize_datetime" ~ "POSIXct", + "stabilize_dbl" ~ "double", + "stabilize_duration" ~ "Duration", + "stabilize_int" ~ "integer", + "stabilize_int64" ~ "integer64", + "stabilize_lgl" ~ "logical", + "stabilize_null" ~ "NULL", + "stabilize_structured_lgl" ~ "logical", + "stabilize_time" ~ "hms", + "stabilize_uuid" ~ "UUID" + ), + r_class_package = dplyr::case_match( + .data$r_class_name, + "Duration" ~ "lubridate", + "integer64" ~ "bit64", + "hms" ~ "hms", + "UUID" ~ "uuid", + .default = "base" + ), + r_class_link = dplyr::case_match( + .data$r_class_name, + "Duration" ~ "Duration-class", + .default = .data$r_class_name + ) + ) usethis::use_data(oas_format_registry, overwrite = TRUE) rm(oas_format_registry_raw, oas_format_registry) diff --git a/inst/templates/010-prepare.R b/inst/templates/010-prepare.R index 9f3a592..59ae541 100644 --- a/inst/templates/010-prepare.R +++ b/inst/templates/010-prepare.R @@ -16,7 +16,8 @@ query = list(), body = NULL, method = NULL, - tidy_policy = nectar::tidy_policy_unknown(),{{#has_security}}{{{security_signature}}},{{/has_security}} + tidy_policy = nectar::tidy_policy_unknown(),{{#has_security}} +{{{security_signature}}},{{/has_security}} call = rlang::caller_env()) { req <- nectar::req_prepare( "{{base_url}}", From 48807bb76d9d21658188b81a049dfe56770f0ec5 Mon Sep 17 00:00:00 2001 From: Jon Harmon Date: Tue, 12 May 2026 06:05:51 -0500 Subject: [PATCH 19/33] Make tests pass for paths --- R/generate_pkg-paths.R | 277 ++++++++------------ inst/templates/paths.R | 16 +- tests/testthat/_snaps/generate_pkg-paths.md | 20 +- tests/testthat/test-generate_pkg-paths.R | 75 +++--- tests/testthat/test-generate_pkg.R | 9 +- 5 files changed, 190 insertions(+), 207 deletions(-) diff --git a/R/generate_pkg-paths.R b/R/generate_pkg-paths.R index d6c39a6..236d906 100644 --- a/R/generate_pkg-paths.R +++ b/R/generate_pkg-paths.R @@ -2,7 +2,7 @@ paths, api_abbr, security_data, - pagination_data, + pagination_data = list(), base_url ) { paths_by_operation <- as_bk_data(paths) @@ -31,7 +31,9 @@ S7::method(as_bk_data, class_paths) <- function(x) { return(list()) } paths_df <- .paths_to_clean_df(x) - return(tibble::deframe(paths_df)) + result <- purrr::pmap(paths_df, .path_row_to_list) + names(result) <- paths_df$operation_id + result } .paths_to_clean_df <- function(x) { @@ -50,34 +52,10 @@ S7::method(as_bk_data, class_paths) <- function(x) { x$endpoint, x$operation ) - x$description = .paths_fill_descriptions(x$description, x$summary) + x$description <- .paths_fill_descriptions(x$description, x$summary) # TODO: Deal with x$global_parameters if present x$parameters <- purrr::map(x$parameters, .prepare_params_df) - # TODO: add breakdown by location - return(tidyr::nest(x, .by = "operation_id")) -} - -## to list --------------------------------------------------------------------- -.paths_endpoints_to_lists <- function(endpoints) { - pmap( - list( - operation_id = .paths_fill_operation_id( - endpoints$operation_id, - endpoints$endpoint, - endpoints$operation - ), - path = endpoints$endpoint, - summary = .paths_fill_summary( - endpoints$summary, - endpoints$endpoint, - endpoints$operation - ), - description = .paths_fill_descriptions(endpoints$description), - params_df = endpoints$parameters, - method = endpoints$operation - ), - .paths_endpoint_to_list - ) + return(x) } ### fill data ------------------------------------------------------------------ @@ -100,40 +78,49 @@ S7::method(as_bk_data, class_paths) <- function(x) { ) } -### create whisker data -------------------------------------------------------- +.paths_fill_descriptions <- function(descriptions, summaries) { + descriptions[is.na(descriptions)] <- summaries[is.na(descriptions)] + descriptions[is.na(descriptions)] <- "" + return(stringr::str_squish(descriptions)) +} + +### create template data ------------------------------------------------------- -.paths_endpoint_to_list <- function( +.path_row_to_list <- function( operation_id, - path, + endpoint, + operation, summary, description, - params_df, - method + tags, + parameters, + ... ) { - params_df <- .prepare_params_df(params_df) - return( - list( - operation_id = operation_id, - path = .path_as_arg(path, params_df), - method = method, - summary = summary, - description = description, - params = .params_to_list(params_df), - params_query = .extract_params_by_location(params_df, "query"), - params_header = .extract_params_by_location(params_df, "header"), - params_cookie = .extract_params_by_location(params_df, "cookie") - ) + list( + operation_id = operation_id, + tag = tags, + path = .path_as_arg(endpoint, parameters), + method = operation, + summary = summary, + description = description, + params = .params_to_list(parameters), + params_query_raw = .extract_params_by_location(parameters, "query"), + params_header_raw = .extract_params_by_location(parameters, "header"), + params_cookie_raw = .extract_params_by_location(parameters, "cookie") ) } .prepare_params_df <- function(params_df) { params_df <- .flatten_params_df(params_df) if (nrow(params_df)) { - params_df$description <- .paths_complete_param_descriptions( - descriptions = params_df$description, - params_schema = params_df$schema, - required = params_df$required, - allow_empty = params_df$allowEmptyValue + params_df$class <- .describe_param_classes( + params_df$schema, + params_df$allowEmptyValue, + params_df$required + ) + params_df$description <- .paths_fill_descriptions( + params_df$description, + params_df$schema$description ) } params_df$schema <- NULL @@ -153,15 +140,16 @@ S7::method(as_bk_data, class_paths) <- function(x) { if (!nrow(params_df)) { return(list()) } - # TODO: Deal with all the available data. - params <- pmap( + purrr::pmap( list( name = params_df$name, + class = params_df$class, description = params_df$description ), - .paths_param_to_list + function(name, class, description) { + list(name = name, class = class, description = description) + } ) - return(params) } .extract_params_by_location <- function(params_df, filter_in) { @@ -171,21 +159,20 @@ S7::method(as_bk_data, class_paths) <- function(x) { return(params_df$name[params_df$`in` == filter_in]) } -.paths_fill_descriptions <- function(descriptions, summaries) { - descriptions[is.na(descriptions)] <- summaries[is.na(descriptions)] - descriptions[is.na(descriptions)] <- "" - return(stringr::str_squish(descriptions)) -} - .describe_param_classes <- function(params_schema, allow_empty, required) { # TODO: Use enum and/or description when available. # - # TODO: What should we do for `object` and `array`? + # TODO: What should we do for `object`? Currently falls back to list, same as + # array. type <- dplyr::left_join( dplyr::select(params_schema, "type", "format"), oas_format_registry, by = c("type", "format") ) + # Fall back to list for unknown types (array, object, etc.) + type$r_class_name <- dplyr::coalesce(type$r_class_name, "list") + type$r_class_package <- dplyr::coalesce(type$r_class_package, "base") + type$r_class_link <- dplyr::coalesce(type$r_class_link, "list") type$r_class_name_display <- stringr::str_remove( glue::glue("{type$r_class_package}::{type$r_class_name}"), "^base::" @@ -209,37 +196,6 @@ S7::method(as_bk_data, class_paths) <- function(x) { return(r_class_descriptions) } -.paths_complete_param_descriptions <- function( - descriptions, - params_schema, - allow_empty, - required -) { - r_class_descriptions <- .describe_param_classes( - params_schema, - allow_empty, - required - ) - - descriptions <- .paths_fill_descriptions( - descriptions, - params_schema$description - ) - - return( - stringr::str_trim( - .glue_pipe_brace("(|{r_class_descriptions}|) |{descriptions}|") - ) - ) -} - -.paths_param_to_list <- function(name, description) { - list( - name = name, - description = description - ) -} - .path_as_arg <- function(path, params_df) { if (!nrow(params_df) || !any(params_df$`in` == "path")) { return(glue('"{path}"')) @@ -253,7 +209,7 @@ S7::method(as_bk_data, class_paths) <- function(x) { .collapse_comma(glue("{x} = {x}")) } -# generate files ---------------------------------------------------------- +# generate files --------------------------------------------------------------- .generate_paths_files <- function( paths_by_operation, @@ -261,70 +217,52 @@ S7::method(as_bk_data, class_paths) <- function(x) { security_data, pagination_data ) { - unlist(imap( - paths_by_operation, - function(path_operation, path_operation_id) { - .generate_paths_operation_files( - path_operation, - path_operation_id, - api_abbr, - security_data, - pagination_data + 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) { + 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) + args <- .params_to_args(params) + args_named <- .params_to_named_args(params) + c( + op, + list( + params = params, + params_query = params_query, + params_header = params_header, + args = args, + args_named = args_named, + test_args = args ) - } - )) -} - -.generate_paths_operation_files <- function( - path_operation, - path_operation_id, - api_abbr, - security_data, - pagination_data -) { - stop("Everything should be prepped before this, I think.") - path_operation <- .prepare_path_operation( - path_operation, - security_data$security_arg_names - ) - file_path <- .generate_paths_file( - path_operation, - path_operation_id, - api_abbr, - security_data, - pagination_data - ) - test_path <- .generate_paths_test_file( - path_operation, - path_operation_id, - pagination_data, - api_abbr - ) - return(c(unname(file_path), unname(test_path))) -} - -.prepare_path_operation <- function(path_operation, security_args) { - stop( - "Do this all in initial parsing? Everything is by operation now, which means 1 path per file; no need to map." - ) - path_operation <- map( - path_operation, - function(path) { - path$params <- .remove_security_args(path$params, security_args) - path$params_cookie <- .prep_param_args(path$params_cookie, security_args) - path$params_header <- .prep_param_args(path$params_header, security_args) - path$params_query <- .prep_param_args(path$params_query, security_args) - path$args <- .params_to_args(path$params) - path$test_args <- path$args - return(path) - } - ) + ) + }) + + # One R file per operation + r_files <- unname(unlist(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") + unique_tags <- unique(tags) + test_files <- unname(unlist(lapply(unique_tags, function(tag_name) { + tag_ops <- prepped[tags == tag_name] + .generate_paths_test_file(tag_ops, tag_name, api_abbr) + }))) + + return(c(r_files, test_files)) } .params_to_args <- function(params) { .collapse_comma(map_chr(params, "name")) %|"|% character() } +.params_to_named_args <- function(params) { + .collapse_comma_self_equal(map_chr(params, "name")) %|"|% character() +} + .remove_security_args <- function(params, security_args) { discard( params, @@ -338,29 +276,44 @@ S7::method(as_bk_data, class_paths) <- function(x) { .collapse_comma_self_equal(setdiff(params, security_args)) %|"|% character() } -.generate_paths_file <- function(path, path_tag_name, api_abbr, security_data) { +.generate_paths_file <- function( + path_operation, + operation_id, + api_abbr, + security_data +) { .bk_use_template( template = "paths.R", data = c( - path, - api_abbr = api_abbr, - has_security = security_data$has_security, - security_signature = security_data$security_signature, - security_arg_list = security_data$security_arg_list + path_operation, + list( + api_abbr = api_abbr, + has_security = security_data$has_security %|0|% FALSE, + security_signature = security_data$security_signature %|0|% "", + security_arg_list = security_data$security_arg_list %|0|% "", + pagination = FALSE, + pagination_fn = "" + ) ), - target = glue("paths-{path_tag_name}-{path$operation_id}.R") + target = glue("paths-{path_operation$tag}-{operation_id}.R") ) } -.generate_paths_test_file <- function(path_tag, path_tag_name, api_abbr) { +.generate_paths_test_file <- function(tag_operations, tag_name, api_abbr) { + paths_list <- unname(imap(tag_operations, function(op, op_id) { + list( + operation_id = op_id, + test_args = op$test_args %|0|% "" + ) + })) .bk_use_template( template = "test-paths.R", data = list( - paths = path_tag, - tag = path_tag_name, + paths = paths_list, + tag = tag_name, api_abbr = api_abbr ), dir = "tests/testthat", - target = glue("test-paths-{path_tag_name}.R") + target = glue("test-paths-{tag_name}.R") ) } diff --git a/inst/templates/paths.R b/inst/templates/paths.R index f20a29d..5239e8e 100644 --- a/inst/templates/paths.R +++ b/inst/templates/paths.R @@ -10,12 +10,10 @@ #' {{#params}} #' @param {{name}} ({{{class}}}) {{{description}}}{{/params}} #' -#' @returns `{{operation_id}}()`: The API response. +#' @returns `{{api_abbr}}_{{operation_id}}()`: The API response. #' @export -{{operation_id}} <- function({{{args}}}{{#has_security}}{{#args}},{{/args}}{{{security_signature}}}{{/has_security}}) { - req <- req_{{operation_id}}( - # TODO: Add params when I have an example that has params - ) +{{api_abbr}}_{{operation_id}} <- function({{#args}}{{{args}}}, {{/args}}{{#has_security}}{{{security_signature}}}, {{/has_security}}max_reqs = Inf, max_tries_per_req = 3) { + req <- req_{{api_abbr}}_{{operation_id}}({{#args_named}}{{{args_named}}}{{/args_named}}{{#has_security}}{{#args_named}}, {{/args_named}}{{{security_arg_list}}}{{/has_security}}) resps <- nectar::req_perform_opinionated( req, max_reqs = max_reqs, # Should only include this with pagination. @@ -24,11 +22,11 @@ return(nectar::resp_parse(resps)) } -#' @rdname {{operation_id}} -#' @returns `req_{{operation_id}}()`: A `httr2_request` request object. -req_{{operation_id}} <- function({{{args}}}{{#has_security}}{{#args}},{{/args}}{{{security_signature}}}{{/has_security}}) { +#' @rdname {{api_abbr}}_{{operation_id}} +#' @returns `req_{{api_abbr}}_{{operation_id}}()`: A `httr2_request` request object. +req_{{api_abbr}}_{{operation_id}} <- function({{#args}}{{{args}}}{{/args}}{{#has_security}}{{#args}}, {{/args}}{{{security_signature}}}{{/has_security}}) { {{api_abbr}}_req_prepare( - path = "{{{path}}}", + path = {{{path}}}, method = "{{method}}"{{#has_security}}, {{security_arg_list}}{{/has_security}}{{#params_query}}, query = list({{params_query}}){{/params_query}}{{#params_header}}, diff --git a/tests/testthat/_snaps/generate_pkg-paths.md b/tests/testthat/_snaps/generate_pkg-paths.md index 4315052..3589e3e 100644 --- a/tests/testthat/_snaps/generate_pkg-paths.md +++ b/tests/testthat/_snaps/generate_pkg-paths.md @@ -3,9 +3,19 @@ Code scrub_path(changed_files) Output - [1] "/R/010-call.R" "/tests/testthat/test-010-call.R" - [3] "/R/020-auth.R" "/R/paths-audit.R" - [5] "/tests/testthat/test-paths-audit.R" "/R/paths-legal.R" - [7] "/tests/testthat/test-paths-legal.R" "/R/paths-debts.R" - [9] "/tests/testthat/test-paths-debts.R" "/tests/testthat/setup.R" + [1] "/R/010-prepare.R" + [2] "/tests/testthat/test-010-prepare.R" + [3] "/R/020-auth.R" + [4] "/R/paths-audit-get_audit_case.R" + [5] "/R/paths-audit-get_audit_category.R" + [6] "/R/paths-audit-get_audit_primary_category.R" + [7] "/R/paths-legal-get_legal_search.R" + [8] "/R/paths-audit-get_names_audit_candidates.R" + [9] "/R/paths-audit-get_names_audit_committees.R" + [10] "/R/paths-debts-get_schedules_schedule_d.R" + [11] "/R/paths-debts-get_schedules_schedule_d_sub_id.R" + [12] "/tests/testthat/test-paths-audit.R" + [13] "/tests/testthat/test-paths-legal.R" + [14] "/tests/testthat/test-paths-debts.R" + [15] "/tests/testthat/setup.R" diff --git a/tests/testthat/test-generate_pkg-paths.R b/tests/testthat/test-generate_pkg-paths.R index d3b3243..6af9233 100644 --- a/tests/testthat/test-generate_pkg-paths.R +++ b/tests/testthat/test-generate_pkg-paths.R @@ -3,15 +3,16 @@ test_that(".generate_paths() generates path files", { skip_on_cran() config <- .read_config(test_path("_fixtures", "guru_beekeeper.yml")) api_definition <- readRDS(test_path("_fixtures", "guru_rapid.rds")) - r_expected <- readLines( - test_path("_fixtures", "guru-paths-apis.R") - ) - tests_expected <- readLines( - test_path("_fixtures", "guru-test-paths-apis.R") - ) + # r_expected <- readLines( + # test_path("_fixtures", "guru-paths-apis.R") + # ) + # tests_expected <- readLines( + # test_path("_fixtures", "guru-test-paths-apis.R") + # ) create_local_package() usethis::use_testthat() + # 7 operations all in "apis" tag -> 7 R files + 1 test file + setup test_result <- .generate_paths( paths = api_definition@paths, api_abbr = config$api_abbr, @@ -20,13 +21,25 @@ test_that(".generate_paths() generates path files", { ) expect_identical( basename(test_result), - c("paths-apis.R", "test-paths-apis.R", "setup.R") + c( + "paths-apis-list_apis.R", + "paths-apis-get_metrics.R", + "paths-apis-get_providers.R", + "paths-apis-get_api.R", + "paths-apis-get_service_api.R", + "paths-apis-get_provider.R", + "paths-apis-get_services.R", + "test-paths-apis.R", + "setup.R" + ) ) - r_result <- readLines("R/paths-apis.R") - expect_identical(r_result, r_expected) - tests_result <- readLines("tests/testthat/test-paths-apis.R") - expect_identical(tests_result, tests_expected) + # Phase 4: update guru-paths-apis-*.R fixtures and re-enable content checks + # Phase 4: update guru-test-paths-apis.R fixture and re-enable content check + # r_result <- readLines("R/paths-apis.R") + # expect_identical(r_result, r_expected) + # tests_result <- readLines("tests/testthat/test-paths-apis.R") + # expect_identical(tests_result, tests_expected) }) test_that("generate_pkg() generates path tests for guru", { @@ -34,18 +47,19 @@ test_that("generate_pkg() generates path tests for guru", { skip_on_cran() config <- readLines(test_path("_fixtures", "guru_beekeeper.yml")) guru_rapid <- readRDS(test_path("_fixtures", "guru_rapid.rds")) - expected_file_content <- readLines( - test_path("_fixtures", "guru-test-paths-apis.R") - ) + # expected_file_content <- readLines( + # test_path("_fixtures", "guru-test-paths-apis.R") + # ) create_local_package() writeLines(config, "_beekeeper.yml") saveRDS(guru_rapid, "guru_rapid.rds") - generate_pkg() - generated_file_content <- readLines("tests/testthat/test-paths-apis.R") - expect_identical(generated_file_content, expected_file_content) + expect_true(file.exists("tests/testthat/test-paths-apis.R")) + # Phase 4: update guru-test-paths-apis.R fixture and re-enable content check + # generated_file_content <- readLines("tests/testthat/test-paths-apis.R") + # expect_identical(generated_file_content, expected_file_content) }) test_that("generate_pkg() generates test setup file for guru", { @@ -60,21 +74,19 @@ test_that("generate_pkg() generates test setup file for guru", { create_local_package() writeLines(config, "_beekeeper.yml") saveRDS(guru_rapid, "guru_rapid.rds") - generate_pkg() - generated_file_content <- readLines("tests/testthat/setup.R") expect_identical(generated_file_content, expected_file_content) }) test_that("generate_pkg() generates path functions for fec", { - # 19 tags, more complicated security + # 3 tags (audit, debts, legal), more complicated security skip_on_cran() config <- readLines(test_path("_fixtures", "fec_subset_beekeeper.yml")) fec_rapid <- readRDS(test_path("_fixtures", "fec_subset_rapid.rds")) - expected_file_content <- readLines( - test_path("_fixtures", "fec-paths-audit.R") - ) + # expected_file_content <- readLines( + # test_path("_fixtures", "fec-paths-audit.R") + # ) create_local_package() writeLines(config, "_beekeeper.yml") @@ -83,8 +95,9 @@ test_that("generate_pkg() generates path functions for fec", { changed_files <- generate_pkg() expect_snapshot(scrub_path(changed_files)) - generated_file_content <- readLines("R/paths-audit.R") - expect_identical(generated_file_content, expected_file_content) + # Phase 4: update fec per-operation fixtures and re-enable content checks + # generated_file_content <- readLines("R/paths-audit.R") + # expect_identical(generated_file_content, expected_file_content) }) test_that("generate_pkg() generates path functions for trello", { @@ -92,16 +105,18 @@ test_that("generate_pkg() generates path functions for trello", { skip_on_cran() config <- readLines(test_path("_fixtures", "trello_beekeeper.yml")) trello_rapid <- readRDS(test_path("_fixtures", "trello_rapid.rds")) - expected_file_content <- readLines( - test_path("_fixtures", "trello-paths-board.R") - ) + # expected_file_content <- readLines( + # test_path("_fixtures", "trello-paths-board.R") + # ) create_local_package() writeLines(config, "_beekeeper.yml") saveRDS(trello_rapid, "trello_rapid.rds") generate_pkg() + expect_true(file.exists("R/paths-board-add_boards.R")) - generated_file_content <- readLines("R/paths-board.R") - expect_identical(generated_file_content, expected_file_content) + # Phase 4: update trello per-operation fixtures and re-enable content checks + # generated_file_content <- readLines("R/paths-board.R") + # expect_identical(generated_file_content, expected_file_content) }) diff --git a/tests/testthat/test-generate_pkg.R b/tests/testthat/test-generate_pkg.R index ad5b259..21a1585 100644 --- a/tests/testthat/test-generate_pkg.R +++ b/tests/testthat/test-generate_pkg.R @@ -9,10 +9,17 @@ test_that("generate_pkg() returns a vector of created files", { test_result <- generate_pkg() test_result <- scrub_path(test_result) + # 7 guru operations all in "apis" tag: 7 R files + 1 test file + setup expected_result <- c( "/R/010-prepare.R", "/tests/testthat/test-010-prepare.R", - "/R/paths-apis.R", + "/R/paths-apis-list_apis.R", + "/R/paths-apis-get_metrics.R", + "/R/paths-apis-get_providers.R", + "/R/paths-apis-get_api.R", + "/R/paths-apis-get_service_api.R", + "/R/paths-apis-get_provider.R", + "/R/paths-apis-get_services.R", "/tests/testthat/test-paths-apis.R", "/tests/testthat/setup.R" ) From eaa294f75d9c75f3f0cf2a33efea84f63be76ed2 Mon Sep 17 00:00:00 2001 From: Jon Harmon Date: Tue, 12 May 2026 06:10:57 -0500 Subject: [PATCH 20/33] Add dplyr to DESCRIPTION --- DESCRIPTION | 1 + 1 file changed, 1 insertion(+) diff --git a/DESCRIPTION b/DESCRIPTION index 296f502..80d583c 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -17,6 +17,7 @@ Depends: R (>= 4.1.0) Imports: cli, + dplyr, fs, glue, httptest2, From 7533231f83e89db427167e6ba010f66ed153e8ba Mon Sep 17 00:00:00 2001 From: Jon Harmon Date: Tue, 12 May 2026 06:23:26 -0500 Subject: [PATCH 21/33] Make R CMD check happy TODO: Move the `inst/talks` dir somewhere that it only counts for pkgdown (slides like in other packages). --- DESCRIPTION | 1 + NAMESPACE | 1 + R/beekeeper-package.R | 2 ++ R/sysdata.rda | Bin 0 -> 906 bytes data-raw/oas_format_registry.R | 12 ++++++------ data/oas_format_registry.rda | Bin 861 -> 0 bytes 6 files changed, 10 insertions(+), 6 deletions(-) create mode 100644 R/sysdata.rda delete mode 100644 data/oas_format_registry.rda diff --git a/DESCRIPTION b/DESCRIPTION index 80d583c..a9876d8 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -55,3 +55,4 @@ Config/testthat/edition: 3 Encoding: UTF-8 LazyData: true Roxygen: list(markdown = TRUE) +RoxygenNote: 8.0.0 diff --git a/NAMESPACE b/NAMESPACE index 154d612..6b4ac9a 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -16,6 +16,7 @@ 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) diff --git a/R/beekeeper-package.R b/R/beekeeper-package.R index 41c3999..a146a86 100644 --- a/R/beekeeper-package.R +++ b/R/beekeeper-package.R @@ -2,6 +2,7 @@ "_PACKAGE" ## usethis namespace: start +# Needed for installation: nectar (likely others, TBD) #' @importFrom cli cli_abort #' @importFrom cli cli_warn #' @importFrom fs file_delete @@ -12,6 +13,7 @@ #' @importFrom glue glue #' @importFrom glue glue_collapse #' @importFrom httptest2 use_httptest2 +#' @importFrom nectar req_prepare #' @importFrom purrr discard #' @importFrom purrr imap #' @importFrom purrr map diff --git a/R/sysdata.rda b/R/sysdata.rda new file mode 100644 index 0000000000000000000000000000000000000000..9698cd2a5a630471c5f9e6c1f07e0bb8946afea6 GIT binary patch literal 906 zcmV;519kjDT4*^jL0KkKSt2WCyZ{D8f5iX)+Ry+4Jwyd1-axH1ONa5&;#BM zL(!lOI}Irr28}erWYa(lfM9?$G|<2~fiW}~OqnudVK7ZJ$i%=9z%Y|aYBXpXGH3t* zXa;})0BuTT2=ob|f?*mqr=bju8f0mZ#Mq#bK?K4YG$uw$X{Me?{ZXT7l=T@sl{9Qp zZ`BeD^WAVE6fjyP0VI+8NFb8(ViISO-J8CO5L7Ap`b{c);0LZx@3u(-1ze8Se^G!8 z49pLB#C}f(k}Y;Qs(JspW*em<@6L0&zIT%>7{UJJ3?fP*Q)^(5aD}$cl8R!lu$fQ{ z7#{(UvWQhgqNXv6D8oR?MkCD0D1l(K7*$LKnChr$cOD{*hP|T8a;#)gRH9wP)P$I< zKnSSLveXJ7!ftK;+?t8L1=K(!A7}zdXh=f_B!MU~C@?}meLJ8CM4DeNlMl2YWrI*T zXC&l8Ic_qRo(1tbL(zhAB8&#cSRNb5mN?*$1#YH zHh^Ze09{XuNp`pm%-Iv)LwPzW!U8x_5$7WQLkZah$e`jO4|z|BAgGL!lgvv1f>nfC z(vlb0@pPa#f=j=$TTM0~et?d;A!bupXd`$bSRrDI?#U(7#F!ylVS}!SSzTqE;3Fw2 zNYzn-K`zPf#kP>Nu_3`S^Z~y>$?c;80GAqarUejh3Uvl4*m!x#1{sFz?p|E_mjlD5>{ax+3kfG2U|AT1sGd63Jj3w zsT?*)5s<}X1ePaGV^C_Wp&9NfKwl;_Hho}eyG4J3ahq(~GJ`ix>lH&limf421eWT$ zkQourYFh}@bXFV;Hl90-OhFp#&DYtJsJPdWparef6bb>r_e>D4tDrrFc*aKQf7TaJ z*^USjq~!|idU-<2bC5ZP1QVkgyvgX1OwoX}sbB%LErXA%Gak?pvtF$Cc0XYR1cn4j z0!wvK;L;`lS5P59k2CS;ww;cIeb*CIx$cre4z*h71RK$ek^?eCKw=tRFw2WzY4C_w gSY>iiW(^8}gic5aX4;rZG-Cf3az!{$kRmH(yuiAESpWb4 literal 0 HcmV?d00001 diff --git a/data-raw/oas_format_registry.R b/data-raw/oas_format_registry.R index 228a74c..e765afe 100644 --- a/data-raw/oas_format_registry.R +++ b/data-raw/oas_format_registry.R @@ -64,7 +64,7 @@ oas_format_registry <- # TODO: Add more specific string formats. .default = "stabilize_chr" ), - r_class_name = dplyr::case_match( + r_class_name = dplyr::replace_values( .data$to_r, "stabilize_base64_to_chr" ~ "character", "stabilize_base64url_to_chr" ~ "character", @@ -82,20 +82,20 @@ oas_format_registry <- "stabilize_time" ~ "hms", "stabilize_uuid" ~ "UUID" ), - r_class_package = dplyr::case_match( + r_class_package = dplyr::recode_values( .data$r_class_name, "Duration" ~ "lubridate", "integer64" ~ "bit64", "hms" ~ "hms", "UUID" ~ "uuid", - .default = "base" + default = "base" ), - r_class_link = dplyr::case_match( + r_class_link = dplyr::recode_values( .data$r_class_name, "Duration" ~ "Duration-class", - .default = .data$r_class_name + default = .data$r_class_name ) ) -usethis::use_data(oas_format_registry, overwrite = TRUE) +usethis::use_data(oas_format_registry, overwrite = TRUE, internal = TRUE) rm(oas_format_registry_raw, oas_format_registry) diff --git a/data/oas_format_registry.rda b/data/oas_format_registry.rda deleted file mode 100644 index dd1bd86d6abf42c2df15b4e60edada4486e399a0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 861 zcmV-j1ETywT4*^jL0KkKS)}ZJg#ZPA|HS|Q+Ry+4JwOE|-axH00aO5&;yWm`}Xvv9y35dXrFd)QWnJ@^$nlfTw0%9;DOb9U; zCQJe_=8Tw_0GNyj(*g`e36lVf5+oGBQ#2-1XcY7(sp&FkX|*&M)HXFrJ}8)Fs~kZf zs|Bub03-95K$p>=6F!H-PsX)DV5!H>iFq(M-Sh661cYYLTSK{@%%BDarU$TSY({M( z2YZ^YhyAjF%{iEUG^Q4KTu`uN!TyGDQerCB@d499DHJi0Ayf}|$g;7bP)q`25#(8| zNG&Qr>fjzxS2aC$%5G#o30Xc%Do^TL`(G-|w48S}3 zNgpG7LneMM99$$14m6mYOHMVuX~W`FLHxv%2B`@h58Bc@oDI<3BiUE&m#&CH?YK!E z|0C=T680j(JW@!U);=i$$rn;@Nj$;@Xi2S!BzT|0(1q>p$1rML9`iuT}q4t5Y}E4Gy3YtrXYoO zt-5t#70l~rHSnbvQUwlUt%&O1F$Koea3O6u`9qe%h|__g3mLJe{xfD7+v-kKZJfg@ za&z##STrE9=wOkI572a_YzMeS{=TVv#UH;bgj)iU6&!Nf1l#o!jP)yUD7$^Gzfawz&vBeVq%hB$ub& n*+0syyrlvUk|J{1|{`#CI2RML1B9r0ji#v+;mS From dc570c430ff915e14a28ba50d985bf5577a6d734 Mon Sep 17 00:00:00 2001 From: Jon Harmon Date: Tue, 12 May 2026 06:55:24 -0500 Subject: [PATCH 22/33] Remove comment about `max_reqs` vs pagination in template. --- inst/templates/paths.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/inst/templates/paths.R b/inst/templates/paths.R index 5239e8e..57abcc7 100644 --- a/inst/templates/paths.R +++ b/inst/templates/paths.R @@ -16,7 +16,7 @@ req <- req_{{api_abbr}}_{{operation_id}}({{#args_named}}{{{args_named}}}{{/args_named}}{{#has_security}}{{#args_named}}, {{/args_named}}{{{security_arg_list}}}{{/has_security}}) resps <- nectar::req_perform_opinionated( req, - max_reqs = max_reqs, # Should only include this with pagination. + max_reqs = max_reqs, max_tries_per_req = max_tries_per_req ) return(nectar::resp_parse(resps)) From 0f1f99e5ef16f85af5ac2bae303d7ed57d2985a6 Mon Sep 17 00:00:00 2001 From: Jon Harmon Date: Tue, 12 May 2026 06:59:51 -0500 Subject: [PATCH 23/33] Ignore Posit Assistant conversation histories --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index b1a5b7f..45a4dc7 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,4 @@ exploration _beekeeper_rapid.rds _slackapi_rapid.rds .positai +conversation-export.json From a475c69cf32a6c478660507a7be90cdd7bb782c5 Mon Sep 17 00:00:00 2001 From: Jon Harmon Date: Tue, 12 May 2026 07:22:15 -0500 Subject: [PATCH 24/33] Update test fixtures to per-path --- .Rbuildignore | 1 + .gitignore | 4 +- R/generate_pkg-paths.R | 4 +- air.toml | 2 + tests/testthat/.gitignore | 1 + .../testthat/_fixtures/000-create_fixtures.R | 28 +- tests/testthat/_fixtures/fec-paths-audit.R | 206 -------------- tests/testthat/_fixtures/fec/_beekeeper.yml | 5 + .../_fixtures/fec/_beekeeper_rapid.rds | Bin 0 -> 164502 bytes .../{ => fec}/fec_subset_beekeeper.yml | 2 +- .../_fixtures/{ => fec}/fec_subset_rapid.rds | Bin .../fec/paths-audit-get_audit_case.R | 138 ++++++++++ .../fec/paths-audit-get_audit_category.R | 83 ++++++ .../paths-audit-get_audit_primary_category.R | 83 ++++++ .../paths-audit-get_names_audit_candidates.R | 35 +++ .../paths-audit-get_names_audit_committees.R | 43 +++ .../paths-debts-get_schedules_schedule_d.R | 158 +++++++++++ ...hs-debts-get_schedules_schedule_d_sub_id.R | 77 ++++++ .../fec/paths-legal-get_legal_search.R | 253 ++++++++++++++++++ tests/testthat/_fixtures/fec_beekeeper.yml | 5 - tests/testthat/_fixtures/fec_rapid.rds | Bin 160084 -> 0 bytes tests/testthat/_fixtures/guru-paths-apis.R | 107 -------- .../010-prepare.R} | 0 tests/testthat/_fixtures/guru/_beekeeper.yml | 5 + .../_fixtures/guru/_beekeeper_rapid.rds | Bin 0 -> 16332 bytes .../_fixtures/guru/paths-apis-get_api.R | 32 +++ .../_fixtures/guru/paths-apis-get_metrics.R | 30 +++ .../_fixtures/guru/paths-apis-get_provider.R | 31 +++ .../_fixtures/guru/paths-apis-get_providers.R | 30 +++ .../guru/paths-apis-get_service_api.R | 33 +++ .../_fixtures/guru/paths-apis-get_services.R | 31 +++ .../_fixtures/guru/paths-apis-list_apis.R | 30 +++ .../_fixtures/{guru-setup.R => guru/setup.R} | 0 .../test-010-prepare.R} | 0 .../test-paths-apis.R} | 0 tests/testthat/_fixtures/guru_beekeeper.yml | 5 - tests/testthat/_fixtures/guru_rapid.rds | Bin 16976 -> 0 bytes .../010-prepare.R} | 0 .../{trello-020-auth.R => trello/020-auth.R} | 0 .../testthat/_fixtures/trello/_beekeeper.yml | 5 + .../_fixtures/trello/_beekeeper_rapid.rds | Bin 0 -> 16173 bytes .../_fixtures/trello/paths-board-add_boards.R | 40 +++ .../paths-board.R} | 0 .../_fixtures/trello/test-paths-board.R | 13 + tests/testthat/_fixtures/trello_beekeeper.yml | 5 - tests/testthat/_fixtures/trello_rapid.rds | Bin 16891 -> 0 bytes tests/testthat/_snaps/generate_pkg-setup.md | 4 +- tests/testthat/helper.R | 10 + tests/testthat/test-generate_pkg-paths.R | 111 ++++---- tests/testthat/test-generate_pkg-prepare.R | 18 +- tests/testthat/test-generate_pkg-security.R | 18 +- tests/testthat/test-generate_pkg-setup.R | 6 +- tests/testthat/test-generate_pkg.R | 31 ++- tests/testthat/test-use_beekeeper.R | 4 +- 54 files changed, 1319 insertions(+), 408 deletions(-) delete mode 100644 tests/testthat/_fixtures/fec-paths-audit.R create mode 100644 tests/testthat/_fixtures/fec/_beekeeper.yml create mode 100644 tests/testthat/_fixtures/fec/_beekeeper_rapid.rds rename tests/testthat/_fixtures/{ => fec}/fec_subset_beekeeper.yml (72%) rename tests/testthat/_fixtures/{ => fec}/fec_subset_rapid.rds (100%) create mode 100644 tests/testthat/_fixtures/fec/paths-audit-get_audit_case.R create mode 100644 tests/testthat/_fixtures/fec/paths-audit-get_audit_category.R create mode 100644 tests/testthat/_fixtures/fec/paths-audit-get_audit_primary_category.R create mode 100644 tests/testthat/_fixtures/fec/paths-audit-get_names_audit_candidates.R create mode 100644 tests/testthat/_fixtures/fec/paths-audit-get_names_audit_committees.R create mode 100644 tests/testthat/_fixtures/fec/paths-debts-get_schedules_schedule_d.R create mode 100644 tests/testthat/_fixtures/fec/paths-debts-get_schedules_schedule_d_sub_id.R create mode 100644 tests/testthat/_fixtures/fec/paths-legal-get_legal_search.R delete mode 100644 tests/testthat/_fixtures/fec_beekeeper.yml delete mode 100644 tests/testthat/_fixtures/fec_rapid.rds delete mode 100644 tests/testthat/_fixtures/guru-paths-apis.R rename tests/testthat/_fixtures/{guru-010-prepare.R => guru/010-prepare.R} (100%) create mode 100644 tests/testthat/_fixtures/guru/_beekeeper.yml create mode 100644 tests/testthat/_fixtures/guru/_beekeeper_rapid.rds create mode 100644 tests/testthat/_fixtures/guru/paths-apis-get_api.R create mode 100644 tests/testthat/_fixtures/guru/paths-apis-get_metrics.R create mode 100644 tests/testthat/_fixtures/guru/paths-apis-get_provider.R create mode 100644 tests/testthat/_fixtures/guru/paths-apis-get_providers.R create mode 100644 tests/testthat/_fixtures/guru/paths-apis-get_service_api.R create mode 100644 tests/testthat/_fixtures/guru/paths-apis-get_services.R create mode 100644 tests/testthat/_fixtures/guru/paths-apis-list_apis.R rename tests/testthat/_fixtures/{guru-setup.R => guru/setup.R} (100%) rename tests/testthat/_fixtures/{guru-test-010-prepare.R => guru/test-010-prepare.R} (100%) rename tests/testthat/_fixtures/{guru-test-paths-apis.R => guru/test-paths-apis.R} (100%) delete mode 100644 tests/testthat/_fixtures/guru_beekeeper.yml delete mode 100644 tests/testthat/_fixtures/guru_rapid.rds rename tests/testthat/_fixtures/{trello-010-prepare.R => trello/010-prepare.R} (100%) rename tests/testthat/_fixtures/{trello-020-auth.R => trello/020-auth.R} (100%) create mode 100644 tests/testthat/_fixtures/trello/_beekeeper.yml create mode 100644 tests/testthat/_fixtures/trello/_beekeeper_rapid.rds create mode 100644 tests/testthat/_fixtures/trello/paths-board-add_boards.R rename tests/testthat/_fixtures/{trello-paths-board.R => trello/paths-board.R} (100%) create mode 100644 tests/testthat/_fixtures/trello/test-paths-board.R delete mode 100644 tests/testthat/_fixtures/trello_beekeeper.yml delete mode 100644 tests/testthat/_fixtures/trello_rapid.rds diff --git a/.Rbuildignore b/.Rbuildignore index f637143..cc723e3 100644 --- a/.Rbuildignore +++ b/.Rbuildignore @@ -18,3 +18,4 @@ ^[.]?air[.]toml$ ^\.vscode$ ^AGENTS\.md$ +^conversation-export\.json$ diff --git a/.gitignore b/.gitignore index 45a4dc7..0cc1754 100644 --- a/.gitignore +++ b/.gitignore @@ -5,9 +5,9 @@ .DS_Store docs inst/doc -_beekeeper.yml +/_beekeeper.yml exploration -_beekeeper_rapid.rds +/_beekeeper_rapid.rds _slackapi_rapid.rds .positai conversation-export.json diff --git a/R/generate_pkg-paths.R b/R/generate_pkg-paths.R index 236d906..1fdfaf3 100644 --- a/R/generate_pkg-paths.R +++ b/R/generate_pkg-paths.R @@ -39,7 +39,9 @@ S7::method(as_bk_data, class_paths) <- function(x) { .paths_to_clean_df <- function(x) { x <- tibble::as_tibble(x) |> tidyr::unnest("operations") - x <- x[!x$deprecated, ] + if (length(x$deprecated)) { + x <- x[!x$deprecated, ] + } x$deprecated <- NULL x$tags <- .paths_fill_tags(x$tags) x$operation_id <- .paths_fill_operation_id( diff --git a/air.toml b/air.toml index e69de29..c3c259d 100644 --- a/air.toml +++ b/air.toml @@ -0,0 +1,2 @@ +[format] +exclude = ["inst/templates", "tests/testthat/_fixtures/trello"] diff --git a/tests/testthat/.gitignore b/tests/testthat/.gitignore index 66e54ae..9eccf6f 100644 --- a/tests/testthat/.gitignore +++ b/tests/testthat/.gitignore @@ -1 +1,2 @@ testthat-problems.rds +_problems diff --git a/tests/testthat/_fixtures/000-create_fixtures.R b/tests/testthat/_fixtures/000-create_fixtures.R index c2c74d7..68475c9 100644 --- a/tests/testthat/_fixtures/000-create_fixtures.R +++ b/tests/testthat/_fixtures/000-create_fixtures.R @@ -1,7 +1,11 @@ apid_url <- "https://api.apis.guru/v2/specs/apis.guru/2.2.0/openapi.yaml" api_abbr <- "guru" -rapid_write_path <- test_path(glue::glue("_fixtures/{api_abbr}_rapid.rds")) -config_path <- test_path(glue::glue("_fixtures/{api_abbr}_beekeeper.yml")) +rapid_write_path <- test_path(glue::glue( + "_fixtures/{api_abbr}/_beekeeper_rapid.rds" +)) +config_path <- test_path(glue::glue( + "_fixtures/{api_abbr}/_beekeeper.yml" +)) apid_url |> url() |> use_beekeeper( @@ -12,8 +16,12 @@ apid_url |> apid_url <- "https://api.apis.guru/v2/specs/fec.gov/1.0/openapi.yaml" api_abbr <- "fec" -rapid_write_path <- test_path(glue::glue("_fixtures/{api_abbr}_rapid.rds")) -config_path <- test_path(glue::glue("_fixtures/{api_abbr}_beekeeper.yml")) +rapid_write_path <- test_path(glue::glue( + "_fixtures/{api_abbr}/_beekeeper_rapid.rds" +)) +config_path <- test_path(glue::glue( + "_fixtures/{api_abbr}/_beekeeper.yml" +)) fec_apid <- apid_url |> url() |> yaml::read_yaml() @@ -41,10 +49,10 @@ fec_rapid@paths <- rapid::as_paths({ x }) rapid_write_path <- test_path(glue::glue( - "_fixtures/{api_abbr}_subset_rapid.rds" + "_fixtures/{api_abbr}/{api_abbr}_subset_rapid.rds" )) config_path <- test_path(glue::glue( - "_fixtures/{api_abbr}_subset_beekeeper.yml" + "_fixtures/{api_abbr}/{api_abbr}_subset_beekeeper.yml" )) fec_rapid |> use_beekeeper( @@ -55,8 +63,12 @@ fec_rapid |> apid_url <- "https://api.apis.guru/v2/specs/trello.com/1.0/openapi.yaml" api_abbr <- "trello" -rapid_write_path <- test_path(glue::glue("_fixtures/{api_abbr}_rapid.rds")) -config_path <- test_path(glue::glue("_fixtures/{api_abbr}_beekeeper.yml")) +rapid_write_path <- test_path(glue::glue( + "_fixtures/{api_abbr}/_beekeeper_rapid.rds" +)) +config_path <- test_path(glue::glue( + "_fixtures/{api_abbr}/_beekeeper.yml" +)) trello_rapid <- apid_url |> url() |> rapid::as_rapid() diff --git a/tests/testthat/_fixtures/fec-paths-audit.R b/tests/testthat/_fixtures/fec-paths-audit.R deleted file mode 100644 index 3f1d0b1..0000000 --- a/tests/testthat/_fixtures/fec-paths-audit.R +++ /dev/null @@ -1,206 +0,0 @@ -# These functions were generated by the {beekeeper} package, based on the paths -# element from the source API description. You should carefully review these -# functions. Missing documentation is tagged with "BKTODO" to make it easier for -# you to search for issues. - -#' Get audit case -#' -#' This endpoint contains Final Audit Reports approved by the Commission since inception. The search can be based on information about the audited committee (Name, FEC ID Number, Type, Election Cycle) or the issues covered in the report. -#' -#' @inheritParams fec_call_api -#' @param audit_case_id Primary/foreign key for audit tables -#' @param cycle Filter records to only those that are applicable to a given two-year period. This cycle follows the traditional House election cycle and subdivides the presidential and Senate elections into comparable two-year blocks. The cycle begins with an odd year and is named for its ending, even year. -#' @param sub_category_id The finding id of an audit. Finding are a category of broader issues. Each category has an unique ID. -#' @param sort_nulls_last Toggle that sorts null values last -#' @param sort_hide_null Hide null values on sorted column(s). -#' @param min_election_cycle Filter records to only those that are applicable to a given two-year period. This cycle follows the traditional House election cycle and subdivides the presidential and Senate elections into comparable two-year blocks. The cycle begins with an odd year and is named for its ending, even year. -#' @param audit_id The audit issue. Each subcategory has an unique ID -#' @param q The name of the committee. If a committee changes its name, the most recent name will be shown. Committee names are not unique. Use committee_id for looking up records. -#' @param per_page The number of results returned per page. Defaults to 20. -#' @param max_election_cycle Filter records to only those that are applicable to a given two-year period. This cycle follows the traditional House election cycle and subdivides the presidential and Senate elections into comparable two-year blocks. The cycle begins with an odd year and is named for its ending, even year. -#' @param candidate_id A unique identifier assigned to each candidate registered with the FEC. If a person runs for several offices, that person will have separate candidate IDs for each office. -#' @param committee_type The one-letter type code of the organization: - C communication cost - D delegate - E electioneering communication - H House - I independent expenditure filer (not a committee) - N PAC - nonqualified - O independent expenditure-only (super PACs) - P presidential - Q PAC - qualified - S Senate - U single candidate independent expenditure - V PAC with non-contribution account, nonqualified - W PAC with non-contribution account, qualified - X party, nonqualified - Y party, qualified - Z national party non-federal account -#' @param qq Name of candidate running for office -#' @param page For paginating through results, starting at page 1 -#' @param committee_id A unique identifier assigned to each committee or filer registered with the FEC. In general committee id's begin with the letter C which is followed by eight digits. -#' @param committee_designation Type of committee: - H or S - Congressional - P - Presidential - X or Y or Z - Party - N or Q - PAC - I - Independent expenditure - O - Super PAC -#' @param primary_category_id Audit category ID (table PK) -#' @param sort_null_only Toggle that filters out all rows having sort column that is non-null -#' @param sort Provide a field to sort by. Use `-` for descending order. ex: `-case_no` -#' @return BKTODO: Return descriptions are not yet implemented in beekeeper -#' @export -fec_get_audit_case <- function( - audit_case_id, - cycle, - sub_category_id, - sort_nulls_last, - sort_hide_null, - min_election_cycle, - audit_id, - q, - per_page, - max_election_cycle, - candidate_id, - committee_type, - qq, - page, - committee_id, - committee_designation, - primary_category_id, - sort_null_only, - sort, - api_key = Sys.getenv("FEC_API_KEY") -) { - fec_call_api( - path = "/audit-case/", - method = "get", - api_key = api_key, - query = list( - audit_case_id = audit_case_id, - cycle = cycle, - sub_category_id = sub_category_id, - sort_nulls_last = sort_nulls_last, - sort_hide_null = sort_hide_null, - min_election_cycle = min_election_cycle, - audit_id = audit_id, - q = q, - per_page = per_page, - max_election_cycle = max_election_cycle, - candidate_id = candidate_id, - committee_type = committee_type, - qq = qq, - page = page, - committee_id = committee_id, - committee_designation = committee_designation, - primary_category_id = primary_category_id, - sort_null_only = sort_null_only, - sort = sort - ) - ) -} - -#' Get audit category -#' -#' This lists the options for the categories and subcategories available in the /audit-search/ endpoint. -#' -#' @inheritParams fec_call_api -#' @param sort_nulls_last Toggle that sorts null values last -#' @param page For paginating through results, starting at page 1 -#' @param primary_category_name Primary Audit Category - No Findings or Issues/Not a Committee - Net Outstanding Campaign/Convention Expenditures/Obligations - Payments/Disgorgements - Allocation Issues - Prohibited Contributions - Disclosure - Recordkeeping - Repayment to US Treasury - Other - Misstatement of Financial Activity - Excessive Contributions - Failure to File Reports/Schedules/Notices - Loans - Referred Findings Not Listed -#' @param sort_hide_null Hide null values on sorted column(s). -#' @param primary_category_id Audit category ID (table PK) -#' @param sort_null_only Toggle that filters out all rows having sort column that is non-null -#' @param per_page The number of results returned per page. Defaults to 20. -#' @param sort Provide a field to sort by. Use `-` for descending order. -#' @return BKTODO: Return descriptions are not yet implemented in beekeeper -#' @export -fec_get_audit_category <- function( - sort_nulls_last, - page, - primary_category_name, - sort_hide_null, - primary_category_id, - sort_null_only, - per_page, - sort, - api_key = Sys.getenv("FEC_API_KEY") -) { - fec_call_api( - path = "/audit-category/", - method = "get", - api_key = api_key, - query = list( - sort_nulls_last = sort_nulls_last, - page = page, - primary_category_name = primary_category_name, - sort_hide_null = sort_hide_null, - primary_category_id = primary_category_id, - sort_null_only = sort_null_only, - per_page = per_page, - sort = sort - ) - ) -} - -#' Get audit primary category -#' -#' This lists the options for the primary categories available in the /audit-search/ endpoint. -#' -#' @inheritParams fec_call_api -#' @param sort_nulls_last Toggle that sorts null values last -#' @param page For paginating through results, starting at page 1 -#' @param primary_category_name Primary Audit Category - No Findings or Issues/Not a Committee - Net Outstanding Campaign/Convention Expenditures/Obligations - Payments/Disgorgements - Allocation Issues - Prohibited Contributions - Disclosure - Recordkeeping - Repayment to US Treasury - Other - Misstatement of Financial Activity - Excessive Contributions - Failure to File Reports/Schedules/Notices - Loans - Referred Findings Not Listed -#' @param sort_hide_null Hide null values on sorted column(s). -#' @param primary_category_id Audit category ID (table PK) -#' @param sort_null_only Toggle that filters out all rows having sort column that is non-null -#' @param per_page The number of results returned per page. Defaults to 20. -#' @param sort Provide a field to sort by. Use `-` for descending order. -#' @return BKTODO: Return descriptions are not yet implemented in beekeeper -#' @export -fec_get_audit_primary_category <- function( - sort_nulls_last, - page, - primary_category_name, - sort_hide_null, - primary_category_id, - sort_null_only, - per_page, - sort, - api_key = Sys.getenv("FEC_API_KEY") -) { - fec_call_api( - path = "/audit-primary-category/", - method = "get", - api_key = api_key, - query = list( - sort_nulls_last = sort_nulls_last, - page = page, - primary_category_name = primary_category_name, - sort_hide_null = sort_hide_null, - primary_category_id = primary_category_id, - sort_null_only = sort_null_only, - per_page = per_page, - sort = sort - ) - ) -} - -#' Get names audit candidates -#' -#' Search for candidates or committees by name. If you're looking for information on a particular person or group, using a name to find the `candidate_id` or `committee_id` on this endpoint can be a helpful first step. -#' -#' @inheritParams fec_call_api -#' @param q Name (candidate or committee) to search for -#' @return BKTODO: Return descriptions are not yet implemented in beekeeper -#' @export -fec_get_names_audit_candidates <- function( - q, - api_key = Sys.getenv("FEC_API_KEY") -) { - fec_call_api( - path = "/names/audit_candidates/", - method = "get", - api_key = api_key, - query = list(q = q) - ) -} - -#' Get names audit committees -#' -#' Search for candidates or committees by name. If you're looking for information on a particular person or group, using a name to find the `candidate_id` or `committee_id` on this endpoint can be a helpful first step. -#' -#' @inheritParams fec_call_api -#' @param q Name (candidate or committee) to search for -#' @return BKTODO: Return descriptions are not yet implemented in beekeeper -#' @export -fec_get_names_audit_committees <- function( - q, - api_key = Sys.getenv("FEC_API_KEY") -) { - fec_call_api( - path = "/names/audit_committees/", - method = "get", - api_key = api_key, - query = list(q = q) - ) -} diff --git a/tests/testthat/_fixtures/fec/_beekeeper.yml b/tests/testthat/_fixtures/fec/_beekeeper.yml new file mode 100644 index 0000000..cbd7a2a --- /dev/null +++ b/tests/testthat/_fixtures/fec/_beekeeper.yml @@ -0,0 +1,5 @@ +api_title: OpenFEC +api_abbr: fec +api_version: '1.0' +rapid_file: _beekeeper_rapid.rds +updated_on: 2026-05-12 07:57:21 diff --git a/tests/testthat/_fixtures/fec/_beekeeper_rapid.rds b/tests/testthat/_fixtures/fec/_beekeeper_rapid.rds new file mode 100644 index 0000000000000000000000000000000000000000..681384a470494f5897fd9998c579366ceebb31b3 GIT binary patch literal 164502 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 literal 0 HcmV?d00001 diff --git a/tests/testthat/_fixtures/fec_subset_beekeeper.yml b/tests/testthat/_fixtures/fec/fec_subset_beekeeper.yml similarity index 72% rename from tests/testthat/_fixtures/fec_subset_beekeeper.yml rename to tests/testthat/_fixtures/fec/fec_subset_beekeeper.yml index 42e2dc2..1f25328 100644 --- a/tests/testthat/_fixtures/fec_subset_beekeeper.yml +++ b/tests/testthat/_fixtures/fec/fec_subset_beekeeper.yml @@ -2,4 +2,4 @@ api_title: OpenFEC api_abbr: fec api_version: '1.0' rapid_file: fec_subset_rapid.rds -updated_on: 2024-03-29 19:53:51 +updated_on: 2026-05-12 07:57:29 diff --git a/tests/testthat/_fixtures/fec_subset_rapid.rds b/tests/testthat/_fixtures/fec/fec_subset_rapid.rds similarity index 100% rename from tests/testthat/_fixtures/fec_subset_rapid.rds rename to tests/testthat/_fixtures/fec/fec_subset_rapid.rds diff --git a/tests/testthat/_fixtures/fec/paths-audit-get_audit_case.R b/tests/testthat/_fixtures/fec/paths-audit-get_audit_case.R new file mode 100644 index 0000000..5ec5712 --- /dev/null +++ b/tests/testthat/_fixtures/fec/paths-audit-get_audit_case.R @@ -0,0 +1,138 @@ +# These functions were generated by the {beekeeper} package, based on the paths +# element from the source API description. You should carefully review these +# functions. + +#' Get audit case +#' +#' This endpoint contains Final Audit Reports approved by the Commission since inception. The search can be based on information about the audited committee (Name, FEC ID Number, Type, Election Cycle) or the issues covered in the report. +#' +#' @inheritParams .shared-params +#' +#' @param audit_case_id (length-1 \code{\link[base:list]{list}}, optional) Primary/foreign key for audit tables +#' @param cycle (length-1 \code{\link[base:list]{list}}, optional) Filter records to only those that are applicable to a given two-year period. This cycle follows the traditional House election cycle and subdivides the presidential and Senate elections into comparable two-year blocks. The cycle begins with an odd year and is named for its ending, even year. +#' @param sub_category_id (length-1 \code{\link[base:character]{character}}, optional) The finding id of an audit. Finding are a category of broader issues. Each category has an unique ID. +#' @param sort_nulls_last (length-1 \code{\link[base:logical]{logical}}, optional) Toggle that sorts null values last +#' @param sort_hide_null (length-1 \code{\link[base:logical]{logical}}, optional) Hide null values on sorted column(s). +#' @param min_election_cycle (length-1 \code{\link[base:list]{list}}, optional) Filter records to only those that are applicable to a given two-year period. This cycle follows the traditional House election cycle and subdivides the presidential and Senate elections into comparable two-year blocks. The cycle begins with an odd year and is named for its ending, even year. +#' @param audit_id (length-1 \code{\link[base:list]{list}}, optional) The audit issue. Each subcategory has an unique ID +#' @param q (length-1 \code{\link[base:list]{list}}, optional) The name of the committee. If a committee changes its name, the most recent name will be shown. Committee names are not unique. Use committee_id for looking up records. +#' @param per_page (length-1 \code{\link[base:list]{list}}, optional) The number of results returned per page. Defaults to 20. +#' @param max_election_cycle (length-1 \code{\link[base:list]{list}}, optional) Filter records to only those that are applicable to a given two-year period. This cycle follows the traditional House election cycle and subdivides the presidential and Senate elections into comparable two-year blocks. The cycle begins with an odd year and is named for its ending, even year. +#' @param candidate_id (length-1 \code{\link[base:list]{list}}, optional) A unique identifier assigned to each candidate registered with the FEC. If a person runs for several offices, that person will have separate candidate IDs for each office. +#' @param committee_type (length-1 \code{\link[base:list]{list}}, optional) The one-letter type code of the organization: - C communication cost - D delegate - E electioneering communication - H House - I independent expenditure filer (not a committee) - N PAC - nonqualified - O independent expenditure-only (super PACs) - P presidential - Q PAC - qualified - S Senate - U single candidate independent expenditure - V PAC with non-contribution account, nonqualified - W PAC with non-contribution account, qualified - X party, nonqualified - Y party, qualified - Z national party non-federal account +#' @param qq (length-1 \code{\link[base:list]{list}}, optional) Name of candidate running for office +#' @param page (length-1 \code{\link[base:list]{list}}, optional) For paginating through results, starting at page 1 +#' @param committee_id (length-1 \code{\link[base:list]{list}}, optional) A unique identifier assigned to each committee or filer registered with the FEC. In general committee id's begin with the letter C which is followed by eight digits. +#' @param api_key (length-1 \code{\link[base:character]{character}}) API key for https://api.data.gov. Get one at https://api.data.gov/signup. +#' @param committee_designation (length-1 \code{\link[base:character]{character}}, optional) Type of committee: - H or S - Congressional - P - Presidential - X or Y or Z - Party - N or Q - PAC - I - Independent expenditure - O - Super PAC +#' @param primary_category_id (length-1 \code{\link[base:character]{character}}, optional) Audit category ID (table PK) +#' @param sort_null_only (length-1 \code{\link[base:logical]{logical}}, optional) Toggle that filters out all rows having sort column that is non-null +#' @param sort (length-1 \code{\link[base:list]{list}}, optional) Provide a field to sort by. Use `-` for descending order. ex: `-case_no` +#' +#' @returns `fec_get_audit_case()`: The API response. +#' @export +fec_get_audit_case <- function( + audit_case_id, + cycle, + sub_category_id, + sort_nulls_last, + sort_hide_null, + min_election_cycle, + audit_id, + q, + per_page, + max_election_cycle, + candidate_id, + committee_type, + qq, + page, + committee_id, + committee_designation, + primary_category_id, + sort_null_only, + sort, + api_key = Sys.getenv("FEC_API_KEY"), + max_reqs = Inf, + max_tries_per_req = 3 +) { + req <- req_fec_get_audit_case( + audit_case_id = audit_case_id, + cycle = cycle, + sub_category_id = sub_category_id, + sort_nulls_last = sort_nulls_last, + sort_hide_null = sort_hide_null, + min_election_cycle = min_election_cycle, + audit_id = audit_id, + q = q, + per_page = per_page, + max_election_cycle = max_election_cycle, + candidate_id = candidate_id, + committee_type = committee_type, + qq = qq, + page = page, + committee_id = committee_id, + committee_designation = committee_designation, + primary_category_id = primary_category_id, + sort_null_only = sort_null_only, + sort = sort, + api_key = api_key + ) + resps <- nectar::req_perform_opinionated( + req, + max_reqs = max_reqs, + max_tries_per_req = max_tries_per_req + ) + return(nectar::resp_parse(resps)) +} + +#' @rdname fec_get_audit_case +#' @returns `req_fec_get_audit_case()`: A `httr2_request` request object. +req_fec_get_audit_case <- function( + audit_case_id, + cycle, + sub_category_id, + sort_nulls_last, + sort_hide_null, + min_election_cycle, + audit_id, + q, + per_page, + max_election_cycle, + candidate_id, + committee_type, + qq, + page, + committee_id, + committee_designation, + primary_category_id, + sort_null_only, + sort, + api_key = Sys.getenv("FEC_API_KEY") +) { + fec_req_prepare( + path = "/audit-case/", + method = "get", + api_key = api_key, + query = list( + audit_case_id = audit_case_id, + cycle = cycle, + sub_category_id = sub_category_id, + sort_nulls_last = sort_nulls_last, + sort_hide_null = sort_hide_null, + min_election_cycle = min_election_cycle, + audit_id = audit_id, + q = q, + per_page = per_page, + max_election_cycle = max_election_cycle, + candidate_id = candidate_id, + committee_type = committee_type, + qq = qq, + page = page, + committee_id = committee_id, + committee_designation = committee_designation, + primary_category_id = primary_category_id, + sort_null_only = sort_null_only, + sort = sort + ) + ) +} diff --git a/tests/testthat/_fixtures/fec/paths-audit-get_audit_category.R b/tests/testthat/_fixtures/fec/paths-audit-get_audit_category.R new file mode 100644 index 0000000..4ad6707 --- /dev/null +++ b/tests/testthat/_fixtures/fec/paths-audit-get_audit_category.R @@ -0,0 +1,83 @@ +# These functions were generated by the {beekeeper} package, based on the paths +# element from the source API description. You should carefully review these +# functions. + +#' Get audit category +#' +#' This lists the options for the categories and subcategories available in the /audit-search/ endpoint. +#' +#' @inheritParams .shared-params +#' +#' @param sort_nulls_last (length-1 \code{\link[base:logical]{logical}}, optional) Toggle that sorts null values last +#' @param page (length-1 \code{\link[base:list]{list}}, optional) For paginating through results, starting at page 1 +#' @param primary_category_name (length-1 \code{\link[base:list]{list}}, optional) Primary Audit Category - No Findings or Issues/Not a Committee - Net Outstanding Campaign/Convention Expenditures/Obligations - Payments/Disgorgements - Allocation Issues - Prohibited Contributions - Disclosure - Recordkeeping - Repayment to US Treasury - Other - Misstatement of Financial Activity - Excessive Contributions - Failure to File Reports/Schedules/Notices - Loans - Referred Findings Not Listed +#' @param sort_hide_null (length-1 \code{\link[base:logical]{logical}}, optional) Hide null values on sorted column(s). +#' @param api_key (length-1 \code{\link[base:character]{character}}) API key for https://api.data.gov. Get one at https://api.data.gov/signup. +#' @param primary_category_id (length-1 \code{\link[base:list]{list}}, optional) Audit category ID (table PK) +#' @param sort_null_only (length-1 \code{\link[base:logical]{logical}}, optional) Toggle that filters out all rows having sort column that is non-null +#' @param per_page (length-1 \code{\link[base:list]{list}}, optional) The number of results returned per page. Defaults to 20. +#' @param sort (length-1 \code{\link[base:character]{character}}, optional) Provide a field to sort by. Use `-` for descending order. +#' +#' @returns `fec_get_audit_category()`: The API response. +#' @export +fec_get_audit_category <- function( + sort_nulls_last, + page, + primary_category_name, + sort_hide_null, + primary_category_id, + sort_null_only, + per_page, + sort, + api_key = Sys.getenv("FEC_API_KEY"), + max_reqs = Inf, + max_tries_per_req = 3 +) { + req <- req_fec_get_audit_category( + sort_nulls_last = sort_nulls_last, + page = page, + primary_category_name = primary_category_name, + sort_hide_null = sort_hide_null, + primary_category_id = primary_category_id, + sort_null_only = sort_null_only, + per_page = per_page, + sort = sort, + api_key = api_key + ) + resps <- nectar::req_perform_opinionated( + req, + max_reqs = max_reqs, + max_tries_per_req = max_tries_per_req + ) + return(nectar::resp_parse(resps)) +} + +#' @rdname fec_get_audit_category +#' @returns `req_fec_get_audit_category()`: A `httr2_request` request object. +req_fec_get_audit_category <- function( + sort_nulls_last, + page, + primary_category_name, + sort_hide_null, + primary_category_id, + sort_null_only, + per_page, + sort, + api_key = Sys.getenv("FEC_API_KEY") +) { + fec_req_prepare( + path = "/audit-category/", + method = "get", + api_key = api_key, + query = list( + sort_nulls_last = sort_nulls_last, + page = page, + primary_category_name = primary_category_name, + sort_hide_null = sort_hide_null, + primary_category_id = primary_category_id, + sort_null_only = sort_null_only, + per_page = per_page, + sort = sort + ) + ) +} diff --git a/tests/testthat/_fixtures/fec/paths-audit-get_audit_primary_category.R b/tests/testthat/_fixtures/fec/paths-audit-get_audit_primary_category.R new file mode 100644 index 0000000..41838c0 --- /dev/null +++ b/tests/testthat/_fixtures/fec/paths-audit-get_audit_primary_category.R @@ -0,0 +1,83 @@ +# These functions were generated by the {beekeeper} package, based on the paths +# element from the source API description. You should carefully review these +# functions. + +#' Get audit primary category +#' +#' This lists the options for the primary categories available in the /audit-search/ endpoint. +#' +#' @inheritParams .shared-params +#' +#' @param sort_nulls_last (length-1 \code{\link[base:logical]{logical}}, optional) Toggle that sorts null values last +#' @param page (length-1 \code{\link[base:list]{list}}, optional) For paginating through results, starting at page 1 +#' @param primary_category_name (length-1 \code{\link[base:list]{list}}, optional) Primary Audit Category - No Findings or Issues/Not a Committee - Net Outstanding Campaign/Convention Expenditures/Obligations - Payments/Disgorgements - Allocation Issues - Prohibited Contributions - Disclosure - Recordkeeping - Repayment to US Treasury - Other - Misstatement of Financial Activity - Excessive Contributions - Failure to File Reports/Schedules/Notices - Loans - Referred Findings Not Listed +#' @param sort_hide_null (length-1 \code{\link[base:logical]{logical}}, optional) Hide null values on sorted column(s). +#' @param api_key (length-1 \code{\link[base:character]{character}}) API key for https://api.data.gov. Get one at https://api.data.gov/signup. +#' @param primary_category_id (length-1 \code{\link[base:list]{list}}, optional) Audit category ID (table PK) +#' @param sort_null_only (length-1 \code{\link[base:logical]{logical}}, optional) Toggle that filters out all rows having sort column that is non-null +#' @param per_page (length-1 \code{\link[base:list]{list}}, optional) The number of results returned per page. Defaults to 20. +#' @param sort (length-1 \code{\link[base:character]{character}}, optional) Provide a field to sort by. Use `-` for descending order. +#' +#' @returns `fec_get_audit_primary_category()`: The API response. +#' @export +fec_get_audit_primary_category <- function( + sort_nulls_last, + page, + primary_category_name, + sort_hide_null, + primary_category_id, + sort_null_only, + per_page, + sort, + api_key = Sys.getenv("FEC_API_KEY"), + max_reqs = Inf, + max_tries_per_req = 3 +) { + req <- req_fec_get_audit_primary_category( + sort_nulls_last = sort_nulls_last, + page = page, + primary_category_name = primary_category_name, + sort_hide_null = sort_hide_null, + primary_category_id = primary_category_id, + sort_null_only = sort_null_only, + per_page = per_page, + sort = sort, + api_key = api_key + ) + resps <- nectar::req_perform_opinionated( + req, + max_reqs = max_reqs, + max_tries_per_req = max_tries_per_req + ) + return(nectar::resp_parse(resps)) +} + +#' @rdname fec_get_audit_primary_category +#' @returns `req_fec_get_audit_primary_category()`: A `httr2_request` request object. +req_fec_get_audit_primary_category <- function( + sort_nulls_last, + page, + primary_category_name, + sort_hide_null, + primary_category_id, + sort_null_only, + per_page, + sort, + api_key = Sys.getenv("FEC_API_KEY") +) { + fec_req_prepare( + path = "/audit-primary-category/", + method = "get", + api_key = api_key, + query = list( + sort_nulls_last = sort_nulls_last, + page = page, + primary_category_name = primary_category_name, + sort_hide_null = sort_hide_null, + primary_category_id = primary_category_id, + sort_null_only = sort_null_only, + per_page = per_page, + sort = sort + ) + ) +} diff --git a/tests/testthat/_fixtures/fec/paths-audit-get_names_audit_candidates.R b/tests/testthat/_fixtures/fec/paths-audit-get_names_audit_candidates.R new file mode 100644 index 0000000..b2adc03 --- /dev/null +++ b/tests/testthat/_fixtures/fec/paths-audit-get_names_audit_candidates.R @@ -0,0 +1,35 @@ +# These functions were generated by the {beekeeper} package, based on the paths +# element from the source API description. You should carefully review these +# functions. + +#' Get names audit candidates +#' +#' Search for candidates or committees by name. If you're looking for information on a particular person or group, using a name to find the `candidate_id` or `committee_id` on this endpoint can be a helpful first step. +#' +#' @inheritParams .shared-params +#' +#' @param api_key (length-1 \code{\link[base:character]{character}}) API key for https://api.data.gov. Get one at https://api.data.gov/signup. +#' @param q (length-1 \code{\link[base:list]{list}}) Name (candidate or committee) to search for +#' +#' @returns `fec_get_names_audit_candidates()`: The API response. +#' @export +fec_get_names_audit_candidates <- function(q, api_key = Sys.getenv("FEC_API_KEY"), max_reqs = Inf, max_tries_per_req = 3) { + req <- req_fec_get_names_audit_candidates(q = q, api_key = api_key) + resps <- nectar::req_perform_opinionated( + req, + max_reqs = max_reqs, + max_tries_per_req = max_tries_per_req + ) + return(nectar::resp_parse(resps)) +} + +#' @rdname fec_get_names_audit_candidates +#' @returns `req_fec_get_names_audit_candidates()`: A `httr2_request` request object. +req_fec_get_names_audit_candidates <- function(q, api_key = Sys.getenv("FEC_API_KEY")) { + fec_req_prepare( + path = "/names/audit_candidates/", + method = "get", + api_key = api_key, + query = list(q = q) + ) +} diff --git a/tests/testthat/_fixtures/fec/paths-audit-get_names_audit_committees.R b/tests/testthat/_fixtures/fec/paths-audit-get_names_audit_committees.R new file mode 100644 index 0000000..fd49ea4 --- /dev/null +++ b/tests/testthat/_fixtures/fec/paths-audit-get_names_audit_committees.R @@ -0,0 +1,43 @@ +# These functions were generated by the {beekeeper} package, based on the paths +# element from the source API description. You should carefully review these +# functions. + +#' Get names audit committees +#' +#' Search for candidates or committees by name. If you're looking for information on a particular person or group, using a name to find the `candidate_id` or `committee_id` on this endpoint can be a helpful first step. +#' +#' @inheritParams .shared-params +#' +#' @param api_key (length-1 \code{\link[base:character]{character}}) API key for https://api.data.gov. Get one at https://api.data.gov/signup. +#' @param q (length-1 \code{\link[base:list]{list}}) Name (candidate or committee) to search for +#' +#' @returns `fec_get_names_audit_committees()`: The API response. +#' @export +fec_get_names_audit_committees <- function( + q, + api_key = Sys.getenv("FEC_API_KEY"), + max_reqs = Inf, + max_tries_per_req = 3 +) { + req <- req_fec_get_names_audit_committees(q = q, api_key = api_key) + resps <- nectar::req_perform_opinionated( + req, + max_reqs = max_reqs, + max_tries_per_req = max_tries_per_req + ) + return(nectar::resp_parse(resps)) +} + +#' @rdname fec_get_names_audit_committees +#' @returns `req_fec_get_names_audit_committees()`: A `httr2_request` request object. +req_fec_get_names_audit_committees <- function( + q, + api_key = Sys.getenv("FEC_API_KEY") +) { + fec_req_prepare( + path = "/names/audit_committees/", + method = "get", + api_key = api_key, + query = list(q = q) + ) +} diff --git a/tests/testthat/_fixtures/fec/paths-debts-get_schedules_schedule_d.R b/tests/testthat/_fixtures/fec/paths-debts-get_schedules_schedule_d.R new file mode 100644 index 0000000..c55633b --- /dev/null +++ b/tests/testthat/_fixtures/fec/paths-debts-get_schedules_schedule_d.R @@ -0,0 +1,158 @@ +# These functions were generated by the {beekeeper} package, based on the paths +# element from the source API description. You should carefully review these +# functions. + +#' Get schedules schedule d +#' +#' Schedule D, it shows debts and obligations owed to or by the committee that are required to be disclosed. +#' +#' @inheritParams .shared-params +#' +#' @param creditor_debtor_name (length-1 \code{\link[base:list]{list}}, optional) +#' @param max_image_number (length-1 \code{\link[base:character]{character}}, optional) Maxium image number of the page where the schedule item is reported +#' @param sort_nulls_last (length-1 \code{\link[base:logical]{logical}}, optional) Toggle that sorts null values last +#' @param max_amount_outstanding_beginning (length-1 \code{\link[base:double]{double}}, optional) +#' @param sort_hide_null (length-1 \code{\link[base:logical]{logical}}, optional) Hide null values on sorted column(s). +#' @param min_payment_period (length-1 \code{\link[base:double]{double}}, optional) +#' @param max_amount_incurred (length-1 \code{\link[base:double]{double}}, optional) +#' @param nature_of_debt (length-1 \code{\link[base:character]{character}}, optional) +#' @param per_page (length-1 \code{\link[base:list]{list}}, optional) The number of results returned per page. Defaults to 20. +#' @param max_amount_outstanding_close (length-1 \code{\link[base:double]{double}}, optional) +#' @param candidate_id (length-1 \code{\link[base:list]{list}}, optional) A unique identifier assigned to each candidate registered with the FEC. If a person runs for several offices, that person will have separate candidate IDs for each office. +#' @param page (length-1 \code{\link[base:list]{list}}, optional) For paginating through results, starting at page 1 +#' @param min_date (length-1 \code{\link[base:Date]{Date}}, optional) Minimum load date +#' @param committee_id (length-1 \code{\link[base:list]{list}}, optional) A unique identifier assigned to each committee or filer registered with the FEC. In general committee id's begin with the letter C which is followed by eight digits. +#' @param min_amount_outstanding_close (length-1 \code{\link[base:double]{double}}, optional) +#' @param api_key (length-1 \code{\link[base:character]{character}}) API key for https://api.data.gov. Get one at https://api.data.gov/signup. +#' @param max_payment_period (length-1 \code{\link[base:double]{double}}, optional) +#' @param min_image_number (length-1 \code{\link[base:character]{character}}, optional) Minium image number of the page where the schedule item is reported +#' @param min_amount_incurred (length-1 \code{\link[base:double]{double}}, optional) +#' @param sort_null_only (length-1 \code{\link[base:logical]{logical}}, optional) Toggle that filters out all rows having sort column that is non-null +#' @param image_number (length-1 \code{\link[base:list]{list}}, optional) An unique identifier for each page where the electronic or paper filing is reported. +#' @param sort (length-1 \code{\link[base:character]{character}}, optional) Provide a field to sort by. Use `-` for descending order. +#' @param min_amount_outstanding_beginning (length-1 \code{\link[base:double]{double}}, optional) +#' @param max_date (length-1 \code{\link[base:Date]{Date}}, optional) Maximum load date +#' +#' @returns `fec_get_schedules_schedule_d()`: The API response. +#' @export +fec_get_schedules_schedule_d <- function( + creditor_debtor_name, + max_image_number, + sort_nulls_last, + max_amount_outstanding_beginning, + sort_hide_null, + min_payment_period, + max_amount_incurred, + nature_of_debt, + per_page, + max_amount_outstanding_close, + candidate_id, + page, + min_date, + committee_id, + min_amount_outstanding_close, + max_payment_period, + min_image_number, + min_amount_incurred, + sort_null_only, + image_number, + sort, + min_amount_outstanding_beginning, + max_date, + api_key = Sys.getenv("FEC_API_KEY"), + max_reqs = Inf, + max_tries_per_req = 3 +) { + req <- req_fec_get_schedules_schedule_d( + creditor_debtor_name = creditor_debtor_name, + max_image_number = max_image_number, + sort_nulls_last = sort_nulls_last, + max_amount_outstanding_beginning = max_amount_outstanding_beginning, + sort_hide_null = sort_hide_null, + min_payment_period = min_payment_period, + max_amount_incurred = max_amount_incurred, + nature_of_debt = nature_of_debt, + per_page = per_page, + max_amount_outstanding_close = max_amount_outstanding_close, + candidate_id = candidate_id, + page = page, + min_date = min_date, + committee_id = committee_id, + min_amount_outstanding_close = min_amount_outstanding_close, + max_payment_period = max_payment_period, + min_image_number = min_image_number, + min_amount_incurred = min_amount_incurred, + sort_null_only = sort_null_only, + image_number = image_number, + sort = sort, + min_amount_outstanding_beginning = min_amount_outstanding_beginning, + max_date = max_date, + api_key = api_key + ) + resps <- nectar::req_perform_opinionated( + req, + max_reqs = max_reqs, + max_tries_per_req = max_tries_per_req + ) + return(nectar::resp_parse(resps)) +} + +#' @rdname fec_get_schedules_schedule_d +#' @returns `req_fec_get_schedules_schedule_d()`: A `httr2_request` request object. +req_fec_get_schedules_schedule_d <- function( + creditor_debtor_name, + max_image_number, + sort_nulls_last, + max_amount_outstanding_beginning, + sort_hide_null, + min_payment_period, + max_amount_incurred, + nature_of_debt, + per_page, + max_amount_outstanding_close, + candidate_id, + page, + min_date, + committee_id, + min_amount_outstanding_close, + max_payment_period, + min_image_number, + min_amount_incurred, + sort_null_only, + image_number, + sort, + min_amount_outstanding_beginning, + max_date, + api_key = Sys.getenv("FEC_API_KEY") +) { + fec_req_prepare( + path = "/schedules/schedule_d/", + method = "get", + api_key = api_key, + query = list( + creditor_debtor_name = creditor_debtor_name, + max_image_number = max_image_number, + sort_nulls_last = sort_nulls_last, + max_amount_outstanding_beginning = max_amount_outstanding_beginning, + sort_hide_null = sort_hide_null, + min_payment_period = min_payment_period, + max_amount_incurred = max_amount_incurred, + nature_of_debt = nature_of_debt, + per_page = per_page, + max_amount_outstanding_close = max_amount_outstanding_close, + candidate_id = candidate_id, + page = page, + min_date = min_date, + committee_id = committee_id, + min_amount_outstanding_close = min_amount_outstanding_close, + max_payment_period = max_payment_period, + min_image_number = min_image_number, + min_amount_incurred = min_amount_incurred, + sort_null_only = sort_null_only, + image_number = image_number, + sort = sort, + min_amount_outstanding_beginning = min_amount_outstanding_beginning, + max_date = max_date + ) + ) +} diff --git a/tests/testthat/_fixtures/fec/paths-debts-get_schedules_schedule_d_sub_id.R b/tests/testthat/_fixtures/fec/paths-debts-get_schedules_schedule_d_sub_id.R new file mode 100644 index 0000000..2ce441d --- /dev/null +++ b/tests/testthat/_fixtures/fec/paths-debts-get_schedules_schedule_d_sub_id.R @@ -0,0 +1,77 @@ +# These functions were generated by the {beekeeper} package, based on the paths +# element from the source API description. You should carefully review these +# functions. + +#' Get schedules schedule d sub id +#' +#' Schedule D, it shows debts and obligations owed to or by the committee that are required to be disclosed. +#' +#' @inheritParams .shared-params +#' +#' @param sort_nulls_last (length-1 \code{\link[base:logical]{logical}}, optional) Toggle that sorts null values last +#' @param per_page (length-1 \code{\link[base:list]{list}}, optional) The number of results returned per page. Defaults to 20. +#' @param sort_null_only (length-1 \code{\link[base:logical]{logical}}, optional) Toggle that filters out all rows having sort column that is non-null +#' @param sort_hide_null (length-1 \code{\link[base:logical]{logical}}, optional) Hide null values on sorted column(s). +#' @param sort (length-1 \code{\link[base:character]{character}}, optional) Provide a field to sort by. Use `-` for descending order. +#' @param api_key (length-1 \code{\link[base:character]{character}}) API key for https://api.data.gov. Get one at https://api.data.gov/signup. +#' @param page (length-1 \code{\link[base:list]{list}}, optional) For paginating through results, starting at page 1 +#' @param sub_id (length-1 \code{\link[base:character]{character}}) +#' +#' @returns `fec_get_schedules_schedule_d_sub_id()`: The API response. +#' @export +fec_get_schedules_schedule_d_sub_id <- function( + sort_nulls_last, + per_page, + sort_null_only, + sort_hide_null, + sort, + page, + sub_id, + api_key = Sys.getenv("FEC_API_KEY"), + max_reqs = Inf, + max_tries_per_req = 3 +) { + req <- req_fec_get_schedules_schedule_d_sub_id( + sort_nulls_last = sort_nulls_last, + per_page = per_page, + sort_null_only = sort_null_only, + sort_hide_null = sort_hide_null, + sort = sort, + page = page, + sub_id = sub_id, + api_key = api_key + ) + resps <- nectar::req_perform_opinionated( + req, + max_reqs = max_reqs, + max_tries_per_req = max_tries_per_req + ) + return(nectar::resp_parse(resps)) +} + +#' @rdname fec_get_schedules_schedule_d_sub_id +#' @returns `req_fec_get_schedules_schedule_d_sub_id()`: A `httr2_request` request object. +req_fec_get_schedules_schedule_d_sub_id <- function( + sort_nulls_last, + per_page, + sort_null_only, + sort_hide_null, + sort, + page, + sub_id, + api_key = Sys.getenv("FEC_API_KEY") +) { + fec_req_prepare( + path = c("/schedules/schedule_d/{sub_id}/", sub_id = sub_id), + method = "get", + api_key = api_key, + query = list( + sort_nulls_last = sort_nulls_last, + per_page = per_page, + sort_null_only = sort_null_only, + sort_hide_null = sort_hide_null, + sort = sort, + page = page + ) + ) +} diff --git a/tests/testthat/_fixtures/fec/paths-legal-get_legal_search.R b/tests/testthat/_fixtures/fec/paths-legal-get_legal_search.R new file mode 100644 index 0000000..a21ae00 --- /dev/null +++ b/tests/testthat/_fixtures/fec/paths-legal-get_legal_search.R @@ -0,0 +1,253 @@ +# These functions were generated by the {beekeeper} package, based on the paths +# element from the source API description. You should carefully review these +# functions. + +#' Get legal search +#' +#' Search legal documents by document type, or across all document types using keywords, parameter values and ranges. +#' +#' @inheritParams .shared-params +#' +#' @param hits_returned (length-1 \code{\link[base:list]{list}}, optional) Number of results to return (max 10) +#' @param af_report_year (length-1 \code{\link[base:character]{character}}, optional) Admin fine report year +#' @param case_max_open_date (length-1 \code{\link[base:Date]{Date}}, optional) The latest date opened of case +#' @param ao_max_issue_date (length-1 \code{\link[base:Date]{Date}}, optional) Latest issue date of advisory opinion +#' @param case_statutory_citation (length-1 \code{\link[base:list]{list}}, optional) Statutory citations +#' @param case_respondents (length-1 \code{\link[base:character]{character}}, optional) Cases respondents +#' @param q (length-1 \code{\link[base:character]{character}}, optional) Text to search legal documents for +#' @param ao_min_issue_date (length-1 \code{\link[base:Date]{Date}}, optional) Earliest issue date of advisory opinion +#' @param af_max_fd_date (length-1 \code{\link[base:Date]{Date}}, optional) The latest Final Determination date +#' @param from_hit (length-1 \code{\link[base:list]{list}}, optional) Get results starting from this index +#' @param af_fd_fine_amount (length-1 \code{\link[base:list]{list}}, optional) Final Determination fine amount +#' @param type (length-1 \code{\link[base:character]{character}}, optional) Legal Document type to refine search by - statutes - regulations - advisory_opinions - murs - admin_fines +#' @param api_key (length-1 \code{\link[base:character]{character}}) API key for https://api.data.gov. Get one at https://api.data.gov/signup. +#' @param af_name (length-1 \code{\link[base:list]{list}}, optional) Admin fine committee name +#' @param ao_requestor_type (length-1 \code{\link[base:list]{list}}, optional) Code of the advisory opinion requestor type. +#' @param ao_statutory_citation (length-1 \code{\link[base:list]{list}}, optional) Statutory citations +#' @param ao_entity_name (length-1 \code{\link[base:list]{list}}, optional) Name of commenter or representative +#' @param mur_type (length-1 \code{\link[base:character]{character}}, optional) Type of MUR : current or archived +#' @param ao_regulatory_citation (length-1 \code{\link[base:list]{list}}, optional) Regulatory citations +#' @param af_committee_id (length-1 \code{\link[base:character]{character}}, optional) Admin fine committee ID +#' @param ao_requestor (length-1 \code{\link[base:character]{character}}, optional) The requestor of the advisory opinion +#' @param case_citation_require_all (length-1 \code{\link[base:logical]{logical}}, optional) Require all citations to be in document (default behavior is any) +#' @param af_min_fd_date (length-1 \code{\link[base:Date]{Date}}, optional) The earliest Final Determination date +#' @param ao_is_pending (length-1 \code{\link[base:logical]{logical}}, optional) AO is pending +#' @param af_rtb_fine_amount (length-1 \code{\link[base:list]{list}}, optional) Reason to Believe fine amount +#' @param case_election_cycles (length-1 \code{\link[base:list]{list}}, optional) Cases election cycles +#' @param ao_category (length-1 \code{\link[base:list]{list}}, optional) Category of the document +#' @param ao_citation_require_all (length-1 \code{\link[base:logical]{logical}}, optional) Require all citations to be in document (default behavior is any) +#' @param case_dispositions (length-1 \code{\link[base:list]{list}}, optional) Cases dispositions +#' @param af_max_rtb_date (length-1 \code{\link[base:Date]{Date}}, optional) The latest Reason to Believe date +#' @param case_min_open_date (length-1 \code{\link[base:Date]{Date}}, optional) The earliest date opened of case +#' @param case_max_close_date (length-1 \code{\link[base:Date]{Date}}, optional) The latest date closed of case +#' @param ao_min_request_date (length-1 \code{\link[base:Date]{Date}}, optional) Earliest request date of advisory opinion +#' @param ao_status (length-1 \code{\link[base:character]{character}}, optional) Status of AO (pending, withdrawn, or final) +#' @param case_doc_category_id (length-1 \code{\link[base:list]{list}}, optional) Select one or more case_doc_category_id to filter by corresponding CASE_DOCUMENT_CATEGORY: - 1 - Conciliation Agreements - 2 - Complaint, Responses, Designation of Counsel and Extensions of Timee - 3 - General Counsel Reports, Briefs, Notifications and Responses - 4 - Certifications - 5 - Civil Penalties, Disgorgements and Other Payments - 6 - Statements of Reasons +#' @param af_min_rtb_date (length-1 \code{\link[base:Date]{Date}}, optional) The earliest Reason to Believe date +#' @param ao_name (length-1 \code{\link[base:list]{list}}, optional) Force advisory opinion name +#' @param case_regulatory_citation (length-1 \code{\link[base:list]{list}}, optional) Regulatory citations +#' @param ao_no (length-1 \code{\link[base:list]{list}}, optional) Force advisory opinion number +#' @param case_min_close_date (length-1 \code{\link[base:Date]{Date}}, optional) The earliest date closed of case +#' @param sort (length-1 \code{\link[base:character]{character}}, optional) Provide a field to sort by. Use `-` for descending order. ex: `-case_no` +#' @param ao_max_request_date (length-1 \code{\link[base:Date]{Date}}, optional) Latest request date of advisory opinion +#' @param case_no (length-1 \code{\link[base:list]{list}}, optional) Enforcement matter case number +#' +#' @returns `fec_get_legal_search()`: The API response. +#' @export +fec_get_legal_search <- function( + hits_returned, + af_report_year, + case_max_open_date, + ao_max_issue_date, + case_statutory_citation, + case_respondents, + q, + ao_min_issue_date, + af_max_fd_date, + from_hit, + af_fd_fine_amount, + type, + af_name, + ao_requestor_type, + ao_statutory_citation, + ao_entity_name, + mur_type, + ao_regulatory_citation, + af_committee_id, + ao_requestor, + case_citation_require_all, + af_min_fd_date, + ao_is_pending, + af_rtb_fine_amount, + case_election_cycles, + ao_category, + ao_citation_require_all, + case_dispositions, + af_max_rtb_date, + case_min_open_date, + case_max_close_date, + ao_min_request_date, + ao_status, + case_doc_category_id, + af_min_rtb_date, + ao_name, + case_regulatory_citation, + ao_no, + case_min_close_date, + sort, + ao_max_request_date, + case_no, + api_key = Sys.getenv("FEC_API_KEY"), + max_reqs = Inf, + max_tries_per_req = 3 +) { + req <- req_fec_get_legal_search( + hits_returned = hits_returned, + af_report_year = af_report_year, + case_max_open_date = case_max_open_date, + ao_max_issue_date = ao_max_issue_date, + case_statutory_citation = case_statutory_citation, + case_respondents = case_respondents, + q = q, + ao_min_issue_date = ao_min_issue_date, + af_max_fd_date = af_max_fd_date, + from_hit = from_hit, + af_fd_fine_amount = af_fd_fine_amount, + type = type, + af_name = af_name, + ao_requestor_type = ao_requestor_type, + ao_statutory_citation = ao_statutory_citation, + ao_entity_name = ao_entity_name, + mur_type = mur_type, + ao_regulatory_citation = ao_regulatory_citation, + af_committee_id = af_committee_id, + ao_requestor = ao_requestor, + case_citation_require_all = case_citation_require_all, + af_min_fd_date = af_min_fd_date, + ao_is_pending = ao_is_pending, + af_rtb_fine_amount = af_rtb_fine_amount, + case_election_cycles = case_election_cycles, + ao_category = ao_category, + ao_citation_require_all = ao_citation_require_all, + case_dispositions = case_dispositions, + af_max_rtb_date = af_max_rtb_date, + case_min_open_date = case_min_open_date, + case_max_close_date = case_max_close_date, + ao_min_request_date = ao_min_request_date, + ao_status = ao_status, + case_doc_category_id = case_doc_category_id, + af_min_rtb_date = af_min_rtb_date, + ao_name = ao_name, + case_regulatory_citation = case_regulatory_citation, + ao_no = ao_no, + case_min_close_date = case_min_close_date, + sort = sort, + ao_max_request_date = ao_max_request_date, + case_no = case_no, + api_key = api_key + ) + resps <- nectar::req_perform_opinionated( + req, + max_reqs = max_reqs, + max_tries_per_req = max_tries_per_req + ) + return(nectar::resp_parse(resps)) +} + +#' @rdname fec_get_legal_search +#' @returns `req_fec_get_legal_search()`: A `httr2_request` request object. +req_fec_get_legal_search <- function( + hits_returned, + af_report_year, + case_max_open_date, + ao_max_issue_date, + case_statutory_citation, + case_respondents, + q, + ao_min_issue_date, + af_max_fd_date, + from_hit, + af_fd_fine_amount, + type, + af_name, + ao_requestor_type, + ao_statutory_citation, + ao_entity_name, + mur_type, + ao_regulatory_citation, + af_committee_id, + ao_requestor, + case_citation_require_all, + af_min_fd_date, + ao_is_pending, + af_rtb_fine_amount, + case_election_cycles, + ao_category, + ao_citation_require_all, + case_dispositions, + af_max_rtb_date, + case_min_open_date, + case_max_close_date, + ao_min_request_date, + ao_status, + case_doc_category_id, + af_min_rtb_date, + ao_name, + case_regulatory_citation, + ao_no, + case_min_close_date, + sort, + ao_max_request_date, + case_no, + api_key = Sys.getenv("FEC_API_KEY") +) { + fec_req_prepare( + path = "/legal/search/", + method = "get", + api_key = api_key, + query = list( + hits_returned = hits_returned, + af_report_year = af_report_year, + case_max_open_date = case_max_open_date, + ao_max_issue_date = ao_max_issue_date, + case_statutory_citation = case_statutory_citation, + case_respondents = case_respondents, + q = q, + ao_min_issue_date = ao_min_issue_date, + af_max_fd_date = af_max_fd_date, + from_hit = from_hit, + af_fd_fine_amount = af_fd_fine_amount, + type = type, + af_name = af_name, + ao_requestor_type = ao_requestor_type, + ao_statutory_citation = ao_statutory_citation, + ao_entity_name = ao_entity_name, + mur_type = mur_type, + ao_regulatory_citation = ao_regulatory_citation, + af_committee_id = af_committee_id, + ao_requestor = ao_requestor, + case_citation_require_all = case_citation_require_all, + af_min_fd_date = af_min_fd_date, + ao_is_pending = ao_is_pending, + af_rtb_fine_amount = af_rtb_fine_amount, + case_election_cycles = case_election_cycles, + ao_category = ao_category, + ao_citation_require_all = ao_citation_require_all, + case_dispositions = case_dispositions, + af_max_rtb_date = af_max_rtb_date, + case_min_open_date = case_min_open_date, + case_max_close_date = case_max_close_date, + ao_min_request_date = ao_min_request_date, + ao_status = ao_status, + case_doc_category_id = case_doc_category_id, + af_min_rtb_date = af_min_rtb_date, + ao_name = ao_name, + case_regulatory_citation = case_regulatory_citation, + ao_no = ao_no, + case_min_close_date = case_min_close_date, + sort = sort, + ao_max_request_date = ao_max_request_date, + case_no = case_no + ) + ) +} diff --git a/tests/testthat/_fixtures/fec_beekeeper.yml b/tests/testthat/_fixtures/fec_beekeeper.yml deleted file mode 100644 index 6bd2871..0000000 --- a/tests/testthat/_fixtures/fec_beekeeper.yml +++ /dev/null @@ -1,5 +0,0 @@ -api_title: OpenFEC -api_abbr: fec -api_version: '1.0' -rapid_file: fec_rapid.rds -updated_on: 2024-03-27 19:14:26 diff --git a/tests/testthat/_fixtures/fec_rapid.rds b/tests/testthat/_fixtures/fec_rapid.rds deleted file mode 100644 index 156bb7250ef0dd9a7286ec6b6c063a9effc82adc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 160084 zcmagF1yoz#(mq^DX^Xc|pg0sO?rx2PcnAsP zYk&8?_j~XETmSWC?KNv=&RHjGCui@O=b72mkBQ8U`KYA~>s`>QmF1ld?kh4g!Y9u^AMakJt9-DL)sf}peM9;XeqlrP1z>+0mAu`Du*im zrM~`8wpmlFA1rjpJ2aGOf^lZlo)*MC8Rr$spxjc9|}m6Mo!x@11FWq-!F|ttf=YX+=ALM+(JzcG{H~ zQq2EN^Aa-_Z*>qmp*&nVHrgrVS};^Y=;}uwteSj66v+g6-$cX|A1R#u6eN+eYbA<9 zGxak&_+{G{9U+uyJe!R5`9o{zuo7$PMkmy`#RIb=AmU`%%tVUvh5}H-XZT z`07Qm34p&T^Oj1<4{*!YiOasG!lmPk383jT{Jcho{gQ&bS1uI3BAA z8_g9j7&2i8%1s6l;zBWTB^`?z2u{u){}4~ORe&UN;*)c#)dM+rJULl>yGh~ZQ-!a9 zY$w;}-5Gszi2z)t6rK2Jd;qDt=2$q zuB(2O?n^s1qCKgPe5g$U`vbq_L(W@Hf9AeUJECqENF>jlqYo*HbHU4xx`rwZP8vj1 zoJnuIU1Dx8i|?jt)9iBdTrmw0BZVwn@m(zGQt3*oyUbh?>EB{o&kCCtwOLsnwes^{ zXgwN|!kh`aB=CB9uGcrgJvA}zrPd2h*kUwDti~oRslAoK#Q%9mn*Yw|yjteha2H2# zl@F(=MY5E0RMs5;vuzX1Imjnxm0`()JARN#(edf*O7Eu5AZM#~B~i5^`7#O~NQ-*V zzHg^vUUB77L{2kTxp1Q%_zivi?xf*YrrguiwH3#^&>uG(gAd4xVt20)33;l{**`1yH|cWMt~Wj_ zE30ZFR#ZfBY=(2a2F>fz_Qv}SoF_dZU-Cr`h6JhesC-p79%z$`rgT9#F4_J}<`+XpL4YaZBjK4E70&k@HLRj8VAy0w z*o}BS^m{<;6wSY_akMoU7XuwBd4`~8&Vw~$mw_Nh`kBs^556F$=>kx*+7upobDVhy zW%ixF6Y#}wef>@$J14*wjt}s)A5r@28{bJ;u+jORGi8%1(AwItB)*Aj)Tw6p zG^`P)-a3yR9YAU~^3L!nm}fG@e4-I2EO!n|l)#s~ja+Wj;9;B-H69CsE=%sB0xhSN zq~EZwguJz%e2BZDt;b)g2pIgX@uBqvIAoF|*o(Fv`@3&MXzebuOv(f2?tsxpsa!YwQvKqyWiYh*!DcSRy5-v)lt0 z?Ijo3Z8{~fF{^&3fSm~-Y(Ju4IDTKVGm8Dmy2=h)(Qwk3m+%My;0$l);jQ7rOpl09 z*E4{A;ce86OMeXrus>2VGzH&H&!;wa)L$1?Z~_8`l%oV%FKd?ox0Ian$jS+6>dB6v z8{EyX8{B%Aya066_W<-0ay!U7L}>Gh;P!S?f?$1Ep7@h=AK`l6yfJhDmEGmL%x7S+ z$=x6U{Aa++cZ??W#*2o8oB@dr1ncxCLX1*7Q3m8f0c6|CZ5N^zbOYA;ugKxnPCr&J z!MXgR<^+Q6-Rho#D#gXdZGPGv3b`vFT`~Otu^eb=In;X_%4z_-T4R|OFKB>f#ovh2 zod6l^ImzugQ&lk2_ma2?w6Ae_>DL06FRtt$*a8VTJ7Ybum+qV5BNTT*I#c-3!hAO1 zMm?TEIXX^KnIQjs!aMJI0)gjJwa?LGLyT{>qN>x#Jr-kwLbayosBpsqAV=kkWvbW# zI^zOW!ok_DozUw`K$9`<-XOwfupxi71Jhtx^IafEn#+K0P)U+t+4v-k4O%uj0M$;D zMEJ%m?XI*{d$k^Vh>_u}*k!7Ir4YgWF+=m;A`dQ!I~?5fV}P)4Mn7=>qg`7}@y^G= z7Ed8;ew+doqlH3Or~;4OZq z6Dzvzz9_Oi*y8A}rtpT5cz4a)5AsV`3~&TRcPz zI{SKD;wGPH zo#Uldayt)4Q4jM8DowBPOs}zI-teeV>henp6qWDrkVNVwj@?GE>&q7Il8XX!6bow) zSDNj8K_fnmi3jH$(LEdRiF&?PZfuFkhYmmrD){Zav{Gvhz3Xy#0LMb~R_Q~Z+Fgz? z>9+2RCW594odlyv-Sqb|Ye}Zbu5R(ZLEpEHZDodi((KOrm_PoqDqJ1D?4R+c&sb7& zr{(>!MqKG_&!=g4zBE$fpKk4~F(m+u{!vdl=*OxWp|g*!9x^xTkg*nTS@5Axl&ENJ znXrhesNIpn16P+?A^Ck#hi#I7Rlzo*F`-WCBeT_EY;=Mo2Q!?I?u3cK9^~A(8C-_@ zL$Ug+i~wEDEHwQSf>`u-WK~9laEmCVZ*DAd@Q82g{I$W7L*|IPB3#k2C8z*qIS zSm5iGnww=Q>@Kadt=I|xmR7n=vD2ovbq3SWt3c+NG%>Rr>-fhYPp*;AQb{3qs)_54JTK_bYpz~- zaidCrZxo(dcsM%Rr!JYaG!z;x%Y-=tM!*V9jpNFUuU5g+6(>T)Vv|jc2nWeT$wZos zxM1n@lwfHBJO!a`NqUNePx3vqm|@RUg@P^UAb{)k+O5;N&0no0h1z>-I<6Phv)r{} zGuM0HX8KDef0 z*Jbv=zImyAv3ut!#JlJ_^R}+i@5Iq2)zrB;n=$U`S*gu7F2Z@4+((MQRzHV%-%a45 zSL2mTf3GFF_u{e3Rd&Scc^)3NfCXHkPZ2 z6Y-`K9JVLvQ_+*ep#vvaz;UNg+ME08lg)M`CuZ27&V&mrDxqN-#A9!V*MR8IFR6a_ zxZo~u-F_!~jv&Ch0%F@mBWseyVbZB@53{sEJ8n|`dGwq0_gBh6$i~(eGRSSjvIc(( zCPD=-15zn8FSQi!b*Z{EF5S*b^ef7dw5#2MogKbn#U$`~Z|96>fjF|E1C@ zvZIHCmPJ#}_g$MarG&Al?3|*v7l(yc&7{-T_h2!0mdcm3m3((XPCldiLfib=j)mh?-3_E&- zgukQYSvf;nYJZwJq`cn7Kfcbq7hyVpJo#M|(CUd>JU#%xTOXT{2kr zJvEo*0Qms5RRi*bxHKY(nlK6u8wu;+vXRqcU#P2|s;?>O_&m!jya6UW( zH!`>Gco|)59Bdyl-WRE!&Rgc!RW)0SYYTUb!rT&Wp!ISu3k_B^A*_O529l{kbSE_{ z$!p-(GBHy#F|cBR_gZF>(>W)$Kb8X9+!GcBok~97H{zk!KJ~6iE>Rm!EvJyTJyr`> z4799`+#6U5*(U7dehOqP7WX3$1mW96lp>m9PqMRY9U61GC`enxj1<4S1uMpBg$P;E zJLLUrn~r`WEs?}*-pO|Eu#~b)sPU`ei{5^4)CKdf{@a;=<)JbWl(J5AE^ka+fJPC} z*bB!RLdR3_aQX`w@V=;fl_6)FFYEZ~=_T@=1W^`v$SIXPHXgmfQQ z_Rh=fmpz8guPTs<&O7mcU{9Vq61@1mhv#tk(b7q8483S!?5aQT5+`T`c#9n>lPw|>;0EV9+7g(xV5EPWd?p*$DXEy?lVJCK?k*Cp{=CId!3|6A7l*OTiuQG zto#q(aCnW<<^rCic&=#9W2WOHE-L$W;>uBUOos4J4Sm{BK*VpQ7?I||)Bqh;gmA?% z%nFD%@I!ruR_6tn|vhi8N?*@^ye^QC7~B z=3Rbm^Z2(I7UP=$qjiv-{nsVp5jU~WkQ!vsHRr?GGu4B~jvTYXAFoVkLk3gw~G@31+OO39C^*C&q6^VLU;-a0}qe*skBjK9nAo3%6|7vHrKyon>3 zHVoRsq(>T8Kq(#thCq(Nm^6zM{bGqktvAU8dr5YM#BSL+N&Cgdk;n;cDy!!X8vPh* zrb(tLCxOPhz~v!g7A^gCcK!L~TFRp5;JEAT7ZA`JnMqWLgIsS3y=w0K%*5H+F|^E0 zZQf~Mh_rF5KGD{qbS1Uplbd81mPCw}KW(K%znXE@L(U_p96Ciy>$ zOspLvF9q*yLv44Ubt#982U<_v0hwiZ#B=ymQ-QR$>^O%eabj`Cx+by z+9}cK7^@V%_mPqG?R!!;JHtC;^~Pee$sl0D=i5fb42h{h#^sLB%%Q*rs1TK6?}XLq zUZo4~^lw9K6?Wa3I@;e@yc?ZZfnZVS>WVOspc{}~pi2juP5UMH6$K39*;sji~OJ3R&LK%x`02b4Xfrs@G6`C8oioErtO|-5?G&BiXBoERS-=!@! zs3JqQXG8kl6JRT_(bs$eMTGc`niN^lCpDN%7^Jsa$4#ug4~+eJ5426#7dU8;dAC{- z8`u{G0<~}d(pQjyOy&e=-3L5vz1rGh4k{IiYBOE9D0)HRzLm^&I zAVWp=tGE2QW5qygeqJ`l$l$>Zr`WWa9$V;U=X_3pov(lmkCnitN7gU)PAH0Ozna7v zV|%P;B`~*4P1te)N8ZzA=t_vg!D)?8if>?T|M|l`JG@DA!0)IgQSMCXA` z`TfCV_#kv6%gU`d?gZQ?#^)pjClQ(!9CGfG9}i%zmgg?`!G^k@%T ztj(^lwX%}nbL8Yjxm{5y64jMp3ep*lR{gM6eQ?ujM#pMY)uuiFBGGfUZG*ioO^p70 zAO?xPb?^MT$d^;cdaWh41EpxW4L83ZT_zUg_}Yy9 zg!=^I>y%Vn?3nghufk)%gkr**ANq<`?ndX}4r540?Df8l*cCW%!G&C0lm+<^ktI1> zefvFB2G90)(m+upNz_vYj3uml_lik~a%o<7rUUs?=}cxqv1)}z9O(CiQx}~TyIzl< zuTd#zW!LCcy~uZrLr_PrK)50zF3X>pvXTN+1LKhm>bJ7^Qll z6T{F6;q)NE#siGvd3*M~ELB-)dy)Iuc{fF%Hs`|B526uhnamCq&#<^r)e5U$vjIGd z;~}5drsW#Mb%83Gss#PKpd8ynNRq)HzQ;o+v?Y39ALYJ=KdcPGVn0gszB=NJ@<$E+ zF|<1f;r^)TcqsMqnMNncwvS_2qwbvdZ{`V+rC`%GROOC6-C1L8v;CUThFmV45dLgi zSB;MQZkg(X^7YTeItPX2LT6T{Ce{I-+4G$P&JRS9gcAJ|mDH3i@n$ef5f9y$y{O*UvoGKktUT!5co^0VDB$fTLBf*>a$8LAmU(gX;O3 zb!^hyU}#X%9M%M>ynb}joV-4ODtYdfa$*688=+9<+79p)IM6gw1DXIs0i9+`-#+TN zi}=0!m=UuvMiN5#hBhIWQLu=!TyhN79=~&OP?3 zWEXgrdm(0++V+Ri$xu*SJs^U^7*3!gEh_;ZMFXz zUE7@JT`j#LOXr(j+xUb$4BxYfPwMT8WklJ*_EV~nUIz6uV1BIxZ@c2_kI?$#BKm;N z6O0;N8^r`hS}g(|28AQzPyIg3=?Rnnnp(mCnymzGru2Agr*vlT*Ct`7f6-mB(>y~< z^tL1Q^tOpvDsBQOTH(xXr-V9%@towWd*g&U{G2S68+tJC|C}!)8nE&4P3u2%z4-tn zxL0qj)QI@@Vq8B{;8PgM<;xyOU0bJ5RpVm~VcGBS-mVKq2=l+RypSOJ3 z<$tqTFB?YPhr4P^*XCgjouIsXfNUjb;{CSu!GMz$@xbyq-F1^H5TdWNNE5unjUz_v zwG_+SW8pk$F8Q=pabd!TkK!hvs?MV7ctAGa>6_~F`HUw>rbdtEql}s+(&VFj4MVx; zqOS3x&j7_yY-qh>cz=F(S9IgS=brEASR#3O|1Hw4(J%yiTWpRa_dmZuAnb+msrejbUSne0#Q4jS_}2dKTRyr?vobb36_D-`bp=danj#PY3jX8i2Y zDb7EPR6-49LXAQ{BWb3U)Bh;P#_=^SaZv$hR>zi^Ca<)NNd9Xf{9O^p#)P#X#rHQ_ z>{ff=A7WIWRN|58jm!aMZQ2x6X(`J^4KS4vgke7~c}4pCn#@xabflA$`~Xx`2oGY% zEnc9$S9#eE-ZyDle|U#LH0pQ2H7s0CHWo- z0p4C))9|HLhimFSJrEyV%oMvn1Z07e(O$y38Jx*$CKxOwD-YF>1<>?FsX&Y&TG!=o zb9-O*hhMRzq#{fGMdc%`MzO{wLo4d6R!X=WN7KD@J5cgK&_u#shyTqx;7tv~U+zUR zbs|v1a(DdOArYe@eT!C5f;RY#;=C*3V^OnXw_37$RRKlF5pCY65c+4>?!f+xy=~au4vb_QMzRA--G-&^z;vxLV}>N! zc4tLSHrYm9d58(ffu#|~NF*3UGpv9$-LG$ z(XjQ0PaUBQ(`}>@N=i}tBwLSEzvi=4X&9}=4C~&WSha1}>Ug#}i~k8|zkP9)O`4x3ac=+yU zdVTWRpZh}C%DbY}HCF~l51fJZZEg|B5iP|tbTH-;Z_m8hVd^)kP`VVow5M%fPUPS^ zcsx=Mc9U=EQa#roKY09)=N|#mP7#gJqOvkJt9(~uCBIaFoppcDxBu9X;rMU(8gild zUttUHufD*R`oXEL%*s@aSHg}_ewPSX?!=MkLPO5F2Gi9u=adcvTyeq8fFs4f+a zWfo+gdf54~=3%7uBg#5J%=9hQggxkxBdDaZAaP9X9$|gbI_%EL3V}Z!mUKOknutvo zrMS6ojP%;X5n7ks#(Vm<0gm=*rgHga5#G?+#KF}B!Edr?q>Rxc;IT81j1o(^5^LOX zu4^Rsh=cg4n@yUZyud>9!ZB^7yTuzqJ}1Bpt?Yc+$67vRhEH>Zi(l}eX<{DH6DGL_UcUiylH@Kn*oH*JJIhoe-gBKvz?SZz! zRDOWYqbAyR@&@4K>$k*;I|K(U3`-Rfbp zh{VVAn>*oX9iHNA89$cSajq0gw$*`AhfF?`k@ZA2CfynMcq8}2J*`}+Ny`b7l? zCowP|ylGyyj*96#@RohYmd*7%-zTCwyiw9})_2Y%=6IX0f@?V%@63-jyD%KWIVAKty%7%=+2kXz1!#cV5%_L6&poK=Jk{}} zQBe8T2>F_f7bmC=u)yqh0&ci&#h(wn=)SucfGwm#*Wl~(4Qe(1sw^;bu@@M^^khDU z&udA}G8n>MY2hXGKvGjVj9dCXdAM?Lf0i&XKjKgp^ADY=f%a|_WNq-!W3)We=eMDW zt)=A(BDyJ|P~-5Zosbh!FgMoQyd?Og62&_f5``p6LH~e*U+uZL=$)!F##nussyjzl z{(SM_?Ky|p8nm^X#CMgtEu?{ALwlcTyo_3@@^&6S`Wd8i|AEUfe>qlqw?Ef|6B7kj8GQK4ZVDVkNNP0$8n zx8jWeq19Qe7BP@*f4@P}BN!(hXs`ofsI8m{oPKFUzO-ZaHvKvv+S`Wk8bJaqi=#{- z$U#3rf8=MUu4NT?6h{5|ZCAP&io@qHiiI{7sP%?gFP4|C4N4M`P_Sk=YgvT$gePBE#^>3_=Fkv-anzK2gLSg~;L z&`+S$WFHVYA%i2+u^~4`6fOg?vk;M!09$tRuP;&txX#qwZwE-tU6Z@1(e(?Dm<*m? z{euC@`Gm?pdc3cmr(d7n2mS{FaC^RJOIJZNinr#&3=;+t=HZQ~qwS1~G;Hq|GvrZF zPoVadao!3tN>Gm#b7FDbfs9?-*vB1dPrIR{5Wv%JYI1uC?hZ@! z?Dx{CGt-kYvcR04aR%I1DmENey9x8H>rrFPOpd$nQ@?_NEfk{_wI0Izub?0H-QqZ& z9sEn2mDR2&WLOb&8mP1CC>i+^S1dZVT8(zFVeESc);Ol7qKj6jd!z6IkbKIhcUDp2vF8r-+`l6NC zIjVk%RfzuHWvvokVkC&z_`}w__wig~%dw4^dh&F?PQ4>kWqfYQ`YTY`7a*YkoR9kVOfdkvu*Ss%e$(f+A{>}1)}T% z;dp`2wIbydNng~IwyUzz+|mmMO8cNX;A<~hAPW#t+3G@qCAh6kbLh}0ereK(==wHk za(;VROT179$i*2sFlvl*nTbCN3vC`dsqIj{t4IaDXC>(*w-MCJl+NZG7T2y?dE@?b zHfwP4Q=kwggcp7#v#Thm4WCF#1s6pAX_alZ!ItOIKPmK) zu&rtA0(<-FQ7sjDuDIrfa6!N~RXEv&yJq0kNzz>w^KDjgu2%nEGiEM{?PUp&#dk>= zQG^w;gLhAa6PVn;tf?_jA5fih{+cmV8JwaA3fLbWW?*WPL50OxS@^KvNUJ8nMsLPe@vvD+`Bf=3wL?5Oeu-#Y3;Mr>|GyJy&oJ&9FO$msiuW5s zF(gfNptLv+Hq5a9l-vKKs5@=9R&tk<CS;vCA8u1YgTjGHd_K@^5ML!{<3WOX?B` zqp+#h^DMpQN?KMJLd@kUYdXm{CJF{~Ve&r9nMMZpgWf!37FhvJDuas%tq5+g3OmDoQJ$&Soh~1z^=+mjwK2(o7g#9@ReE zm!=ZhZ5;n{R$Ti*qqrUkw1J1l8)?9$wL8rDNJw|^0q{8}#@IWIfur)oTr_Q5+cR68 zP~J${RwrOb@!%)f(<_I8#`GQ4s+cdGR?iUH-Kg4_OTI17*%E#e7}ej`xy`D)><`9- zd1Mt=1EW}CX3qY+&uePxAA#C%)A${=O;>AKR(=Ulfk$w~?`VU&)+%pAGNrz*iWg*i zBpFyw@)M!HJF`-j%17-1j0G5U0&x>K5TndN^|&bEhPTSTyyb}1;`L+-w5BX5I`P2cgp z7{Ks8^}uet0n=E!KyGUVA-4;>&suhi5ubyDG=Py{mz=PP&4-N-9a$;y(6QPZW?EON zn39C0aUrig)5te~!(Yn7x|MgQ;n0I6Aq)Tc^wUw3F*RcwAa|Mckdystomkh$XG4cw zDh<-Y9_qDB?#E#nOnu%G#HA|3XwuyiA8R4N+EMBLfh0H|h2q!rGCrpIi6P8f-joUb z{3wdQ`GwfYx)JSaOSpA3552N<&YEfzUL@(;p z!EcV-T3o-ArzRPXUPfc#`PFzqUzkmatB;NdO*Pea3>o(5Pfy+Vs<c*>D#}0e4mfi2 zLCtaIJRpeV8FIPZ-A+j+n4MLXs3BP5y01cR#?Feh>i|C6b%+$C7WTu z21MICDjQmY$)LFv=VI7l&5abb^$hKwYKEdqgIR#Sf7%gtjc1&OY-8(f9f0vp^h|AIwu{*ep^T#-x)j1cWaW)Na@|b>ehZ@wToVyWuS?GIZf9yw;L8*myB8C z{X=9q$G39C+=D#&N8BJ zAjwRh5w();FH}#z3b*DjR|Mx&4PBU~UjNzrRZU=saxJAteg+<$aGwV`TV>cXPN5hh z><_)iiB;HEI4t;H$BBH$h4fxkT8!FTahacZ6aFE@E4`CM-n)I@5~<7pP$9(9JFM2l z#mG{OteZ90y{7W)UrIDVL5BIT5rVJIXeswZrfaNnmx%`K{|4o?Ld&_e?8%xH#|1q5 z0RPb2+3#C~;~hm?K)=@Xa@*;p*b}WO0jv2&x)R2ugpu(|-1}VLWkGQguX)G(0;XI^ zx*QPS#Sg%PP;%d`6c_PzsYW}Ptc_MheI2rEyXH0XYdWsPq?Wu?Z$2r)i-nV9j&I3m zC;d)8r#fWhjsCAF%zU-v5GC3ZwpTObr2-whMILuA)73xm8)pChvRuF`E%+tcW^?G0 zutog`HpBl%WL$?3K!iWNqHygP&&{8`_M0EhDoXLcA-({9IDv=~TK&Rw>GXG_4)6~C zQ-HQAF0lIHVmHxI@*we5{O?r_IUF<Jxvdz(cJH*UjmX=#v5vT{& zC(}3&?Jc35+=Bz}4WYu1=X}P-qa@8lMg8WZrYHTxN}RfEU%wnCJrqq-Yu|a>*p`;w zQ_bA{(rSPZKD~<`J^{NoKv?=jl=#)PohdKCxcsv)Ml+)wrwOsG8Oh>pxn$S=TQNGx zyASMlVv?)Hr+Dz(bbB?Vg)d;A6E(U#-$x-X#n|nyP#RS*EdOtz%069yEg!}~bMQEU zg9iIg=zceiLH}25mvhjd)cVJRI1Xz&b3-FVG%x9xGYgy&YAF+H6^6a!hZ&idAEY&J zoE4W)wh6Vl4$MH<+{TUU*w1Y&d*W}-ieo#SbHg;+0@w#}EQW!t@}*7(Kuv3R`D7J@ zYN^v5_CGqn-$x;Nj9IdiXQC!YK)caWlR=r#?P{UKjP5eEELCGgOT?h6O?7&zrH*&L z9$W6{6)Y)c{-pE3Zgt66RZ8y2qh8fj?b`d2*rMjNoAI~!*w#B&HE$ob?98M=sP+>w zPXlCz)2=X+;gfhl6W1p$$dE*Zy{aLOEyy5hEeWpv9x*#KkLxt)D_1Tz$hVf@*}h}N z)W3-5iG_?x%znf9H_ND{)Sh$8Nu0<@-f3GO{fLG91Bc=sgKRnk3!1_(G&f$?@y_Ty zse@7PjeG)p(I>*35rW^x!%ZX)_|=@VnbjO3!G^Pem%%>sOutq8yXIhmBjDmELm0wE zij|^UQL%+yCh!gpC3blnr{enXbA8*EOC^3u&*szL<#-GWg`OkE;8CyXJJNE8u-bQ8 z7tHR8(ynJU>Htk*OAJX-k!?{cf8}QN-tT1s;kJ*PEoF-J>&OfFlKltuf`jBE*|wrDP} z&V*_FL|DA!r~H!|m@Q}HY6pqOiXG__E6zVx1ai^Y%#(gDRCOQaedTHJJ{LU5Rq4Dm zOy11=%?#c!TW5+$ooh5=cE$fRoAHoskRCX*<_v4E_jMt%J-rK99P202B*VR%-%gTc zU1J-M4~|$?BX?y{N|OMZfQW(4v#0u|J4$}N7sIQI;cd9>#|!Kw2FqjB%h$`yW9`Nn z&mYKIG-@m2qsWN%%kE}q(to_#YB#M+^2xGtup(nh|1r8nua)GMWyM%nzV>afmzm75 zPdmvi>Q}$bb|iA??ODkBs#BP&4o_VNMe&cO@`KM@Jf*43oO)U|eEcaJ>5){QPtylM z+7deIS3MfX>;oow)&XOtg_07q7G012R(Buib)4QZWiBT*p zVHpjxq=#ksOXlzf@TTk?%giPa0(*A?f5lH}IZuMk<30pB(znAzbqeN5?V+!_! z6=pV!Yr!|pJ}P>Knc15j#*&}D3LxkBo^DzCr^&nR9EuB_>OD;Co*$1U& z22F$a4hvp(y(1qf&z?LBjG{+D`_W&Nygpks-+zWF-FR{*HR}AR?4*w0D0xx5oBdDy zM7FC$w7g*~A!nd+W4Ko^gze^Qj;l@t);+jW{ACaGCOSiVp4qf7uh#6Rb7*7Uao#~DjYojt~;Gr{6} z7Ap_`@;C&J24Y#fSG0WmTF!?&>!W?rSHJg+eo_nsiY(8rl&B(S6iXvCzUVMF+f~wQ zW}!I%vkQG0L(lD=<_AhT->hEItmA-IzFy~aYRy=zKU3ki&hR@Jkh!<7!Okrsp+R-Y zI`%3>exhEA6e@J#(&nuVKpW?Udz`y#-a&n^&bM8l@KP>eK3~o87zAw_u zpuYDfVI94d9-1jCrgt1`3ny3_ zi~k0(p){S-GZCZ-c}i~VB1hpNLC2B%^eBkc_iEqavm zvjiX4@#lQCj-(Ta*wRrHsD^akTkZLMj1EgtWpKRdE;rC21gN<<0|M3!*(@m0M2VC_E?ud~=PqW@ia(%q5` zT`SHT>CZ6%HsZrM!4{@43@gLB-jeRtMZuzMgZs9URSP{Bzv;Ah0V&hs}%(o z^&kDcva!V!hM!>~^o0c~IZ&?mk^s$%EBl`HScfEn-gq4>F1YyUHSz-=AYuc5m$XZj zItdKo^3FXZ1s`~ zz7*ZF=(ebxb*t^>y7K?o8_OX53c(e3^_B-8D#{Y`ztC2=`)p}bq z5oBt%rgonDr+-suy&3+TQ=*5&aluWCLV{2Mg{Ml#PnUi;OpaSqp_1yhXq>b6hNl%# zhs`qQY5d~oq7av!ZLz$iOPtOq_H)Raio8Jv437BMsc1BMfM=HygL||b@?D;v-R`n1 zd^+YE^~$L^B)2Uw9CZxK$tGQBZi)7nw0%aJ^rUX9X`A4R8*boSjFCG1BuB(jp_}_I z<-zGn^c(Nxn;pBXb)FxMb`@O5Kxg$cq9>1C=o&t9a?Hx>37hKEGE{INY&@h=eoPMG ziS1qqQmZ)G#!IXxQI~z|IT-UI_^t9$$}ZFxUQJ82+38Tme)1)lIHu2YF!sqs3+6Rf z3P$%&Q+;-jenzdPDy3wwUjpzOudw6Hx};S?W2=ayq0#d1YNn!QkT;R6nWJW z1G|-{jBT5clXy zy%Xtff8}szsq660Q|~G2O*PBsMg08ck>PW7fZB(s( zQM6rqGXJEyf4%`dD$O1p^t`cZi{|25BCPMnsF7`Z+%)&OgaN06h1YbSlUCv2%hwR# zV)xzjEn=jHpkd4$Yh#G=-OnxSAg!B2D`;4HI5u*om&(%)2b1%ToR%H6Llj&B1ji8mb~!DVZ3g*7Wt6k4i;o5}0`g zCw&Axs)lG&Xf!?uQRWfNg|jddNKu0mkr5O!YINrOSx&-7hR0zo==EG!;)#vdwvcmk z=DNPTSEw8POItdu9jE;2of}beUJAyVSDVEB-!z&2AEMqdIpROunNw}nmklb)A(mji>Q+b&EvG+6bWIi(H%c~j@0;L)97MK|GfCFa2d`25 zR5BRD@=}0qhTd=fvx`$J9VVYaMvvjLx_NO1nWFMADzaR1e9F=(0>~7qkorbkj0=>y zah0P5S@40sy$2W0I`b4*sa;VgALQxkbdus#7ID?x< zYn;s7I6aczS|+v7L?b(h#8Tw?!Y~w?oj)6O-U{)&H!ens@!mSI?kR^Mecfsdx96_< zsf?y(2AdJ367f6;c9vjI9hPA}8n>h&DCdRdVW8R6QtK^D@zDY)Fr3Eb7*=KM)8dHb zMvtbqE5s7CyP{r;s<{-3bFWo(N>1AMXL6*QOzs{X_U(poEGMW6{*F^;ljvu&=@?32 z_<#Xiv7sEjS~zMuZqLO9Y&t$l)wk^oIcmJi5%&n;EcKt2VZsNEg*9Gvk`P8Eh|q;3 zoD&A}Y&!k&HkPCXWEU=K?-SC!a@I49DN0nDDRUE4k06m^@1vI&*jFxJ01jlT8yX3x zP*P6>#oGE?dsDxU@8G{UPje+-(Yh$Ib`mJI3!IA3iW;8iFPTpE!#5>F{{9gyc|dhD z|0O6lDEXSni``j&t>29am1Ut#2xY_H1R2k`ZTawlxCf_#>A12*C^+%F?x&EC(t`f zC|aKxuZ(@!j}Upd)Ao_>9d%GK#PBpaO7OF>#ckjIHFBc)vpjbBF)O!4i__q-S0KOOB2h1h0pex4B#tMpkssXLByQ|ee@HSwi{HQYjDPlcKG z_q-|+d`dLCCI%^+zc?NntQWN{PyQLPHf1V>mTNU%Qy#k~T1uW+yQ9@#`;KdYgj(v! zrVrTxVyR9l7RQOE?El8VW)^z8S^pfz%tT8rb?Gh6X}s|@ptj<_(Zi*s`&Q%Y&mR=L zR*glu-nOQVMYZDcCK7tEl#NBeNo6j|&^vCyc=VYQBW%gNZ1frWSnhPVlj2G`Jdw%? z4l+qKPoAk#HMP$ksmh5YaAsvi3P{uczZvO~63RgT^{(@R@piKRa1Q7WoJXek~{K6 zti!uy#Pm%6oh@G7ePVE25k5slgd)nM}-JtIy&O~cI}T{ysM zJYBI0JE3J~*5?eeC-uR|+d2z4^2@_&@oj10zc&D_~qn~4ap+rqQU zml0)8;@z%QbGo&^;QWO}1aJqaJe2(Ldi~5Jx58ZO#`=8RD;zmdr%vp!DJLr>G$Gi$ zKeX5Xs#&m6bhYHVnml;l70P*WOSlM9V@S=sTxj=;2O=XtVkkE73o2({x67Nm|Hh)p z*;AiG)~j7SZN&B)phLZfMeyat7sHV-*WDnLK8V3%FEC6+-ULBq+{T9tOo{L;BNtAhCaFX*h?gXC0X7F_l}6+n^*(W?nR4yO8++1QpF4WQmxp1q51Xg#GI>DZ)dC=FeEMkw|3 zl+DK7UO_oU2&|gKK-x1IdzGyG*Q{J&P1KV!_t7Rel2*&&JLx76LaObO{UsWIr{a1C zuF$n~&BGS;3XMy9<|6Z?W|{@9BHRiSQ0imMuyc&dJ`?1OKT3SF36lg%l0jU_YRMwh z>>elkZILYcOXYx&vm;`yWDJ~y1W*>a$nU5`~7Y<6g1RHoJh#}s6 z#8I4Rj57azLCs65z)pADE+xMiEF_3VZbP*3xCt{M#QCN)#nBCd*lwUW_`U7&wD9dC zkSW7Ra2v*Z!5JJgUK?D%aF2rTd8R~ny7B>W?GYnJK0iLN@nT1e6bxW2!d9xxf4szneC>ynL^_2hH*tS-h=j`Y4JXV?> z0pC_7Zf;lnmLOcrQiUQ6TE9ntJd^o!<$7W3Q22Z6dLV(-Sk}zudS@Is6|(>_N1Z=w zRxl`J)hBE0*4eW|!bcVBz^ALZh~#6P$ycX+KM7(SwcXnIXUh4{|C$qUdu_%?1lC6X zEps?llP{j3iz^>&;)D;{wZ6+^gp4L|_(p&BSFwNM(vNk{plEA-N&kpRA7{Cf_5V$} z;Bw@pC;lgatNP!J$(NTw+}(c$tPTI4tjQOR)Ls5+AJ3O+IyIOzZWwWJ(Zd*3lhWzy z;Bw&sdF{><&lK`70VAh|t)n+8hiFyJWBDw71u;Ko?5;YV)T}k1Y#!(;a%h3s+}?6z zFq1qsaabzt0VKTfXmh_5R1D=paokqK4WwB2ol)?!NgqC8r_^k~zQyXrtL+HXj2S7p zM+i-6&(>v?C}e43N6M+kn8akHo0nO1YS&-;i>TjrC@;Di>0Ru=>_#-sE?*5C^K^>1 zSFL)x4Ug87(P_YgaUCh#IGK)N)bej!6KsR^SlYa&V2*Wx0Vl+LNIJVfKNte?ikDLP zbEs9Bi5P`|+(eFOaiN&S3!1Ku;macOdA*Di#w&o2Kr#Bv{w!Z$~4He}ip51-$<6q|{xn)GmVKF>yj zYaDO}reoH*Z~E`ebol$W7G-4L=uxyRb)DY;5_5xfc%k5K;F@4MVrQHRsf)LQ7T?rw z5IGGD%Z8>0J8-_^)`B{^dL;S{X5fOf{L+e-;pI_MCTlic+6#=0YX%i#g61;) z-sH2hZFXW`t-pJH^L-DPBlkHIE_ikBqmC{w-3$g5wx_o%{wo33j3C^y=BmpGf5z8O z9z$tz;_<4E+0txGLR3%^uo8U zHZ_~YO|d_`cj%m?u69^I&*nH|QB*e!3IzOuN_8mzCHReg?>$+|VE<-fJ}V3GjSv!`c?z9Z=^#hymK9aDkzVQ;@?n@yGV~_0?8?&ei@~tE2IvnY);$MNgk! zT3`1GVuu8n#Si}q+y{+VcGDUL3dV12)n&|a0b{UP@jau{hOlu3;FXE1?|5e`>bPy4 zgADvoNJ)+F*r)6mUIxH4i^K{R2HnK_Us@0FGc$g&-s7P-DgSfpXd9kxi*x>D_%y%^;V(%H0q!j-D+$@Pw#YJjE6|2%8arlyf+)T{)SZAD={!_}A8>rxT#gExp&actzt zOsAl`h{Yr^NSnJTY1|B62p6Sh{f z^q->1p*V&BRGU4C+NIQcj|364JyC$r=7JYgcsF%P%LwP3QG(*u6h9d71_tKCHV|N6 zf72v4pcW8-&>(M@hikWJsV-YaoB!}MO{R>)MA76=ISB6MJMO&gOln661$wrQo3%|6 zJxdU89CRrMK7G^*@^`2vJDspf_cJbjC}QgFn#ymT0uq^Y0kJumlU8&RL#UVsHp-=p z_w<}@P-N7C_>O>5tmhQk8zhZx_$FJHbC&z`h;kXh7s%~KzoXSVt|w-SvAlq#63dHx=5lVNRY^Qw_C|4a4 zE7bUXx%1`gJAj5+R-9fQ>p7R|SvZN%X@;`n{G8>|xA2xXmnHswOpk<`!|H6D!YDV8LsGTJ#2nr?iPh8UOTMS)%HH?PB1Wet z+27W)icDWaRtC223Z6uC{Yd-RQFR-pW2sW3I^XVSJZ*9OzjtF}V!l^+*?Yfq$xiVQ zd+))coT+aMo3>a@=Wg4dd>JUR+6Tx_FJM_u?8kKUF5^6d<_WbI1rA7Hb*;v{$8^oq zde?~hJ7sM_;d>_pCl`JGC4HQS$vhKlO+#Jt>H(4Gl)Y_mtu-0!n(~(SI6_MyrNm{~i~ln{C(UfB zBC6Lihmpy#o8m9k#RV{7fwJTSBWI9^p~pd+>axcvM0?BhHoHrFt3qV!%Rsg+J9=?e zQTISUUq(H~0$ZG-=?mnW4bDsRO50lh5awM<74bCq<=%!!TdCm&=Z+s3h72$qJ?SW1 zBb1$qvceG~4(5VJY52y<4#(>b)6Mu%pX*5RwVI7d<6otAi}i8N`@INKrJuk6svFe6@WcsUeO^MbqaH;bNf zdk?W0;c=}$_(}}i@cjAKG~Dpy<^>OIr2wt+|G5l+LO2R!KzQ`Ox!Pf1a!p zr>nI3=gmq#B~o3N-1o~|w}hQ!Sg~|&hYrVE;@U*yYP7*)FWaRBSzSvamuUtbeF!;N z{Hw*Binw5Td+`#A{%SLDsH{3flB7*8g0)HatC>1^?naEOja`a?cLna8;$J5p5ae^l zbk}}G%w-|E^6rzv_03e?}HWOJmgIyKov^fnZ1UmA8lDCq`806VH? z3}rf}#D*F!Ou)s;t>qWg+u|&bhQ#-x&L=*{R8-@TlVxwe`x>Gy){Nw;w_d51lVohC z3)3#$pOeb4&yZZC4_90N#!%4k-K9rUiTwD!uJfYwvU5^3MytAQ`d~XV6Vm+v+6A2^ z7b0_xz{8cK3QHU>GzHvZW8k92dggTx-QuR$0Zsv=^>><{^Pg7OR;&7+>URhZR)N3} zqLJcNjC#6SUf2$4Ko46OeonPgag?5LG>MulJWfM1Zn!9piKrF39rVvbhQs;tpee;{ zZdQ?k0QQ!23W;b6e}Wwa!2Nz@Yv_|=6)3a zs>(Zi5Q#?$UZMDNIkzCfwXENrrW|vm-pM>=^f(?S_ACT;&24o>azzK?I?Fji5`%hb zuG0uSjhq!R>kagE7;nQHG}gI9F6$GnM{g{Kk;cqg%AbS6t-A2)eiMZ_=EzD`4>UzA zhdHGYWd}cm&Sy0i;phknioe;rYZ~}dKS?~MDZi%`8MTzA3P)Jd@jYrHU)ixf(0Mgw z#>venDk%Fi@O6>a7d(LzCdAdqT=~+*f0?4V4*pSIh zHI1vYyo~oP3$%X~YsXf@AIxK(+LTl4;pXV|DW-a)Kb0hvpRWWqqUJE%pJ9%hA-rNOkLwaV?M)8%D}mqz@g8^DCOP=6WH)OW5L zxSqyUpPpK&I)Xnv10=!6k!L?mIi z3o0=kUM`QPgk7SKG9X1Zih^YEw|8To@?BG*p78-gJcoC9jmq1zv7N;W*sHYWeao}f(~4cocl3s4a`!@Un)MPpup@N-pXAx!CC<6Vv5aX z+G`VD|J9bC3axz`(}9y2W}?2?>NQK=UdHco%9q7ao=&HD(f0XXtej{qnw|mC>kVdj zPG;>kfot{E;9MeOL51a84+qmeez1(%zG?9WXj+gv&+`NEPP^N|AjJ3*d^-IF(jrDf zm(6g`hO<3sW8or&KgZmiSa)bpj^|waA8bF7khhA>*@j7u&>`wpdf=l05Bz-x>@9Ph zWon9MzSB80#~;OWkBtxL>p!ALZ4qsMlG@zegO1UL)ni8bKU@p3-4Yu4xVyX}M~W$$ zPvwl>Ls?x9#RV=)AM7Ev5yhX-N5Ixv`Ai%r5zUoP(LBbyG#R|;r*1#0u=+AUiB0Uv z7?I!^#!t5`LMt*vXT%V!$EE``FVmKOs7o1%GbfC7QH~X8BOKliqm8@}jBtIdyPqteH>DdXDo^ddzywGzM^l^Ks75FOM~A-6`hU`a zPrhau;>^+J?Y1?l*duhgS?noBm?~5IUQg5LO{>(@M)Q&~%+WxPDo}-IK;(b43i7#N z?51a3`o@Cv5%i|#(~TsRDI=h{je$nxxmUYMQbu0298bk+^QedS;Rbk)9vJnOZx^ze zTfJR|2Il;YZ^G~V3hn&kq`b9je-6`vgBvsQBaB#+-^@^zs@E0y7{dzJoDV1*(EXit zVTv8sf*n1D@X~6D+J!dUr~vLz|rZE;cdJh&Y(6kDCs7X*K0(~dDYPR*qAngyB4U-G)ZY718z{B+N~KE_dX^>8uMW!;#Vf(^o;UaXkUPV{UH3WO zvOs2{=4@@Jl7w~u!zJ7oLU7K)+07fVe4|{bX#$ zDhOTEogJ1Z^F>^mWlyNlzAkqBUarZo#3@O#2zeu(0E@%NroY z1l>_$q3Gld%Sdsd^!7xMCQZWKN5c2eg6oGt(gG-D2sZOv&ILH2pC-nAm2wO7sBaIm zu}Wk%-nqIslPcY2HkUkSpns%(nLfm;b!R3Lz2DSMe9sIddVinH@Wg7CF8H$KNa7t- zTB7c4di%{X`+4kUh|NUH4ag%(!3V2?pmXxz8+pLs22eu3bQw}gBHgMC1B5<(Zh@iM zzqH|r!S5aOkfhKrteb=CG%@&xv(uTJj@$)R7oN72TGK7k>F zf3(lvs8m1$q8x|BdH9C{zB*1$2@3xa`1ZyB^*^`)JIE4lhr~O>k@$xS-=BbHzy%oo z$IiY0@!x6=iTyv7fR=&10)r9YpAc}3Z^-=rDSN^iNecFHO0X}2|8Jbf;3!02Fi886}3d(c|dT#aM6Yl&$|V?Q3QI;movX!gnqbucVZ|F zMUbn+lcb=K1UE>`rpPrYo1ub7SSeiheBST-8^Y-^An`OQ2IqKp$?%Z$&f3lcv{Q`8 zT4ZAUJ|`&sv=iMU=%D0jlSZm7cT#lEnTChM67MKi@pIIC?I_8 zsPjvJV=kB%tiprp_PbOt2}uavP@iFc2Npn>c(Eb@H3&A+0Cn-vSTv_0fYH+B3{K-0V4&-+7! z>V>8BSNs)Udxsw~;Z~UWm7#d9Gsb7fQ=+!pSG8j>G=-!m9g_(@xrGn54AF~D?(W{7 z*0RvJJCWl?L`{Jp@kR^=XRJpO%%P~I6@J=V*Bz=mSH|_HG35NPe;%y!gST_Dh%?j~ z2cj|$+%^#emZzgUBsNtr<5;#>)e!AdePtae{)PsV=HRa~X=EV{c9TH5c+gGjn* zvY~p)C>Tf|I`Wv3b*l&?rlJUtFXU$5o2iA-!CMe$l{HPPt_+q?x}aFJ4%@`}yg;h^ z=P)vk2_9#>lNYKR&h%*mKgpza4(QwCbrHU-$^pdI7qdYQ@Ko->lmW-vXp87y*}s5r zL3iVe<)sD}P6y{q`$<;|N{2MAQT5EzE6=RnHgc8PGwLfT&=~%9?yb48rOhH z02cxi19GdT1 z-O)kbX^nw3)tODF@5?Q?>{$nlYP}CQj(%_FhOAQ#-e1uGJ+y)dr|};q?s^C}iEiMO zdpveCg-q8QFHlkx;purfN(YMEZYYztd?p~Su8KiPM?Su7;tN)^L>$LO2sB~ z#81b&`*)B}DKdbT`XS@ewJCl*Kz$L#+=@DyG2dmj`enTXql4@2r&xv@e~1iKD1xBdBQ+ zjIm=wzwhJW=axX%Sb5HEdW1OI2^ygcl-MBl#6=^X^LraY2dCfo;=z%2%< zu*?M9WAqckTPdP@RXOHSMtBmFunX%tZ46UHByhlvDvI24`M*7moUJhW!3k*Me(Lx7 zdcV9rz*jo*BFejTIBG<6$;eK0X`&E@?do$NZX_#b2%GNprA-E>OW2hi7z+aqpa;Kb z%y{Fw>3czj=~pSRcn>fL(d z16GI-mG*_Wnaxf-^YdxnM^b)L&3K62>W>honQA%G-~wvza_da_f%iM;QhdaHA}FVe z`MOtEN)dRcZ8?1WknRAXWU#t@@$NSt0`20p3M`K}v*;q)2QBmY;JVgod3`MbRrIE)7urT6Y34Y~vT;m0U)WZB3 zHGO7>wMy8=Axf{k&xkzb3bI8L%rIP{QzO~hR;RA*uLGM1VYZG9z<@nXQK0!|*|_Sy z;xvC=U)_Zw>#NK6EKDllMFHeU@gwy2Clbn50t6NS{0pLEd) zm;?_{DTesb$3SlB!jnqwMRhi?xAE&*d3+P;`8wt5f$p>E0(toK1{Q$qZG4O~?qSd% zoE-u&dO0ph(d#XA-}Ax-gw?ieY%?W-ZrMRzS{R4GZ>;|DN7hcpswVP?SI+ExLa7=& zg&fj;7a^S^|2aS*(LOAv&l~Q``^%f*=9PyK+Z)e|NqTe0)6>UE%mUUvioW&n5VQ7( zdfXFdJaQx>M}m8H8thJQ?$$3t1=SDQ@cIRCVT?T(_xLKWG(Y*SdBB&}@?5B10>!vLBdDV3*BC07X3{hq4xp4M; z*3oTugt3-LcAn|xf<1@dv8_EQ39(<7QrDO_Jq0%*s1dxKUK$3`%e%;Dc@>AGT5+u^ zexC~gd64Ny)8d%GQM^CRBgW}s*L%gLTd~5`4G;h@zyd>fy7=~f>f1n|q@YoEQ)Vo4 zcBEz;<>Z0PDuFcY=0R!Y8}#uB&pU zL4O-+?ZKB5TWI{8<2qa4pV!wpa?6tPyS&cbw5g^o=gT_(qZJ0`dFulPpoz0v-J}Xm zeS0IjJm!R0u?CT}&;G|3QYjHFWN{ks$7v&G>}vnuQ`0Jee%XLJ{Akq!bn@9(_uAI? z89nWJ=C-UI8bGk*>!QW}K>EiZh^=hh+nr+UofmE1EnH?;(XB5?Ra+)essLK3ig_XqxyR#DN{Z4?5rjOMa z7<{tsYdqmS31bRwM}K*$MMJOogx{^W1e(Jj720oghmEX@z`DAfV@SOxC__B*XO1q~ z32^Gq?N_}|)QDe1*!>KVeX3=`0ydj6@ymxDseEC-XVz0kwD9ib^ruTs3LAg0r=%ah~PfH;p}J&Wc~4Nmv!^9$@=t35cCO?6fxjJsDS=*!;*n z39#uBv`SAQ0nEnF!u>msM8WWd`cjG|l)>C~?MbGQ0LmhiAYT4EZ~uPE6-GHc8P$Rl zOUT>f=utLA7xJ@PR*fQ7Ksh|is8fOPEGv!>Bc&Hh*kR3;1hRrs{6{NG6+||Icp+N4 z8_ZZkVb`&{#(A0%(dTmaG}f}U$sO-zac~weR-LgRE%@@i78gVAgz=VUyxn*hD>m|> z$|{Qq?yjpinB*I>Ta4bnS?mpNR9}ij9m-%ie;iEDQs>+_C=iU;tqhXL6|^2EWgQ^R zn{zdqk13KsiZ=({r2*AcQf3C_`;+T?HjG5=fFgda7BuUkF8(jlgAgj01J$~+VYg`= zCe#Fd&EDim>(k8rNZ<77y@&h>^~Lvy;NWgfXV$&FwbFaBp~-wpkvnZv@I z>hcuHkQA{Amr`MZ8r9qmy}DtPfkX_52_ioK`vlLHf}lz0$-)%Z zq-NUe%=v;VHg!I|I{Kj0d1}cc0x%`Id*}3*ckI7saMt;~$AEtYC(Aw5N2SU&pZ01%DzQ-IU!>>e1 zbsSduM_i@V;vqJ~8VXo**N~=unM{Hs!JUGoMldrPBZ`JbX@?$Mq>fvn+^+-&juBt< z3_|r0YLcRm3G*LA@@ZtBxLuQmhqkZvLu4<7n0Jgwm9ilkumdU`*E9bRu1*CtuuanG zIZI4MXVcUXu?(EFx2%NPvBzcvw19L8{-TdX7yUkqqw1LDd5_EVh_{coAONS9ngh{gXffSlg~GIEYFT!r zRaTQa1;APYlCwr4RB!gIiD-4o@Jvz@gZ)w*ZGb5Ach?zNvAEVHB#|WB?J2=V&1lRX z*<20Ow46pu;Ysdt+Sds`8h-T@Vcv?gltWjPBbRB_3zNF)Op_$W;!0(wPOD78Ms2+< zG5&>SZnAnY8u-S77jMsTw4;%xXLYB#O3l<`J>~5QiEq9hK=qF;*F^G_5a$j<7C0sy_IKBBe;nTs6HJ-gQb@I_P+_jt)KhGko;wMVI?Nd!sJD@)n#u*Pgt*=PhwmHhmk~h6p z9`=2OJ#J>Y3o`Ax-M>-W(mk&qkQ_qkkGZ=#31qViKh4{c2PI2>kmAq%F6ta{eQU@) zaWl9rmH|_=9F2{;|JZG6?_T3YR49jNDf}$|Ge9Yve#p%dB>`?CG?K%cVm5B}xi`EO z!MZR!N~ZOtLbwRWd&&cH$P>byHFM$k^f+sGKZGf3Qq#RP7$Y-+XuOWUe$B#pnPF2! zuC;2l31T)ps8lEs^8>3%buMIWx6L?gA57*wux*g={vDf??*MnNO@i3#u?>eVu)i#0 zXT)I&kzTVYYA8l2)7YXerg-W-!1dj3B|4yXmoWxsL*591u!kUSH5FbVIl&h*M91cv zIH9wSajZ>wN`#O~k4MS;H#vAw1EiR`OXEm9yWOw#v-)KY@A7DSmO>Y(=_?a0BdApo9BkybWmjwL(?J&y^QS z6Jd;)J(xLD>9elqqw6_2FY&Fh&4Qh&I1VKxs|CYr54@M1ry@O$2MmL zZ@;mFI^|I)g)y=pa)fhR##u<+_obiJ4Q@}E!L7H6f5^v~dcAH)n*Bo+ienv|LqE`= z=nw+tP_2WavEzN%bd_RGYM}QpT!BfuPb7hWbnfx9N_t*H=m~0ixZBMxo7fOIBI&|wYgPeb*y2GkU;?58O)

Qr3Du|R%L|lP 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%0AxG1~o{8iV~NjOQz56Kr)j&-aZ>W3x&2YG%6_i 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�~7YXD{uaW-Q**bc%E-gv$Q((pUSpvtuQl|C-BO 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 literal 0 HcmV?d00001 diff --git a/tests/testthat/_fixtures/guru/paths-apis-get_api.R b/tests/testthat/_fixtures/guru/paths-apis-get_api.R new file mode 100644 index 0000000..ce634ff --- /dev/null +++ b/tests/testthat/_fixtures/guru/paths-apis-get_api.R @@ -0,0 +1,32 @@ +# These functions were generated by the {beekeeper} package, based on the paths +# element from the source API description. You should carefully review these +# functions. + +#' Retrieve one version of a particular API +#' +#' Returns the API entry for one specific version of an API where there is no serviceName. +#' +#' +#' @param provider (length-1 \code{\link[base:character]{character}}) +#' @param api (length-1 \code{\link[base:character]{character}}) +#' +#' @returns `guru_get_api()`: The API response. +#' @export +guru_get_api <- function(provider, api, max_reqs = Inf, max_tries_per_req = 3) { + req <- req_guru_get_api(provider = provider, api = api) + resps <- nectar::req_perform_opinionated( + req, + max_reqs = max_reqs, + max_tries_per_req = max_tries_per_req + ) + return(nectar::resp_parse(resps)) +} + +#' @rdname guru_get_api +#' @returns `req_guru_get_api()`: A `httr2_request` request object. +req_guru_get_api <- function(provider, api) { + guru_req_prepare( + path = c("/specs/{provider}/{api}.json", provider = provider, api = api), + method = "get" + ) +} diff --git a/tests/testthat/_fixtures/guru/paths-apis-get_metrics.R b/tests/testthat/_fixtures/guru/paths-apis-get_metrics.R new file mode 100644 index 0000000..53b8235 --- /dev/null +++ b/tests/testthat/_fixtures/guru/paths-apis-get_metrics.R @@ -0,0 +1,30 @@ +# These functions were generated by the {beekeeper} package, based on the paths +# element from the source API description. You should carefully review these +# functions. + +#' Get basic metrics +#' +#' Some basic metrics for the entire directory. Just stunning numbers to put on a front page and are intended purely for WoW effect :) +#' +#' +#' +#' @returns `guru_get_metrics()`: The API response. +#' @export +guru_get_metrics <- function(max_reqs = Inf, max_tries_per_req = 3) { + req <- req_guru_get_metrics() + resps <- nectar::req_perform_opinionated( + req, + max_reqs = max_reqs, + max_tries_per_req = max_tries_per_req + ) + return(nectar::resp_parse(resps)) +} + +#' @rdname guru_get_metrics +#' @returns `req_guru_get_metrics()`: A `httr2_request` request object. +req_guru_get_metrics <- function() { + guru_req_prepare( + path = "/metrics.json", + method = "get" + ) +} diff --git a/tests/testthat/_fixtures/guru/paths-apis-get_provider.R b/tests/testthat/_fixtures/guru/paths-apis-get_provider.R new file mode 100644 index 0000000..c60f023 --- /dev/null +++ b/tests/testthat/_fixtures/guru/paths-apis-get_provider.R @@ -0,0 +1,31 @@ +# These functions were generated by the {beekeeper} package, based on the paths +# element from the source API description. You should carefully review these +# functions. + +#' List all APIs for a particular provider +#' +#' List all APIs in the directory for a particular providerName Returns links to the individual API entry for each API. +#' +#' +#' @param provider (length-1 \code{\link[base:character]{character}}) +#' +#' @returns `guru_get_provider()`: The API response. +#' @export +guru_get_provider <- function(provider, max_reqs = Inf, max_tries_per_req = 3) { + req <- req_guru_get_provider(provider = provider) + resps <- nectar::req_perform_opinionated( + req, + max_reqs = max_reqs, + max_tries_per_req = max_tries_per_req + ) + return(nectar::resp_parse(resps)) +} + +#' @rdname guru_get_provider +#' @returns `req_guru_get_provider()`: A `httr2_request` request object. +req_guru_get_provider <- function(provider) { + guru_req_prepare( + path = c("/{provider}.json", provider = provider), + method = "get" + ) +} diff --git a/tests/testthat/_fixtures/guru/paths-apis-get_providers.R b/tests/testthat/_fixtures/guru/paths-apis-get_providers.R new file mode 100644 index 0000000..215d235 --- /dev/null +++ b/tests/testthat/_fixtures/guru/paths-apis-get_providers.R @@ -0,0 +1,30 @@ +# These functions were generated by the {beekeeper} package, based on the paths +# element from the source API description. You should carefully review these +# functions. + +#' List all providers +#' +#' List all the providers in the directory +#' +#' +#' +#' @returns `guru_get_providers()`: The API response. +#' @export +guru_get_providers <- function(max_reqs = Inf, max_tries_per_req = 3) { + req <- req_guru_get_providers() + resps <- nectar::req_perform_opinionated( + req, + max_reqs = max_reqs, + max_tries_per_req = max_tries_per_req + ) + return(nectar::resp_parse(resps)) +} + +#' @rdname guru_get_providers +#' @returns `req_guru_get_providers()`: A `httr2_request` request object. +req_guru_get_providers <- function() { + guru_req_prepare( + path = "/providers.json", + method = "get" + ) +} diff --git a/tests/testthat/_fixtures/guru/paths-apis-get_service_api.R b/tests/testthat/_fixtures/guru/paths-apis-get_service_api.R new file mode 100644 index 0000000..1fc24d9 --- /dev/null +++ b/tests/testthat/_fixtures/guru/paths-apis-get_service_api.R @@ -0,0 +1,33 @@ +# These functions were generated by the {beekeeper} package, based on the paths +# element from the source API description. You should carefully review these +# functions. + +#' Retrieve one version of a particular API with a serviceName. +#' +#' Returns the API entry for one specific version of an API where there is a serviceName. +#' +#' +#' @param provider (length-1 \code{\link[base:character]{character}}) +#' @param service (length-1 \code{\link[base:character]{character}}) +#' @param api (length-1 \code{\link[base:character]{character}}) +#' +#' @returns `guru_get_service_api()`: The API response. +#' @export +guru_get_service_api <- function(provider, service, api, max_reqs = Inf, max_tries_per_req = 3) { + req <- req_guru_get_service_api(provider = provider, service = service, api = api) + resps <- nectar::req_perform_opinionated( + req, + max_reqs = max_reqs, + max_tries_per_req = max_tries_per_req + ) + return(nectar::resp_parse(resps)) +} + +#' @rdname guru_get_service_api +#' @returns `req_guru_get_service_api()`: A `httr2_request` request object. +req_guru_get_service_api <- function(provider, service, api) { + guru_req_prepare( + path = c("/specs/{provider}/{service}/{api}.json", provider = provider, service = service, api = api), + method = "get" + ) +} diff --git a/tests/testthat/_fixtures/guru/paths-apis-get_services.R b/tests/testthat/_fixtures/guru/paths-apis-get_services.R new file mode 100644 index 0000000..9d8c18e --- /dev/null +++ b/tests/testthat/_fixtures/guru/paths-apis-get_services.R @@ -0,0 +1,31 @@ +# These functions were generated by the {beekeeper} package, based on the paths +# element from the source API description. You should carefully review these +# functions. + +#' List all serviceNames for a particular provider +#' +#' List all serviceNames in the directory for a particular providerName +#' +#' +#' @param provider (length-1 \code{\link[base:character]{character}}) +#' +#' @returns `guru_get_services()`: The API response. +#' @export +guru_get_services <- function(provider, max_reqs = Inf, max_tries_per_req = 3) { + req <- req_guru_get_services(provider = provider) + resps <- nectar::req_perform_opinionated( + req, + max_reqs = max_reqs, + max_tries_per_req = max_tries_per_req + ) + return(nectar::resp_parse(resps)) +} + +#' @rdname guru_get_services +#' @returns `req_guru_get_services()`: A `httr2_request` request object. +req_guru_get_services <- function(provider) { + guru_req_prepare( + path = c("/{provider}/services.json", provider = provider), + method = "get" + ) +} diff --git a/tests/testthat/_fixtures/guru/paths-apis-list_apis.R b/tests/testthat/_fixtures/guru/paths-apis-list_apis.R new file mode 100644 index 0000000..13cd1ef --- /dev/null +++ b/tests/testthat/_fixtures/guru/paths-apis-list_apis.R @@ -0,0 +1,30 @@ +# These functions were generated by the {beekeeper} package, based on the paths +# element from the source API description. You should carefully review these +# functions. + +#' List all APIs +#' +#' List all APIs in the directory. Returns links to the OpenAPI definitions for each API in the directory. If API exist in multiple versions `preferred` one is explicitly marked. Some basic info from the OpenAPI definition is cached inside each object. This allows you to generate some simple views without needing to fetch the OpenAPI definition for each API. +#' +#' +#' +#' @returns `guru_list_apis()`: The API response. +#' @export +guru_list_apis <- function(max_reqs = Inf, max_tries_per_req = 3) { + req <- req_guru_list_apis() + resps <- nectar::req_perform_opinionated( + req, + max_reqs = max_reqs, + max_tries_per_req = max_tries_per_req + ) + return(nectar::resp_parse(resps)) +} + +#' @rdname guru_list_apis +#' @returns `req_guru_list_apis()`: A `httr2_request` request object. +req_guru_list_apis <- function() { + guru_req_prepare( + path = "/list.json", + method = "get" + ) +} diff --git a/tests/testthat/_fixtures/guru-setup.R b/tests/testthat/_fixtures/guru/setup.R similarity index 100% rename from tests/testthat/_fixtures/guru-setup.R rename to tests/testthat/_fixtures/guru/setup.R diff --git a/tests/testthat/_fixtures/guru-test-010-prepare.R b/tests/testthat/_fixtures/guru/test-010-prepare.R similarity index 100% rename from tests/testthat/_fixtures/guru-test-010-prepare.R rename to tests/testthat/_fixtures/guru/test-010-prepare.R diff --git a/tests/testthat/_fixtures/guru-test-paths-apis.R b/tests/testthat/_fixtures/guru/test-paths-apis.R similarity index 100% rename from tests/testthat/_fixtures/guru-test-paths-apis.R rename to tests/testthat/_fixtures/guru/test-paths-apis.R diff --git a/tests/testthat/_fixtures/guru_beekeeper.yml b/tests/testthat/_fixtures/guru_beekeeper.yml deleted file mode 100644 index bafdc56..0000000 --- a/tests/testthat/_fixtures/guru_beekeeper.yml +++ /dev/null @@ -1,5 +0,0 @@ -api_title: APIs.guru -api_abbr: guru -api_version: 2.2.0 -rapid_file: guru_rapid.rds -updated_on: 2024-03-27 19:14:00 diff --git a/tests/testthat/_fixtures/guru_rapid.rds b/tests/testthat/_fixtures/guru_rapid.rds deleted file mode 100644 index 692b3288dbafa2da556e549de826c3b123747715..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16976 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 NQORXca`B+w{tuO0nH2y4 diff --git a/tests/testthat/_fixtures/trello-010-prepare.R b/tests/testthat/_fixtures/trello/010-prepare.R similarity index 100% rename from tests/testthat/_fixtures/trello-010-prepare.R rename to tests/testthat/_fixtures/trello/010-prepare.R diff --git a/tests/testthat/_fixtures/trello-020-auth.R b/tests/testthat/_fixtures/trello/020-auth.R similarity index 100% rename from tests/testthat/_fixtures/trello-020-auth.R rename to tests/testthat/_fixtures/trello/020-auth.R diff --git a/tests/testthat/_fixtures/trello/_beekeeper.yml b/tests/testthat/_fixtures/trello/_beekeeper.yml new file mode 100644 index 0000000..e7113dc --- /dev/null +++ b/tests/testthat/_fixtures/trello/_beekeeper.yml @@ -0,0 +1,5 @@ +api_title: Trello +api_abbr: trello +api_version: '1.0' +rapid_file: _beekeeper_rapid.rds +updated_on: 2026-05-12 07:57:43 diff --git a/tests/testthat/_fixtures/trello/_beekeeper_rapid.rds b/tests/testthat/_fixtures/trello/_beekeeper_rapid.rds new file mode 100644 index 0000000000000000000000000000000000000000..a55737eb9513f2785794f7e262a1748e8eef4344 GIT binary patch literal 16173 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 your application key +#' @param token (length-1 \code{\link[base:character]{character}}) Getting a token from a user +#' +#' @returns `trello_add_boards()`: The API response. +#' @export +trello_add_boards <- function( + key = Sys.getenv("TRELLO_KEY"), + token = Sys.getenv("TRELLO_TOKEN"), max_reqs = Inf, max_tries_per_req = 3 +) { + req <- req_trello_add_boards(key = key, token = token) + resps <- nectar::req_perform_opinionated( + req, + max_reqs = max_reqs, + max_tries_per_req = max_tries_per_req + ) + return(nectar::resp_parse(resps)) +} + +#' @rdname trello_add_boards +#' @returns `req_trello_add_boards()`: A `httr2_request` request object. +req_trello_add_boards <- function( + key = Sys.getenv("TRELLO_KEY"), + token = Sys.getenv("TRELLO_TOKEN") +) { + trello_req_prepare( + path = "/boards", + method = "post", + key = key, token = token + ) +} diff --git a/tests/testthat/_fixtures/trello-paths-board.R b/tests/testthat/_fixtures/trello/paths-board.R similarity index 100% rename from tests/testthat/_fixtures/trello-paths-board.R rename to tests/testthat/_fixtures/trello/paths-board.R diff --git a/tests/testthat/_fixtures/trello/test-paths-board.R b/tests/testthat/_fixtures/trello/test-paths-board.R new file mode 100644 index 0000000..eabaa6b --- /dev/null +++ b/tests/testthat/_fixtures/trello/test-paths-board.R @@ -0,0 +1,13 @@ +# These tests were generated by the {beekeeper} package, based on the paths +# element from the source API description. You will likely need to supply +# arguments for the tests to succeed. We recommend expanding these tests to +# check for specific expectations, rather than simply using a snapshot. + +with_mock_dir("api/path/board", { + test_that("trello_add_boards() returns expected result", { + expect_snapshot({ + test_result <- trello_add_boards() + test_result + }) + }) +}) diff --git a/tests/testthat/_fixtures/trello_beekeeper.yml b/tests/testthat/_fixtures/trello_beekeeper.yml deleted file mode 100644 index 2627140..0000000 --- a/tests/testthat/_fixtures/trello_beekeeper.yml +++ /dev/null @@ -1,5 +0,0 @@ -api_title: Trello -api_abbr: trello -api_version: '1.0' -rapid_file: trello_rapid.rds -updated_on: 2024-03-29 21:06:50 diff --git a/tests/testthat/_fixtures/trello_rapid.rds b/tests/testthat/_fixtures/trello_rapid.rds deleted file mode 100644 index 55d2c57f5bf78e3cfe62c2b6db1065ae37b931fc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16891 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 diff --git a/tests/testthat/_snaps/generate_pkg-setup.md b/tests/testthat/_snaps/generate_pkg-setup.md index 1b8f40c..ad9a2b7 100644 --- a/tests/testthat/_snaps/generate_pkg-setup.md +++ b/tests/testthat/_snaps/generate_pkg-setup.md @@ -22,9 +22,9 @@ [1] "2.2.0" $rapid_file - [1] "guru_rapid.rds" + [1] "_beekeeper_rapid.rds" $updated_on - [1] "2024-03-27 19:14:00 UTC" + [1] "2026-05-12 07:57:02 UTC" diff --git a/tests/testthat/helper.R b/tests/testthat/helper.R index e8b8f22..164d6a3 100644 --- a/tests/testthat/helper.R +++ b/tests/testthat/helper.R @@ -58,3 +58,13 @@ scrub_path <- function(input, keep_dirs = c("R", "tests")) { "\\1" ) } + +# Find all fixture files matching a regexp and read their contents. +# Returns a named list of character vectors, where names are the fixture +# filenames with the "{api_abbr}-" prefix stripped. +load_expected_files <- function(api_abbr, regexp) { + test_dir <- test_path("_fixtures", api_abbr) + files <- fs::dir_ls(test_dir, regexp = regexp) + names(files) <- fs::path_file(files) + purrr::map(files, readLines) +} diff --git a/tests/testthat/test-generate_pkg-paths.R b/tests/testthat/test-generate_pkg-paths.R index 6af9233..7d6bfbc 100644 --- a/tests/testthat/test-generate_pkg-paths.R +++ b/tests/testthat/test-generate_pkg-paths.R @@ -1,14 +1,24 @@ test_that(".generate_paths() generates path files", { # 1 tag, no security skip_on_cran() - config <- .read_config(test_path("_fixtures", "guru_beekeeper.yml")) - api_definition <- readRDS(test_path("_fixtures", "guru_rapid.rds")) - # r_expected <- readLines( - # test_path("_fixtures", "guru-paths-apis.R") - # ) - # tests_expected <- readLines( - # test_path("_fixtures", "guru-test-paths-apis.R") - # ) + skip_on_covr() + config <- .read_config(test_path("_fixtures", "guru", "_beekeeper.yml")) + api_definition <- readRDS(test_path( + "_fixtures", + "guru", + "_beekeeper_rapid.rds" + )) + + api_abbr <- "guru" + expected_path_contents <- load_expected_files( + api_abbr, + paste0("/paths-.+\\.R$") + ) + expected_test_contents <- load_expected_files( + api_abbr, + paste0("/test-paths-.+\\.R$") + ) + create_local_package() usethis::use_testthat() @@ -34,46 +44,46 @@ test_that(".generate_paths() generates path files", { ) ) - # Phase 4: update guru-paths-apis-*.R fixtures and re-enable content checks - # Phase 4: update guru-test-paths-apis.R fixture and re-enable content check - # r_result <- readLines("R/paths-apis.R") - # expect_identical(r_result, r_expected) - # tests_result <- readLines("tests/testthat/test-paths-apis.R") - # expect_identical(tests_result, tests_expected) + purrr::iwalk(expected_path_contents, \(expected, name) { + expect_identical(readLines(file.path("R", name)), expected) + }) + + purrr::iwalk(expected_test_contents, \(expected, name) { + expect_identical(readLines(file.path("tests", "testthat", name)), expected) + }) }) test_that("generate_pkg() generates path tests for guru", { # 1 tag, no security skip_on_cran() - config <- readLines(test_path("_fixtures", "guru_beekeeper.yml")) - guru_rapid <- readRDS(test_path("_fixtures", "guru_rapid.rds")) - # expected_file_content <- readLines( - # test_path("_fixtures", "guru-test-paths-apis.R") - # ) + skip_on_covr() + config <- readLines(test_path("_fixtures", "guru", "_beekeeper.yml")) + guru_rapid <- readRDS(test_path("_fixtures", "guru", "_beekeeper_rapid.rds")) + expected_file_content <- readLines( + test_path("_fixtures", "guru", "test-paths-apis.R") + ) create_local_package() writeLines(config, "_beekeeper.yml") - saveRDS(guru_rapid, "guru_rapid.rds") + saveRDS(guru_rapid, "_beekeeper_rapid.rds") generate_pkg() - expect_true(file.exists("tests/testthat/test-paths-apis.R")) - # Phase 4: update guru-test-paths-apis.R fixture and re-enable content check - # generated_file_content <- readLines("tests/testthat/test-paths-apis.R") - # expect_identical(generated_file_content, expected_file_content) + generated_file_content <- readLines("tests/testthat/test-paths-apis.R") + expect_identical(generated_file_content, expected_file_content) }) test_that("generate_pkg() generates test setup file for guru", { # 1 tag, no security skip_on_cran() - config <- readLines(test_path("_fixtures", "guru_beekeeper.yml")) - guru_rapid <- readRDS(test_path("_fixtures", "guru_rapid.rds")) + config <- readLines(test_path("_fixtures", "guru", "_beekeeper.yml")) + guru_rapid <- readRDS(test_path("_fixtures", "guru", "_beekeeper_rapid.rds")) expected_file_content <- readLines( - test_path("_fixtures", "guru-setup.R") + test_path("_fixtures", "guru", "setup.R") ) create_local_package() writeLines(config, "_beekeeper.yml") - saveRDS(guru_rapid, "guru_rapid.rds") + saveRDS(guru_rapid, "_beekeeper_rapid.rds") generate_pkg() generated_file_content <- readLines("tests/testthat/setup.R") expect_identical(generated_file_content, expected_file_content) @@ -82,11 +92,15 @@ test_that("generate_pkg() generates test setup file for guru", { test_that("generate_pkg() generates path functions for fec", { # 3 tags (audit, debts, legal), more complicated security skip_on_cran() - config <- readLines(test_path("_fixtures", "fec_subset_beekeeper.yml")) - fec_rapid <- readRDS(test_path("_fixtures", "fec_subset_rapid.rds")) - # expected_file_content <- readLines( - # test_path("_fixtures", "fec-paths-audit.R") - # ) + config <- readLines(test_path("_fixtures", "fec", "fec_subset_beekeeper.yml")) + fec_rapid <- readRDS(test_path("_fixtures", "fec", "fec_subset_rapid.rds")) + expected_file_content <- readLines( + test_path( + "_fixtures", + "fec", + "paths-audit-get_names_audit_candidates.R" + ) + ) create_local_package() writeLines(config, "_beekeeper.yml") @@ -95,28 +109,31 @@ test_that("generate_pkg() generates path functions for fec", { changed_files <- generate_pkg() expect_snapshot(scrub_path(changed_files)) - # Phase 4: update fec per-operation fixtures and re-enable content checks - # generated_file_content <- readLines("R/paths-audit.R") - # expect_identical(generated_file_content, expected_file_content) + generated_file_content <- readLines( + "R/paths-audit-get_names_audit_candidates.R" + ) + expect_identical(generated_file_content, expected_file_content) }) test_that("generate_pkg() generates path functions for trello", { # some tags failed before this, more complicated security skip_on_cran() - config <- readLines(test_path("_fixtures", "trello_beekeeper.yml")) - trello_rapid <- readRDS(test_path("_fixtures", "trello_rapid.rds")) - # expected_file_content <- readLines( - # test_path("_fixtures", "trello-paths-board.R") - # ) + skip_on_covr() + config <- readLines(test_path("_fixtures", "trello", "_beekeeper.yml")) + trello_rapid <- readRDS(test_path( + "_fixtures", + "trello", + "_beekeeper_rapid.rds" + )) + expected_file_content <- readLines( + test_path("_fixtures", "trello", "paths-board-add_boards.R") + ) create_local_package() writeLines(config, "_beekeeper.yml") - saveRDS(trello_rapid, "trello_rapid.rds") + saveRDS(trello_rapid, "_beekeeper_rapid.rds") generate_pkg() - expect_true(file.exists("R/paths-board-add_boards.R")) - - # Phase 4: update trello per-operation fixtures and re-enable content checks - # generated_file_content <- readLines("R/paths-board.R") - # expect_identical(generated_file_content, expected_file_content) + generated_file_content <- readLines("R/paths-board-add_boards.R") + expect_identical(generated_file_content, expected_file_content) }) diff --git a/tests/testthat/test-generate_pkg-prepare.R b/tests/testthat/test-generate_pkg-prepare.R index 142b335..19136d1 100644 --- a/tests/testthat/test-generate_pkg-prepare.R +++ b/tests/testthat/test-generate_pkg-prepare.R @@ -1,11 +1,21 @@ test_that(".generate_prepare() generates prepare file.", { skip_on_cran() - config <- .read_config(test_path("_fixtures", "guru_beekeeper.yml")) - api_definition <- readRDS(test_path("_fixtures", "guru_rapid.rds")) - prepare_expected <- readLines(test_path("_fixtures", "guru-010-prepare.R")) + skip_on_covr() + config <- .read_config(test_path("_fixtures", "guru", "_beekeeper.yml")) + api_definition <- readRDS(test_path( + "_fixtures", + "guru", + "_beekeeper_rapid.rds" + )) + prepare_expected <- readLines(test_path( + "_fixtures", + "guru", + "010-prepare.R" + )) t_prepare_expected <- readLines(test_path( "_fixtures", - "guru-test-010-prepare.R" + "guru", + "test-010-prepare.R" )) create_local_package() diff --git a/tests/testthat/test-generate_pkg-security.R b/tests/testthat/test-generate_pkg-security.R index f8519d7..3ce2430 100644 --- a/tests/testthat/test-generate_pkg-security.R +++ b/tests/testthat/test-generate_pkg-security.R @@ -1,8 +1,20 @@ test_that(".generate_security() generates security file", { skip_on_cran() - config <- .read_config(test_path("_fixtures", "trello_beekeeper.yml")) - api_definition <- readRDS(test_path("_fixtures", "trello_rapid.rds")) - security_expected <- readLines(test_path("_fixtures", "trello-020-auth.R")) + config <- .read_config(test_path( + "_fixtures", + "trello", + "_beekeeper.yml" + )) + api_definition <- readRDS(test_path( + "_fixtures", + "trello", + "_beekeeper_rapid.rds" + )) + security_expected <- readLines(test_path( + "_fixtures", + "trello", + "020-auth.R" + )) create_local_package() test_result <- .generate_security( diff --git a/tests/testthat/test-generate_pkg-setup.R b/tests/testthat/test-generate_pkg-setup.R index 5e7ec78..58eefac 100644 --- a/tests/testthat/test-generate_pkg-setup.R +++ b/tests/testthat/test-generate_pkg-setup.R @@ -14,7 +14,7 @@ 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")) + config <- .read_config(test_path("_fixtures", "guru", "_beekeeper.yml")) expect_s3_class(config$updated_on, c("POSIXlt", "POSIXt")) expect_snapshot({ config @@ -23,8 +23,8 @@ test_that(".read_config() reads configs", { test_that(".read_api_definition() reads api_definitions", { api_definition <- .read_api_definition( - test_path("_fixtures"), - "guru_rapid.rds" + test_path("_fixtures", "guru"), + "_beekeeper_rapid.rds" ) expect_s7_class(api_definition, rapid::class_rapid) }) diff --git a/tests/testthat/test-generate_pkg.R b/tests/testthat/test-generate_pkg.R index 21a1585..eede0b3 100644 --- a/tests/testthat/test-generate_pkg.R +++ b/tests/testthat/test-generate_pkg.R @@ -1,11 +1,15 @@ 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_rapid.rds")) + 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, "guru_rapid.rds") + saveRDS(api_definition, "_beekeeper_rapid.rds") test_result <- generate_pkg() test_result <- scrub_path(test_result) @@ -29,18 +33,31 @@ test_that("generate_pkg() returns a vector of created files", { test_that("generate_pkg() generates call function with API keys", { skip_on_cran() + skip_on_covr() local_mocked_bindings( .generate_paths = function(...) { character() } ) - config_text <- readLines(test_path("_fixtures", "trello_beekeeper.yml")) - api_definition <- readRDS(test_path("_fixtures", "trello_rapid.rds")) - prepare_expected <- readLines(test_path("_fixtures", "trello-010-prepare.R")) + 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", + "010-prepare.R" + )) create_local_package() writeLines(config_text, "_beekeeper.yml") - saveRDS(api_definition, "trello_rapid.rds") + saveRDS(api_definition, "_beekeeper_rapid.rds") generate_pkg() diff --git a/tests/testthat/test-use_beekeeper.R b/tests/testthat/test-use_beekeeper.R index 49e8061..5718e95 100644 --- a/tests/testthat/test-use_beekeeper.R +++ b/tests/testthat/test-use_beekeeper.R @@ -4,7 +4,7 @@ test_that("config writes a yml", { invisible(TRUE) } ) - rapid_path <- test_path("_fixtures/guru_rapid.rds") + 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") @@ -19,7 +19,7 @@ test_that("config writes a yml", { expect_identical(guru_rapid, reread_rapid) test_result_file <- scrub_config(readLines(config_path)) expected_result_file <- scrub_config( - readLines(test_path("_fixtures", "guru_beekeeper.yml")) + readLines(test_path("_fixtures", "guru", "_beekeeper.yml")) ) expect_identical( test_result_file, From 0f182ee20cdf43b2bba3dcfd80fbccb14629ce2e Mon Sep 17 00:00:00 2001 From: Jon Harmon Date: Tue, 12 May 2026 08:52:19 -0500 Subject: [PATCH 25/33] Tag for qcthat --- tests/testthat/_snaps/generate_pkg-paths.md | 2 +- tests/testthat/test-generate_pkg-paths.R | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/testthat/_snaps/generate_pkg-paths.md b/tests/testthat/_snaps/generate_pkg-paths.md index 3589e3e..615cc86 100644 --- a/tests/testthat/_snaps/generate_pkg-paths.md +++ b/tests/testthat/_snaps/generate_pkg-paths.md @@ -1,4 +1,4 @@ -# generate_pkg() generates path functions for fec +# generate_pkg() generates path functions for fec (#65) Code scrub_path(changed_files) diff --git a/tests/testthat/test-generate_pkg-paths.R b/tests/testthat/test-generate_pkg-paths.R index 7d6bfbc..2368401 100644 --- a/tests/testthat/test-generate_pkg-paths.R +++ b/tests/testthat/test-generate_pkg-paths.R @@ -1,4 +1,4 @@ -test_that(".generate_paths() generates path files", { +test_that(".generate_paths() generates path files (#65)", { # 1 tag, no security skip_on_cran() skip_on_covr() @@ -53,7 +53,7 @@ test_that(".generate_paths() generates path files", { }) }) -test_that("generate_pkg() generates path tests for guru", { +test_that("generate_pkg() generates path tests for guru (#65)", { # 1 tag, no security skip_on_cran() skip_on_covr() @@ -89,7 +89,7 @@ test_that("generate_pkg() generates test setup file for guru", { expect_identical(generated_file_content, expected_file_content) }) -test_that("generate_pkg() generates path functions for fec", { +test_that("generate_pkg() generates path functions for fec (#65)", { # 3 tags (audit, debts, legal), more complicated security skip_on_cran() config <- readLines(test_path("_fixtures", "fec", "fec_subset_beekeeper.yml")) @@ -115,7 +115,7 @@ test_that("generate_pkg() generates path functions for fec", { expect_identical(generated_file_content, expected_file_content) }) -test_that("generate_pkg() generates path functions for trello", { +test_that("generate_pkg() generates path functions for trello (#65)", { # some tags failed before this, more complicated security skip_on_cran() skip_on_covr() From 541e7ff044700decf04e1ae2e2f6cf39657a51a7 Mon Sep 17 00:00:00 2001 From: Jon Harmon Date: Tue, 12 May 2026 09:32:00 -0500 Subject: [PATCH 26/33] Always `@inheritParams .shared-params` for `max_tries_per_req` (and, for now, for `max_reqs`, but that will become function-specific soon). --- inst/templates/010-prepare.R | 6 ++--- inst/templates/paths.R | 3 +-- .../fec/paths-audit-get_audit_case.R | 3 +-- .../fec/paths-audit-get_audit_category.R | 3 +-- .../paths-audit-get_audit_primary_category.R | 3 +-- .../paths-audit-get_names_audit_candidates.R | 3 +-- .../paths-audit-get_names_audit_committees.R | 2 +- .../paths-debts-get_schedules_schedule_d.R | 3 +-- ...hs-debts-get_schedules_schedule_d_sub_id.R | 3 +-- .../fec/paths-legal-get_legal_search.R | 3 +-- tests/testthat/_fixtures/guru/010-prepare.R | 6 ++--- .../_fixtures/guru/paths-apis-get_api.R | 2 +- .../_fixtures/guru/paths-apis-get_metrics.R | 2 +- .../_fixtures/guru/paths-apis-get_provider.R | 2 +- .../_fixtures/guru/paths-apis-get_providers.R | 2 +- .../guru/paths-apis-get_service_api.R | 2 +- .../_fixtures/guru/paths-apis-get_services.R | 2 +- .../_fixtures/guru/paths-apis-list_apis.R | 2 +- tests/testthat/_fixtures/trello/010-prepare.R | 6 ++--- .../_fixtures/trello/paths-board-add_boards.R | 3 +-- tests/testthat/_fixtures/trello/paths-board.R | 23 ------------------- tests/testthat/helper.R | 2 +- 22 files changed, 27 insertions(+), 59 deletions(-) delete mode 100644 tests/testthat/_fixtures/trello/paths-board.R diff --git a/inst/templates/010-prepare.R b/inst/templates/010-prepare.R index 59ae541..3de93aa 100644 --- a/inst/templates/010-prepare.R +++ b/inst/templates/010-prepare.R @@ -1,8 +1,8 @@ #' Generate a request for the {{api_title}} API #' -#' Prepare a request for the Slack API, using the opinionated framework defined -#' in [nectar::req_init()], [nectar::req_modify()], [nectar::req_tidy_policy()], -#' and [nectar::req_pagination_policy()]. +#' Prepare a request for the {{api_title}} API, using the opinionated framework +#' defined in [nectar::req_init()], [nectar::req_modify()], +#' [nectar::req_tidy_policy()], and [nectar::req_pagination_policy()]. #' #' You may wish to export this function (if the API changes often or you do not #' fully implement the API, for example). diff --git a/inst/templates/paths.R b/inst/templates/paths.R index 57abcc7..f5eb9a1 100644 --- a/inst/templates/paths.R +++ b/inst/templates/paths.R @@ -5,10 +5,9 @@ #' {{summary}} #' #' {{description}} -#'{{#has_security}} -#' @inheritParams .shared-params{{/has_security}} #' {{#params}} #' @param {{name}} ({{{class}}}) {{{description}}}{{/params}} +#' @inheritParams .shared-params #' #' @returns `{{api_abbr}}_{{operation_id}}()`: The API response. #' @export diff --git a/tests/testthat/_fixtures/fec/paths-audit-get_audit_case.R b/tests/testthat/_fixtures/fec/paths-audit-get_audit_case.R index 5ec5712..bb2f401 100644 --- a/tests/testthat/_fixtures/fec/paths-audit-get_audit_case.R +++ b/tests/testthat/_fixtures/fec/paths-audit-get_audit_case.R @@ -6,8 +6,6 @@ #' #' This endpoint contains Final Audit Reports approved by the Commission since inception. The search can be based on information about the audited committee (Name, FEC ID Number, Type, Election Cycle) or the issues covered in the report. #' -#' @inheritParams .shared-params -#' #' @param audit_case_id (length-1 \code{\link[base:list]{list}}, optional) Primary/foreign key for audit tables #' @param cycle (length-1 \code{\link[base:list]{list}}, optional) Filter records to only those that are applicable to a given two-year period. This cycle follows the traditional House election cycle and subdivides the presidential and Senate elections into comparable two-year blocks. The cycle begins with an odd year and is named for its ending, even year. #' @param sub_category_id (length-1 \code{\link[base:character]{character}}, optional) The finding id of an audit. Finding are a category of broader issues. Each category has an unique ID. @@ -28,6 +26,7 @@ #' @param primary_category_id (length-1 \code{\link[base:character]{character}}, optional) Audit category ID (table PK) #' @param sort_null_only (length-1 \code{\link[base:logical]{logical}}, optional) Toggle that filters out all rows having sort column that is non-null #' @param sort (length-1 \code{\link[base:list]{list}}, optional) Provide a field to sort by. Use `-` for descending order. ex: `-case_no` +#' @inheritParams .shared-params #' #' @returns `fec_get_audit_case()`: The API response. #' @export diff --git a/tests/testthat/_fixtures/fec/paths-audit-get_audit_category.R b/tests/testthat/_fixtures/fec/paths-audit-get_audit_category.R index 4ad6707..4a0abed 100644 --- a/tests/testthat/_fixtures/fec/paths-audit-get_audit_category.R +++ b/tests/testthat/_fixtures/fec/paths-audit-get_audit_category.R @@ -6,8 +6,6 @@ #' #' This lists the options for the categories and subcategories available in the /audit-search/ endpoint. #' -#' @inheritParams .shared-params -#' #' @param sort_nulls_last (length-1 \code{\link[base:logical]{logical}}, optional) Toggle that sorts null values last #' @param page (length-1 \code{\link[base:list]{list}}, optional) For paginating through results, starting at page 1 #' @param primary_category_name (length-1 \code{\link[base:list]{list}}, optional) Primary Audit Category - No Findings or Issues/Not a Committee - Net Outstanding Campaign/Convention Expenditures/Obligations - Payments/Disgorgements - Allocation Issues - Prohibited Contributions - Disclosure - Recordkeeping - Repayment to US Treasury - Other - Misstatement of Financial Activity - Excessive Contributions - Failure to File Reports/Schedules/Notices - Loans - Referred Findings Not Listed @@ -17,6 +15,7 @@ #' @param sort_null_only (length-1 \code{\link[base:logical]{logical}}, optional) Toggle that filters out all rows having sort column that is non-null #' @param per_page (length-1 \code{\link[base:list]{list}}, optional) The number of results returned per page. Defaults to 20. #' @param sort (length-1 \code{\link[base:character]{character}}, optional) Provide a field to sort by. Use `-` for descending order. +#' @inheritParams .shared-params #' #' @returns `fec_get_audit_category()`: The API response. #' @export diff --git a/tests/testthat/_fixtures/fec/paths-audit-get_audit_primary_category.R b/tests/testthat/_fixtures/fec/paths-audit-get_audit_primary_category.R index 41838c0..e60e714 100644 --- a/tests/testthat/_fixtures/fec/paths-audit-get_audit_primary_category.R +++ b/tests/testthat/_fixtures/fec/paths-audit-get_audit_primary_category.R @@ -6,8 +6,6 @@ #' #' This lists the options for the primary categories available in the /audit-search/ endpoint. #' -#' @inheritParams .shared-params -#' #' @param sort_nulls_last (length-1 \code{\link[base:logical]{logical}}, optional) Toggle that sorts null values last #' @param page (length-1 \code{\link[base:list]{list}}, optional) For paginating through results, starting at page 1 #' @param primary_category_name (length-1 \code{\link[base:list]{list}}, optional) Primary Audit Category - No Findings or Issues/Not a Committee - Net Outstanding Campaign/Convention Expenditures/Obligations - Payments/Disgorgements - Allocation Issues - Prohibited Contributions - Disclosure - Recordkeeping - Repayment to US Treasury - Other - Misstatement of Financial Activity - Excessive Contributions - Failure to File Reports/Schedules/Notices - Loans - Referred Findings Not Listed @@ -17,6 +15,7 @@ #' @param sort_null_only (length-1 \code{\link[base:logical]{logical}}, optional) Toggle that filters out all rows having sort column that is non-null #' @param per_page (length-1 \code{\link[base:list]{list}}, optional) The number of results returned per page. Defaults to 20. #' @param sort (length-1 \code{\link[base:character]{character}}, optional) Provide a field to sort by. Use `-` for descending order. +#' @inheritParams .shared-params #' #' @returns `fec_get_audit_primary_category()`: The API response. #' @export diff --git a/tests/testthat/_fixtures/fec/paths-audit-get_names_audit_candidates.R b/tests/testthat/_fixtures/fec/paths-audit-get_names_audit_candidates.R index b2adc03..3b09659 100644 --- a/tests/testthat/_fixtures/fec/paths-audit-get_names_audit_candidates.R +++ b/tests/testthat/_fixtures/fec/paths-audit-get_names_audit_candidates.R @@ -6,10 +6,9 @@ #' #' Search for candidates or committees by name. If you're looking for information on a particular person or group, using a name to find the `candidate_id` or `committee_id` on this endpoint can be a helpful first step. #' -#' @inheritParams .shared-params -#' #' @param api_key (length-1 \code{\link[base:character]{character}}) API key for https://api.data.gov. Get one at https://api.data.gov/signup. #' @param q (length-1 \code{\link[base:list]{list}}) Name (candidate or committee) to search for +#' @inheritParams .shared-params #' #' @returns `fec_get_names_audit_candidates()`: The API response. #' @export diff --git a/tests/testthat/_fixtures/fec/paths-audit-get_names_audit_committees.R b/tests/testthat/_fixtures/fec/paths-audit-get_names_audit_committees.R index fd49ea4..0c9eb20 100644 --- a/tests/testthat/_fixtures/fec/paths-audit-get_names_audit_committees.R +++ b/tests/testthat/_fixtures/fec/paths-audit-get_names_audit_committees.R @@ -6,10 +6,10 @@ #' #' Search for candidates or committees by name. If you're looking for information on a particular person or group, using a name to find the `candidate_id` or `committee_id` on this endpoint can be a helpful first step. #' -#' @inheritParams .shared-params #' #' @param api_key (length-1 \code{\link[base:character]{character}}) API key for https://api.data.gov. Get one at https://api.data.gov/signup. #' @param q (length-1 \code{\link[base:list]{list}}) Name (candidate or committee) to search for +#' @inheritParams .shared-params #' #' @returns `fec_get_names_audit_committees()`: The API response. #' @export diff --git a/tests/testthat/_fixtures/fec/paths-debts-get_schedules_schedule_d.R b/tests/testthat/_fixtures/fec/paths-debts-get_schedules_schedule_d.R index c55633b..9651ae8 100644 --- a/tests/testthat/_fixtures/fec/paths-debts-get_schedules_schedule_d.R +++ b/tests/testthat/_fixtures/fec/paths-debts-get_schedules_schedule_d.R @@ -6,8 +6,6 @@ #' #' Schedule D, it shows debts and obligations owed to or by the committee that are required to be disclosed. #' -#' @inheritParams .shared-params -#' #' @param creditor_debtor_name (length-1 \code{\link[base:list]{list}}, optional) #' @param max_image_number (length-1 \code{\link[base:character]{character}}, optional) Maxium image number of the page where the schedule item is reported #' @param sort_nulls_last (length-1 \code{\link[base:logical]{logical}}, optional) Toggle that sorts null values last @@ -32,6 +30,7 @@ #' @param sort (length-1 \code{\link[base:character]{character}}, optional) Provide a field to sort by. Use `-` for descending order. #' @param min_amount_outstanding_beginning (length-1 \code{\link[base:double]{double}}, optional) #' @param max_date (length-1 \code{\link[base:Date]{Date}}, optional) Maximum load date +#' @inheritParams .shared-params #' #' @returns `fec_get_schedules_schedule_d()`: The API response. #' @export diff --git a/tests/testthat/_fixtures/fec/paths-debts-get_schedules_schedule_d_sub_id.R b/tests/testthat/_fixtures/fec/paths-debts-get_schedules_schedule_d_sub_id.R index 2ce441d..5d4dbfb 100644 --- a/tests/testthat/_fixtures/fec/paths-debts-get_schedules_schedule_d_sub_id.R +++ b/tests/testthat/_fixtures/fec/paths-debts-get_schedules_schedule_d_sub_id.R @@ -6,8 +6,6 @@ #' #' Schedule D, it shows debts and obligations owed to or by the committee that are required to be disclosed. #' -#' @inheritParams .shared-params -#' #' @param sort_nulls_last (length-1 \code{\link[base:logical]{logical}}, optional) Toggle that sorts null values last #' @param per_page (length-1 \code{\link[base:list]{list}}, optional) The number of results returned per page. Defaults to 20. #' @param sort_null_only (length-1 \code{\link[base:logical]{logical}}, optional) Toggle that filters out all rows having sort column that is non-null @@ -16,6 +14,7 @@ #' @param api_key (length-1 \code{\link[base:character]{character}}) API key for https://api.data.gov. Get one at https://api.data.gov/signup. #' @param page (length-1 \code{\link[base:list]{list}}, optional) For paginating through results, starting at page 1 #' @param sub_id (length-1 \code{\link[base:character]{character}}) +#' @inheritParams .shared-params #' #' @returns `fec_get_schedules_schedule_d_sub_id()`: The API response. #' @export diff --git a/tests/testthat/_fixtures/fec/paths-legal-get_legal_search.R b/tests/testthat/_fixtures/fec/paths-legal-get_legal_search.R index a21ae00..015872d 100644 --- a/tests/testthat/_fixtures/fec/paths-legal-get_legal_search.R +++ b/tests/testthat/_fixtures/fec/paths-legal-get_legal_search.R @@ -6,8 +6,6 @@ #' #' Search legal documents by document type, or across all document types using keywords, parameter values and ranges. #' -#' @inheritParams .shared-params -#' #' @param hits_returned (length-1 \code{\link[base:list]{list}}, optional) Number of results to return (max 10) #' @param af_report_year (length-1 \code{\link[base:character]{character}}, optional) Admin fine report year #' @param case_max_open_date (length-1 \code{\link[base:Date]{Date}}, optional) The latest date opened of case @@ -51,6 +49,7 @@ #' @param sort (length-1 \code{\link[base:character]{character}}, optional) Provide a field to sort by. Use `-` for descending order. ex: `-case_no` #' @param ao_max_request_date (length-1 \code{\link[base:Date]{Date}}, optional) Latest request date of advisory opinion #' @param case_no (length-1 \code{\link[base:list]{list}}, optional) Enforcement matter case number +#' @inheritParams .shared-params #' #' @returns `fec_get_legal_search()`: The API response. #' @export diff --git a/tests/testthat/_fixtures/guru/010-prepare.R b/tests/testthat/_fixtures/guru/010-prepare.R index 515a67a..b0bd09f 100644 --- a/tests/testthat/_fixtures/guru/010-prepare.R +++ b/tests/testthat/_fixtures/guru/010-prepare.R @@ -1,8 +1,8 @@ #' Generate a request for the APIs.guru API #' -#' Prepare a request for the Slack API, using the opinionated framework defined -#' in [nectar::req_init()], [nectar::req_modify()], [nectar::req_tidy_policy()], -#' and [nectar::req_pagination_policy()]. +#' Prepare a request for the APIs.guru API, using the opinionated framework +#' defined in [nectar::req_init()], [nectar::req_modify()], +#' [nectar::req_tidy_policy()], and [nectar::req_pagination_policy()]. #' #' You may wish to export this function (if the API changes often or you do not #' fully implement the API, for example). diff --git a/tests/testthat/_fixtures/guru/paths-apis-get_api.R b/tests/testthat/_fixtures/guru/paths-apis-get_api.R index ce634ff..d2d6c1d 100644 --- a/tests/testthat/_fixtures/guru/paths-apis-get_api.R +++ b/tests/testthat/_fixtures/guru/paths-apis-get_api.R @@ -6,9 +6,9 @@ #' #' Returns the API entry for one specific version of an API where there is no serviceName. #' -#' #' @param provider (length-1 \code{\link[base:character]{character}}) #' @param api (length-1 \code{\link[base:character]{character}}) +#' @inheritParams .shared-params #' #' @returns `guru_get_api()`: The API response. #' @export diff --git a/tests/testthat/_fixtures/guru/paths-apis-get_metrics.R b/tests/testthat/_fixtures/guru/paths-apis-get_metrics.R index 53b8235..c4238f7 100644 --- a/tests/testthat/_fixtures/guru/paths-apis-get_metrics.R +++ b/tests/testthat/_fixtures/guru/paths-apis-get_metrics.R @@ -6,7 +6,7 @@ #' #' Some basic metrics for the entire directory. Just stunning numbers to put on a front page and are intended purely for WoW effect :) #' -#' +#' @inheritParams .shared-params #' #' @returns `guru_get_metrics()`: The API response. #' @export diff --git a/tests/testthat/_fixtures/guru/paths-apis-get_provider.R b/tests/testthat/_fixtures/guru/paths-apis-get_provider.R index c60f023..5f9a813 100644 --- a/tests/testthat/_fixtures/guru/paths-apis-get_provider.R +++ b/tests/testthat/_fixtures/guru/paths-apis-get_provider.R @@ -6,8 +6,8 @@ #' #' List all APIs in the directory for a particular providerName Returns links to the individual API entry for each API. #' -#' #' @param provider (length-1 \code{\link[base:character]{character}}) +#' @inheritParams .shared-params #' #' @returns `guru_get_provider()`: The API response. #' @export diff --git a/tests/testthat/_fixtures/guru/paths-apis-get_providers.R b/tests/testthat/_fixtures/guru/paths-apis-get_providers.R index 215d235..7b76e13 100644 --- a/tests/testthat/_fixtures/guru/paths-apis-get_providers.R +++ b/tests/testthat/_fixtures/guru/paths-apis-get_providers.R @@ -6,7 +6,7 @@ #' #' List all the providers in the directory #' -#' +#' @inheritParams .shared-params #' #' @returns `guru_get_providers()`: The API response. #' @export diff --git a/tests/testthat/_fixtures/guru/paths-apis-get_service_api.R b/tests/testthat/_fixtures/guru/paths-apis-get_service_api.R index 1fc24d9..7129ee8 100644 --- a/tests/testthat/_fixtures/guru/paths-apis-get_service_api.R +++ b/tests/testthat/_fixtures/guru/paths-apis-get_service_api.R @@ -6,10 +6,10 @@ #' #' Returns the API entry for one specific version of an API where there is a serviceName. #' -#' #' @param provider (length-1 \code{\link[base:character]{character}}) #' @param service (length-1 \code{\link[base:character]{character}}) #' @param api (length-1 \code{\link[base:character]{character}}) +#' @inheritParams .shared-params #' #' @returns `guru_get_service_api()`: The API response. #' @export diff --git a/tests/testthat/_fixtures/guru/paths-apis-get_services.R b/tests/testthat/_fixtures/guru/paths-apis-get_services.R index 9d8c18e..7a0376c 100644 --- a/tests/testthat/_fixtures/guru/paths-apis-get_services.R +++ b/tests/testthat/_fixtures/guru/paths-apis-get_services.R @@ -6,8 +6,8 @@ #' #' List all serviceNames in the directory for a particular providerName #' -#' #' @param provider (length-1 \code{\link[base:character]{character}}) +#' @inheritParams .shared-params #' #' @returns `guru_get_services()`: The API response. #' @export diff --git a/tests/testthat/_fixtures/guru/paths-apis-list_apis.R b/tests/testthat/_fixtures/guru/paths-apis-list_apis.R index 13cd1ef..c728af7 100644 --- a/tests/testthat/_fixtures/guru/paths-apis-list_apis.R +++ b/tests/testthat/_fixtures/guru/paths-apis-list_apis.R @@ -6,7 +6,7 @@ #' #' List all APIs in the directory. Returns links to the OpenAPI definitions for each API in the directory. If API exist in multiple versions `preferred` one is explicitly marked. Some basic info from the OpenAPI definition is cached inside each object. This allows you to generate some simple views without needing to fetch the OpenAPI definition for each API. #' -#' +#' @inheritParams .shared-params #' #' @returns `guru_list_apis()`: The API response. #' @export diff --git a/tests/testthat/_fixtures/trello/010-prepare.R b/tests/testthat/_fixtures/trello/010-prepare.R index 491fad6..7233490 100644 --- a/tests/testthat/_fixtures/trello/010-prepare.R +++ b/tests/testthat/_fixtures/trello/010-prepare.R @@ -1,8 +1,8 @@ #' Generate a request for the Trello API #' -#' Prepare a request for the Slack API, using the opinionated framework defined -#' in [nectar::req_init()], [nectar::req_modify()], [nectar::req_tidy_policy()], -#' and [nectar::req_pagination_policy()]. +#' Prepare a request for the Trello API, using the opinionated framework +#' defined in [nectar::req_init()], [nectar::req_modify()], +#' [nectar::req_tidy_policy()], and [nectar::req_pagination_policy()]. #' #' You may wish to export this function (if the API changes often or you do not #' fully implement the API, for example). diff --git a/tests/testthat/_fixtures/trello/paths-board-add_boards.R b/tests/testthat/_fixtures/trello/paths-board-add_boards.R index db58b41..0b6548b 100644 --- a/tests/testthat/_fixtures/trello/paths-board-add_boards.R +++ b/tests/testthat/_fixtures/trello/paths-board-add_boards.R @@ -6,10 +6,9 @@ #' #' addBoards() #' -#' @inheritParams .shared-params -#' #' @param key (length-1 \code{\link[base:character]{character}}) Generate your application key #' @param token (length-1 \code{\link[base:character]{character}}) Getting a token from a user +#' @inheritParams .shared-params #' #' @returns `trello_add_boards()`: The API response. #' @export diff --git a/tests/testthat/_fixtures/trello/paths-board.R b/tests/testthat/_fixtures/trello/paths-board.R deleted file mode 100644 index f6e0023..0000000 --- a/tests/testthat/_fixtures/trello/paths-board.R +++ /dev/null @@ -1,23 +0,0 @@ -# These functions were generated by the {beekeeper} package, based on the paths -# element from the source API description. You should carefully review these -# functions. Missing documentation is tagged with "BKTODO" to make it easier for -# you to search for issues. - -#' addBoards() -#' -#' BKTODO: No description provided. -#' -#' @inheritParams trello_call_api -#' @return BKTODO: Return descriptions are not yet implemented in beekeeper -#' @export -trello_add_boards <- function( - key = Sys.getenv("TRELLO_KEY"), - token = Sys.getenv("TRELLO_TOKEN") -) { - trello_call_api( - path = "/boards", - method = "post", - key = key, - token = token - ) -} diff --git a/tests/testthat/helper.R b/tests/testthat/helper.R index 164d6a3..c82bd19 100644 --- a/tests/testthat/helper.R +++ b/tests/testthat/helper.R @@ -61,7 +61,7 @@ scrub_path <- function(input, keep_dirs = c("R", "tests")) { # Find all fixture files matching a regexp and read their contents. # Returns a named list of character vectors, where names are the fixture -# filenames with the "{api_abbr}-" prefix stripped. +# filenames. load_expected_files <- function(api_abbr, regexp) { test_dir <- test_path("_fixtures", api_abbr) files <- fs::dir_ls(test_dir, regexp = regexp) From 4d64090d5c3b00a87d82ecb4c5e99168705311d9 Mon Sep 17 00:00:00 2001 From: Jon Harmon Date: Tue, 12 May 2026 10:09:09 -0500 Subject: [PATCH 27/33] Remove styler Will be replaced by air soon. --- DESCRIPTION | 1 - R/generate_pkg-template.R | 1 - beekeeper.Rproj | 6 ++---- tests/testthat/_fixtures/DESCRIPTION | 9 ++++----- .../fec/paths-audit-get_names_audit_candidates.R | 2 +- tests/testthat/_fixtures/guru/010-prepare.R | 5 ++--- tests/testthat/_fixtures/guru/paths-apis-get_api.R | 6 +++--- .../_fixtures/guru/paths-apis-get_metrics.R | 2 +- .../_fixtures/guru/paths-apis-get_provider.R | 4 ++-- .../_fixtures/guru/paths-apis-get_providers.R | 2 +- .../_fixtures/guru/paths-apis-get_service_api.R | 8 ++++---- .../_fixtures/guru/paths-apis-get_services.R | 4 ++-- .../testthat/_fixtures/guru/paths-apis-list_apis.R | 2 +- tests/testthat/_fixtures/guru/test-paths-apis.R | 2 ++ tests/testthat/_fixtures/trello/010-prepare.R | 7 +++---- tests/testthat/_fixtures/trello/020-auth.R | 1 + .../_fixtures/trello/paths-board-add_boards.R | 14 +++++--------- tests/testthat/test-generate_pkg-paths.R | 3 --- 18 files changed, 34 insertions(+), 45 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index a9876d8..4b20b56 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -30,7 +30,6 @@ Imports: snakecase, stbl (>= 0.3.0), stringr, - styler, testthat, tibble, tidyr, diff --git a/R/generate_pkg-template.R b/R/generate_pkg-template.R index 92fcbdf..555355d 100644 --- a/R/generate_pkg-template.R +++ b/R/generate_pkg-template.R @@ -18,7 +18,6 @@ check_dots_empty() dir <- match.arg(dir) target <- .bk_use_template_impl(template, data, target, dir) - capture.output(styler::style_file(target)) return(invisible(target)) } diff --git a/beekeeper.Rproj b/beekeeper.Rproj index 2307ac2..270314b 100644 --- a/beekeeper.Rproj +++ b/beekeeper.Rproj @@ -1,8 +1,7 @@ Version: 1.0 -ProjectId: becf4a99-cd70-4096-862c-21c74eb2c5bc -RestoreWorkspace: No -SaveWorkspace: No +RestoreWorkspace: Default +SaveWorkspace: Default AlwaysSaveHistory: Default EnableCodeIndexing: Yes @@ -15,7 +14,6 @@ LaTeX: pdfLaTeX AutoAppendNewline: Yes StripTrailingWhitespace: Yes -LineEndingConversion: Posix BuildType: Package PackageUseDevtools: Yes diff --git a/tests/testthat/_fixtures/DESCRIPTION b/tests/testthat/_fixtures/DESCRIPTION index 4c50a0b..b669652 100644 --- a/tests/testthat/_fixtures/DESCRIPTION +++ b/tests/testthat/_fixtures/DESCRIPTION @@ -14,7 +14,7 @@ License: MIT + file LICENSE URL: https://beekeeper.api2r.org, https://github.com/jonthegeek/beekeeper BugReports: https://github.com/jonthegeek/beekeeper/issues -Imports: +Imports: cli, desc, fs, @@ -25,18 +25,17 @@ Imports: rlang (>= 1.1.0), rprojroot, S7, - styler, usethis, utils, yaml -Suggests: +Suggests: covr, knitr, rmarkdown, stringr, testthat, withr -VignetteBuilder: +VignetteBuilder: knitr Remotes: jonthegeek/nectar, @@ -44,4 +43,4 @@ Remotes: Config/testthat/edition: 3 Encoding: UTF-8 Roxygen: list(markdown = TRUE) -RoxygenNote: 7.2.3 +RoxygenNote: 7.2.3 \ No newline at end of file diff --git a/tests/testthat/_fixtures/fec/paths-audit-get_names_audit_candidates.R b/tests/testthat/_fixtures/fec/paths-audit-get_names_audit_candidates.R index 3b09659..f0433dd 100644 --- a/tests/testthat/_fixtures/fec/paths-audit-get_names_audit_candidates.R +++ b/tests/testthat/_fixtures/fec/paths-audit-get_names_audit_candidates.R @@ -5,7 +5,7 @@ #' Get names audit candidates #' #' Search for candidates or committees by name. If you're looking for information on a particular person or group, using a name to find the `candidate_id` or `committee_id` on this endpoint can be a helpful first step. -#' +#' #' @param api_key (length-1 \code{\link[base:character]{character}}) API key for https://api.data.gov. Get one at https://api.data.gov/signup. #' @param q (length-1 \code{\link[base:list]{list}}) Name (candidate or committee) to search for #' @inheritParams .shared-params diff --git a/tests/testthat/_fixtures/guru/010-prepare.R b/tests/testthat/_fixtures/guru/010-prepare.R index b0bd09f..d4dc478 100644 --- a/tests/testthat/_fixtures/guru/010-prepare.R +++ b/tests/testthat/_fixtures/guru/010-prepare.R @@ -16,8 +16,7 @@ guru_req_prepare <- function( body = NULL, method = NULL, tidy_policy = nectar::tidy_policy_unknown(), - call = rlang::caller_env() -) { + call = rlang::caller_env()) { req <- nectar::req_prepare( "https://api.apis.guru/v2", path = path, @@ -27,6 +26,6 @@ guru_req_prepare <- function( tidy_policy = tidy_policy, call = call ) - + return(req) } diff --git a/tests/testthat/_fixtures/guru/paths-apis-get_api.R b/tests/testthat/_fixtures/guru/paths-apis-get_api.R index d2d6c1d..4a44a7c 100644 --- a/tests/testthat/_fixtures/guru/paths-apis-get_api.R +++ b/tests/testthat/_fixtures/guru/paths-apis-get_api.R @@ -5,9 +5,9 @@ #' Retrieve one version of a particular API #' #' Returns the API entry for one specific version of an API where there is no serviceName. -#' -#' @param provider (length-1 \code{\link[base:character]{character}}) -#' @param api (length-1 \code{\link[base:character]{character}}) +#' +#' @param provider (length-1 \code{\link[base:character]{character}}) +#' @param api (length-1 \code{\link[base:character]{character}}) #' @inheritParams .shared-params #' #' @returns `guru_get_api()`: The API response. diff --git a/tests/testthat/_fixtures/guru/paths-apis-get_metrics.R b/tests/testthat/_fixtures/guru/paths-apis-get_metrics.R index c4238f7..04d2e6b 100644 --- a/tests/testthat/_fixtures/guru/paths-apis-get_metrics.R +++ b/tests/testthat/_fixtures/guru/paths-apis-get_metrics.R @@ -5,7 +5,7 @@ #' Get basic metrics #' #' Some basic metrics for the entire directory. Just stunning numbers to put on a front page and are intended purely for WoW effect :) -#' +#' #' @inheritParams .shared-params #' #' @returns `guru_get_metrics()`: The API response. diff --git a/tests/testthat/_fixtures/guru/paths-apis-get_provider.R b/tests/testthat/_fixtures/guru/paths-apis-get_provider.R index 5f9a813..195d18d 100644 --- a/tests/testthat/_fixtures/guru/paths-apis-get_provider.R +++ b/tests/testthat/_fixtures/guru/paths-apis-get_provider.R @@ -5,8 +5,8 @@ #' List all APIs for a particular provider #' #' List all APIs in the directory for a particular providerName Returns links to the individual API entry for each API. -#' -#' @param provider (length-1 \code{\link[base:character]{character}}) +#' +#' @param provider (length-1 \code{\link[base:character]{character}}) #' @inheritParams .shared-params #' #' @returns `guru_get_provider()`: The API response. diff --git a/tests/testthat/_fixtures/guru/paths-apis-get_providers.R b/tests/testthat/_fixtures/guru/paths-apis-get_providers.R index 7b76e13..ca20227 100644 --- a/tests/testthat/_fixtures/guru/paths-apis-get_providers.R +++ b/tests/testthat/_fixtures/guru/paths-apis-get_providers.R @@ -5,7 +5,7 @@ #' List all providers #' #' List all the providers in the directory -#' +#' #' @inheritParams .shared-params #' #' @returns `guru_get_providers()`: The API response. diff --git a/tests/testthat/_fixtures/guru/paths-apis-get_service_api.R b/tests/testthat/_fixtures/guru/paths-apis-get_service_api.R index 7129ee8..146bf54 100644 --- a/tests/testthat/_fixtures/guru/paths-apis-get_service_api.R +++ b/tests/testthat/_fixtures/guru/paths-apis-get_service_api.R @@ -5,10 +5,10 @@ #' Retrieve one version of a particular API with a serviceName. #' #' Returns the API entry for one specific version of an API where there is a serviceName. -#' -#' @param provider (length-1 \code{\link[base:character]{character}}) -#' @param service (length-1 \code{\link[base:character]{character}}) -#' @param api (length-1 \code{\link[base:character]{character}}) +#' +#' @param provider (length-1 \code{\link[base:character]{character}}) +#' @param service (length-1 \code{\link[base:character]{character}}) +#' @param api (length-1 \code{\link[base:character]{character}}) #' @inheritParams .shared-params #' #' @returns `guru_get_service_api()`: The API response. diff --git a/tests/testthat/_fixtures/guru/paths-apis-get_services.R b/tests/testthat/_fixtures/guru/paths-apis-get_services.R index 7a0376c..266e954 100644 --- a/tests/testthat/_fixtures/guru/paths-apis-get_services.R +++ b/tests/testthat/_fixtures/guru/paths-apis-get_services.R @@ -5,8 +5,8 @@ #' List all serviceNames for a particular provider #' #' List all serviceNames in the directory for a particular providerName -#' -#' @param provider (length-1 \code{\link[base:character]{character}}) +#' +#' @param provider (length-1 \code{\link[base:character]{character}}) #' @inheritParams .shared-params #' #' @returns `guru_get_services()`: The API response. diff --git a/tests/testthat/_fixtures/guru/paths-apis-list_apis.R b/tests/testthat/_fixtures/guru/paths-apis-list_apis.R index c728af7..d8a7e16 100644 --- a/tests/testthat/_fixtures/guru/paths-apis-list_apis.R +++ b/tests/testthat/_fixtures/guru/paths-apis-list_apis.R @@ -5,7 +5,7 @@ #' List all APIs #' #' List all APIs in the directory. Returns links to the OpenAPI definitions for each API in the directory. If API exist in multiple versions `preferred` one is explicitly marked. Some basic info from the OpenAPI definition is cached inside each object. This allows you to generate some simple views without needing to fetch the OpenAPI definition for each API. -#' +#' #' @inheritParams .shared-params #' #' @returns `guru_list_apis()`: The API response. diff --git a/tests/testthat/_fixtures/guru/test-paths-apis.R b/tests/testthat/_fixtures/guru/test-paths-apis.R index eecb1ef..c0b452e 100644 --- a/tests/testthat/_fixtures/guru/test-paths-apis.R +++ b/tests/testthat/_fixtures/guru/test-paths-apis.R @@ -52,4 +52,6 @@ with_mock_dir("api/path/apis", { test_result }) }) + + }) diff --git a/tests/testthat/_fixtures/trello/010-prepare.R b/tests/testthat/_fixtures/trello/010-prepare.R index 7233490..64954de 100644 --- a/tests/testthat/_fixtures/trello/010-prepare.R +++ b/tests/testthat/_fixtures/trello/010-prepare.R @@ -17,10 +17,9 @@ trello_req_prepare <- function( body = NULL, method = NULL, tidy_policy = nectar::tidy_policy_unknown(), - key = Sys.getenv("TRELLO_KEY"), - token = Sys.getenv("TRELLO_TOKEN"), - call = rlang::caller_env() -) { +key = Sys.getenv("TRELLO_KEY"), +token = Sys.getenv("TRELLO_TOKEN"), + call = rlang::caller_env()) { req <- nectar::req_prepare( "https://trello.com/1", path = path, diff --git a/tests/testthat/_fixtures/trello/020-auth.R b/tests/testthat/_fixtures/trello/020-auth.R index ce22fd8..a606dad 100644 --- a/tests/testthat/_fixtures/trello/020-auth.R +++ b/tests/testthat/_fixtures/trello/020-auth.R @@ -35,3 +35,4 @@ api_key = token ) } + diff --git a/tests/testthat/_fixtures/trello/paths-board-add_boards.R b/tests/testthat/_fixtures/trello/paths-board-add_boards.R index 0b6548b..318b516 100644 --- a/tests/testthat/_fixtures/trello/paths-board-add_boards.R +++ b/tests/testthat/_fixtures/trello/paths-board-add_boards.R @@ -5,17 +5,15 @@ #' addBoards() #' #' addBoards() -#' +#' #' @param key (length-1 \code{\link[base:character]{character}}) Generate your application key #' @param token (length-1 \code{\link[base:character]{character}}) Getting a token from a user #' @inheritParams .shared-params #' #' @returns `trello_add_boards()`: The API response. #' @export -trello_add_boards <- function( - key = Sys.getenv("TRELLO_KEY"), - token = Sys.getenv("TRELLO_TOKEN"), max_reqs = Inf, max_tries_per_req = 3 -) { +trello_add_boards <- function(key = Sys.getenv("TRELLO_KEY"), +token = Sys.getenv("TRELLO_TOKEN"), max_reqs = Inf, max_tries_per_req = 3) { req <- req_trello_add_boards(key = key, token = token) resps <- nectar::req_perform_opinionated( req, @@ -27,10 +25,8 @@ trello_add_boards <- function( #' @rdname trello_add_boards #' @returns `req_trello_add_boards()`: A `httr2_request` request object. -req_trello_add_boards <- function( - key = Sys.getenv("TRELLO_KEY"), - token = Sys.getenv("TRELLO_TOKEN") -) { +req_trello_add_boards <- function(key = Sys.getenv("TRELLO_KEY"), +token = Sys.getenv("TRELLO_TOKEN")) { trello_req_prepare( path = "/boards", method = "post", diff --git a/tests/testthat/test-generate_pkg-paths.R b/tests/testthat/test-generate_pkg-paths.R index 2368401..916bfde 100644 --- a/tests/testthat/test-generate_pkg-paths.R +++ b/tests/testthat/test-generate_pkg-paths.R @@ -1,7 +1,6 @@ test_that(".generate_paths() generates path files (#65)", { # 1 tag, no security skip_on_cran() - skip_on_covr() config <- .read_config(test_path("_fixtures", "guru", "_beekeeper.yml")) api_definition <- readRDS(test_path( "_fixtures", @@ -56,7 +55,6 @@ test_that(".generate_paths() generates path files (#65)", { test_that("generate_pkg() generates path tests for guru (#65)", { # 1 tag, no security skip_on_cran() - skip_on_covr() config <- readLines(test_path("_fixtures", "guru", "_beekeeper.yml")) guru_rapid <- readRDS(test_path("_fixtures", "guru", "_beekeeper_rapid.rds")) expected_file_content <- readLines( @@ -118,7 +116,6 @@ test_that("generate_pkg() generates path functions for fec (#65)", { test_that("generate_pkg() generates path functions for trello (#65)", { # some tags failed before this, more complicated security skip_on_cran() - skip_on_covr() config <- readLines(test_path("_fixtures", "trello", "_beekeeper.yml")) trello_rapid <- readRDS(test_path( "_fixtures", From fc4673c8537a5986041f7095ca2b9b98dd92b1ec Mon Sep 17 00:00:00 2001 From: Jon Harmon Date: Tue, 12 May 2026 10:51:31 -0500 Subject: [PATCH 28/33] Better coverage --- tests/testthat/_snaps/generate_pkg-paths.md | 21 -- tests/testthat/helper.R | 35 +++ tests/testthat/test-generate_pkg-paths.R | 254 ++++++++++++++------ tests/testthat/test-generate_pkg-prepare.R | 1 - tests/testthat/test-generate_pkg-security.R | 44 ++-- tests/testthat/test-generate_pkg.R | 1 - tests/testthat/test-utils.R | 61 ++++- 7 files changed, 297 insertions(+), 120 deletions(-) delete mode 100644 tests/testthat/_snaps/generate_pkg-paths.md diff --git a/tests/testthat/_snaps/generate_pkg-paths.md b/tests/testthat/_snaps/generate_pkg-paths.md deleted file mode 100644 index 615cc86..0000000 --- a/tests/testthat/_snaps/generate_pkg-paths.md +++ /dev/null @@ -1,21 +0,0 @@ -# generate_pkg() generates path functions for fec (#65) - - Code - scrub_path(changed_files) - Output - [1] "/R/010-prepare.R" - [2] "/tests/testthat/test-010-prepare.R" - [3] "/R/020-auth.R" - [4] "/R/paths-audit-get_audit_case.R" - [5] "/R/paths-audit-get_audit_category.R" - [6] "/R/paths-audit-get_audit_primary_category.R" - [7] "/R/paths-legal-get_legal_search.R" - [8] "/R/paths-audit-get_names_audit_candidates.R" - [9] "/R/paths-audit-get_names_audit_committees.R" - [10] "/R/paths-debts-get_schedules_schedule_d.R" - [11] "/R/paths-debts-get_schedules_schedule_d_sub_id.R" - [12] "/tests/testthat/test-paths-audit.R" - [13] "/tests/testthat/test-paths-legal.R" - [14] "/tests/testthat/test-paths-debts.R" - [15] "/tests/testthat/setup.R" - diff --git a/tests/testthat/helper.R b/tests/testthat/helper.R index c82bd19..d9d338a 100644 --- a/tests/testthat/helper.R +++ b/tests/testthat/helper.R @@ -68,3 +68,38 @@ load_expected_files <- function(api_abbr, regexp) { names(files) <- fs::path_file(files) purrr::map(files, readLines) } + +# A mock for .bk_use_template_impl() that records calls and returns a +# predictable path, without writing any files or needing a usethis project. +make_spy_impl <- function() { + calls <- list() + list( + mock = function(template, data, target, dir) { + calls[[length(calls) + 1]] <<- list( + template = template, + data = data, + target = target, + dir = dir + ) + file.path(dir, target) + }, + calls = function() calls + ) +} + +# A mock that renders templates to a temp dir using whisker directly, so the +# output can be visually confirmed against fixture files. +make_writing_impl <- function(tmp) { + function(template, data, target, dir) { + template_path <- system.file("templates", template, package = "beekeeper") + rendered <- whisker::whisker.render( + readLines(template_path, warn = FALSE), + data + ) + out_dir <- file.path(tmp, dir) + fs::dir_create(out_dir) + out_path <- file.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 916bfde..2d4782d 100644 --- a/tests/testthat/test-generate_pkg-paths.R +++ b/tests/testthat/test-generate_pkg-paths.R @@ -1,4 +1,15 @@ -test_that(".generate_paths() generates path files (#65)", { +test_that(".generate_paths() returns empty character for empty paths (#65)", { + skip_on_cran() + result <- .generate_paths( + paths = rapid::class_paths(), + api_abbr = "test", + security_data = list(), + base_url = "https://example.com" + ) + expect_identical(result, character()) +}) + +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")) @@ -7,29 +18,20 @@ test_that(".generate_paths() generates path files (#65)", { "guru", "_beekeeper_rapid.rds" )) + spy <- make_spy_impl() + local_mocked_bindings(.bk_use_template_impl = spy$mock) - api_abbr <- "guru" - expected_path_contents <- load_expected_files( - api_abbr, - paste0("/paths-.+\\.R$") - ) - expected_test_contents <- load_expected_files( - api_abbr, - paste0("/test-paths-.+\\.R$") - ) - - create_local_package() - usethis::use_testthat() - - # 7 operations all in "apis" tag -> 7 R files + 1 test file + setup - test_result <- .generate_paths( + result <- .generate_paths( paths = api_definition@paths, api_abbr = config$api_abbr, security_data = list(), base_url = api_definition@servers@url ) + + calls <- spy$calls() + expect_identical( - basename(test_result), + basename(result), c( "paths-apis-list_apis.R", "paths-apis-get_metrics.R", @@ -43,81 +45,174 @@ test_that(".generate_paths() generates path files (#65)", { ) ) + # 7 paths.R calls + 1 test-paths.R call + 1 setup.R call + expect_length(calls, 9L) + expect_identical( + purrr::map_chr(calls, "template"), + c(rep("paths.R", 7L), "test-paths.R", "setup.R") + ) + + # Spot-check data for the first (simplest) path call + first <- calls[[1]]$data + expect_identical(first$operation_id, "list_apis") + expect_identical(first$tag, "apis") + expect_identical(first$method, "get") + expect_false(first$has_security) + + # Spot-check a path call that has path parameters + get_api <- calls[[4]]$data + expect_identical(get_api$operation_id, "get_api") + expect_length(get_api$params, 2L) + + # Check test-paths.R data + test_call <- calls[[8]] + expect_identical(test_call$dir, "tests/testthat") + expect_identical(test_call$target, "test-paths-apis.R") + expect_length(test_call$data$paths, 7L) + + # Check setup.R data + setup_call <- calls[[9]] + expect_identical(setup_call$dir, "tests/testthat") + expect_identical(setup_call$data$base_url, api_definition@servers@url) +}) + +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")) + api_definition <- readRDS(test_path( + "_fixtures", + "guru", + "_beekeeper_rapid.rds" + )) + 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")) + + tmp <- withr::local_tempdir() + local_mocked_bindings(.bk_use_template_impl = make_writing_impl(tmp)) + + .generate_paths( + paths = api_definition@paths, + api_abbr = config$api_abbr, + security_data = list(), + base_url = api_definition@servers@url + ) + purrr::iwalk(expected_path_contents, \(expected, name) { - expect_identical(readLines(file.path("R", name)), expected) + expect_identical(readLines(file.path(tmp, "R", name)), expected) }) - purrr::iwalk(expected_test_contents, \(expected, name) { - expect_identical(readLines(file.path("tests", "testthat", name)), expected) + expect_identical( + readLines(file.path(tmp, "tests", "testthat", name)), + expected + ) }) + expect_identical( + readLines(file.path(tmp, "tests", "testthat", "setup.R")), + expected_setup_content + ) }) -test_that("generate_pkg() generates path tests for guru (#65)", { - # 1 tag, no security +test_that(".generate_paths() calls correct templates for fec (#65)", { + # 3 tags (audit, debts, legal), more complicated security skip_on_cran() - config <- readLines(test_path("_fixtures", "guru", "_beekeeper.yml")) - guru_rapid <- readRDS(test_path("_fixtures", "guru", "_beekeeper_rapid.rds")) - expected_file_content <- readLines( - test_path("_fixtures", "guru", "test-paths-apis.R") - ) + config <- .read_config(test_path( + "_fixtures", + "fec", + "fec_subset_beekeeper.yml" + )) + api_definition <- readRDS(test_path( + "_fixtures", + "fec", + "fec_subset_rapid.rds" + )) + spy <- make_spy_impl() + local_mocked_bindings(.bk_use_template_impl = spy$mock) - create_local_package() - writeLines(config, "_beekeeper.yml") - saveRDS(guru_rapid, "_beekeeper_rapid.rds") - generate_pkg() + security_data <- .generate_security( + config$api_abbr, + api_definition@components@security_schemes + ) + result <- .generate_paths( + paths = api_definition@paths, + api_abbr = config$api_abbr, + security_data = security_data, + base_url = api_definition@servers@url + ) - generated_file_content <- readLines("tests/testthat/test-paths-apis.R") - expect_identical(generated_file_content, expected_file_content) -}) + calls <- spy$calls() -test_that("generate_pkg() generates test setup file for guru", { - # 1 tag, no security - skip_on_cran() - config <- readLines(test_path("_fixtures", "guru", "_beekeeper.yml")) - guru_rapid <- readRDS(test_path("_fixtures", "guru", "_beekeeper_rapid.rds")) - expected_file_content <- readLines( - test_path("_fixtures", "guru", "setup.R") + expect_identical( + basename(result), + c( + "paths-audit-get_audit_case.R", + "paths-audit-get_audit_category.R", + "paths-audit-get_audit_primary_category.R", + "paths-legal-get_legal_search.R", + "paths-audit-get_names_audit_candidates.R", + "paths-audit-get_names_audit_committees.R", + "paths-debts-get_schedules_schedule_d.R", + "paths-debts-get_schedules_schedule_d_sub_id.R", + "test-paths-audit.R", + "test-paths-legal.R", + "test-paths-debts.R", + "setup.R" + ) ) - create_local_package() - writeLines(config, "_beekeeper.yml") - saveRDS(guru_rapid, "_beekeeper_rapid.rds") - generate_pkg() - generated_file_content <- readLines("tests/testthat/setup.R") - expect_identical(generated_file_content, expected_file_content) + # 1 auth + 8 path R files + 3 test files + 1 setup = 13 calls + expect_length(calls, 13L) + + # Security data should be threaded through to paths (.generate_security() + # wrote the auth file as calls[[1]], so paths start at calls[[2]]) + first_path <- calls[[2]]$data + expect_true(first_path$has_security) + expect_identical(first_path$api_abbr, "fec") }) -test_that("generate_pkg() generates path functions for fec (#65)", { - # 3 tags (audit, debts, legal), more complicated security +test_that(".generate_paths() writes correct paths.R for fec (#65)", { + # Visual confirmation: 3 tags, complicated security skip_on_cran() - config <- readLines(test_path("_fixtures", "fec", "fec_subset_beekeeper.yml")) - fec_rapid <- readRDS(test_path("_fixtures", "fec", "fec_subset_rapid.rds")) + config <- .read_config(test_path( + "_fixtures", + "fec", + "fec_subset_beekeeper.yml" + )) + api_definition <- readRDS(test_path( + "_fixtures", + "fec", + "fec_subset_rapid.rds" + )) expected_file_content <- readLines( - test_path( - "_fixtures", - "fec", - "paths-audit-get_names_audit_candidates.R" - ) + test_path("_fixtures", "fec", "paths-audit-get_names_audit_candidates.R") ) - create_local_package() - writeLines(config, "_beekeeper.yml") - saveRDS(fec_rapid, "fec_subset_rapid.rds") + tmp <- withr::local_tempdir() + local_mocked_bindings(.bk_use_template_impl = make_writing_impl(tmp)) - changed_files <- generate_pkg() - expect_snapshot(scrub_path(changed_files)) + security_data <- .generate_security( + config$api_abbr, + api_definition@components@security_schemes + ) + .generate_paths( + paths = api_definition@paths, + api_abbr = config$api_abbr, + security_data = security_data, + base_url = api_definition@servers@url + ) - generated_file_content <- readLines( - "R/paths-audit-get_names_audit_candidates.R" + expect_identical( + readLines(file.path(tmp, "R", "paths-audit-get_names_audit_candidates.R")), + expected_file_content ) - expect_identical(generated_file_content, expected_file_content) }) -test_that("generate_pkg() generates path functions for trello (#65)", { - # some tags failed before this, more complicated security +test_that(".generate_paths() writes correct paths.R for trello (#65)", { + # Visual confirmation: more complicated security skip_on_cran() - config <- readLines(test_path("_fixtures", "trello", "_beekeeper.yml")) - trello_rapid <- readRDS(test_path( + config <- .read_config(test_path("_fixtures", "trello", "_beekeeper.yml")) + api_definition <- readRDS(test_path( "_fixtures", "trello", "_beekeeper_rapid.rds" @@ -126,11 +221,22 @@ test_that("generate_pkg() generates path functions for trello (#65)", { test_path("_fixtures", "trello", "paths-board-add_boards.R") ) - create_local_package() - writeLines(config, "_beekeeper.yml") - saveRDS(trello_rapid, "_beekeeper_rapid.rds") + tmp <- withr::local_tempdir() + local_mocked_bindings(.bk_use_template_impl = make_writing_impl(tmp)) + + security_data <- .generate_security( + config$api_abbr, + api_definition@components@security_schemes + ) + .generate_paths( + paths = api_definition@paths, + api_abbr = config$api_abbr, + security_data = security_data, + base_url = api_definition@servers@url + ) - generate_pkg() - generated_file_content <- readLines("R/paths-board-add_boards.R") - expect_identical(generated_file_content, expected_file_content) + expect_identical( + readLines(file.path(tmp, "R", "paths-board-add_boards.R")), + expected_file_content + ) }) diff --git a/tests/testthat/test-generate_pkg-prepare.R b/tests/testthat/test-generate_pkg-prepare.R index 19136d1..123037a 100644 --- a/tests/testthat/test-generate_pkg-prepare.R +++ b/tests/testthat/test-generate_pkg-prepare.R @@ -1,6 +1,5 @@ test_that(".generate_prepare() generates prepare file.", { skip_on_cran() - skip_on_covr() config <- .read_config(test_path("_fixtures", "guru", "_beekeeper.yml")) api_definition <- readRDS(test_path( "_fixtures", diff --git a/tests/testthat/test-generate_pkg-security.R b/tests/testthat/test-generate_pkg-security.R index 3ce2430..e8f8566 100644 --- a/tests/testthat/test-generate_pkg-security.R +++ b/tests/testthat/test-generate_pkg-security.R @@ -1,22 +1,37 @@ -test_that(".generate_security() generates security file", { - skip_on_cran() - config <- .read_config(test_path( +test_that(".generate_security() returns empty list for no security", { + result <- .generate_security("test", rapid::class_security_schemes()) + expect_identical(result, list()) +}) + +test_that("as_bk_data() dispatches correctly for security_scheme_details", { + trello_rapid <- readRDS(test_path( "_fixtures", "trello", - "_beekeeper.yml" + "_beekeeper_rapid.rds" )) + details <- trello_rapid@components@security_schemes@details + result <- as_bk_data(details) + expect_length(result, 2L) + expect_identical(result[[1]]$type, "api_key") + expect_identical(result[[1]]$arg_name, "key") + expect_identical(result[[2]]$arg_name, "token") +}) + +test_that("as_bk_data() returns empty list for empty api_key_security_scheme", { + expect_identical(as_bk_data(rapid::class_api_key_security_scheme()), list()) +}) + +test_that(".generate_security() generates security file for trello", { + skip_on_cran() + config <- .read_config(test_path("_fixtures", "trello", "_beekeeper.yml")) api_definition <- readRDS(test_path( "_fixtures", "trello", "_beekeeper_rapid.rds" )) - security_expected <- readLines(test_path( - "_fixtures", - "trello", - "020-auth.R" - )) - create_local_package() - + 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)) test_result <- .generate_security( config$api_abbr, api_definition@components@security_schemes @@ -34,7 +49,8 @@ test_that(".generate_security() generates security file", { "security_signature" ) ) - - security_result <- scrub_testpkg(readLines("R/020-auth.R")) - expect_identical(security_result, security_expected) + expect_identical( + readLines(file.path(tmp, "R", "020-auth.R")), + security_expected + ) }) diff --git a/tests/testthat/test-generate_pkg.R b/tests/testthat/test-generate_pkg.R index eede0b3..5835443 100644 --- a/tests/testthat/test-generate_pkg.R +++ b/tests/testthat/test-generate_pkg.R @@ -33,7 +33,6 @@ test_that("generate_pkg() returns a vector of created files", { test_that("generate_pkg() generates call function with API keys", { skip_on_cran() - skip_on_covr() local_mocked_bindings( .generate_paths = function(...) { character() diff --git a/tests/testthat/test-utils.R b/tests/testthat/test-utils.R index 385fbe9..df3f86d 100644 --- a/tests/testthat/test-utils.R +++ b/tests/testthat/test-utils.R @@ -1,10 +1,53 @@ -test_that("%|0|% works", { - expect_identical( - character() %|0|% "foo", - "foo" - ) - expect_identical( - "foo" %|0|% "bar", - "foo" - ) +test_that("%|0|% works (#noissue)", { + expect_identical(character() %|0|% "foo", "foo") + expect_identical("foo" %|0|% "bar", "foo") +}) + +test_that("%|\"|% works (#noissue)", { + expect_identical("" %|"|% "foo", "foo") + expect_identical("foo" %|"|% "bar", "foo") +}) + +test_that(".coalesce() works (#noissue)", { + expect_identical(.coalesce(c("a", NA), c("x", "y")), c("a", "y")) +}) + +test_that(".collapse_comma() works (#noissue)", { + expect_identical(.collapse_comma(c("a", "b", "c")), "a, b, c") +}) + +test_that(".collapse_comma_newline() works (#noissue)", { + expect_identical(.collapse_comma_newline(c("a", "b")), "a,\nb") +}) + +test_that(".collapse_quote_comma() works (#noissue)", { + expect_identical(.collapse_quote_comma(c("a", "b")), '"a", "b"') +}) + +test_that(".paste0_if() works (#noissue)", { + expect_identical(.paste0_if("x", TRUE, "!"), "x!") + expect_identical(.paste0_if("x", FALSE, "!"), "x") +}) + +test_that(".glue_pipe_brace() works (#noissue)", { + x <- "world" + expect_identical(.glue_pipe_brace("hello |{x}|"), "hello world") +}) + +test_that(".to_snake() works (#noissue)", { + expect_identical(.to_snake("camelCase"), "camel_case") +}) + +test_that(".flatten_df() returns data frame unchanged (#noissue)", { + df <- data.frame(x = 1:2) + expect_identical(.flatten_df(df), df) +}) + +test_that(".flatten_df() row-binds a list of data frames (#noissue)", { + result <- .flatten_df(list(data.frame(x = 1), data.frame(x = 2))) + expect_equal(result, data.frame(x = c(1, 2))) +}) + +test_that(".flatten_df() returns empty data frame for NULL (#noissue)", { + expect_equal(.flatten_df(NULL), data.frame()) }) From 273a835bf181fea596eba40aed13cd9b3b18b33a Mon Sep 17 00:00:00 2001 From: Jon Harmon Date: Tue, 12 May 2026 11:11:26 -0500 Subject: [PATCH 29/33] Remove unused functions. --- R/generate_pkg-call.R | 30 ------------------------------ 1 file changed, 30 deletions(-) delete mode 100644 R/generate_pkg-call.R diff --git a/R/generate_pkg-call.R b/R/generate_pkg-call.R deleted file mode 100644 index 385e455..0000000 --- a/R/generate_pkg-call.R +++ /dev/null @@ -1,30 +0,0 @@ -.generate_call <- function(config, api_definition, security_data) { - touched_files <- c( - .generate_call_r(config, api_definition, security_data), - .generate_call_test(config$api_abbr) - ) - return(touched_files) -} - -.generate_call_r <- function(config, api_definition, security_data) { - .bk_use_template( - template = "010-call.R", - data = list( - api_title = config$api_title, - api_abbr = config$api_abbr, - base_url = api_definition@servers@url, - has_security = security_data$has_security, - security_arg_helps = security_data$security_arg_helps, - security_signature = security_data$security_signature, - security_arg_list = security_data$security_arg_list - ) - ) -} - -.generate_call_test <- function(api_abbr) { - .bk_use_template( - template = "test-010-call.R", - dir = "tests/testthat", - data = list(api_abbr = api_abbr) - ) -} From b0a0fb9523a0c494525e8bc39ba1fab15651c960 Mon Sep 17 00:00:00 2001 From: Jon Harmon Date: Tue, 12 May 2026 12:47:56 -0500 Subject: [PATCH 30/33] Generate shared params --- R/generate_pkg-shared.R | 10 ++++ R/generate_pkg.R | 2 + air.toml | 2 +- tests/testthat/_fixtures/guru/000-shared.R | 14 ++++++ tests/testthat/_fixtures/trello/000-shared.R | 16 +++++++ tests/testthat/_snaps/aaa-conditions.md | 10 ++++ tests/testthat/test-aaa-conditions.R | 8 ++++ tests/testthat/test-as_bk_data.R | 10 ++++ tests/testthat/test-generate_pkg-shared.R | 49 ++++++++++++++++++++ tests/testthat/test-generate_pkg.R | 1 + 10 files changed, 121 insertions(+), 1 deletion(-) create mode 100644 R/generate_pkg-shared.R create mode 100644 tests/testthat/_fixtures/guru/000-shared.R create mode 100644 tests/testthat/_fixtures/trello/000-shared.R create mode 100644 tests/testthat/_snaps/aaa-conditions.md create mode 100644 tests/testthat/test-aaa-conditions.R create mode 100644 tests/testthat/test-as_bk_data.R create mode 100644 tests/testthat/test-generate_pkg-shared.R diff --git a/R/generate_pkg-shared.R b/R/generate_pkg-shared.R new file mode 100644 index 0000000..7564df1 --- /dev/null +++ b/R/generate_pkg-shared.R @@ -0,0 +1,10 @@ +.generate_shared_params <- function(security_data) { + shared_file_path <- .bk_use_template( + template = "000-shared.R", + data = list( + shared_arg_helps = list(), + security_arg_helps = security_data$security_arg_helps + ) + ) + return(shared_file_path) +} diff --git a/R/generate_pkg.R b/R/generate_pkg.R index 6d6e9c2..2cbba5b 100644 --- a/R/generate_pkg.R +++ b/R/generate_pkg.R @@ -42,7 +42,9 @@ generate_pkg <- function( pagination_data = pagination_data, base_url = api_definition@servers@url ) + shared_file_path <- .generate_shared_params(security_data) touched_files <- c( + shared_file_path, prep_files, security_data$security_file_path, pagination_data$pagination_file_path, diff --git a/air.toml b/air.toml index c3c259d..f19c117 100644 --- a/air.toml +++ b/air.toml @@ -1,2 +1,2 @@ [format] -exclude = ["inst/templates", "tests/testthat/_fixtures/trello"] +exclude = ["inst/templates", "tests/testthat/_fixtures"] diff --git a/tests/testthat/_fixtures/guru/000-shared.R b/tests/testthat/_fixtures/guru/000-shared.R new file mode 100644 index 0000000..7d8be44 --- /dev/null +++ b/tests/testthat/_fixtures/guru/000-shared.R @@ -0,0 +1,14 @@ +#' Parameters used in multiple functions +#' +#' Reused parameter definitions are gathered here for easier editing. +#' +#' @param max_reqs (`integer`) The maximum number of separate requests to +#' perform. Passed on to [nectar::req_perform_opinionated()]. +#' @param max_tries_per_req (`integer`) The maximum number of times to attempt +#' each individual request. Passed on to [nectar::req_perform_opinionated()]. +#' @param req (`httr2_request`) The request object to modify. +#' @param ... These dots are for future extensions and must be empty. +#' +#' @name .shared-params +#' @keywords internal +NULL diff --git a/tests/testthat/_fixtures/trello/000-shared.R b/tests/testthat/_fixtures/trello/000-shared.R new file mode 100644 index 0000000..2364930 --- /dev/null +++ b/tests/testthat/_fixtures/trello/000-shared.R @@ -0,0 +1,16 @@ +#' Parameters used in multiple functions +#' +#' Reused parameter definitions are gathered here for easier editing. +#' +#' @param max_reqs (`integer`) The maximum number of separate requests to +#' perform. Passed on to [nectar::req_perform_opinionated()]. +#' @param max_tries_per_req (`integer`) The maximum number of times to attempt +#' each individual request. Passed on to [nectar::req_perform_opinionated()]. +#' @param req (`httr2_request`) The request object to modify. +#' @param ... These dots are for future extensions and must be empty. +#' @param key An API key provided by the API provider. This key is not clearly documented in the API description. Check the API documentation for details. +#' @param token An API key provided by the API provider. This key is not clearly documented in the API description. Check the API documentation for details. +#' +#' @name .shared-params +#' @keywords internal +NULL diff --git a/tests/testthat/_snaps/aaa-conditions.md b/tests/testthat/_snaps/aaa-conditions.md new file mode 100644 index 0000000..df12762 --- /dev/null +++ b/tests/testthat/_snaps/aaa-conditions.md @@ -0,0 +1,10 @@ +# .pkg_abort works + + Code + (expect_pkg_error_classes(.pkg_abort("This is a test error", c("subclass", + "test_error")), "beekeeper", "subclass", "test_error")) + Output + + Error: + ! This is a test error + diff --git a/tests/testthat/test-aaa-conditions.R b/tests/testthat/test-aaa-conditions.R new file mode 100644 index 0000000..3772d0a --- /dev/null +++ b/tests/testthat/test-aaa-conditions.R @@ -0,0 +1,8 @@ +test_that(".pkg_abort works", { + stbl::expect_pkg_error_snapshot( + .pkg_abort("This is a test error", c("subclass", "test_error")), + "beekeeper", + "subclass", + "test_error" + ) +}) diff --git a/tests/testthat/test-as_bk_data.R b/tests/testthat/test-as_bk_data.R new file mode 100644 index 0000000..ad41182 --- /dev/null +++ b/tests/testthat/test-as_bk_data.R @@ -0,0 +1,10 @@ +test_that("as_bk_data warns for unknown classes", { + expect_warning( + { + test_result <- as_bk_data("a") + }, + "No method for as_bk_data" + ) + expect_type(test_result, "list") + expect_length(test_result, 0) +}) diff --git a/tests/testthat/test-generate_pkg-shared.R b/tests/testthat/test-generate_pkg-shared.R new file mode 100644 index 0000000..d580bb8 --- /dev/null +++ b/tests/testthat/test-generate_pkg-shared.R @@ -0,0 +1,49 @@ +test_that(".generate_shared_params() returns file path for no-security API (#65)", { + skip_on_cran() + tmp <- withr::local_tempdir() + local_mocked_bindings(.bk_use_template_impl = make_writing_impl(tmp)) + + result <- .generate_shared_params(list()) + + expect_identical(result, file.path(tmp, "R", "000-shared.R")) +}) + +test_that(".generate_shared_params() writes correct content for no-security API (#65)", { + skip_on_cran() + shared_expected <- readLines(test_path("_fixtures", "guru", "000-shared.R")) + tmp <- withr::local_tempdir() + local_mocked_bindings(.bk_use_template_impl = make_writing_impl(tmp)) + + .generate_shared_params(list()) + + expect_identical( + readLines(file.path(tmp, "R", "000-shared.R")), + shared_expected + ) +}) + +test_that(".generate_shared_params() writes security params for API with security (#65)", { + 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(test_path( + "_fixtures", + "trello", + "_beekeeper.yml" + )) + security_data <- .generate_security( + trello_config$api_abbr, + trello_rapid@components@security_schemes + ) + 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")), + shared_expected + ) +}) diff --git a/tests/testthat/test-generate_pkg.R b/tests/testthat/test-generate_pkg.R index 5835443..1ab806c 100644 --- a/tests/testthat/test-generate_pkg.R +++ b/tests/testthat/test-generate_pkg.R @@ -15,6 +15,7 @@ test_that("generate_pkg() returns a vector of created files", { test_result <- scrub_path(test_result) # 7 guru operations all in "apis" tag: 7 R files + 1 test file + setup expected_result <- c( + "/R/000-shared.R", "/R/010-prepare.R", "/tests/testthat/test-010-prepare.R", "/R/paths-apis-list_apis.R", From 42d5f33b70adf8411f1be1e695bc2a23231aa88f Mon Sep 17 00:00:00 2001 From: Jon Harmon Date: Tue, 12 May 2026 13:09:37 -0500 Subject: [PATCH 31/33] Use whisker directly in tests --- DESCRIPTION | 1 + 1 file changed, 1 insertion(+) diff --git a/DESCRIPTION b/DESCRIPTION index 4b20b56..943b0dd 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -43,6 +43,7 @@ Suggests: knitr, rmarkdown, rvest, + whisker, withr VignetteBuilder: knitr From f0d84772d16122de954cbdfc699e386cf436d4db Mon Sep 17 00:00:00 2001 From: Jon Harmon Date: Tue, 12 May 2026 13:23:15 -0500 Subject: [PATCH 32/33] Slightly better param docs --- R/generate_pkg-paths.R | 7 +- .../fec/paths-audit-get_audit_case.R | 40 ++++----- .../fec/paths-audit-get_audit_category.R | 18 ++-- .../paths-audit-get_audit_primary_category.R | 18 ++-- .../paths-audit-get_names_audit_candidates.R | 4 +- .../paths-audit-get_names_audit_committees.R | 4 +- .../paths-debts-get_schedules_schedule_d.R | 48 +++++------ ...hs-debts-get_schedules_schedule_d_sub_id.R | 16 ++-- .../fec/paths-legal-get_legal_search.R | 86 +++++++++---------- .../_fixtures/guru/paths-apis-get_api.R | 4 +- .../_fixtures/guru/paths-apis-get_provider.R | 2 +- .../guru/paths-apis-get_service_api.R | 6 +- .../_fixtures/guru/paths-apis-get_services.R | 2 +- .../_fixtures/trello/paths-board-add_boards.R | 4 +- 14 files changed, 130 insertions(+), 129 deletions(-) diff --git a/R/generate_pkg-paths.R b/R/generate_pkg-paths.R index 1fdfaf3..385b9e1 100644 --- a/R/generate_pkg-paths.R +++ b/R/generate_pkg-paths.R @@ -184,11 +184,11 @@ S7::method(as_bk_data, class_paths) <- function(x) { .compile_param_class_descriptions <- function(type, allow_empty, required) { r_class_descriptions <- .glue_pipe_brace( - "length-1 \\code{\\link[|{type$r_class_package}|:|{type$r_class_link}|]{|{type$r_class_name_display}|}}" + "length-1 [|{type$r_class_package}|::|{type$r_class_link}|()]" ) |> .paste0_if( allow_empty, - " or \\code{\\link[base:NULL]{NULL}}" + " or `NULL`" ) |> .paste0_if( !required, @@ -246,7 +246,8 @@ S7::method(as_bk_data, class_paths) <- function(x) { .generate_paths_file(op, op_id, api_abbr, security_data) }))) - # One test file per tag (operations grouped by tag, preserving encounter order) + # One test file per tag (operations grouped by tag, preserving encounter + # order) tags <- map_chr(prepped, "tag") unique_tags <- unique(tags) test_files <- unname(unlist(lapply(unique_tags, function(tag_name) { diff --git a/tests/testthat/_fixtures/fec/paths-audit-get_audit_case.R b/tests/testthat/_fixtures/fec/paths-audit-get_audit_case.R index bb2f401..4444ada 100644 --- a/tests/testthat/_fixtures/fec/paths-audit-get_audit_case.R +++ b/tests/testthat/_fixtures/fec/paths-audit-get_audit_case.R @@ -6,26 +6,26 @@ #' #' This endpoint contains Final Audit Reports approved by the Commission since inception. The search can be based on information about the audited committee (Name, FEC ID Number, Type, Election Cycle) or the issues covered in the report. #' -#' @param audit_case_id (length-1 \code{\link[base:list]{list}}, optional) Primary/foreign key for audit tables -#' @param cycle (length-1 \code{\link[base:list]{list}}, optional) Filter records to only those that are applicable to a given two-year period. This cycle follows the traditional House election cycle and subdivides the presidential and Senate elections into comparable two-year blocks. The cycle begins with an odd year and is named for its ending, even year. -#' @param sub_category_id (length-1 \code{\link[base:character]{character}}, optional) The finding id of an audit. Finding are a category of broader issues. Each category has an unique ID. -#' @param sort_nulls_last (length-1 \code{\link[base:logical]{logical}}, optional) Toggle that sorts null values last -#' @param sort_hide_null (length-1 \code{\link[base:logical]{logical}}, optional) Hide null values on sorted column(s). -#' @param min_election_cycle (length-1 \code{\link[base:list]{list}}, optional) Filter records to only those that are applicable to a given two-year period. This cycle follows the traditional House election cycle and subdivides the presidential and Senate elections into comparable two-year blocks. The cycle begins with an odd year and is named for its ending, even year. -#' @param audit_id (length-1 \code{\link[base:list]{list}}, optional) The audit issue. Each subcategory has an unique ID -#' @param q (length-1 \code{\link[base:list]{list}}, optional) The name of the committee. If a committee changes its name, the most recent name will be shown. Committee names are not unique. Use committee_id for looking up records. -#' @param per_page (length-1 \code{\link[base:list]{list}}, optional) The number of results returned per page. Defaults to 20. -#' @param max_election_cycle (length-1 \code{\link[base:list]{list}}, optional) Filter records to only those that are applicable to a given two-year period. This cycle follows the traditional House election cycle and subdivides the presidential and Senate elections into comparable two-year blocks. The cycle begins with an odd year and is named for its ending, even year. -#' @param candidate_id (length-1 \code{\link[base:list]{list}}, optional) A unique identifier assigned to each candidate registered with the FEC. If a person runs for several offices, that person will have separate candidate IDs for each office. -#' @param committee_type (length-1 \code{\link[base:list]{list}}, optional) The one-letter type code of the organization: - C communication cost - D delegate - E electioneering communication - H House - I independent expenditure filer (not a committee) - N PAC - nonqualified - O independent expenditure-only (super PACs) - P presidential - Q PAC - qualified - S Senate - U single candidate independent expenditure - V PAC with non-contribution account, nonqualified - W PAC with non-contribution account, qualified - X party, nonqualified - Y party, qualified - Z national party non-federal account -#' @param qq (length-1 \code{\link[base:list]{list}}, optional) Name of candidate running for office -#' @param page (length-1 \code{\link[base:list]{list}}, optional) For paginating through results, starting at page 1 -#' @param committee_id (length-1 \code{\link[base:list]{list}}, optional) A unique identifier assigned to each committee or filer registered with the FEC. In general committee id's begin with the letter C which is followed by eight digits. -#' @param api_key (length-1 \code{\link[base:character]{character}}) API key for https://api.data.gov. Get one at https://api.data.gov/signup. -#' @param committee_designation (length-1 \code{\link[base:character]{character}}, optional) Type of committee: - H or S - Congressional - P - Presidential - X or Y or Z - Party - N or Q - PAC - I - Independent expenditure - O - Super PAC -#' @param primary_category_id (length-1 \code{\link[base:character]{character}}, optional) Audit category ID (table PK) -#' @param sort_null_only (length-1 \code{\link[base:logical]{logical}}, optional) Toggle that filters out all rows having sort column that is non-null -#' @param sort (length-1 \code{\link[base:list]{list}}, optional) Provide a field to sort by. Use `-` for descending order. ex: `-case_no` +#' @param audit_case_id (length-1 [base::list()], optional) Primary/foreign key for audit tables +#' @param cycle (length-1 [base::list()], optional) Filter records to only those that are applicable to a given two-year period. This cycle follows the traditional House election cycle and subdivides the presidential and Senate elections into comparable two-year blocks. The cycle begins with an odd year and is named for its ending, even year. +#' @param sub_category_id (length-1 [base::character()], optional) The finding id of an audit. Finding are a category of broader issues. Each category has an unique ID. +#' @param sort_nulls_last (length-1 [base::logical()], optional) Toggle that sorts null values last +#' @param sort_hide_null (length-1 [base::logical()], optional) Hide null values on sorted column(s). +#' @param min_election_cycle (length-1 [base::list()], optional) Filter records to only those that are applicable to a given two-year period. This cycle follows the traditional House election cycle and subdivides the presidential and Senate elections into comparable two-year blocks. The cycle begins with an odd year and is named for its ending, even year. +#' @param audit_id (length-1 [base::list()], optional) The audit issue. Each subcategory has an unique ID +#' @param q (length-1 [base::list()], optional) The name of the committee. If a committee changes its name, the most recent name will be shown. Committee names are not unique. Use committee_id for looking up records. +#' @param per_page (length-1 [base::list()], optional) The number of results returned per page. Defaults to 20. +#' @param max_election_cycle (length-1 [base::list()], optional) Filter records to only those that are applicable to a given two-year period. This cycle follows the traditional House election cycle and subdivides the presidential and Senate elections into comparable two-year blocks. The cycle begins with an odd year and is named for its ending, even year. +#' @param candidate_id (length-1 [base::list()], optional) A unique identifier assigned to each candidate registered with the FEC. If a person runs for several offices, that person will have separate candidate IDs for each office. +#' @param committee_type (length-1 [base::list()], optional) The one-letter type code of the organization: - C communication cost - D delegate - E electioneering communication - H House - I independent expenditure filer (not a committee) - N PAC - nonqualified - O independent expenditure-only (super PACs) - P presidential - Q PAC - qualified - S Senate - U single candidate independent expenditure - V PAC with non-contribution account, nonqualified - W PAC with non-contribution account, qualified - X party, nonqualified - Y party, qualified - Z national party non-federal account +#' @param qq (length-1 [base::list()], optional) Name of candidate running for office +#' @param page (length-1 [base::list()], optional) For paginating through results, starting at page 1 +#' @param committee_id (length-1 [base::list()], optional) A unique identifier assigned to each committee or filer registered with the FEC. In general committee id's begin with the letter C which is followed by eight digits. +#' @param api_key (length-1 [base::character()]) API key for https://api.data.gov. Get one at https://api.data.gov/signup. +#' @param committee_designation (length-1 [base::character()], optional) Type of committee: - H or S - Congressional - P - Presidential - X or Y or Z - Party - N or Q - PAC - I - Independent expenditure - O - Super PAC +#' @param primary_category_id (length-1 [base::character()], optional) Audit category ID (table PK) +#' @param sort_null_only (length-1 [base::logical()], optional) Toggle that filters out all rows having sort column that is non-null +#' @param sort (length-1 [base::list()], optional) Provide a field to sort by. Use `-` for descending order. ex: `-case_no` #' @inheritParams .shared-params #' #' @returns `fec_get_audit_case()`: The API response. diff --git a/tests/testthat/_fixtures/fec/paths-audit-get_audit_category.R b/tests/testthat/_fixtures/fec/paths-audit-get_audit_category.R index 4a0abed..d779c74 100644 --- a/tests/testthat/_fixtures/fec/paths-audit-get_audit_category.R +++ b/tests/testthat/_fixtures/fec/paths-audit-get_audit_category.R @@ -6,15 +6,15 @@ #' #' This lists the options for the categories and subcategories available in the /audit-search/ endpoint. #' -#' @param sort_nulls_last (length-1 \code{\link[base:logical]{logical}}, optional) Toggle that sorts null values last -#' @param page (length-1 \code{\link[base:list]{list}}, optional) For paginating through results, starting at page 1 -#' @param primary_category_name (length-1 \code{\link[base:list]{list}}, optional) Primary Audit Category - No Findings or Issues/Not a Committee - Net Outstanding Campaign/Convention Expenditures/Obligations - Payments/Disgorgements - Allocation Issues - Prohibited Contributions - Disclosure - Recordkeeping - Repayment to US Treasury - Other - Misstatement of Financial Activity - Excessive Contributions - Failure to File Reports/Schedules/Notices - Loans - Referred Findings Not Listed -#' @param sort_hide_null (length-1 \code{\link[base:logical]{logical}}, optional) Hide null values on sorted column(s). -#' @param api_key (length-1 \code{\link[base:character]{character}}) API key for https://api.data.gov. Get one at https://api.data.gov/signup. -#' @param primary_category_id (length-1 \code{\link[base:list]{list}}, optional) Audit category ID (table PK) -#' @param sort_null_only (length-1 \code{\link[base:logical]{logical}}, optional) Toggle that filters out all rows having sort column that is non-null -#' @param per_page (length-1 \code{\link[base:list]{list}}, optional) The number of results returned per page. Defaults to 20. -#' @param sort (length-1 \code{\link[base:character]{character}}, optional) Provide a field to sort by. Use `-` for descending order. +#' @param sort_nulls_last (length-1 [base::logical()], optional) Toggle that sorts null values last +#' @param page (length-1 [base::list()], optional) For paginating through results, starting at page 1 +#' @param primary_category_name (length-1 [base::list()], optional) Primary Audit Category - No Findings or Issues/Not a Committee - Net Outstanding Campaign/Convention Expenditures/Obligations - Payments/Disgorgements - Allocation Issues - Prohibited Contributions - Disclosure - Recordkeeping - Repayment to US Treasury - Other - Misstatement of Financial Activity - Excessive Contributions - Failure to File Reports/Schedules/Notices - Loans - Referred Findings Not Listed +#' @param sort_hide_null (length-1 [base::logical()], optional) Hide null values on sorted column(s). +#' @param api_key (length-1 [base::character()]) API key for https://api.data.gov. Get one at https://api.data.gov/signup. +#' @param primary_category_id (length-1 [base::list()], optional) Audit category ID (table PK) +#' @param sort_null_only (length-1 [base::logical()], optional) Toggle that filters out all rows having sort column that is non-null +#' @param per_page (length-1 [base::list()], optional) The number of results returned per page. Defaults to 20. +#' @param sort (length-1 [base::character()], optional) Provide a field to sort by. Use `-` for descending order. #' @inheritParams .shared-params #' #' @returns `fec_get_audit_category()`: The API response. diff --git a/tests/testthat/_fixtures/fec/paths-audit-get_audit_primary_category.R b/tests/testthat/_fixtures/fec/paths-audit-get_audit_primary_category.R index e60e714..259e0c2 100644 --- a/tests/testthat/_fixtures/fec/paths-audit-get_audit_primary_category.R +++ b/tests/testthat/_fixtures/fec/paths-audit-get_audit_primary_category.R @@ -6,15 +6,15 @@ #' #' This lists the options for the primary categories available in the /audit-search/ endpoint. #' -#' @param sort_nulls_last (length-1 \code{\link[base:logical]{logical}}, optional) Toggle that sorts null values last -#' @param page (length-1 \code{\link[base:list]{list}}, optional) For paginating through results, starting at page 1 -#' @param primary_category_name (length-1 \code{\link[base:list]{list}}, optional) Primary Audit Category - No Findings or Issues/Not a Committee - Net Outstanding Campaign/Convention Expenditures/Obligations - Payments/Disgorgements - Allocation Issues - Prohibited Contributions - Disclosure - Recordkeeping - Repayment to US Treasury - Other - Misstatement of Financial Activity - Excessive Contributions - Failure to File Reports/Schedules/Notices - Loans - Referred Findings Not Listed -#' @param sort_hide_null (length-1 \code{\link[base:logical]{logical}}, optional) Hide null values on sorted column(s). -#' @param api_key (length-1 \code{\link[base:character]{character}}) API key for https://api.data.gov. Get one at https://api.data.gov/signup. -#' @param primary_category_id (length-1 \code{\link[base:list]{list}}, optional) Audit category ID (table PK) -#' @param sort_null_only (length-1 \code{\link[base:logical]{logical}}, optional) Toggle that filters out all rows having sort column that is non-null -#' @param per_page (length-1 \code{\link[base:list]{list}}, optional) The number of results returned per page. Defaults to 20. -#' @param sort (length-1 \code{\link[base:character]{character}}, optional) Provide a field to sort by. Use `-` for descending order. +#' @param sort_nulls_last (length-1 [base::logical()], optional) Toggle that sorts null values last +#' @param page (length-1 [base::list()], optional) For paginating through results, starting at page 1 +#' @param primary_category_name (length-1 [base::list()], optional) Primary Audit Category - No Findings or Issues/Not a Committee - Net Outstanding Campaign/Convention Expenditures/Obligations - Payments/Disgorgements - Allocation Issues - Prohibited Contributions - Disclosure - Recordkeeping - Repayment to US Treasury - Other - Misstatement of Financial Activity - Excessive Contributions - Failure to File Reports/Schedules/Notices - Loans - Referred Findings Not Listed +#' @param sort_hide_null (length-1 [base::logical()], optional) Hide null values on sorted column(s). +#' @param api_key (length-1 [base::character()]) API key for https://api.data.gov. Get one at https://api.data.gov/signup. +#' @param primary_category_id (length-1 [base::list()], optional) Audit category ID (table PK) +#' @param sort_null_only (length-1 [base::logical()], optional) Toggle that filters out all rows having sort column that is non-null +#' @param per_page (length-1 [base::list()], optional) The number of results returned per page. Defaults to 20. +#' @param sort (length-1 [base::character()], optional) Provide a field to sort by. Use `-` for descending order. #' @inheritParams .shared-params #' #' @returns `fec_get_audit_primary_category()`: The API response. diff --git a/tests/testthat/_fixtures/fec/paths-audit-get_names_audit_candidates.R b/tests/testthat/_fixtures/fec/paths-audit-get_names_audit_candidates.R index f0433dd..da32a64 100644 --- a/tests/testthat/_fixtures/fec/paths-audit-get_names_audit_candidates.R +++ b/tests/testthat/_fixtures/fec/paths-audit-get_names_audit_candidates.R @@ -6,8 +6,8 @@ #' #' Search for candidates or committees by name. If you're looking for information on a particular person or group, using a name to find the `candidate_id` or `committee_id` on this endpoint can be a helpful first step. #' -#' @param api_key (length-1 \code{\link[base:character]{character}}) API key for https://api.data.gov. Get one at https://api.data.gov/signup. -#' @param q (length-1 \code{\link[base:list]{list}}) Name (candidate or committee) to search for +#' @param api_key (length-1 [base::character()]) API key for https://api.data.gov. Get one at https://api.data.gov/signup. +#' @param q (length-1 [base::list()]) Name (candidate or committee) to search for #' @inheritParams .shared-params #' #' @returns `fec_get_names_audit_candidates()`: The API response. diff --git a/tests/testthat/_fixtures/fec/paths-audit-get_names_audit_committees.R b/tests/testthat/_fixtures/fec/paths-audit-get_names_audit_committees.R index 0c9eb20..41c3c69 100644 --- a/tests/testthat/_fixtures/fec/paths-audit-get_names_audit_committees.R +++ b/tests/testthat/_fixtures/fec/paths-audit-get_names_audit_committees.R @@ -7,8 +7,8 @@ #' Search for candidates or committees by name. If you're looking for information on a particular person or group, using a name to find the `candidate_id` or `committee_id` on this endpoint can be a helpful first step. #' #' -#' @param api_key (length-1 \code{\link[base:character]{character}}) API key for https://api.data.gov. Get one at https://api.data.gov/signup. -#' @param q (length-1 \code{\link[base:list]{list}}) Name (candidate or committee) to search for +#' @param api_key (length-1 [base::character()]) API key for https://api.data.gov. Get one at https://api.data.gov/signup. +#' @param q (length-1 [base::list()]) Name (candidate or committee) to search for #' @inheritParams .shared-params #' #' @returns `fec_get_names_audit_committees()`: The API response. diff --git a/tests/testthat/_fixtures/fec/paths-debts-get_schedules_schedule_d.R b/tests/testthat/_fixtures/fec/paths-debts-get_schedules_schedule_d.R index 9651ae8..d529d72 100644 --- a/tests/testthat/_fixtures/fec/paths-debts-get_schedules_schedule_d.R +++ b/tests/testthat/_fixtures/fec/paths-debts-get_schedules_schedule_d.R @@ -6,30 +6,30 @@ #' #' Schedule D, it shows debts and obligations owed to or by the committee that are required to be disclosed. #' -#' @param creditor_debtor_name (length-1 \code{\link[base:list]{list}}, optional) -#' @param max_image_number (length-1 \code{\link[base:character]{character}}, optional) Maxium image number of the page where the schedule item is reported -#' @param sort_nulls_last (length-1 \code{\link[base:logical]{logical}}, optional) Toggle that sorts null values last -#' @param max_amount_outstanding_beginning (length-1 \code{\link[base:double]{double}}, optional) -#' @param sort_hide_null (length-1 \code{\link[base:logical]{logical}}, optional) Hide null values on sorted column(s). -#' @param min_payment_period (length-1 \code{\link[base:double]{double}}, optional) -#' @param max_amount_incurred (length-1 \code{\link[base:double]{double}}, optional) -#' @param nature_of_debt (length-1 \code{\link[base:character]{character}}, optional) -#' @param per_page (length-1 \code{\link[base:list]{list}}, optional) The number of results returned per page. Defaults to 20. -#' @param max_amount_outstanding_close (length-1 \code{\link[base:double]{double}}, optional) -#' @param candidate_id (length-1 \code{\link[base:list]{list}}, optional) A unique identifier assigned to each candidate registered with the FEC. If a person runs for several offices, that person will have separate candidate IDs for each office. -#' @param page (length-1 \code{\link[base:list]{list}}, optional) For paginating through results, starting at page 1 -#' @param min_date (length-1 \code{\link[base:Date]{Date}}, optional) Minimum load date -#' @param committee_id (length-1 \code{\link[base:list]{list}}, optional) A unique identifier assigned to each committee or filer registered with the FEC. In general committee id's begin with the letter C which is followed by eight digits. -#' @param min_amount_outstanding_close (length-1 \code{\link[base:double]{double}}, optional) -#' @param api_key (length-1 \code{\link[base:character]{character}}) API key for https://api.data.gov. Get one at https://api.data.gov/signup. -#' @param max_payment_period (length-1 \code{\link[base:double]{double}}, optional) -#' @param min_image_number (length-1 \code{\link[base:character]{character}}, optional) Minium image number of the page where the schedule item is reported -#' @param min_amount_incurred (length-1 \code{\link[base:double]{double}}, optional) -#' @param sort_null_only (length-1 \code{\link[base:logical]{logical}}, optional) Toggle that filters out all rows having sort column that is non-null -#' @param image_number (length-1 \code{\link[base:list]{list}}, optional) An unique identifier for each page where the electronic or paper filing is reported. -#' @param sort (length-1 \code{\link[base:character]{character}}, optional) Provide a field to sort by. Use `-` for descending order. -#' @param min_amount_outstanding_beginning (length-1 \code{\link[base:double]{double}}, optional) -#' @param max_date (length-1 \code{\link[base:Date]{Date}}, optional) Maximum load date +#' @param creditor_debtor_name (length-1 [base::list()], optional) +#' @param max_image_number (length-1 [base::character()], optional) Maxium image number of the page where the schedule item is reported +#' @param sort_nulls_last (length-1 [base::logical()], optional) Toggle that sorts null values last +#' @param max_amount_outstanding_beginning (length-1 [base::double()], optional) +#' @param sort_hide_null (length-1 [base::logical()], optional) Hide null values on sorted column(s). +#' @param min_payment_period (length-1 [base::double()], optional) +#' @param max_amount_incurred (length-1 [base::double()], optional) +#' @param nature_of_debt (length-1 [base::character()], optional) +#' @param per_page (length-1 [base::list()], optional) The number of results returned per page. Defaults to 20. +#' @param max_amount_outstanding_close (length-1 [base::double()], optional) +#' @param candidate_id (length-1 [base::list()], optional) A unique identifier assigned to each candidate registered with the FEC. If a person runs for several offices, that person will have separate candidate IDs for each office. +#' @param page (length-1 [base::list()], optional) For paginating through results, starting at page 1 +#' @param min_date (length-1 [base::Date()], optional) Minimum load date +#' @param committee_id (length-1 [base::list()], optional) A unique identifier assigned to each committee or filer registered with the FEC. In general committee id's begin with the letter C which is followed by eight digits. +#' @param min_amount_outstanding_close (length-1 [base::double()], optional) +#' @param api_key (length-1 [base::character()]) API key for https://api.data.gov. Get one at https://api.data.gov/signup. +#' @param max_payment_period (length-1 [base::double()], optional) +#' @param min_image_number (length-1 [base::character()], optional) Minium image number of the page where the schedule item is reported +#' @param min_amount_incurred (length-1 [base::double()], optional) +#' @param sort_null_only (length-1 [base::logical()], optional) Toggle that filters out all rows having sort column that is non-null +#' @param image_number (length-1 [base::list()], optional) An unique identifier for each page where the electronic or paper filing is reported. +#' @param sort (length-1 [base::character()], optional) Provide a field to sort by. Use `-` for descending order. +#' @param min_amount_outstanding_beginning (length-1 [base::double()], optional) +#' @param max_date (length-1 [base::Date()], optional) Maximum load date #' @inheritParams .shared-params #' #' @returns `fec_get_schedules_schedule_d()`: The API response. diff --git a/tests/testthat/_fixtures/fec/paths-debts-get_schedules_schedule_d_sub_id.R b/tests/testthat/_fixtures/fec/paths-debts-get_schedules_schedule_d_sub_id.R index 5d4dbfb..6666c89 100644 --- a/tests/testthat/_fixtures/fec/paths-debts-get_schedules_schedule_d_sub_id.R +++ b/tests/testthat/_fixtures/fec/paths-debts-get_schedules_schedule_d_sub_id.R @@ -6,14 +6,14 @@ #' #' Schedule D, it shows debts and obligations owed to or by the committee that are required to be disclosed. #' -#' @param sort_nulls_last (length-1 \code{\link[base:logical]{logical}}, optional) Toggle that sorts null values last -#' @param per_page (length-1 \code{\link[base:list]{list}}, optional) The number of results returned per page. Defaults to 20. -#' @param sort_null_only (length-1 \code{\link[base:logical]{logical}}, optional) Toggle that filters out all rows having sort column that is non-null -#' @param sort_hide_null (length-1 \code{\link[base:logical]{logical}}, optional) Hide null values on sorted column(s). -#' @param sort (length-1 \code{\link[base:character]{character}}, optional) Provide a field to sort by. Use `-` for descending order. -#' @param api_key (length-1 \code{\link[base:character]{character}}) API key for https://api.data.gov. Get one at https://api.data.gov/signup. -#' @param page (length-1 \code{\link[base:list]{list}}, optional) For paginating through results, starting at page 1 -#' @param sub_id (length-1 \code{\link[base:character]{character}}) +#' @param sort_nulls_last (length-1 [base::logical()], optional) Toggle that sorts null values last +#' @param per_page (length-1 [base::list()], optional) The number of results returned per page. Defaults to 20. +#' @param sort_null_only (length-1 [base::logical()], optional) Toggle that filters out all rows having sort column that is non-null +#' @param sort_hide_null (length-1 [base::logical()], optional) Hide null values on sorted column(s). +#' @param sort (length-1 [base::character()], optional) Provide a field to sort by. Use `-` for descending order. +#' @param api_key (length-1 [base::character()]) API key for https://api.data.gov. Get one at https://api.data.gov/signup. +#' @param page (length-1 [base::list()], optional) For paginating through results, starting at page 1 +#' @param sub_id (length-1 [base::character()]) #' @inheritParams .shared-params #' #' @returns `fec_get_schedules_schedule_d_sub_id()`: The API response. diff --git a/tests/testthat/_fixtures/fec/paths-legal-get_legal_search.R b/tests/testthat/_fixtures/fec/paths-legal-get_legal_search.R index 015872d..d25908f 100644 --- a/tests/testthat/_fixtures/fec/paths-legal-get_legal_search.R +++ b/tests/testthat/_fixtures/fec/paths-legal-get_legal_search.R @@ -6,49 +6,49 @@ #' #' Search legal documents by document type, or across all document types using keywords, parameter values and ranges. #' -#' @param hits_returned (length-1 \code{\link[base:list]{list}}, optional) Number of results to return (max 10) -#' @param af_report_year (length-1 \code{\link[base:character]{character}}, optional) Admin fine report year -#' @param case_max_open_date (length-1 \code{\link[base:Date]{Date}}, optional) The latest date opened of case -#' @param ao_max_issue_date (length-1 \code{\link[base:Date]{Date}}, optional) Latest issue date of advisory opinion -#' @param case_statutory_citation (length-1 \code{\link[base:list]{list}}, optional) Statutory citations -#' @param case_respondents (length-1 \code{\link[base:character]{character}}, optional) Cases respondents -#' @param q (length-1 \code{\link[base:character]{character}}, optional) Text to search legal documents for -#' @param ao_min_issue_date (length-1 \code{\link[base:Date]{Date}}, optional) Earliest issue date of advisory opinion -#' @param af_max_fd_date (length-1 \code{\link[base:Date]{Date}}, optional) The latest Final Determination date -#' @param from_hit (length-1 \code{\link[base:list]{list}}, optional) Get results starting from this index -#' @param af_fd_fine_amount (length-1 \code{\link[base:list]{list}}, optional) Final Determination fine amount -#' @param type (length-1 \code{\link[base:character]{character}}, optional) Legal Document type to refine search by - statutes - regulations - advisory_opinions - murs - admin_fines -#' @param api_key (length-1 \code{\link[base:character]{character}}) API key for https://api.data.gov. Get one at https://api.data.gov/signup. -#' @param af_name (length-1 \code{\link[base:list]{list}}, optional) Admin fine committee name -#' @param ao_requestor_type (length-1 \code{\link[base:list]{list}}, optional) Code of the advisory opinion requestor type. -#' @param ao_statutory_citation (length-1 \code{\link[base:list]{list}}, optional) Statutory citations -#' @param ao_entity_name (length-1 \code{\link[base:list]{list}}, optional) Name of commenter or representative -#' @param mur_type (length-1 \code{\link[base:character]{character}}, optional) Type of MUR : current or archived -#' @param ao_regulatory_citation (length-1 \code{\link[base:list]{list}}, optional) Regulatory citations -#' @param af_committee_id (length-1 \code{\link[base:character]{character}}, optional) Admin fine committee ID -#' @param ao_requestor (length-1 \code{\link[base:character]{character}}, optional) The requestor of the advisory opinion -#' @param case_citation_require_all (length-1 \code{\link[base:logical]{logical}}, optional) Require all citations to be in document (default behavior is any) -#' @param af_min_fd_date (length-1 \code{\link[base:Date]{Date}}, optional) The earliest Final Determination date -#' @param ao_is_pending (length-1 \code{\link[base:logical]{logical}}, optional) AO is pending -#' @param af_rtb_fine_amount (length-1 \code{\link[base:list]{list}}, optional) Reason to Believe fine amount -#' @param case_election_cycles (length-1 \code{\link[base:list]{list}}, optional) Cases election cycles -#' @param ao_category (length-1 \code{\link[base:list]{list}}, optional) Category of the document -#' @param ao_citation_require_all (length-1 \code{\link[base:logical]{logical}}, optional) Require all citations to be in document (default behavior is any) -#' @param case_dispositions (length-1 \code{\link[base:list]{list}}, optional) Cases dispositions -#' @param af_max_rtb_date (length-1 \code{\link[base:Date]{Date}}, optional) The latest Reason to Believe date -#' @param case_min_open_date (length-1 \code{\link[base:Date]{Date}}, optional) The earliest date opened of case -#' @param case_max_close_date (length-1 \code{\link[base:Date]{Date}}, optional) The latest date closed of case -#' @param ao_min_request_date (length-1 \code{\link[base:Date]{Date}}, optional) Earliest request date of advisory opinion -#' @param ao_status (length-1 \code{\link[base:character]{character}}, optional) Status of AO (pending, withdrawn, or final) -#' @param case_doc_category_id (length-1 \code{\link[base:list]{list}}, optional) Select one or more case_doc_category_id to filter by corresponding CASE_DOCUMENT_CATEGORY: - 1 - Conciliation Agreements - 2 - Complaint, Responses, Designation of Counsel and Extensions of Timee - 3 - General Counsel Reports, Briefs, Notifications and Responses - 4 - Certifications - 5 - Civil Penalties, Disgorgements and Other Payments - 6 - Statements of Reasons -#' @param af_min_rtb_date (length-1 \code{\link[base:Date]{Date}}, optional) The earliest Reason to Believe date -#' @param ao_name (length-1 \code{\link[base:list]{list}}, optional) Force advisory opinion name -#' @param case_regulatory_citation (length-1 \code{\link[base:list]{list}}, optional) Regulatory citations -#' @param ao_no (length-1 \code{\link[base:list]{list}}, optional) Force advisory opinion number -#' @param case_min_close_date (length-1 \code{\link[base:Date]{Date}}, optional) The earliest date closed of case -#' @param sort (length-1 \code{\link[base:character]{character}}, optional) Provide a field to sort by. Use `-` for descending order. ex: `-case_no` -#' @param ao_max_request_date (length-1 \code{\link[base:Date]{Date}}, optional) Latest request date of advisory opinion -#' @param case_no (length-1 \code{\link[base:list]{list}}, optional) Enforcement matter case number +#' @param hits_returned (length-1 [base::list()], optional) Number of results to return (max 10) +#' @param af_report_year (length-1 [base::character()], optional) Admin fine report year +#' @param case_max_open_date (length-1 [base::Date()], optional) The latest date opened of case +#' @param ao_max_issue_date (length-1 [base::Date()], optional) Latest issue date of advisory opinion +#' @param case_statutory_citation (length-1 [base::list()], optional) Statutory citations +#' @param case_respondents (length-1 [base::character()], optional) Cases respondents +#' @param q (length-1 [base::character()], optional) Text to search legal documents for +#' @param ao_min_issue_date (length-1 [base::Date()], optional) Earliest issue date of advisory opinion +#' @param af_max_fd_date (length-1 [base::Date()], optional) The latest Final Determination date +#' @param from_hit (length-1 [base::list()], optional) Get results starting from this index +#' @param af_fd_fine_amount (length-1 [base::list()], optional) Final Determination fine amount +#' @param type (length-1 [base::character()], optional) Legal Document type to refine search by - statutes - regulations - advisory_opinions - murs - admin_fines +#' @param api_key (length-1 [base::character()]) API key for https://api.data.gov. Get one at https://api.data.gov/signup. +#' @param af_name (length-1 [base::list()], optional) Admin fine committee name +#' @param ao_requestor_type (length-1 [base::list()], optional) Code of the advisory opinion requestor type. +#' @param ao_statutory_citation (length-1 [base::list()], optional) Statutory citations +#' @param ao_entity_name (length-1 [base::list()], optional) Name of commenter or representative +#' @param mur_type (length-1 [base::character()], optional) Type of MUR : current or archived +#' @param ao_regulatory_citation (length-1 [base::list()], optional) Regulatory citations +#' @param af_committee_id (length-1 [base::character()], optional) Admin fine committee ID +#' @param ao_requestor (length-1 [base::character()], optional) The requestor of the advisory opinion +#' @param case_citation_require_all (length-1 [base::logical()], optional) Require all citations to be in document (default behavior is any) +#' @param af_min_fd_date (length-1 [base::Date()], optional) The earliest Final Determination date +#' @param ao_is_pending (length-1 [base::logical()], optional) AO is pending +#' @param af_rtb_fine_amount (length-1 [base::list()], optional) Reason to Believe fine amount +#' @param case_election_cycles (length-1 [base::list()], optional) Cases election cycles +#' @param ao_category (length-1 [base::list()], optional) Category of the document +#' @param ao_citation_require_all (length-1 [base::logical()], optional) Require all citations to be in document (default behavior is any) +#' @param case_dispositions (length-1 [base::list()], optional) Cases dispositions +#' @param af_max_rtb_date (length-1 [base::Date()], optional) The latest Reason to Believe date +#' @param case_min_open_date (length-1 [base::Date()], optional) The earliest date opened of case +#' @param case_max_close_date (length-1 [base::Date()], optional) The latest date closed of case +#' @param ao_min_request_date (length-1 [base::Date()], optional) Earliest request date of advisory opinion +#' @param ao_status (length-1 [base::character()], optional) Status of AO (pending, withdrawn, or final) +#' @param case_doc_category_id (length-1 [base::list()], optional) Select one or more case_doc_category_id to filter by corresponding CASE_DOCUMENT_CATEGORY: - 1 - Conciliation Agreements - 2 - Complaint, Responses, Designation of Counsel and Extensions of Timee - 3 - General Counsel Reports, Briefs, Notifications and Responses - 4 - Certifications - 5 - Civil Penalties, Disgorgements and Other Payments - 6 - Statements of Reasons +#' @param af_min_rtb_date (length-1 [base::Date()], optional) The earliest Reason to Believe date +#' @param ao_name (length-1 [base::list()], optional) Force advisory opinion name +#' @param case_regulatory_citation (length-1 [base::list()], optional) Regulatory citations +#' @param ao_no (length-1 [base::list()], optional) Force advisory opinion number +#' @param case_min_close_date (length-1 [base::Date()], optional) The earliest date closed of case +#' @param sort (length-1 [base::character()], optional) Provide a field to sort by. Use `-` for descending order. ex: `-case_no` +#' @param ao_max_request_date (length-1 [base::Date()], optional) Latest request date of advisory opinion +#' @param case_no (length-1 [base::list()], optional) Enforcement matter case number #' @inheritParams .shared-params #' #' @returns `fec_get_legal_search()`: The API response. diff --git a/tests/testthat/_fixtures/guru/paths-apis-get_api.R b/tests/testthat/_fixtures/guru/paths-apis-get_api.R index 4a44a7c..346f0a6 100644 --- a/tests/testthat/_fixtures/guru/paths-apis-get_api.R +++ b/tests/testthat/_fixtures/guru/paths-apis-get_api.R @@ -6,8 +6,8 @@ #' #' Returns the API entry for one specific version of an API where there is no serviceName. #' -#' @param provider (length-1 \code{\link[base:character]{character}}) -#' @param api (length-1 \code{\link[base:character]{character}}) +#' @param provider (length-1 [base::character()]) +#' @param api (length-1 [base::character()]) #' @inheritParams .shared-params #' #' @returns `guru_get_api()`: The API response. diff --git a/tests/testthat/_fixtures/guru/paths-apis-get_provider.R b/tests/testthat/_fixtures/guru/paths-apis-get_provider.R index 195d18d..d4230b1 100644 --- a/tests/testthat/_fixtures/guru/paths-apis-get_provider.R +++ b/tests/testthat/_fixtures/guru/paths-apis-get_provider.R @@ -6,7 +6,7 @@ #' #' List all APIs in the directory for a particular providerName Returns links to the individual API entry for each API. #' -#' @param provider (length-1 \code{\link[base:character]{character}}) +#' @param provider (length-1 [base::character()]) #' @inheritParams .shared-params #' #' @returns `guru_get_provider()`: The API response. diff --git a/tests/testthat/_fixtures/guru/paths-apis-get_service_api.R b/tests/testthat/_fixtures/guru/paths-apis-get_service_api.R index 146bf54..2c6c727 100644 --- a/tests/testthat/_fixtures/guru/paths-apis-get_service_api.R +++ b/tests/testthat/_fixtures/guru/paths-apis-get_service_api.R @@ -6,9 +6,9 @@ #' #' Returns the API entry for one specific version of an API where there is a serviceName. #' -#' @param provider (length-1 \code{\link[base:character]{character}}) -#' @param service (length-1 \code{\link[base:character]{character}}) -#' @param api (length-1 \code{\link[base:character]{character}}) +#' @param provider (length-1 [base::character()]) +#' @param service (length-1 [base::character()]) +#' @param api (length-1 [base::character()]) #' @inheritParams .shared-params #' #' @returns `guru_get_service_api()`: The API response. diff --git a/tests/testthat/_fixtures/guru/paths-apis-get_services.R b/tests/testthat/_fixtures/guru/paths-apis-get_services.R index 266e954..37dd8e8 100644 --- a/tests/testthat/_fixtures/guru/paths-apis-get_services.R +++ b/tests/testthat/_fixtures/guru/paths-apis-get_services.R @@ -6,7 +6,7 @@ #' #' List all serviceNames in the directory for a particular providerName #' -#' @param provider (length-1 \code{\link[base:character]{character}}) +#' @param provider (length-1 [base::character()]) #' @inheritParams .shared-params #' #' @returns `guru_get_services()`: The API response. diff --git a/tests/testthat/_fixtures/trello/paths-board-add_boards.R b/tests/testthat/_fixtures/trello/paths-board-add_boards.R index 318b516..c87dbd8 100644 --- a/tests/testthat/_fixtures/trello/paths-board-add_boards.R +++ b/tests/testthat/_fixtures/trello/paths-board-add_boards.R @@ -6,8 +6,8 @@ #' #' addBoards() #' -#' @param key (length-1 \code{\link[base:character]{character}}) Generate your application key -#' @param token (length-1 \code{\link[base:character]{character}}) Getting a token from a user +#' @param key (length-1 [base::character()]) Generate your application key +#' @param token (length-1 [base::character()]) Getting a token from a user #' @inheritParams .shared-params #' #' @returns `trello_add_boards()`: The API response. From a8212ee57aada3e5eabfc7b2c3891fc12c565ab7 Mon Sep 17 00:00:00 2001 From: Jon Harmon Date: Tue, 12 May 2026 13:28:04 -0500 Subject: [PATCH 33/33] And language and remove LazyData --- DESCRIPTION | 2 +- _pkgdown.yml | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/DESCRIPTION b/DESCRIPTION index 943b0dd..32a370c 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -53,6 +53,6 @@ Remotes: Config/roxygen2/version: 8.0.0 Config/testthat/edition: 3 Encoding: UTF-8 -LazyData: true +Language: en-US Roxygen: list(markdown = TRUE) RoxygenNote: 8.0.0 diff --git a/_pkgdown.yml b/_pkgdown.yml index 38f322a..1dd6490 100644 --- a/_pkgdown.yml +++ b/_pkgdown.yml @@ -1,3 +1,6 @@ url: https://beekeeper.api2r.org +development: + mode: auto template: bootstrap: 5 +lang: en-US