R/license_note.R

Defines functions prep_authors write_license_note

Documented in write_license_note

#' Generate LICENSE.note file.
#'
#' LICENSE.note generated by this function contains information about all
#' recursive dependencies in Rust crate.
#'
#' @param path character scalar, the R package directory
#' @param quiet logical scalar, whether to signal successful writing of
#' LICENSE.note (default is `FALSE`)
#' @param force logical scalar, whether to regenerate LICENSE.note if
#' LICENSE.note already exists (default is `TRUE`)
#'
#' @return text printed to LICENSE.note (invisibly).
#'
#' @export
#'
#' @examples
#' \dontrun{
#' write_license_note()
#' }
write_license_note <- function(
    path = ".",
    quiet = FALSE,
    force = TRUE) {
  check_string(path, class = "rextendr_error")
  check_bool(quiet, class = "rextendr_error")
  check_bool(force, class = "rextendr_error")

  outfile <- rprojroot::find_package_root_file(
    "LICENSE.note",
    path = path
  )

  args <- c(
    "metadata",
    "--format-version=1"
  )

  metadata <- run_cargo(
    args,
    wd = find_extendr_crate(path = path),
    echo = FALSE,
    parse_json = TRUE
  )

  packages <- metadata[["packages"]]

  # did we actually get the recursive dependency metadata we need?
  required_variables <- c("name", "repository", "authors", "license", "id")

  packages_exist <- is.data.frame(packages) &&
    !is.null(packages) &&
    nrow(packages) > 0 &&
    all(required_variables %in% names(packages))

  if (!packages_exist) {
    cli::cli_abort(
      "Unable to write LICENSE.note.",
      "Metadata for recursive dependencies not found.",
      call = rlang::caller_call(),
      class = "rextendr_error"
    )
  }

  # exclude current package from LICENSE.note
  current_package <- metadata[["resolve"]][["root"]]

  current_package_exists <- length(current_package) == 1 &&
    is.character(current_package) &&
    !is.null(current_package)

  if (!current_package_exists) {
    cli::cli_abort(
      "Unable to write LICENSE.note.",
      "Failed to identify current Rust crate.",
      call = rlang::caller_call(),
      class = "rextendr_error"
    )
  }

  packages <- packages[packages[["id"]] != current_package, ]

  # replace missing values
  packages[["respository"]] <- replace_na(
    packages[["repository"]],
    "unknown"
  )

  packages[["licenses"]] <- replace_na(
    packages[["repository"]],
    "not provided"
  )

  # remove email addresses and special characters and combine all authors
  # of a crate into a single character scalar
  packages[["authors"]] <- unlist(Map(
    prep_authors,
    packages[["authors"]],
    packages[["name"]]
  ))

  separator <- "-------------------------------------------------------------"

  note_header <- paste0(
    "The binary compiled from the source code of this package ",
    "contains the following Rust crates:\n",
    "\n",
    "\n",
    separator
  )

  note_body <- paste0(
    "\n",
    "Name:        ", packages[["name"]], "\n",
    "Repository:  ", packages[["repository"]], "\n",
    "Authors:     ", packages[["authors"]], "\n",
    "License:     ", packages[["license"]], "\n",
    "\n",
    separator
  )

  write_file(
    text = c(note_header, note_body),
    path = outfile,
    search_root_from = path,
    quiet = quiet,
    overwrite = force
  )
}

prep_authors <- function(authors, package) {
  authors <- ifelse(
    is.na(authors),
    paste0(package, " authors"),
    authors
  )

  authors <- stringi::stri_replace_all_regex(authors, r"(\ <.+?>)", "")

  paste0(authors, collapse = ", ")
}
extendr/rextendr documentation built on April 5, 2025, 1:53 a.m.