Skip to content
Merged
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
98 changes: 98 additions & 0 deletions crates/ark/src/modules/positron/packages_pane.R
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,104 @@
as.list(version)
}

# Return detail fields for a single installed package by name.
#' @export
.ps.rpc.pkg_detail <- function(name) {
installed <- rownames(utils::installed.packages())
if (!nzchar(name) || !(name %in% installed)) {
return(NULL)
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It will be more efficient to just go directly to utils::packageDescription(), which will return NA if the package is not installed. This avoids reading and parsing DESCRIPTION of all installed packages for this existence check. installed.packages() docs say:

It will be slow when thousands of packages are installed, so do not use it to find out if a named package is installed (use find.package or system.file) nor to find out if a package is usable (call requireNamespace or require and check the return value) nor to find details of a small number of packages (use packageDescription).

fields <- c(
"Title",
"Author",
"Maintainer",
"License",
"Depends",
"Imports",
"LinkingTo",
"Repository",
"Date/Publication"
)
d <- utils::packageDescription(name, fields = fields)

# Collapse DCF whitespace; return NULL for missing (NA) fields.
clean <- function(x) {
if (is.null(x) || is.na(x)) {
return(NULL)
}
trimws(gsub("\\s+", " ", x, perl = TRUE))
}

# Parse a comma-separated dependency field into bare package names
# (strip version constraints like "(>= 1.0)").
parse_deps <- function(x) {
if (is.null(x) || is.na(x)) {
return(character(0))
}
parts <- trimws(strsplit(x, ",")[[1]])
names <- trimws(sub("\\(.*\\)", "", parts))
names[nzchar(names)]
}

# Reduce an R License field to its primary license: the first alternative
# (before "|"), without the "+ file LICENSE" clause.
primary_license <- function(x) {
if (is.null(x)) {
return(NULL)
}
first <- trimws(strsplit(x, "\\|")[[1]][1])
first <- trimws(sub(
"\\s*\\+\\s*file\\s+.*$",
"",
first,
ignore.case = TRUE
))
if (!nzchar(first)) {
return(NULL)
}
first
}

base_pkgs <- rownames(utils::installed.packages(priority = "base"))

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given the downsides to calling installed.packages() when it can be avoided, I would just hardwire these. They don't change. I have a history of doing same:

https://github.com/r-lib/usethis/blob/bd15dceaf98338b6758becc38193fdaff2d9776a/R/tidyverse.R#L363-L403

If you really want to compute it, I'd suggest:

tools:::.get_standard_package_names()$base

deps <- unique(c(
parse_deps(d$Depends),
parse_deps(d$Imports),
parse_deps(d$LinkingTo)
))
deps <- setdiff(deps, c("R", base_pkgs))

out <- list(name = name, dependencyCount = length(deps))

# Prefer Maintainer for author display; fall back to Author.
author <- clean(d$Maintainer)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FWIW a CRAN package absolutely must have a Maintainer. But I suppose this pane could populate from a non-CRAN source? Just thought I'd mention.

if (is.null(author)) {
author <- clean(d$Author)
}
title <- clean(d$Title)
license <- primary_license(clean(d$License))
repo <- clean(d$Repository)
published <- clean(d[["Date/Publication"]])

if (!is.null(title)) {
out$title <- title
}
if (!is.null(author)) {
out$author <- author
}
if (!is.null(license)) {
out$license <- license
}
if (!is.null(repo)) {
out$sourceRepository <- repo
}
if (!is.null(published)) {
out$publishedDate <- published

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you definitely want to drop the NULLs (I guess that's favorable on the other side?), something along these lines might be more readable:

out <- list(
    name = name,
    title = title,
    author = author,
    license = license,
    sourceRepository = repo,
    publishedDate = published
)
Filter(Negate(is.null), out)

}

out
}


# Return the list of outdated packages with their latest available versions.
#
# `utils::old.packages()` queries the user's configured repositories, so
Expand Down
Loading