diff --git a/DESCRIPTION b/DESCRIPTION index 585a51285a..c7fffa1b49 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -122,6 +122,7 @@ Collate: 'coord-transform.R' 'data.R' 'docs_layer.R' + 'edition.R' 'facet-.R' 'facet-grid-.R' 'facet-null.R' diff --git a/R/edition.R b/R/edition.R new file mode 100644 index 0000000000..0d4ac73187 --- /dev/null +++ b/R/edition.R @@ -0,0 +1,73 @@ + +ggplot_edition <- new.env(parent = emptyenv()) + +local_edition <- function(edition, .env = parent.frame()) { + stopifnot(is_zap(edition) || (is.numeric(edition) && length(edition) == 1)) + pkg <- get_pkg_name(.env) + local_bindings(!!pkg := edition, .env = ggplot_edition, .frame = .env) +} + +edition_get <- function(.env = parent.frame(), default = 2024L) { + pkg <- get_pkg_name(.env) + + # Try to query edition from cache + edition <- env_get(ggplot_edition, nm = pkg, default = NULL) + if (!is.null(edition)) { + return(edition) + } + + # Try to query package description + desc_file <- find_description(path = ".", package = pkg) + if (is.null(desc_file)) { + return(default) + } + + # Look up edition from the description + field_name <- "Config/ggplot2/edition" + edition <- as.integer(read.dcf(desc_file, fields = field_name)) + + # Cache result + env_bind(ggplot_edition, !!pkg := edition) + return(edition) +} + +edition_deprecate <- function(edition, ..., .env = parent.frame()) { + check_number_whole(edition) + if (edition_get(.env) < edition) { + return(invisible(NULL)) + } + + edition <- I(paste0("edition ", edition)) + lifecycle::deprecate_stop(edition, ...) +} + +edition_require <- function(edition, what, .env = parent.frame()) { + check_number_whole(edition) + current <- edition_get(.env) + if (current >= edition) { + return(invisible(NULL)) + } + msg <- paste0(what, " requires the ", edition, " edition of {.pkg ggplot2}.") + cli::cli_abort(msg) +} + +find_description <- function(path, package = NULL) { + if (!is.null(package)) { + path <- system.file(package = package, "DESCRIPTION") + } else { + path <- file.path(path, "DESCRIPTION") + } + if (!file.exists(path)) { + return(NULL) + } + path +} + +get_pkg_name <- function(env = parent.frame()) { + env <- topenv(env) + name <- environmentName(env) + if (!isNamespace(env) && name != "R_GlobalEnv") { + return(NULL) + } + name +} diff --git a/tests/testthat/_snaps/edition.md b/tests/testthat/_snaps/edition.md new file mode 100644 index 0000000000..53b698d408 --- /dev/null +++ b/tests/testthat/_snaps/edition.md @@ -0,0 +1,16 @@ +# basic edition infrastructure works as intended + + Code + edition_deprecate(2025, what = "foo()") + Condition + Error: + ! `foo()` was deprecated in ggplot2 edition 2025 and is now defunct. + +--- + + Code + edition_require(2025, what = "foo()") + Condition + Error in `edition_require()`: + ! foo() requires the 2025 edition of ggplot2. + diff --git a/tests/testthat/test-edition.R b/tests/testthat/test-edition.R new file mode 100644 index 0000000000..20378a164d --- /dev/null +++ b/tests/testthat/test-edition.R @@ -0,0 +1,10 @@ +test_that("basic edition requirement and deprecation works as intended", { + + local_edition(2025) + expect_snapshot(edition_deprecate(2025, what = "foo()"), error = TRUE) + expect_silent(edition_require(2025, what = "foo()")) + + local_edition(2024) + expect_silent(edition_deprecate(2025, what = "foo()")) + expect_snapshot(edition_require(2025, what = "foo()"), error = TRUE) +})