Nothing
#' 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
Any scripts or data that you put into this service are public.
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.