From 0154df1af31f440ff7f956eb1ac390989d048bb9 Mon Sep 17 00:00:00 2001 From: Teun van den Brand Date: Tue, 26 Nov 2024 13:56:48 +0100 Subject: [PATCH 1/3] initial infrastructure --- DESCRIPTION | 3 +++ R/edition.R | 72 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 75 insertions(+) create mode 100644 R/edition.R diff --git a/DESCRIPTION b/DESCRIPTION index 585a51285a..78c1675077 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -33,11 +33,13 @@ Depends: R (>= 4.0) Imports: cli, + desc, grDevices, grid, gtable (>= 0.1.1), isoband, lifecycle (> 1.0.1), + pkgload, rlang (>= 1.1.0), scales (>= 1.3.0), stats, @@ -122,6 +124,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..9c2baa590c --- /dev/null +++ b/R/edition.R @@ -0,0 +1,72 @@ + +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 <- find_description(path = ".", package = pkg) + if (is.null(desc)) { + return(default) + } + + # Look up edition from the description + field_name <- "Config/ggplot2/edition" + edition <- as.integer(desc$get_field(field_name, default = default)) + + # 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)) { + return(desc::desc(package = package)) + } else { + try_fetch( + pkgload::pkg_desc(path), + error = function(e) NULL + ) + } +} + +get_pkg_name <- function(env = parent.frame()) { + env <- topenv(env) + name <- environmentName(env) + if (!isNamespace(env) && name != "R_GlobalEnv") { + return(NULL) + } + name +} From 28ca4f99fa2f059eeec1aee9ef8f5375a4215041 Mon Sep 17 00:00:00 2001 From: Teun van den Brand Date: Mon, 6 Jan 2025 11:54:32 +0100 Subject: [PATCH 2/3] avoid desc/pkgload dependencies --- DESCRIPTION | 2 -- R/edition.R | 17 +++++++++-------- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index 78c1675077..c7fffa1b49 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -33,13 +33,11 @@ Depends: R (>= 4.0) Imports: cli, - desc, grDevices, grid, gtable (>= 0.1.1), isoband, lifecycle (> 1.0.1), - pkgload, rlang (>= 1.1.0), scales (>= 1.3.0), stats, diff --git a/R/edition.R b/R/edition.R index 9c2baa590c..0d4ac73187 100644 --- a/R/edition.R +++ b/R/edition.R @@ -17,14 +17,14 @@ edition_get <- function(.env = parent.frame(), default = 2024L) { } # Try to query package description - desc <- find_description(path = ".", package = pkg) - if (is.null(desc)) { + 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(desc$get_field(field_name, default = default)) + edition <- as.integer(read.dcf(desc_file, fields = field_name)) # Cache result env_bind(ggplot_edition, !!pkg := edition) @@ -53,13 +53,14 @@ edition_require <- function(edition, what, .env = parent.frame()) { find_description <- function(path, package = NULL) { if (!is.null(package)) { - return(desc::desc(package = package)) + path <- system.file(package = package, "DESCRIPTION") } else { - try_fetch( - pkgload::pkg_desc(path), - error = function(e) NULL - ) + path <- file.path(path, "DESCRIPTION") } + if (!file.exists(path)) { + return(NULL) + } + path } get_pkg_name <- function(env = parent.frame()) { From 43c10eeae6dcc56f065180cd695e25e804ede844 Mon Sep 17 00:00:00 2001 From: Teun van den Brand Date: Mon, 6 Jan 2025 12:10:58 +0100 Subject: [PATCH 3/3] add basic test --- tests/testthat/_snaps/edition.md | 16 ++++++++++++++++ tests/testthat/test-edition.R | 10 ++++++++++ 2 files changed, 26 insertions(+) create mode 100644 tests/testthat/_snaps/edition.md create mode 100644 tests/testthat/test-edition.R 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) +})