R/cff-read.R

Defines functions cff_safe_read_citation cff_read_bib cff_read_citation_file cff_read_citation cff_read_description cff_read_cff_citation cff_read

Documented in cff_read cff_read_bib cff_read_cff_citation cff_read_citation cff_read_description

#' Read an external file as a [`cff`] object
#'
#' @description
#' Read files and convert them to [`cff`] objects. Supported files are:
#' - `CITATION.cff` files.
#' - `DESCRIPTION` files.
#' - \R citation files (usually located in `inst/CITATION`).
#' - BibTeX files (with extension `*.bib`).
#'
#' [cff_read()] attempts to guess the type of file provided in `path`. However,
#' we provide aliases for each specific file type:
#' - [cff_read_cff_citation()], which uses [yaml::read_yaml()].
#' - [cff_read_description()], which uses [desc::desc()].
#' - [cff_read_citation()], which uses [utils::readCitationFile()].
#' - [cff_read_bib()], which requires \CRANpkg{bibtex} (>= 0.5.0) and uses
#'   [bibtex::read.bib()].
#'
#' @param path A path to a file.
#' @param encoding Encoding to be assumed for `path`. See [base::readLines()].
#' @param meta A list of package metadata as obtained by
#'   [utils::packageDescription()] or `NULL` (the default). See **Details**.
#' @param ... Arguments passed to other functions, for example to
#'   [yaml::read_yaml()] or [bibtex::read.bib()].
#'
#' @inheritParams cff_create
#'
#' @return
#' - `cff_read_cff_citation()` and `cff_read_description()` return an object
#'   with class `cff`.
#' - `cff_read_citation()` and `cff_read_bib()` return an object of classes
#'   [`cff_ref_lst, cff`][cff_ref_lst] as defined by the
#'   `definitions.reference` specified in the following guide:
#' ```{r child = "man/chunks/schema-guide.Rmd"}
#' ```
#'
#' Learn more about the \CRANpkg{cffr} class system in [cff_class].
#'
#' @details
#' For details of `cff_read_description()`, see [cff_create()].
#'
#' ## The `meta` object
#'
#' Section 1.9 CITATION files of *Writing R Extensions* (R Core Team 2026)
#' specifies how to create dynamic `CITATION` files using a `meta` object.
#' Therefore, the `meta` argument in [cff_read_citation()] may be needed to
#' read some files correctly.
#'
#' @references
#' R Core Team (2026). *Writing R Extensions*.
#' <https://cran.r-project.org/doc/manuals/r-release/R-exts.html>.
#'
#' Hernangómez D (2022). "BibTeX and CFF, a potential crosswalk."
#' \CRANpkg{cffr} vignette.
#' <https://docs.ropensci.org/cffr/articles/bibtex-cff.html>.
#'
#' @seealso
#' The underlying functions used for reading external files:
#' - [yaml::read_yaml()] for `CITATION.cff` files.
#' - [desc::desc()] for `DESCRIPTION` files.
#' - [utils::readCitationFile()] for \R citation files.
#' - [bibtex::read.bib()] for BibTeX files (extension `*.bib`).
#'
#' @family reading
#' @rdname cff_read
#' @export
#' @encoding UTF-8
#' @examples
#' # Create a `cff` object from a `CITATION.cff` file.
#' from_cff_file <- cff_read(system.file("examples/CITATION_basic.cff",
#'   package = "cffr"
#' ))
#'
#' head(from_cff_file, 7)
#'
#' # Create a `cff` object from DESCRIPTION.
#' from_desc <- cff_read(system.file("examples/DESCRIPTION_basic",
#'   package = "cffr"
#' ))
#'
#' from_desc
#'
#' # Create a `cff` object from BibTeX.
#' if (requireNamespace("bibtex", quietly = TRUE)) {
#'   from_bib <- cff_read(system.file("examples/example.bib",
#'     package = "cffr"
#'   ))
#'
#'   # First item only.
#'   from_bib[[1]]
#' }
#' # Create a `cff` object from CITATION.
#' from_citation <- cff_read(system.file("CITATION", package = "cffr"))
#'
#' # First item only.
#' from_citation[[1]]
#'
cff_read <- function(path, ...) {
  if (length(path) > 1) {
    cli::cli_abort(
      "{.arg path} must have length {.val {1}}, not {.val {length(path)}}."
    )
  }

  file_exist_abort(path, abort = TRUE, call = environment())
  filetype <- detect_x_source(path)

  if (filetype == "dontknow") {
    cli::cli_abort(c(
      "Cannot recognize the file type of {.file {path}}.",
      "i" = paste(
        "Use a specific function, such as",
        "{.fn cffr::cff_read_description}."
      )
    ))
  }

  endobj <- switch(filetype,
    "cff_citation" = cff_read_cff_citation(path, ...),
    "description" = cff_read_description(path, ...),
    "bib" = cff_read_bib(path, ...),
    "citation" = cff_read_citation(path, ...),
    cli::cli_abort("Cannot read {.file {path}}.")
  )

  endobj
}

