/
author.R
146 lines (129 loc) · 4.76 KB
/
author.R
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
146
#' 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] (YOUR-ORCID-ID)` 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())
}