library(tools)
library(data.table)
# 1. Fetch CRAN metadata and reverse dependencies (Depends: data.table)
cran_db = as.data.table(CRAN_package_db())
cran_deps = package_dependencies("data.table", db = as.matrix(cran_db), reverse = TRUE, which = "Depends")$data.table
cran_sub = cran_db[Package %in% cran_deps]
# 2. Fetch Bioconductor metadata and reverse dependencies (Depends: data.table)
bioc_views_url = "https://bioconductor.org/packages/release/bioc/VIEWS"
tf = tempfile()
download.file(bioc_views_url, tf, quiet = TRUE)
bioc_views = as.data.table(read.dcf(tf))
bioc_deps = package_dependencies("data.table", db = as.matrix(bioc_views), reverse = TRUE, which = "Depends")$data.table
bioc_sub = bioc_views[Package %in% bioc_deps]
# 3. Harvest existing tracked links dynamically from GitHub issue #3076 body
issue_body = tryCatch({
res = httr::GET("https://api.github.com/repos/Rdatatable/data.table/issues/3076", httr::user_agent("Rdatatable"))
httr::content(res, "parsed")$body
}, error = function(e) "")
known_links = list()
fixed_links = list()
lines = strsplit(issue_body, "\r?\n")[[1]]
for (l in lines) {
# Parse table rows: | # | [pkg](repo) | [Host](host_url) | Last Upload | [✅](issue) | [✅](pr) | [date](fix) |
if (grepl(r"{^\|.*\|.*\|.*\|}", l)) {
parts = strsplit(l, "\\|")[[1]]
if (length(parts) > 0 && parts[1] == "") parts = parts[-1]
parts = trimws(parts)
if (length(parts) >= 4 && !any(grepl("Last Upload|:---", parts))) {
pkg_col = if (grepl("^[0-9]+$", parts[1])) 2 else 1
pkg = sub(r"{^\[([^]]+)\].*}", "\\1", parts[pkg_col])
issue_col = pkg_col + 3
pr_col = pkg_col + 4
fix_col = pkg_col + 5
if (length(parts) >= issue_col && grepl(r"{https?://}", parts[issue_col])) {
m_iss = regmatches(parts[issue_col], regexec(r"{\((https?://[^)]+)\)}", parts[issue_col]))[[1]]
if (length(m_iss) > 1) {
known_links[[length(known_links) + 1]] = data.table(Package = pkg, Type = "Issue", URL = m_iss[2])
}
}
if (length(parts) >= pr_col && grepl(r"{https?://}", parts[pr_col])) {
m_pr = regmatches(parts[pr_col], regexec(r"{\((https?://[^)]+)\)}", parts[pr_col]))[[1]]
if (length(m_pr) > 1) {
known_links[[length(known_links) + 1]] = data.table(Package = pkg, Type = "PR", URL = m_pr[2])
}
}
if (length(parts) >= fix_col && grepl(r"{https?://}", parts[fix_col])) {
fixed_links[[length(fixed_links) + 1]] = data.table(Package = pkg, Fixed_In_Dev = parts[fix_col])
}
}
}
}
links_dt = if (length(known_links) > 0) {
dcast(unique(rbindlist(known_links)), Package ~ Type, value.var = "URL", fun.aggregate = function(x) x[1])
} else {
data.table(Package = character(), Issue = character(), PR = character())
}
fixed_dt = if (length(fixed_links) > 0) unique(rbindlist(fixed_links)) else data.table(Package = character(), Fixed_In_Dev = character())
# Helper: extract source forge repository (GitHub/GitLab) from package fields or links
extract_repo <- function(pkg, url_fields, issue_url, pr_url) {
ref_url = na.omit(c(issue_url, pr_url))
if (length(ref_url) > 0) {
m = regmatches(ref_url[1], regexec(r"{(https?://[^/]+/[^/]+/[^/#?]+)}", ref_url[1]))[[1]]
if (length(m) > 1) {
repo = sub(r"{/(issues|pull|tree|blob|commit)/?.*$}", "", m[2])
repo = sub(r"{/-/issues/?.*$}", "", repo)
return(repo)
}
}
urls = unlist(strsplit(url_fields[!is.na(url_fields)], "[,\n\t ]+"))
urls = urls[urls != ""]
repo_urls = grep("git(hub|lab)", urls, value = TRUE)
repo_urls = repo_urls[!grepl("\\.github\\.io", repo_urls)]
if (length(repo_urls) > 0) {
m = regmatches(repo_urls[1], regexec(r"{(https?://[^/]+/[^/]+/[^/#?]+)}", repo_urls[1]))[[1]]
if (length(m) > 1) {
repo = sub(r"{\.git$}", "", m[2])
repo = sub(r"{/(issues|pull)/?.*$}", "", repo)
repo = sub(r"{/-/issues/?.*$}", "", repo)
return(repo)
}
}
NA_character_
}
# CRAN data
cran_dt = data.table(
Package_Name = cran_sub$Package,
Host_Name = "CRAN",
Host_URL = sprintf("https://cran.r-project.org/package=%s", cran_sub$Package),
Last_Upload = as.character(as.Date(as.POSIXct(cran_sub[["Date/Publication"]]))),
URL_Fields = paste(cran_sub$URL, cran_sub$BugReports, sep = ", ")
)
# Bioconductor data
bioc_dt = data.table(
Package_Name = bioc_sub$Package,
Host_Name = "Bioconductor",
Host_URL = sprintf("https://bioconductor.org/packages/%s", bioc_sub$Package),
Last_Upload = bioc_sub[["Date/Publication"]],
URL_Fields = paste(bioc_sub$URL, bioc_sub$BugReports, bioc_sub$git_url, sep = ", ")
)
all_pkgs = rbind(cran_dt, bioc_dt)
all_pkgs = merge(all_pkgs, links_dt, by.x = "Package_Name", by.y = "Package", all.x = TRUE)
all_pkgs = merge(all_pkgs, fixed_dt, by.x = "Package_Name", by.y = "Package", all.x = TRUE)
all_pkgs[, Repo := mapply(extract_repo, Package_Name, URL_Fields, Issue, PR)]
all_pkgs[, Package := ifelse(!is.na(Repo) & Repo != "", sprintf("[%s](%s)", Package_Name, Repo), Package_Name)]
all_pkgs[, Host := sprintf("[%s](%s)", Host_Name, Host_URL)]
all_pkgs[, `Issue?` := ifelse(!is.na(Issue) & Issue != "", sprintf("[✅](%s)", Issue), "")]
all_pkgs[, `PR?` := ifelse(!is.na(PR) & PR != "", sprintf("[✅](%s)", PR), "")]
all_pkgs[, `Fixed in dev` := ifelse(!is.na(Fixed_In_Dev), Fixed_In_Dev, "")]
# Sort by Host (CRAN then Bioconductor) and descending Last_Upload
all_pkgs[, Host_Order := factor(Host_Name, levels = c("CRAN", "Bioconductor"))]
setorder(all_pkgs, Host_Order, -Last_Upload)
# Leftmost column numbered 1...n with empty header
all_pkgs[, ` ` := .I]
out_dt = all_pkgs[, .(` `, Package, Host, `Last Upload` = Last_Upload, `Issue?`, `PR?`, `Fixed in dev`)]
writeLines(knitr::kable(out_dt, format = "markdown", align = c("r", "l", "l", "l", "c", "c", "l")))
CRAN + BioC:

I've only recently realized how bad
Depends:is, thanks to Jan's importing vignette. I just made its discouragement stronger : 51590bcWe could disallow Depends. This would also be beneficial to
cedta()'s awkward implementation on its last line where it needs to do the tryCatch just for packages which Depend; that line could be removed.But before we disallow Depends, we'd need to ask 69 CRAN packages to change from Depends to Imports. (Most revdeps already Import.) The longer we leave it, the greater the potential for new packages using Depends to be added to CRAN and the harder it will be to change.
Repro script to re-generate this table