#' @rdname cff_read
#' @export
#' @encoding UTF-8
cff_read_cff_citation <- function(path, ...) {
  file_exist_abort(path, abort = TRUE, call = environment())

  cffobj <- yaml::read_yaml(
    path,
    ...,
    # Always read languages as a list (#105).
    handlers = list(map = function(x) {
      if (length(x$languages) == 1) {
        x$languages <- list(x$languages)
      }
      x
    })
  )
  new_cff(cffobj)
}

#' @rdname cff_read
#' @export
#' @encoding UTF-8
cff_read_description <- function(
  path,
  cff_version = "1.2.0",
  gh_keywords = TRUE,
  authors_roles = c("aut", "cre"),
  ...
) {
  file_exist_abort(path, abort = TRUE, call = environment())

  pkg <- desc::desc(path)
  pkg$coerce_authors_at_r()

  msg <- paste0(
    'To cite package "',
    pkg$get("Package"),
    '" in publications use:'
  )

  field_list <- list(
    "cff-version" = cff_version,
    message = msg,
    type = "software",
    title = get_desc_title(pkg),
    version = get_desc_version(pkg),
    authors = get_desc_authors(pkg, authors_roles = authors_roles),
    abstract = get_desc_abstract(pkg),
    repository = get_desc_repository(pkg),
    "repository-code" = get_desc_urls(pkg)$repo,
    url = get_desc_urls(pkg)$url,
    identifiers = get_desc_urls(pkg)$identifiers,
    "date-released" = get_desc_date_released(pkg),
    contact = get_desc_contacts(pkg),
    keywords = get_desc_keywords(pkg),
    license = unlist(get_desc_license(pkg)),
    commit = get_desc_sha(pkg),
    doi = get_desc_doi(pkg)
  )

  if (gh_keywords) {
    ghtopics <- get_gh_topics(field_list)
    field_list$keywords <- desc_gh_keywords(field_list$keywords, ghtopics)
  }

  new_cff(field_list)
}

#' @rdname cff_read
#' @export
#' @encoding UTF-8
cff_read_citation <- function(path, meta = NULL, ...) {
  file_exist_abort(path, abort = TRUE, call = environment())

  if (!any(is.null(meta), inherits(meta, "packageDescription"))) {
    # nolint start
    # Object for cli only.
    ex <- packageDescription("cffr")
    # nolint end

    cli::cli_alert_warning(paste0(
      "{.arg meta} must be {.code NULL} or {.obj_type_friendly {ex}}, ",
      "not {.obj_type_friendly {meta}}. Using {.code meta = NULL}."
    ))
    meta <- NULL
  }

  new_meta <- clean_package_meta(meta)
  the_cit <- try(cff_read_citation_file(path, meta = new_meta), silent = TRUE)

  # If there is an error, try again.
  if (inherits(the_cit, "try-error")) {
    cli::cli_alert_warning(paste0(
      "Could not read {.file {path}} with the provided {.arg meta}. ",
      'Trying {.code utils::packageDescription("base")}.'
    ))
    new_meta <- packageDescription("base")
    the_cit <- try(cff_read_citation_file(path, meta = new_meta), silent = TRUE)
    if (inherits(the_cit, "try-error")) {
      cli::cli_alert_danger(
        "Cannot read {.file {path}}. Returning {.code NULL}."
      )
      return(NULL)
    }
  }
  tocff <- as_cff(the_cit)

  tocff
}

cff_read_citation_file <- function(path, meta) {
  utils::readCitationFile(path, meta = meta)
}

#' @family bibtex
#' @rdname cff_read
#' @export
#' @encoding UTF-8
cff_read_bib <- function(path, encoding = "UTF-8", ...) {
  file_exist_abort(path, abort = TRUE, call = environment())

  # nocov start
  if (!requireNamespace("bibtex", quietly = TRUE)) {
    cli::cli_abort(c(
      "The {.pkg bibtex} package is required.",
      "i" = 'Install it with {.run install.packages("bibtex")}.'
    ))
  }
  # nocov end

  # Read from tempfile.
  read_bib <- bibtex::read.bib(file = path, encoding = encoding, ...)

  tocff <- as_cff(read_bib)
  tocff
}

# Internal safe ----
#' Internal safe version of cff_read_citation.
#' @noRd
cff_safe_read_citation <- function(desc_path, cit_path) {
  if (!file_exist_abort(cit_path) || !file_exist_abort(desc_path)) {
    return(NULL)
  }
  # Create metadata.
  meta <- desc_to_meta(desc_path)
  meta <- clean_package_meta(meta)

  the_cit <- try(utils::readCitationFile(cit_path, meta = meta), silent = TRUE)
  # Try to read the citation.
  if (inherits(the_cit, "try-error")) {
    return(NULL)
  }

  # Keep names for downstream conversion.
  tocff <- as_cff(the_cit)
  tocff
}

# See utils.R

Try the cffr package in your browser

Any scripts or data that you put into this service are public.

cffr documentation built on Aug. 24, 2026, 5:11 p.m.