Skip to content

Commit 6d02e7d

Browse files
authored
Merge pull request #22 from e-kotov/issue-21-add-version-parameter
Issue 21 add version parameter
2 parents 633782a + bf8f504 commit 6d02e7d

7 files changed

Lines changed: 278 additions & 14 deletions

File tree

.Rbuildignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,4 @@
1414
^man/figures/logo\.png$
1515
^man/figures/card\.png$
1616
^CRAN-SUBMISSION$
17+
^.gemini$

.gemini/settings.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"maxSessionTurns": 50,
3+
"telemetry": {
4+
"enabled": false,
5+
"target": "gcp"
6+
}
7+
}

R/to_txt.R

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@
1111
#' \item a package name not installed (which will then be downloaded from CRAN).
1212
#' }
1313
#' @param file Optional. Save path for the output text file. If set, the function will return the path to the file instead of the combined text. Defaults to `NULL`.
14-
#' @param force_fetch `logical`. If `TRUE`, the package source will be fetched from CRAN as a tar.gz archive even if the package is already installed locally. Default is `FALSE`.
14+
#' @param force_fetch `logical`. If `TRUE`, the package source will be fetched from CRAN as a tar.gz archive even if the package is already installed locally. Default is `FALSE`, but when `version` is specified, it will be set to `TRUE`.
15+
#' @param version Optional. A `character` string specifying the package version to fetch from CRAN. If not provided, the latest version will be used.
1516
#' @param content A character vector specifying which components to include in the output.
1617
#' Possible values are:
1718
#' \itemize{
@@ -70,6 +71,7 @@ rdd_to_txt <- function(
7071
file = NULL,
7172
content = "all",
7273
force_fetch = FALSE,
74+
version = NULL,
7375
keep_files = "none",
7476
cache_path = getOption("rdocdump.cache_path"),
7577
repos = getOption("rdocdump.repos", getOption("repos"))
@@ -98,7 +100,13 @@ rdd_to_txt <- function(
98100
}
99101

100102
# Resolve package source path using the existing helper.
101-
pkg_info <- resolve_pkg_path(pkg, cache_path, force_fetch = force_fetch)
103+
pkg_info <- resolve_pkg_path(
104+
pkg,
105+
cache_path,
106+
force_fetch = force_fetch || !is.null(version),
107+
version = version,
108+
repos = repos
109+
)
102110
pkg_path <- pkg_info$pkg_path
103111

104112
# Initialize component texts.
@@ -122,9 +130,9 @@ rdd_to_txt <- function(
122130
file = NULL,
123131
include_tests = FALSE,
124132
include_roxygen = FALSE,
125-
force_fetch = force_fetch,
133+
force_fetch = force_fetch || !is.null(version),
126134
cache_path = cache_path,
127-
keep_files = "both", # make sure the files are not deleted prematurely, as rdd_to_txt will take care of that later
135+
keep_files = "both" # make sure the files are not deleted prematurely, as rdd_to_txt will take care of that later
128136
)
129137
}
130138

R/util_resolve_pkg_path.R

