1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145
|
#' Add an author to the `Authors@R` field in DESCRIPTION
#'
#' @description
#' `use_author()` adds a person to the `Authors@R` field of the DESCRIPTION
#' file, creating that field if necessary. It will not modify, e.g., the role(s)
#' or email of an existing author (judged using their "Given Family" name). For
#' that we recommend editing DESCRIPTION directly. Or, for programmatic use,
#' consider calling the more specialized functions available in the \pkg{desc}
#' package directly.
#'
#' `use_author()` also surfaces two other situations you might want to address:
#' * Explicit use of the fields `Author` or `Maintainer`. We recommend switching
#' to the more modern `Authors@R` field instead, because it offers richer
#' metadata for various downstream uses. (Note that `Authors@R` is *eventually*
#' processed to create `Author` and `Maintainer` fields, but only when the
#' `tar.gz` is built from package source.)
#' * Presence of the fake author placed by [create_package()] and
#' [use_description()]. This happens when \pkg{usethis} has to create a
#' DESCRIPTION file and the user hasn't given any author information via the
#' `fields` argument or the global option `"usethis.description"`. The
#' placeholder looks something like `First Last <first.last@example.com> [aut,
#' cre]` and `use_author()` offers to remove it in interactive sessions.
#'
#' @inheritParams utils::person
#' @inheritDotParams utils::person
#' @export
#' @examples
#' \dontrun{
#' use_author(
#' given = "Lucy",
#' family = "van Pelt",
#' role = c("aut", "cre"),
#' email = "lucy@example.com",
#' comment = c(ORCID = "LUCY-ORCID-ID")
#' )
#'
#' use_author("Charlie", "Brown")
#' }
#'
use_author <- function(given = NULL, family = NULL, ..., role = "ctb") {
check_is_package("use_author()")
maybe_name(given)
maybe_name(family)
check_character(role)
d <- proj_desc()
challenge_legacy_author_fields(d)
# We only need to consider Authors@R
authors_at_r_already <- d$has_fields("Authors@R")
if (authors_at_r_already) {
check_author_is_novel(given, family, d)
}
# This person is not already in Authors@R
author <- utils::person(given = given, family = family, role = role, ...)
aut_fmt <- format(author, style = 'text')
if (authors_at_r_already) {
ui_bullets(c(
"v" = "Adding to {.field Authors@R} in DESCRIPTION:",
" " = "{aut_fmt}"
))
} else {
ui_bullets(c(
"v" = "Creating {.field Authors@R} field in DESCRIPTION and adding:",
" " = "{aut_fmt}"
))
}
d$add_author(given = given, family = family, role = role, ...)
challenge_default_author(d)
d$write()
invisible(TRUE)
}
challenge_legacy_author_fields <- function(d = proj_desc()) {
has_legacy_field <- d$has_fields("Author") || d$has_fields("Maintainer")
if (!has_legacy_field) {
return(invisible())
}
ui_bullets(c(
"x" = "Found legacy {.field Author} and/or {.field Maintainer} field in
DESCRIPTION.",
" " = "usethis only supports modification of the {.field Authors@R} field.",
"i" = "We recommend one of these paths forward:",
"_" = "Delete the legacy fields and rebuild with {.fun use_author}; or",
"_" = "Convert to {.field Authors@R} with
{.fun desc::desc_coerce_authors_at_r}, then delete the legacy fields."
))
if (ui_yep("Do you want to cancel this operation and sort that out first?")) {
ui_abort("Cancelling.")
}
invisible()
}
check_author_is_novel <- function(given = NULL, family = NULL, d = proj_desc()) {
authors <- d$get_authors()
authors_given <- purrr::map(authors, "given")
authors_family <- purrr::map(authors, "family")
m <- purrr::map2_lgl(authors_given, authors_family, function(x, y) {
identical(x, given) && identical(y, family)
})
if (any(m)) {
aut_name <- glue("{given %||% ''} {family %||% ''}")
ui_abort(c(
"x" = "{.val {aut_name}} already appears in {.field Authors@R}.",
" " = "Please make the desired change directly in DESCRIPTION or call the
{.pkg desc} package directly."
))
}
invisible()
}
challenge_default_author <- function(d = proj_desc()) {
defaults <- usethis_description_defaults()
default_author <- eval(parse(text = defaults[["Authors@R"]]))
authors <- d$get_authors()
m <- map_lgl(
authors,
# the `person` class is pretty weird!
function(x) identical(x, unclass(default_author)[[1]])
)
if (any(m)) {
ui_bullets(c(
"i" = "{.field Authors@R} appears to include a placeholder author:",
" " = "{format(default_author, style = 'text')}"
))
if(is_interactive() && ui_yep("Would you like to remove it?")) {
# TODO: Do I want to suppress this output?
# Authors removed: First Last, NULL NULL.
do.call(d$del_author, unclass(default_author)[[1]])
}
}
return(invisible())
}
|