Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .Rbuildignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,6 @@
^_pkgdown\.yml$
^docs$
^pkgdown$
^_beekeeper\.yml$
^_beekeeper_rapid\.rds$
^exploration$
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,7 @@
.httr-oauth
.DS_Store
docs
inst/doc
_beekeeper.yml
exploration
_beekeeper_rapid.rds
32 changes: 28 additions & 4 deletions DESCRIPTION
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,38 @@ Description: Automatically generate R package skeletons from 'application
(OAS)'. The skeletons implement best practices to streamline package
development.
License: MIT + file LICENSE
URL: https://github.com/jonthegeek/beekeeper,
https://jonthegeek.github.io/beekeeper/
URL: https://jonthegeek.github.io/beekeeper/,
https://github.com/jonthegeek/beekeeper
BugReports: https://github.com/jonthegeek/beekeeper/issues
Imports:
rlang (>= 1.1.0)
cli,
desc,
fs,
glue,
lubridate,
nectar,
rapid,
rlang (>= 1.1.0),
rprojroot,
S7,
stbl,
styler,
usethis,
utils,
yaml
Suggests:
covr,
testthat (>= 3.0.0)
knitr,
rmarkdown,
stringr,
testthat,
withr
VignetteBuilder:
knitr
Remotes:
jonthegeek/nectar,
jonthegeek/rapid,
rconsortium/S7
Config/testthat/edition: 3
Encoding: UTF-8
Roxygen: list(markdown = TRUE)
Expand Down
8 changes: 8 additions & 0 deletions NAMESPACE
Original file line number Diff line number Diff line change
@@ -1,2 +1,10 @@
# Generated by roxygen2: do not edit by hand

export(generate_pkg)
export(pkg_agent)
export(use_beekeeper)
importFrom(glue,glue)
importFrom(nectar,call_api)
importFrom(rapid,as_rapid)
importFrom(usethis,use_build_ignore)
importFrom(usethis,use_package)
1 change: 1 addition & 0 deletions R/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
todo.R
4 changes: 4 additions & 0 deletions R/beekeeper-package.R
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,9 @@
"_PACKAGE"

## usethis namespace: start
#' @importFrom glue glue
#' @importFrom nectar call_api
#' @importFrom rapid as_rapid
#' @importFrom usethis use_package
## usethis namespace: end
NULL
133 changes: 133 additions & 0 deletions R/generate.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
#' Use a beekeeper config file to generate code
#'
#' Creates or updates package files based on the information in a beekeeper
#' 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 pkg_agent A string to identify this package, for use in the
#' `user_agent` argument of [nectar::call_api()].
#'
#' @return `TRUE` invisibly.
#' @export
generate_pkg <- function(config_file = "_beekeeper.yml",
pkg_agent = pkg_agent(fs::path_dir(config_file))) {
.assert_is_pkg()
config <- .read_config(config_file)
api_definition <- readRDS(
fs::path(fs::path_dir(config_file), config$rapid_file)
)

# This will be a series of functions, each of which generates one or more
# files.
.generate_call(
api_title = config$api_title,
api_abbr = config$api_abbr,
base_url = api_definition@servers@url,
pkg_agent = pkg_agent
)

return(invisible(TRUE))
}

.generate_call <- function(api_title,
api_abbr,
base_url,
pkg_agent) {
api_title <- stbl::stabilize_chr_scalar(
api_title,
allow_null = FALSE,
allow_zero_length = FALSE,
allow_na = FALSE
)
api_abbr <- stbl::stabilize_chr_scalar(
api_abbr,
allow_null = FALSE,
allow_zero_length = FALSE,
allow_na = FALSE
)
base_url <- stbl::stabilize_chr_scalar(
base_url,
allow_null = FALSE,
allow_zero_length = FALSE,
allow_na = FALSE
)
pkg_agent <- stbl::stabilize_chr_scalar(
pkg_agent,
allow_null = FALSE,
allow_zero_length = FALSE,
allow_na = FALSE
)

usethis::use_directory("R")
usethis::use_package("nectar")

data_for_call <- list(
api_title = api_title,
api_abbr = api_abbr,
base_url = base_url,
pkg_agent = pkg_agent
)

template <- "010-call.R"

# Eventually this will also include a test-generator.
.bk_use_template(
template = template,
data = data_for_call
)
return(invisible(TRUE))
}


