From e20f7e77d27051e285982dbd077ea87930a887ee Mon Sep 17 00:00:00 2001 From: Carl Suster Date: Tue, 9 Jan 2024 17:03:16 +1100 Subject: [PATCH 1/4] Support NEWS in Rd and md formats and under inst/ Mirrors the search path documented in utils::news() for NEWS files. Uses the internal machinery of tools to build news databases. --- R/show-news.R | 23 ++++++++++-- tests/testthat/test-show-news.R | 63 +++++++++++++++++++++++++++++++++ 2 files changed, 83 insertions(+), 3 deletions(-) create mode 100644 tests/testthat/test-show-news.R diff --git a/R/show-news.R b/R/show-news.R index 2349bec0b..03630ed68 100644 --- a/R/show-news.R +++ b/R/show-news.R @@ -7,15 +7,32 @@ #' @export show_news <- function(pkg = ".", latest = TRUE, ...) { pkg <- as.package(pkg) - news_path <- path(pkg$path, "NEWS") - if (!file_exists(news_path)) { + news_path <- path_abs(c( + file.path(pkg$path, "inst", "NEWS.Rd"), + file.path(pkg$path, "NEWS.md"), + file.path(pkg$path, "NEWS"), + file.path(pkg$path, "inst", "NEWS") + )) + news_path <- news_path[file_exists(news_path)] + + if (length(news_path) == 0) { cli::cli_abort("No NEWS found") } + news_db <- switch (path_ext(news_path), + Rd = ("tools" %:::% ".build_news_db_from_package_NEWS_Rd")(news_path), + md = ("tools" %:::% ".build_news_db_from_package_NEWS_md")(news_path), + ("tools" %:::% ".news_reader_default")(news_path) + ) + + if (is.null(news_db)) { + cli::cli_abort("Unable to parse NEWS") + } + check_dots_used(action = getOption("devtools.ellipsis_action", rlang::warn)) - out <- utils::news(..., db = ("tools" %:::% ".news_reader_default")(news_path)) + out <- utils::news(..., db = news_db) if (latest) { ver <- numeric_version(out$Version) recent <- ver == max(ver) diff --git a/tests/testthat/test-show-news.R b/tests/testthat/test-show-news.R new file mode 100644 index 000000000..344502f1d --- /dev/null +++ b/tests/testthat/test-show-news.R @@ -0,0 +1,63 @@ +test_that("can read NEWS.md in root directory", { + skip_on_cran() + + pkg <- local_package_create() + write( + "# test 0.0.1\n\n* Initial CRAN submission.\n", + path(pkg, "NEWS.md") + ) + + expect_no_error(show_news(pkg)) +}) + +test_that("can read NEWS.Rd in inst directory", { + skip_on_cran() + + pkg <- local_package_create() + + dir_create(pkg, "inst") + write( + "\\name{NEWS} +\\title{News for Package 'test'} + +\\section{CHANGES IN test VERSION 0.0.1}{ + \\itemize{ + \\item First version + } +}", + path(pkg, "inst", "NEWS.Rd") + ) + + expect_no_error(show_news(pkg)) +}) + +test_that("can read NEWS in inst directory", { + skip_on_cran() + + pkg <- local_package_create() + + dir_create(pkg, "inst") + write("v0.1-1 (2024-01-09)\n\n o first release", path(pkg, "inst", "NEWS")) + + expect_no_error(show_news(pkg)) +}) + +test_that("fails when NEWS is missing", { + skip_on_cran() + + pkg <- local_package_create() + + expect_error(show_news(pkg)) +}) + +test_that("fails when NEWS is improperly formatted", { + skip_on_cran() + + pkg <- local_package_create() + suppressMessages(usethis::with_project(pkg, use_news_md())) + + dir_create(pkg, "inst") + file_create(pkg, "inst", "NEWS.Rd") + + expect_error(show_news(pkg)) +}) From ef59f621fe37fd7dd4f18d26630dd6b5102bf8ac Mon Sep 17 00:00:00 2001 From: Hadley Wickham Date: Wed, 21 Jan 2026 12:36:11 -0600 Subject: [PATCH 2/4] Add news bullet --- NEWS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/NEWS.md b/NEWS.md index 9c2d1d782..c9bf0c24c 100644 --- a/NEWS.md +++ b/NEWS.md @@ -3,6 +3,7 @@ * New `check_mac_devel()` function to check a package using the macOS builder at https://mac.r-project.org/macbuilder/submit.html (@nfrerebeau, #2507) * `is_loading()` is now re-exported from pkgload (#2556). * `load_all()` now errors if called recursively, i.e. if you accidentally include a `load_all()` call in one of your R source files (#2617). +* `show_news()` now looks for NEWS files in the same locations as `utils::news()`: `inst/NEWS.Rd`, `NEWS.md`, `NEWS`, and `inst/NEWS` (@arcresu, #2499). # devtools 2.4.6 From b01e1ba6da138a577f590fec6d023d6d8dbdf05f Mon Sep 17 00:00:00 2001 From: Hadley Wickham Date: Wed, 21 Jan 2026 12:46:55 -0600 Subject: [PATCH 3/4] Extract out find_news() and test that --- R/show-news.R | 50 ++++++++++++++----------- tests/testthat/_snaps/show-news.md | 18 +++++++++ tests/testthat/test-show-news.R | 59 ++++++------------------------ 3 files changed, 59 insertions(+), 68 deletions(-) create mode 100644 tests/testthat/_snaps/show-news.md diff --git a/R/show-news.R b/R/show-news.R index 23e238996..15cbc6411 100644 --- a/R/show-news.R +++ b/R/show-news.R @@ -7,30 +7,22 @@ #' @export show_news <- function(pkg = ".", latest = TRUE, ...) { pkg <- as.package(pkg) + news_path <- find_news(pkg$path) - news_path <- path_abs(c( - file.path(pkg$path, "inst", "NEWS.Rd"), - file.path(pkg$path, "NEWS.md"), - file.path(pkg$path, "NEWS"), - file.path(pkg$path, "inst", "NEWS") - )) - news_path <- news_path[file_exists(news_path)] - - if (length(news_path) == 0) { - cli::cli_abort("No NEWS found") - } - - news_db <- switch( - path_ext(news_path), - Rd = ("tools" %:::% ".build_news_db_from_package_NEWS_Rd")(news_path), - md = ("tools" %:::% ".build_news_db_from_package_NEWS_md")(news_path), - ("tools" %:::% ".news_reader_default")(news_path) + withCallingHandlers( + { + news_db <- switch( + path_ext(news_path), + Rd = ("tools" %:::% ".build_news_db_from_package_NEWS_Rd")(news_path), + md = ("tools" %:::% ".build_news_db_from_package_NEWS_md")(news_path), + ("tools" %:::% ".news_reader_default")(news_path) + ) + }, + error = function(e) { + cli::cli_abort("Failed to parse NEWS file.", parent = e) + } ) - if (is.null(news_db)) { - cli::cli_abort("Unable to parse NEWS") - } - check_dots_used(action = getOption("devtools.ellipsis_action", rlang::warn)) out <- utils::news(..., db = news_db) @@ -42,3 +34,19 @@ show_news <- function(pkg = ".", latest = TRUE, ...) { out } } + +find_news <- function(path) { + news_paths <- c( + file.path(path, "inst", "NEWS.Rd"), + file.path(path, "NEWS.md"), + file.path(path, "NEWS"), + file.path(path, "inst", "NEWS") + ) + news_path <- news_paths[file_exists(news_paths)] + + if (length(news_path) == 0) { + cli::cli_abort("Failed to find NEWS file.") + } + + path_abs(news_path[[1]]) +} diff --git a/tests/testthat/_snaps/show-news.md b/tests/testthat/_snaps/show-news.md new file mode 100644 index 000000000..acc660f98 --- /dev/null +++ b/tests/testthat/_snaps/show-news.md @@ -0,0 +1,18 @@ +# fails when NEWS is missing or improperly formatted + + Code + show_news(pkg) + Condition + Error in `find_news()`: + ! Failed to find NEWS file. + +--- + + Code + show_news(pkg) + Condition + Error in `show_news()`: + ! Failed to parse NEWS file. + Caused by error: + ! NEWS.Rd: Sections \title, and \name must exist and be unique in Rd files + diff --git a/tests/testthat/test-show-news.R b/tests/testthat/test-show-news.R index 344502f1d..5f96d38e7 100644 --- a/tests/testthat/test-show-news.R +++ b/tests/testthat/test-show-news.R @@ -1,63 +1,28 @@ -test_that("can read NEWS.md in root directory", { - skip_on_cran() - - pkg <- local_package_create() - write( - "# test 0.0.1\n\n* Initial CRAN submission.\n", - path(pkg, "NEWS.md") - ) - - expect_no_error(show_news(pkg)) -}) - -test_that("can read NEWS.Rd in inst directory", { - skip_on_cran() - - pkg <- local_package_create() - - dir_create(pkg, "inst") - write( - "\\name{NEWS} -\\title{News for Package 'test'} - -\\section{CHANGES IN test VERSION 0.0.1}{ - \\itemize{ - \\item First version - } -}", - path(pkg, "inst", "NEWS.Rd") - ) - - expect_no_error(show_news(pkg)) -}) - -test_that("can read NEWS in inst directory", { - skip_on_cran() - +test_that("find_news() finds NEWS in all expected locations", { pkg <- local_package_create() dir_create(pkg, "inst") - write("v0.1-1 (2024-01-09)\n\n o first release", path(pkg, "inst", "NEWS")) + file_create(pkg, "inst", "NEWS") + expect_equal(path_rel(find_news(pkg), pkg), path("inst/NEWS")) - expect_no_error(show_news(pkg)) -}) + file_create(pkg, "NEWS") + expect_equal(path_rel(find_news(pkg), pkg), path("NEWS")) -test_that("fails when NEWS is missing", { - skip_on_cran() + file_create(pkg, "NEWS.md") + expect_equal(path_rel(find_news(pkg), pkg), path("NEWS.md")) - pkg <- local_package_create() - - expect_error(show_news(pkg)) + file_create(pkg, "inst", "NEWS.Rd") + expect_equal(path_rel(find_news(pkg), pkg), path("inst/NEWS.Rd")) }) -test_that("fails when NEWS is improperly formatted", { +test_that("fails when NEWS is missing or improperly formatted", { skip_on_cran() pkg <- local_package_create() - suppressMessages(usethis::with_project(pkg, use_news_md())) + expect_snapshot(show_news(pkg), error = TRUE) dir_create(pkg, "inst") file_create(pkg, "inst", "NEWS.Rd") - expect_error(show_news(pkg)) + expect_snapshot(show_news(pkg), error = TRUE) }) From 49288b96b607b64a4edcef603e6408630a49f328 Mon Sep 17 00:00:00 2001 From: Hadley Wickham Date: Wed, 21 Jan 2026 13:00:43 -0600 Subject: [PATCH 4/4] Skip on old R --- tests/testthat/test-show-news.R | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/testthat/test-show-news.R b/tests/testthat/test-show-news.R index 5f96d38e7..3691da53f 100644 --- a/tests/testthat/test-show-news.R +++ b/tests/testthat/test-show-news.R @@ -17,6 +17,7 @@ test_that("find_news() finds NEWS in all expected locations", { test_that("fails when NEWS is missing or improperly formatted", { skip_on_cran() + skip_unless_r(">= 4.2.0") # different error message pkg <- local_package_create() expect_snapshot(show_news(pkg), error = TRUE)