Lines changed: 59 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ resolve_pkg_path <- function(
1414
pkg,
1515
cache_path = NULL,
1616
force_fetch = FALSE,
17+
version = NULL,
1718
repos = getOption("rdocdump.repos", getOption("repos"))
1819
) {
1920
if (!is.character(pkg) || length(pkg) != 1L) {
@@ -111,7 +112,7 @@ resolve_pkg_path <- function(
111112
} else {
112113
NULL
113114
}
114-
if (!is.null(pkg_found)) {
115+
if (!is.null(pkg_found) && is.null(version)) {
115116
# Installed package found.
116117
return(list(
117118
pkg_path = pkg_found,
@@ -134,12 +135,63 @@ resolve_pkg_path <- function(
134135
"Using a repository URL from posit.co or r-universe.dev may result in pre-built binaries being downloaded instead of the package source."
135136
)
136137
}
137-
dp <- utils::download.packages(
138-
pkg,
139-
destdir = dest_dir,
140-
type = "source",
141-
repos = repos
142-
)
138+
139+
if (!is.null(version)) {
140+
# Construct URL for a specific version.
141+
repo_url <- repos[1] # Use the first repo.
142+
tar_filename <- paste0(pkg, "_", version, ".tar.gz")
143+
# Try archive first.
144+
url <- file.path(repo_url, "src/contrib/Archive", pkg, tar_filename)
145+
# Try downloading.
146+
res <- try(
147+
suppressWarnings(
148+
utils::download.file(
149+
url,
150+
destfile = file.path(dest_dir, tar_filename),
151+
mode = "wb",
152+
quiet = TRUE
153+
)
154+
),
155+
silent = TRUE
156+
)
157+
# If archive fails, try main contrib.
158+
if (inherits(res, "try-error") || res != 0) {
159+
url <- file.path(repo_url, "src/contrib", tar_filename)
160+
res <- try(
161+
suppressWarnings(
162+
utils::download.file(
163+
url,
164+
destfile = file.path(dest_dir, tar_filename),
165+
mode = "wb",
166+
quiet = TRUE
167+
)
168+
),
169+
silent = TRUE
170+
)
171+
}
172+
if (inherits(res, "try-error") || res != 0) {
173+
stop(paste(
174+
"Could not download package",
175+
pkg,
176+
"version",
177+
version,
178+
"from",
179+
url
180+
))
181+
}
182+
dp <- matrix(
183+
c(tar_filename, file.path(dest_dir, tar_filename)),
184+
nrow = 1
185+
)
186+
} else {
187+
dp <- utils::download.packages(
188+
pkg,
189+
destdir = dest_dir,
190+
type = "source",
191+
repos = repos
192+
)
193+
}
194+
143195
if (nrow(dp) < 1L) {
144196
stop("Package not found on CRAN.")
145197
}

man/rdd_to_txt.Rd

Lines changed: 4 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

man/resolve_pkg_path.Rd

Lines changed: 4 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

tests/testthat/test-to_txt.R

Lines changed: 191 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,17 @@
11
# Tests for the rdd_to_txt function in the rdocdump package
2-
fake_resolve_pkg_path <- function(pkg, cache_path, force_fetch, repos) {
2+
fake_resolve_pkg_path <- function(
3+
pkg,
4+
cache_path,
5+
force_fetch,
6+
version,
7+
repos
8+
) {
39
list(
410
pkg_path = pkg,
511
is_installed = FALSE,
612
pkg_name = "testpkg",
713
tar_path = NULL,
14+
version = NULL,
815
extracted_path = NULL
916
)
1017
}
@@ -262,3 +269,186 @@ test_that("rdd_to_txt keeps both tar.gz archive and extracted files when keep_fi
262269
# Clean up the cache directory.
263270
unlink(cache_dir, recursive = TRUE)
264271
})
272+
273+
test_that("rdd_to_txt fetches a specific package version when 'version' is provided", {
274+
skip_on_cran()
275+
skip_if_offline()
276+
277+
cache_dir <- tempfile("cache_version_test")
278+
dir.create(cache_dir)
279+
280+
# Use a package with a known version history, e.g., "jsonlite"
281+
# and a version that is not the latest.
282+
pkg_name <- "ini"
283+
pkg_version <- "0.1"
284+
285+
old_repos <- getOption("repos")
286+
options(repos = c(CRAN = "https://cloud.r-project.org"))
287+
288+
# Fetch the package with the specified version.
289+
out <- suppressWarnings(rdd_to_txt(
290+
pkg_name,
291+
version = pkg_version,
292+
force_fetch = TRUE,
293+
keep_files = "extracted",
294+
cache_path = cache_dir
295+
))
296+
297+
options(repos = old_repos)
298+
299+
# Check that the extracted directory for the correct version exists.
300+
expected_dir <- file.path(cache_dir, pkg_name, pkg_version)
301+
expect_true(dir.exists(expected_dir))
302+
303+
# Verify the version from the DESCRIPTION file.
304+
desc_file <- file.path(expected_dir, "DESCRIPTION")
305+
expect_true(file.exists(desc_file))
306+
307+
desc_content <- readLines(desc_file)
308+
version_line <- grep("Version:", desc_content, value = TRUE)
309+
expect_true(grepl(pkg_version, version_line))
310+
311+
# Clean up the cache directory.
312+
unlink(cache_dir, recursive = TRUE)
313+
})
314+
315+
test_that("rdd_to_txt sets force_fetch=TRUE internally whenever 'version' is provided", {
316+
calls <- new.env(parent = emptyenv())
317+
calls$resolve_force_fetch <- NULL
318+
calls$rdd_force_fetch <- NULL
319+
320+
fake_resolve_pkg_path_spy <- function(
321+
pkg,
322+
cache_path,
323+
force_fetch,
324+
version,
325+
repos
326+
) {
327+
calls$resolve_force_fetch <- force_fetch
328+
list(
329+
pkg_path = tempfile("pkg_"),
330+
is_installed = FALSE,
331+
pkg_name = "ini",
332+
tar_path = NULL,
333+
version = version,
334+
extracted_path = NULL
335+
)
336+
}
337+
338+
fake_rdd_extract_code_spy <- function(
339+
pkg,
340+
file,
341+
include_tests,
342+
include_roxygen,
343+
force_fetch,
344+
cache_path,
345+
keep_files
346+
) {
347+
calls$rdd_force_fetch <- force_fetch
348+
"CODE"
349+
}
350+
351+
# No-op helpers to avoid touching filesystem or requiring real pkg content
352+
fake_cleanup_files <- function(pkg_info, keep_files) invisible(NULL)
353+
fake_combine_rd <- function(...) "" # not used when content = "code"
354+
fake_combine_vignettes <- function(...) "" # not used when content = "code"
355+
356+
local_mocked_bindings(
357+
resolve_pkg_path = fake_resolve_pkg_path_spy,
358+
rdd_extract_code = fake_rdd_extract_code_spy,
359+
cleanup_files = fake_cleanup_files,
360+
combine_rd = fake_combine_rd,
361+
combine_vignettes = fake_combine_vignettes,
362+
.package = "rdocdump"
363+
)
364+
365+
out <- rdd_to_txt(
366+
"ini",
367+
version = "0.1", # << the trigger
368+
force_fetch = FALSE, # user-specified FALSE should be overridden
369+
content = "code",
370+
keep_files = "none"
371+
)
372+
373+
expect_identical(out, "CODE")
374+
expect_true(isTRUE(calls$resolve_force_fetch))
375+
expect_true(isTRUE(calls$rdd_force_fetch))
376+
})
377+
378+
test_that("rdd_to_txt passes through force_fetch unchanged when 'version' is NULL", {
379+
calls <- new.env(parent = emptyenv())
380+
calls$resolve_force_fetch <- NULL
381+
calls$rdd_force_fetch <- NULL
382+
383+
fake_resolve_pkg_path_spy <- function(
384+
pkg,
385+
cache_path,
386+
force_fetch,
387+
version,
388+
repos
389+
) {
390+
calls$resolve_force_fetch <- force_fetch
391+
list(
392+
pkg_path = tempfile("pkg_"),
393+
is_installed = FALSE,
394+
pkg_name = "ini",
395+
tar_path = NULL,
396+
version = version,
397+
extracted_path = NULL
398+
)
399+
}
400+
401+
fake_rdd_extract_code_spy <- function(
402+
pkg,
403+
file,
404+
include_tests,
405+
include_roxygen,
406+
force_fetch,
407+
cache_path,
408+
keep_files
409+
) {
410+
calls$rdd_force_fetch <- force_fetch
411+
"CODE"
412+
}
413+
414+
fake_cleanup_files <- function(pkg_info, keep_files) invisible(NULL)
415+
fake_combine_rd <- function(...) ""
416+
fake_combine_vignettes <- function(...) ""
417+
418+
local_mocked_bindings(
419+
resolve_pkg_path = fake_resolve_pkg_path_spy,
420+
rdd_extract_code = fake_rdd_extract_code_spy,
421+
cleanup_files = fake_cleanup_files,
422+
combine_rd = fake_combine_rd,
423+
combine_vignettes = fake_combine_vignettes,
424+
.package = "rdocdump"
425+
)
426+
427+
# Case A: user passes force_fetch = FALSE, version = NULL -> internal should be FALSE
428+
out_A <- rdd_to_txt(
429+
"ini",
430+
version = NULL,
431+
force_fetch = FALSE,
432+
content = "code",
433+
keep_files = "none"
434+
)
435+
expect_identical(out_A, "CODE")
436+
expect_false(isTRUE(calls$resolve_force_fetch))
437+
expect_false(isTRUE(calls$rdd_force_fetch))
438+
439+
# Reset captured values
440+
calls$resolve_force_fetch <- NULL
441+
calls$rdd_force_fetch <- NULL
442+
443+
# Case B: user passes force_fetch = TRUE, version = NULL -> internal should be TRUE
444+
out_B <- rdd_to_txt(
445+
"ini",
446+
version = NULL,
447+
force_fetch = TRUE,
448+
content = "code",
449+
keep_files = "none"
450+
)
451+
expect_identical(out_B, "CODE")
452+
expect_true(isTRUE(calls$resolve_force_fetch))
453+
expect_true(isTRUE(calls$rdd_force_fetch))
454+
})

0 commit comments

Comments
 (0)