#' Use a template in this package
#'
#' @param template The name of the template.
#' @param dir The directory where the file should be created.
#' @param data A list of variables to apply to the template.
#'
#' @return The path to the generated or updated file, invisibly.
#' @keywords internal
.bk_use_template <- function(template,
dir = c("R", "tests/testthat"),
data) {
dir <- match.arg(dir)
target <- usethis::proj_path(dir, template)
save_as <- fs::path_rel(target, usethis::proj_path())

usethis::use_template(
template = template,
save_as = save_as,
data = data,
package = "beekeeper"
)
utils::capture.output({
styler::style_file(target)
})

return(invisible(target))
}

#' 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
pkg_agent <- function(path = ".") {
pkg_desc <- desc::desc(file = path)
pkg_name <- pkg_desc$get_field("Package")
pkg_url_glue <- ""
pkg_url <- pkg_desc$get_urls()
if (length(pkg_url)) {
pkg_url_glue <- glue::glue(
" ({pkg_url[[1]]})"
)
}
return(
glue::glue("{pkg_name}{pkg_url_glue}")
)
}
91 changes: 91 additions & 0 deletions R/use_beekeeper.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
#' Configure a package to use beekeeper
#'
#' Create a configuration file for a package to use beekeeper. The configuration
#' file tracks information that will be used for generation of other functions,
#' and the timestamp when the configuration was last updated or used.
#'
#' @inheritParams rlang::args_dots_empty
#' @param x An object to use to define the configuration, such as a
#' [rapid::rapid()] or a url to an OpenAPI 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 the configuration file to write.
#' @param rapid_file The path to the rapid rds file to write.
#'
#' @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
#' written, and the path to that file is saved in the config file.
#' @export
use_beekeeper <- S7::new_generic(
"use_beekeeper",
"x",
function(x,
api_abbr,
...,
config_file = "_beekeeper.yml",
rapid_file = "_beekeeper_rapid.rds") {
rlang::check_dots_empty()
S7::S7_dispatch()
}
)

#' @importFrom usethis use_build_ignore
S7::method(use_beekeeper, rapid::rapid) <-
function(x,
api_abbr,
...,
config_file = "_beekeeper.yml",
rapid_file = "_beekeeper_rapid.rds") {
api_abbr <- stbl::stabilize_chr_scalar(
api_abbr,
allow_null = FALSE,
allow_zero_length = FALSE,
allow_na = FALSE
)
config_file <- stbl::stabilize_chr_scalar(
config_file,
allow_null = FALSE,
allow_zero_length = FALSE,
allow_na = FALSE
)
rapid_file <- stbl::stabilize_chr_scalar(
rapid_file,
allow_null = FALSE,
allow_zero_length = FALSE,
allow_na = FALSE
)
saveRDS(x, rapid_file)

use_build_ignore(c(config_file, rapid_file))

yaml::write_yaml(
list(
api_title = x@info@title,
api_abbr = api_abbr,
api_version = x@info@version,
rapid_file = fs::path_rel(rapid_file, fs::path_dir(config_file)),
updated_on = as.character(lubridate::now(tzone = "UTC"))
),
file = config_file
)
return(invisible(config_file))
}

S7::method(use_beekeeper, S7::class_any) <-
function(x,
api_abbr,
...,
config_file = "_beekeeper.yml",
rapid_file = "_beekeeper_rapid.rds") {
x <- as_rapid(x)
use_beekeeper(x, api_abbr, ..., config_file = config_file)
}

.read_config <- function(config_file = "_beekeeper.yml") {
config <- yaml::read_yaml(config_file)
config$updated_on <- lubridate::parse_date_time(
config$updated_on,
orders = c("ymd HMS", "ymd H", "ymd")
)
return(config)
}
31 changes: 31 additions & 0 deletions R/utils.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
#' 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 = usethis::proj_get()) {
root_file <- rlang::try_fetch(
rprojroot::find_package_root_file(path = base_path),
error = function(cnd) NULL
)
!is.null(root_file)
}

#' 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::cli_abort(c(
"Can't generate package files outside of a package.",
x = "{.path {base_path}} is not inside a package."
))
}
3 changes: 3 additions & 0 deletions R/zzz.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.onLoad <- function(...) {
S7::methods_register() # nocov
}
Loading