diff --git a/DESCRIPTION b/DESCRIPTION index 52df64ae..62dd5d70 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -45,6 +45,6 @@ Suggests: testthat, webmockr, jsonlite -RoxygenNote: 7.2.3 +RoxygenNote: 7.3.2 VignetteBuilder: knitr Config/testthat/edition: 3 diff --git a/NAMESPACE b/NAMESPACE index ad58cbcf..652ec811 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -76,6 +76,8 @@ S3method(pillar_shaft,pkg_metric_error) S3method(pkg_assess,list_of_pkg_ref) S3method(pkg_assess,pkg_ref) S3method(pkg_assess,tbl_df) +S3method(pkg_metric_export,list) +S3method(pkg_metric_export,tbl_df) S3method(pkg_score,list_of_pkg_metric) S3method(pkg_score,tbl_df) S3method(print,pkg_ref) @@ -116,6 +118,8 @@ export(get_assessments) export(metric_score) export(pkg_assess) export(pkg_metric) +export(pkg_metric_export) +export(pkg_metric_import) export(pkg_ref) export(pkg_score) export(score_error_NA) diff --git a/R/pkg_metric_export.R b/R/pkg_metric_export.R new file mode 100644 index 00000000..a50b64e9 --- /dev/null +++ b/R/pkg_metric_export.R @@ -0,0 +1,89 @@ +# Converts snake_case to PascalCase +to_pascal_case <- function(x) { + words <- strsplit(x, "_")[[1]] + paste0(toupper(substring(words, 1, 1)), substring(words, 2), collapse = "") +} + +#' Flatten scored metrics to DCF-style key-value pairs +#' +#' `pkg_metric_export()` converts scored package metrics into a flat list of +#' character strings in DCF (Debian Control File) style. Each metric is +#' written as a line in the form `Metric: `, where `` is the +#' PascalCase transformation of the metric name. +#' +#' This is useful for exporting results from `pkg_score()` into formats that +#' require simple key-value structures. +#' +#' @param x A `tbl_df` or `list` returned by `pkg_score()`, containing scored +#' `pkg_metric` values. The input may come from `pkg_score.tbl_df()` (for +#' many packages) or `pkg_score.list_of_pkg_metric()` (for a single package). +#' @param ... additional arguments unused +#' +#' @return A character vector, where each element is a DCF-style line +#' representing one scored metric (e.g., `"MetricHasNews: 1"`). +#' @seealso pkg_metric_import +#' @examples +#' \dontrun{ +#' # For a single package +#' metrics <- pkg_score(pkg_assess(pkg_ref("riskmetric")))[[1]] +#' pkg_metric_export(metrics) +#' +#' # For a data frame of packages +#' library(dplyr) +#' scores <- pkg_score(pkg_assess(as_tibble(pkg_ref("riskmetric")))) +#' pkg_metric_export(scores[1, ]) # flatten first package's results +#' } +#' +#' @export +pkg_metric_export <- function(x, ...) { + UseMethod("pkg_metric_export") +} + +#' @export +pkg_metric_export.list <- function(x, file = "PACKAGES", ...) { + record <- list() + for (name in names(x)) { + pascal_name <- paste0("Metric", to_pascal_case(name)) + value <- as.character(x[[name]][1]) + + # Replace NA values with "NA" string + record[[pascal_name]] <- ifelse(is.na(value), "NA", value) + } + + # Convert to one-row data frame + df <- as.data.frame(record, stringsAsFactors = FALSE) + write.dcf(df, file = file) + df +} + +#' @export +pkg_metric_export.tbl_df <- function(x, file = "PACKAGES", ...) { + x <- x[, setdiff(names(x), "pkg_ref")] + + records <- lapply(seq_len(nrow(x)), function(i) { + row <- x[i, , drop = FALSE] + record <- list() + + for (name in names(row)) { + value <- as.character(row[[name]]) + + # Replace NA values with "NA" string + value <- ifelse(is.na(row[[name]]), "NA", value) + + # Leave Package and Version untouched + if (name %in% c("package", "version")) { + pascal_name <- paste0(toupper(substring(name, 1, 1)), substring(name, 2)) + record[[pascal_name]] <- value + } else { + pascal_name <- paste0("Metric", to_pascal_case(name)) + record[[pascal_name]] <- value + } + } + + record + }) + + df <- do.call(rbind.data.frame, lapply(records, as.data.frame, stringsAsFactors = FALSE)) + write.dcf(df, file = file) + df +} diff --git a/R/pkg_metric_import.R b/R/pkg_metric_import.R new file mode 100644 index 00000000..cd1ac52b --- /dev/null +++ b/R/pkg_metric_import.R @@ -0,0 +1,15 @@ +#' Import metrics +#' +#' @param file (`character`) Path to the file to import. +#' +#' @returns The object in the same format and class as generated by riskmetric. +#' @export +#' @seealso pkg_metric_export() +pkg_metric_import <- function(file) { + if (!file.exists(file)) { + stop("Please provide a path to a file.") + } + dcf <- read.dcf(file) + dcf <- as.data.frame(dcf) + dcf +} diff --git a/man/pkg_assess.Rd b/man/pkg_assess.Rd index a5b7733c..fd7c810b 100644 --- a/man/pkg_assess.Rd +++ b/man/pkg_assess.Rd @@ -43,25 +43,25 @@ assessment applied. \section{Assessment function catalog}{ \describe{ -\item{\code{\link{assess_last_30_bugs_status}}}{vector indicating whether BugReports status is closed} \item{\code{\link{assess_covr_coverage}}}{Package unit test coverage} -\item{\code{\link{assess_size_codebase}}}{number of lines of code base} -\item{\code{\link{assess_export_help}}}{exported objects have documentation} -\item{\code{\link{assess_r_cmd_check}}}{Package check results} -\item{\code{\link{assess_dependencies}}}{Package dependency footprint} -\item{\code{\link{assess_reverse_dependencies}}}{List of reverse dependencies a package has} -\item{\code{\link{assess_license}}}{software is released with an acceptable license} -\item{\code{\link{assess_has_maintainer}}}{a vector of associated maintainers} +\item{\code{\link{assess_has_news}}}{number of discovered NEWS files} \item{\code{\link{assess_remote_checks}}}{Number of OS flavors that passed/warned/errored on R CMD check} +\item{\code{\link{assess_news_current}}}{NEWS file contains entry for current version number} +\item{\code{\link{assess_r_cmd_check}}}{Package check results} \item{\code{\link{assess_exported_namespace}}}{Objects exported by package} -\item{\code{\link{assess_has_website}}}{a vector of associated website urls} -\item{\code{\link{assess_downloads_1yr}}}{number of downloads in the past year} -\item{\code{\link{assess_has_news}}}{number of discovered NEWS files} \item{\code{\link{assess_has_vignettes}}}{number of discovered vignettes files} -\item{\code{\link{assess_has_examples}}}{proportion of discovered function files with examples} +\item{\code{\link{assess_export_help}}}{exported objects have documentation} +\item{\code{\link{assess_has_website}}}{a vector of associated website urls} +\item{\code{\link{assess_has_maintainer}}}{a vector of associated maintainers} +\item{\code{\link{assess_last_30_bugs_status}}}{vector indicating whether BugReports status is closed} +\item{\code{\link{assess_size_codebase}}}{number of lines of code base} \item{\code{\link{assess_has_source_control}}}{a vector of associated source control urls} \item{\code{\link{assess_has_bug_reports_url}}}{presence of a bug reports url in repository} -\item{\code{\link{assess_news_current}}}{NEWS file contains entry for current version number} +\item{\code{\link{assess_downloads_1yr}}}{number of downloads in the past year} +\item{\code{\link{assess_reverse_dependencies}}}{List of reverse dependencies a package has} +\item{\code{\link{assess_has_examples}}}{proportion of discovered function files with examples} +\item{\code{\link{assess_dependencies}}}{Package dependency footprint} +\item{\code{\link{assess_license}}}{software is released with an acceptable license} } } diff --git a/man/pkg_metric_export.Rd b/man/pkg_metric_export.Rd new file mode 100644 index 00000000..ebcfeb00 --- /dev/null +++ b/man/pkg_metric_export.Rd @@ -0,0 +1,45 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/pkg_metric_export.R +\name{pkg_metric_export} +\alias{pkg_metric_export} +\title{Flatten scored metrics to DCF-style key-value pairs} +\usage{ +pkg_metric_export(x, ...) +} +\arguments{ +\item{x}{A `tbl_df` or `list` returned by `pkg_score()`, containing scored +`pkg_metric` values. The input may come from `pkg_score.tbl_df()` (for +many packages) or `pkg_score.list_of_pkg_metric()` (for a single package).} + +\item{...}{additional arguments unused} +} +\value{ +A character vector, where each element is a DCF-style line + representing one scored metric (e.g., `"MetricHasNews: 1"`). +} +\description{ +`pkg_metric_export()` converts scored package metrics into a flat list of +character strings in DCF (Debian Control File) style. Each metric is +written as a line in the form `Metric: `, where `` is the +PascalCase transformation of the metric name. +} +\details{ +This is useful for exporting results from `pkg_score()` into formats that +require simple key-value structures. +} +\examples{ +\dontrun{ +# For a single package +metrics <- pkg_score(pkg_assess(pkg_ref("riskmetric")))[[1]] +pkg_metric_export(metrics) + +# For a data frame of packages +library(dplyr) +scores <- pkg_score(pkg_assess(as_tibble(pkg_ref("riskmetric")))) +pkg_metric_export(scores[1, ]) # flatten first package's results +} + +} +\seealso{ +pkg_metric_import +} diff --git a/man/pkg_metric_import.Rd b/man/pkg_metric_import.Rd new file mode 100644 index 00000000..1fdbffa5 --- /dev/null +++ b/man/pkg_metric_import.Rd @@ -0,0 +1,20 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/pkg_metric_import.R +\name{pkg_metric_import} +\alias{pkg_metric_import} +\title{Import metrics} +\usage{ +pkg_metric_import(file) +} +\arguments{ +\item{file}{(`character`) Path to the file to import.} +} +\value{ +The object in the same format and class as generated by riskmetric. +} +\description{ +Import metrics +} +\seealso{ +pkg_metric_export() +} diff --git a/man/riskmetric.Rd b/man/riskmetric.Rd index 87bcdb8f..366392f1 100644 --- a/man/riskmetric.Rd +++ b/man/riskmetric.Rd @@ -2,8 +2,8 @@ % Please edit documentation in R/riskmetric-package.R \docType{package} \name{riskmetric} -\alias{riskmetric} \alias{riskmetric-package} +\alias{riskmetric} \title{riskmetric} \description{ Facilities for assessing R packages against a number of metrics to help