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