1 2#' Read a DESCRIPTION file 3#' 4#' This is a convenience wrapper for \code{description$new()}. 5#' Very often you want to read an existing \code{DESCRIPTION} 6#' file, and to do this you can just supply the path to the file or its 7#' directory to \code{desc()}. 8#' 9#' @param cmd A command to create a description from scratch. 10#' Currently only \code{"!new"} is implemented. If it does not start 11#' with an exclamation mark, it will be interpreted as the \sQuote{file} 12#' argument. 13#' @param file Name of the \code{DESCRIPTION} file to load. If all of 14#' \sQuote{cmd}, \sQuote{file} and \sQuote{text} are \code{NULL} (the 15#' default), then the \code{DESCRIPTION} file in the current working 16#' directory is used. The file can also be an R package (source, or 17#' binary), in which case the DESCRIPTION file is extracted from it, but 18#' note that in this case \code{$write()} cannot write the file back in 19#' the package archive. 20#' @param text A character scalar containing the full DESCRIPTION. 21#' Character vectors are collapsed into a character scalar, with 22#' newline as the separator. 23#' @param package If not NULL, then the name of an installed package 24#' and the DESCRIPTION file of this package will be loaded. 25#' 26#' @export 27#' @examples 28#' desc(package = "desc") 29#' DESCRIPTION <- system.file("DESCRIPTION", package = "desc") 30#' desc(DESCRIPTION) 31 32desc <- function(cmd = NULL, file = NULL, text = NULL, package = NULL) { 33 description$new(cmd, file, text, package) 34} 35 36#' Read, write, update, validate DESCRIPTION files 37#' 38#' @section Constructors: 39#' 40#' There are two ways of creating a description object. The first 41#' is reading an already existing \code{DESCRIPTION} file; simply give 42#' the name of the file as an argument. The default is 43#' \code{DESCRIPTION}: \preformatted{ x <- description$new() 44#' x2 <- description$new("path/to/DESCRIPTION")} 45#' 46#' The second way is creating a description object from scratch, 47#' supply \code{"!new"} as an argument to do this. 48#' \preformatted{ x3 <- description$new("!new")} 49#' 50#' The complete API reference: 51#' \preformatted{description$new(cmd = NULL, file = NULL, text = NULL, 52#' package = NULL)} 53#' \describe{ 54#' \item{cmd:}{A command to create a description from scratch. 55#' Currently only \code{"!new"} is implemented. If it does not start 56#' with an exclamation mark, it will be interpreted as a \sQuote{file} 57#' argument.} 58#' \item{file:}{Name of the \code{DESCRIPTION} file to load. If it is 59#' a directory, then we assume that it is inside an R package and 60#' conduct a search for the package root directory, i.e. the first 61#' directory up the tree that contains a \code{DESCRIPTION} file. 62#' If \sQuote{cmd}, \sQuote{file}, \sQuote{text} and \sQuote{package} 63#' are all \code{NULL} (the default), then the search is started from 64#' the working directory. The file can also be an R package (source, or 65#' binary), in which case the DESCRIPTION file is extracted from it, 66#' but note that in this case \code{$write()} cannot write the file 67#' back in the package archive.} 68#' \item{text:}{A character scalar containing the full DESCRIPTION. 69#' Character vectors are collapsed into a character scalar, with 70#' newline as the separator.} 71#' \item{package}{If not NULL, then the name of an installed package 72#' and the DESCRIPTION file of this package will be loaded.} 73#' } 74#' 75#' @section Setting and Querying fields: 76#' Set a field with \code{$set} and query it with \code{$get}: 77#' \preformatted{ x <- description$new("!new") 78#' x$get("Package) 79#' x$set("Package", "foobar") 80#' x$set(Title = "Example Package for 'description'") 81#' x$get("Package")} 82#' Note that \code{$set} has two forms. You can either give the field name 83#' and new value as two arguments; or you can use a single named argument, 84#' the argument name is the field name, the argument value is the field 85#' value. 86#' 87#' The \code{$fields} method simply lists the fields in the object: 88#' \preformatted{ x$fields()} 89#' 90#' The \code{$has_fields} method checks if one or multiple fields are 91#' present in a description object: \preformatted{ x$has_fields("Package") 92#' x$has_fields(c("Title", "foobar"))} 93#' 94#' The \code{$del} method removes the specified fields: 95#' \preformatted{ x$set(foo = "bar") 96#' x$del("foo")} 97#' 98#' \code{$get_field} is similar to \code{$get}, but it queries a single 99#' field, it returns an unnamed vector if found, and returns the 100#' specified \code{default} value if not. By default it throws an error 101#' if the field is not found. 102#' 103#' The complete API reference: 104#' \preformatted{ description$get(keys) 105#' description$get_field(key, default, trim_ws = TRUE, squish_ws = trim_ws) 106#' description$set(...) 107#' description$fields() 108#' description$has_fields(keys) 109#' description$del(keys)} 110#' \describe{ 111#' \item{key:}{A character string (length one), the key to query.} 112#' \item{default:}{If specified and \code{key} is missing, this value 113#' is returned. If not specified, an error is thrown.} 114#' \item{trim_ws:}{Whether to trim leading and trailing whitespace 115#' from the returned value.} 116#' \item{squish_ws:}{Whether to reduce repeated whitespace in the 117#' returned value.} 118#' \item{keys:}{A character vector of keys to query, check or delete.} 119#' \item{...:}{This must be either two unnamed arguments, the key and 120#' and the value to set; or an arbitrary number of named arguments, 121#' names are used as keys, values as values to set.} 122#' } 123#' 124#' @section Normalizing: 125#' Format DESCRIPTION in a standard way. \code{$str} formats each 126#' field in a standard way and returns them (it does not change the 127#' object itself), \code{$print} is used to print it to the 128#' screen. The \code{$normalize} function normalizes each field (i.e. 129#' it changes the object). Normalization means reformatting the fields, 130#' via \code{$reformat_fields()} and also reordering them via 131#' \code{$reorder_fields()}. The format of the various fields is 132#' opinionated and you might like it or not. Note that \code{desc} only 133#' re-formats fields that it updates, and only on demand, so if your 134#' formatting preferences differ, you can still manually edit 135#' \code{DESCRIPTION} and \code{desc} will respect your edits. 136#' 137#' \preformatted{ description$str(by_field = FALSE, normalize = TRUE, 138#' mode = c("file", "screen")) 139#' description$normalize() 140#' description$reformat_fields() 141#' description$reorder_fields() 142#' description$print() 143#' } 144#' \describe{ 145#' \item{by_field:}{Whether to return the normalized format 146#' by field, or collapsed into a character scalar.} 147#' \item{normalize:}{Whether to reorder and reformat the fields.} 148#' \item{mode:}{\sQuote{file} mode formats the fields as they are 149#' written to a file with the \code{write} method. \sQuote{screen} 150#' mode adds extra markup to some fields, e.g. formats the 151#' \code{Authors@R} field in a readable way.} 152#' } 153#' 154#' @section Writing it to file: 155#' The \code{$write} method writes the description to a file. 156#' By default it writes it to the file it was created from, if it was 157#' created from a file. Otherwise giving a file name is compulsory: 158#' \preformatted{ x$write(file = "DESCRIPTION")} 159#' 160#' The \code{normalize} argument controls whether the fields are 161#' reformatted according to a standard style. By default they are not. 162#' 163#' The API: 164#' \preformatted{ description$write(file = NULL, normalize = NULL)} 165#' \describe{ 166#' \item{file:}{Path to write the description to. If it was created 167#' from a file in the first place, then it is written to the same 168#' file. Otherwise this argument must be specified.} 169#' \item{normalize:}{Whether to reformat the fields in a standard way.} 170#' } 171#' 172#' @section Version numbers: 173#' 174#' \preformatted{ description$get_version() 175#' description$set_version(version) 176#' description$bump_version(which = c("patch", "minor", "major", "dev")) 177#' } 178#' 179#' \describe{ 180#' \item{version:}{A string or a \code{\link[base]{package_version}} 181#' object.} 182#' \item{which:}{Which component of the version number to increase. 183#' See details just below.} 184#' } 185#' 186#' These functions are simple helpers to make it easier to query, set and 187#' increase the version number of a package. 188#' 189#' \code{$get_version()} returns the version number as a 190#' \code{\link[base]{package_version}} object. It throws an error if the 191#' package does not have a \sQuote{Version} field. 192#' 193#' \code{$set_version()} takes a string or a 194#' \code{\link[base]{package_version}} object and sets the \sQuote{Version} 195#' field to it. 196#' 197#' \code{$bump_version()} increases the version number. The \code{which} 198#' parameter specifies which component to increase. 199#' It can be a string referring to a component: \sQuote{major}, 200#' \sQuote{minor}, \sQuote{patch} or \sQuote{dev}, or an integer 201#' scalar, for the latter components are counted from one, and the 202#' beginning. I.e. component one is equivalent to \sQuote{major}. 203#' 204#' If a component is bumped, then the ones after it are zeroed out. 205#' Trailing zero components are omitted from the new version number, 206#' but if the old version number had at least two or three components, then 207#' the one will also have two or three. 208#' 209#' The bumping of the \sQuote{dev} version (the fourth component) is 210#' special: if the original version number had less than four components, 211#' and the \sQuote{dev} version is bumped, then it is set to \code{9000} 212#' instead of \code{1}. This is a convention often used by R developers, 213#' it was originally invented by Winston Chang. 214#' 215#' Both \code{$set_version()} and \code{$bump_version()} use dots to 216#' separate the version number components. 217#' 218#' @section Dependencies: 219#' These functions handle the fields that define how the R package 220#' uses another R packages. See \code{\link{dep_types}} for the 221#' list of fields in this group. 222#' 223#' The \code{$get_deps} method returns all declared dependencies, in a 224#' data frame with columns: \code{type}, \code{package} and \code{version}. 225#' \code{type} is the name of the dependency field, \code{package} is the 226#' name of the R package, and \code{version} is the required version. If 227#' no specific versions are required, then this is a \code{"\*"}. 228#' 229#' The \code{$set_deps} method is the opposite of \code{$get_deps} and 230#' it sets all dependencies. The input is a data frame, with the same 231#' structure as the return value of \code{$get_deps}. 232#' 233#' The \code{$has_dep} method checks if a package is included in the 234#' dependencies. It returns a logical scalar. If \code{type} is not 235#' \sQuote{any}, then it has to match as well. 236#' 237#' The \code{$del_deps} method removes all declared dependencies. 238#' 239#' The \code{$set_dep} method adds or updates a single dependency. By 240#' default it adds the package to the \code{Imports} field. 241#' 242#' The API: 243#' \preformatted{ description$set_dep(package, type = dep_types, version = "\*") 244#' description$set_deps(deps) 245#' description$get_deps() 246#' description$has_dep(package, type = c("any", dep_types)) 247#' description$del_dep(package, type = c("all", dep_types)) 248#' description$del_deps() 249#' } 250#' \describe{ 251#' \item{package:}{Name of the package to add to or remove from the 252#' dependencies.} 253#' \item{type:}{Dependency type, see \code{\link{dep_types}}. For 254#' \code{$del_dep} it may also be \code{"all"}, and then the package 255#' will be deleted from all dependency types.} 256#' \item{version:}{Required version. Defaults to \code{"\*"}, which means 257#' no explicit version requirements.} 258#' \item{deps:}{A data frame with columns \code{type}, \code{package} and 259#' \code{version}. \code{$get_deps} returns the same format.} 260#' } 261#' 262#' @section Collate fields: 263#' Collate fields contain lists of file names with R source code, 264#' and the package has a separate API for them. In brief, you can 265#' use \code{$add_to_collate} to add one or more files to the main or 266#' other collate field. You can use \code{$del_from_collate} to remove 267#' it from there. 268#' 269#' The API: 270#' \preformatted{ description$set_collate(files, which = c("main", "windows", "unix")) 271#' description$get_collate(which = c("main", "windows", "unix")) 272#' description$del_collate(which = c("all", "main", "windows", "unix")) 273#' description$add_to_collate(files, which = c("default", "all", "main", 274#' "windows", "unix")) 275#' description$del_from_collate(files, which = c("all", "main", 276#' "windows", "unix")) 277#' } 278#' \describe{ 279#' \item{files:}{The files to add or remove, in a character vector.} 280#' \item{which:}{Which collate field to manipulate. \code{"default"} for 281#' \code{$add_to_collate} means all existing collate fields, or the 282#' main one if none exist.} 283#' } 284#' 285#' @section Authors: 286#' There is a specialized API for the \code{Authors@R} field, 287#' to add and remove authors, update their roles, change the maintainer, 288#' etc. 289#' 290#' The API: 291#' \preformatted{ description$get_authors() 292#' description$set_authors(authors) 293#' description$get_author(role) 294#' description$get_maintainer() 295#' description$coerce_authors_at_r() 296#' } 297#' \describe{ 298#' \item{authors:}{A \code{person} object, a list of authors.} 299#' \item{role:}{The role to query. See \code{person} for details.} 300#' } 301#' \code{$get_authors} returns a \code{person} object, the parsed 302#' authors. See \code{\link[utils]{person}} for details. 303#' 304#' \code{$get_author} returns a \code{person} object, all authors with 305#' the specified role. 306#' 307#' \code{$get_maintainer} returns the maintainer of the package. It works 308#' with \code{Authors@R} fields and with traditional \code{Maintainer} 309#' fields as well. 310#' 311#' \code{$coerce_authors_at_r} converts an \code{Author} field to one with 312#' a \code{person} object. This coercion may be necessary for other 313#' functions such as \code{$get_authors}. 314#' 315#' \preformatted{ description$add_author(given = NULL, family = NULL, email = NULL, 316#' role = NULL, comment = NULL, orcid = NULL) 317#' description$add_me(role = "ctb", comment = NULL, orcid = NULL) 318#' description$add_author_gh(username, role = "ctb", comment = NULL, orcid = NULL) 319#' } 320#' Add a new author. The arguments correspond to the arguments of the 321#' \code{\link[utils]{person}} function. \code{add_me} is a convenience 322#' function, it adds the current user as an author, and it needs the 323#' \code{whoami} package to be installed. It'll add your ORCID ID 324#' if you provide it as argument or save it as \code{ORCID_ID} environment 325#' variable in .Renviron. 326#' The full name is parsed by \code{add_me} and \code{add_author_gh} using 327#' \code{as.person} and collapsing the given name and the family name 328#' in order to e.g. have the first and middle names together as given 329#' name. This approach might be limited to some full name structures. 330#' 331#' \preformatted{ description$del_author(given = NULL, family = NULL, email = NULL, 332#' role = NULL, comment = NULL, orcid = NULL) 333#' } 334#' Remove an author, or multiple authors. The author(s) to be removed 335#' can be specified via any field(s). All authors matching all 336#' specifications will be removed. E.g. if only \code{given = "Joe"} 337#' is supplied, then all authors whole given name matches \code{Joe} will 338#' be removed. The specifications can be (PCRE) regular expressions. 339#' 340#' \preformatted{ description$add_role(role, given = NULL, family = NULL, email = NULL, 341#' comment = NULL, orcid = NULL) 342#' description$add_orcid(orcid, given = NULL, family = NULL, email = NULL, 343#' comment = NULL, role = NULL) 344#' description$del_role(role, given = NULL, family = NULL, email = NULL, 345#' comment = NULL, orcid = NULL) 346#' description$change_maintainer(given = NULL, family = NULL, 347#' email = NULL, comment = NULL, orcid = NULL) 348#' } 349#' \code{role} is the role to add or delete. The other arguments 350#' are used to select a subset of the authors, on which the operation 351#' is performed, similarly to \code{$del_author}. 352#' 353#' @section URLs: 354#' 355#' We provide helper functions for manipulating URLs in the \code{URL} 356#' field: 357#' 358#' \preformatted{ description$get_urls() 359#' description$set_urls(urls) 360#' description$add_urls(urls) 361#' description$del_urls(pattern) 362#' description$clear_urls() 363#' } 364#' \describe{ 365#' \item{urls:}{Character vector of URLs to set or add.} 366#' \item{pattern:}{Perl compatible regular expression to specify the 367#' URLs to be removed.} 368#' } 369#' \code{$get_urls()} returns all urls in a character vector. If no URL 370#' fields are present, a zero length vector is returned. 371#' 372#' \code{$set_urls()} sets the URL field to the URLs specified in the 373#' character vector argument. 374#' 375#' \code{$add_urls()} appends the specified URLs to the URL field. It 376#' creates the field if it does not exists. Duplicate URLs are removed. 377#' 378#' \code{$del_urls()} deletes the URLs that match the specified pattern. 379#' 380#' \code{$clear_urls()} deletes all URLs. 381#' 382#' @section Remotes: 383#' 384#' \code{devtools}, \code{remotes} and some other packages support the 385#' non-standard \code{Remotes} field in \code{DESCRIPTION}. This field 386#' can be used to specify locations of dependent packages: GitHub or 387#' BitBucket repositories, generic git repositories, etc. Please see the 388#' \sQuote{Package remotes} vignette in the \code{devtools} package. 389#' 390#' \code{desc} has helper functions for manipulating the \code{Remotes} 391#' field: 392#' 393#' \preformatted{ description$get_remotes() 394#' description$get_remotes() 395#' description$set_remotes(remotes) 396#' description$add_remotes(remotes) 397#' description$del_remotes(pattern) 398#' description$clear_remotes() 399#' } 400#' \describe{ 401#' \item{remotes:}{Character vector of remote dependency locations to 402#' set or add.} 403#' \item{pattern:}{Perl compatible regular expression to specify the 404#' remote dependency locations to remove.} 405#' } 406#' \code{$get_remotes()} returns all remotes in a character vector. 407#' If no URL fields are present, a zero length vector is returned. 408#' 409#' \code{$set_remotes()} sets the URL field to the Remotes specified in the 410#' character vector argument. 411#' 412#' \code{$add_remotes()} appends the specified remotes to the 413#' \code{Remotes} field. It creates the field if it does not exists. 414#' Duplicate remotes are removed. 415#' 416#' \code{$del_remotes()} deletes the remotes that match the specified 417#' pattern. 418#' 419#' \code{$clear_remotes()} deletes all remotes. 420#' 421#' @section Built: 422#' 423#' The \sQuote{Built} field is used in binary packages to store information 424#' about when and how a binary package was built. 425#' 426#' \code{$get_built()} returns the built information as a list with fields 427#' \sQuote{R}, \sQuote{Platform}, \sQuote{Date}, \sQuote{OStype}. It throws an 428#' error if the package does not have a \sQuote{Built} field. 429#' 430#' @section Encodings: 431#' When creating a `description` object, `desc` observes the `Encoding` 432#' field, if present, and uses the specified encoding to parse the file. 433#' Internally, it converts all fields to UTF-8. 434#' 435#' When writing a `description` object to a file, `desc` uses the 436#' `Encoding` field (if present), and converts all fields to the specified 437#' encoding. 438#' 439#' We suggest that whenever you need to use non-ASCII characters in your 440#' package, you use the UTF-8 encoding, for maximum portability. 441#' 442#' @export 443#' @importFrom R6 R6Class 444#' @docType class 445#' @format An R6 class. 446#' 447#' @examples 448#' ## Create a template 449#' desc <- description$new("!new") 450#' desc 451#' 452#' ## Read a file 453#' desc2 <- description$new(file = system.file("DESCRIPTION", 454#' package = "desc")) 455#' desc2 456#' 457#' ## Remove a field 458#' desc2$del("LazyData") 459#' 460#' ## Add another one 461#' desc2$set(VignetteBuilder = "knitr") 462#' desc2$get("VignetteBuilder") 463#' desc2 464 465description <- R6Class("description", 466 public = list( 467 468 ## Either from a file, or from a character vector 469 initialize = function(cmd = NULL, file = NULL, text = NULL, package = NULL) 470 idesc_create(self, private, cmd, file, text, package), 471 472 write = function(file = NULL) 473 idesc_write(self, private, file), 474 475 fields = function() 476 idesc_fields(self, private), 477 478 has_fields = function(keys) 479 idesc_has_fields(self, private, keys), 480 481 get = function(keys) 482 idesc_get(self, private, keys), 483 484 get_field = function(key, default = stop("Field '", key, "' not found"), 485 trim_ws = TRUE, squish_ws = trim_ws) 486 idesc_get_field(self, private, key, default, trim_ws, squish_ws), 487 488 get_or_fail = function(keys) 489 idesc_get_or_fail(self, private, keys), 490 491 get_list = function(key, default = stop("Field '", key, "' not found"), 492 sep = ",", trim_ws = TRUE, squish_ws = trim_ws) 493 idesc_get_list(self, private, key, default, sep, trim_ws, squish_ws), 494 495 set = function(...) 496 idesc_set(self, private, ...), 497 498 set_list = function(key, list_value, sep = ", ") 499 idesc_set_list(self, private, key, list_value, sep), 500 501 del = function(keys) 502 idesc_del(self, private, keys), 503 504 validate = function() 505 idesc_validate(self, private), 506 507 print = function() 508 idesc_print(self, private), 509 510 str = function(by_field = FALSE, normalize = TRUE, 511 mode = c("file", "screen")) 512 idesc_str(self, private, by_field, normalize, mode), 513 514 to_latex = function() 515 idesc_to_latex(self, private), 516 517 normalize = function() 518 idesc_normalize(self, private), 519 520 reformat_fields = function() 521 idesc_reformat_fields(self, private), 522 523 reorder_fields = function() 524 idesc_reorder_fields(self, private), 525 526 ## ----------------------------------------------------------------- 527 ## Version numbers 528 529 get_version = function() 530 idesc_get_version(self, private), 531 532 set_version = function(version) 533 idesc_set_version(self, private, version), 534 535 bump_version = function(which) 536 idesc_bump_version(self, private, which), 537 538 ## ----------------------------------------------------------------- 539 ## Package dependencies 540 541 set_dep = function(package, type = desc::dep_types, version = "*") 542 idesc_set_dep(self, private, package, match.arg(type), version), 543 544 set_deps = function(deps) 545 idesc_set_deps(self, private, deps), 546 547 get_deps = function() 548 idesc_get_deps(self, private), 549 550 del_dep = function(package, type = c("all", desc::dep_types)) 551 idesc_del_dep(self, private, package, match.arg(type)), 552 553 del_deps = function() 554 idesc_del_deps(self, private), 555 556 has_dep = function(package, type = c("any", desc::dep_types)) 557 idesc_has_dep(self, private, package, match.arg(type)), 558 559 ## ----------------------------------------------------------------- 560 ## Collate fields 561 562 set_collate = function(files, which = c("main", "windows", "unix")) 563 idesc_set_collate(self, private, files, match.arg(which)), 564 565 get_collate = function(which = c("main", "windows", "unix")) 566 idesc_get_collate(self, private, match.arg(which)), 567 568 del_collate = function(which = c("all", "main", "windows", "unix")) 569 idesc_del_collate(self, private, match.arg(which)), 570 571 add_to_collate = function(files, 572 which = c("default", "all", "main", "windows", "unix")) 573 idesc_add_to_collate(self, private, files, match.arg(which)), 574 575 del_from_collate = function(files, 576 which = c("all", "main", "windows", "unix")) 577 idesc_del_from_collate(self, private, files, match.arg(which)), 578 579 ## ----------------------------------------------------------------- 580 ## Authors@R 581 582 get_authors = function() 583 idesc_get_authors(self, private), 584 585 get_author = function(role = "cre") 586 idesc_get_author(self, private, role), 587 588 set_authors = function(authors) 589 idesc_set_authors(self, private, authors), 590 591 add_author = function(given = NULL, family = NULL, email = NULL, 592 role = NULL, comment = NULL, orcid = NULL) 593 idesc_add_author(self, private, given, family, email, role, comment, 594 orcid), 595 596 add_role = function(role, given = NULL, family = NULL, email = NULL, 597 comment = NULL, orcid = NULL) 598 idesc_add_role(self, private, role, given, family, email, comment, 599 orcid), 600 601 add_orcid = function(orcid, given = NULL, family = NULL, email = NULL, 602 comment = NULL, role = NULL) 603 idesc_add_orcid(self, private, role = role, given = given, family = family, 604 email = email, comment = comment, 605 orcid = orcid), 606 607 del_author = function(given = NULL, family = NULL, email = NULL, 608 role = NULL, comment = NULL, orcid = NULL) 609 idesc_del_author(self, private, given, family, email, role, comment, 610 orcid), 611 612 del_role = function(role, given = NULL, family = NULL, email = NULL, 613 comment = NULL, orcid = NULL) 614 idesc_del_role(self, private, role, given, family, email, comment, 615 orcid), 616 617 change_maintainer = function(given = NULL, family = NULL, email = NULL, 618 comment = NULL, orcid = NULL) 619 idesc_change_maintainer(self, private, given, family, email, comment, 620 orcid), 621 622 add_me = function(role = "ctb", comment = NULL, orcid = NULL) 623 idesc_add_me(self, private, role, comment, orcid), 624 625 add_author_gh = function(username, role = "ctb", comment = NULL, orcid = NULL) 626 idesc_add_author_gh(self, private, role = role, 627 username = username, 628 comment = comment, orcid = orcid), 629 630 get_maintainer = function() 631 idesc_get_maintainer(self, private), 632 633 coerce_authors_at_r = function() 634 idesc_coerce_authors_at_r(self, private), 635 636 ## ----------------------------------------------------------------- 637 ## URL 638 639 get_urls = function() 640 idesc_get_urls(self, private), 641 642 set_urls = function(urls) 643 idesc_set_urls(self, private, urls), 644 645 add_urls = function(urls) 646 idesc_add_urls(self, private, urls), 647 648 del_urls = function(pattern) 649 idesc_del_urls(self, private, pattern), 650 651 clear_urls = function() 652 idesc_clear_urls(self, private), 653 654 ## ----------------------------------------------------------------- 655 ## Remotes 656 657 get_remotes = function() 658 idesc_get_remotes(self, private), 659 660 set_remotes = function(remotes) 661 idesc_set_remotes(self, private, remotes), 662 663 add_remotes = function(remotes) 664 idesc_add_remotes(self, private, remotes), 665 666 del_remotes = function(pattern) 667 idesc_del_remotes(self, private, pattern), 668 669 clear_remotes = function() 670 idesc_clear_remotes(self, private), 671 672 ## ----------------------------------------------------------------- 673 ## Built 674 675 get_built = function() 676 idesc_get_built(self, private) 677 ), 678 679 private = list( 680 data = NULL, 681 path = NULL, 682 notws = character() # entries without trailing ws 683 ) 684) 685 686idesc_create <- function(self, private, cmd, file, text, package) { 687 688 if (!is.null(cmd) && substring(cmd, 1, 1) != "!") { 689 file <- cmd 690 cmd <- NULL 691 } 692 693 if (!is.null(cmd)) { 694 if (!is.null(file)) warning("file argument ignored") 695 if (!is.null(text)) warning("text argument ignored") 696 if (!is.null(package)) warning("package argument ignored") 697 idesc_create_cmd(self, private, cmd) 698 699 } else if (is.null(cmd) && is.null(file) && is.null(text) && 700 is.null(package)) { 701 idesc_create_file(self, private, ".") 702 703 } else if (!is.null(file)) { 704 if (!is.null(text)) warning("text argument ignored") 705 if (!is.null(package)) warning("package argument ignored") 706 idesc_create_file(self, private, file) 707 708 } else if (!is.null(text)) { 709 if (!is.null(package)) warning("package argument ignored") 710 idesc_create_text(self, private, text) 711 712 } else { 713 idesc_create_package(self, private, package) 714 } 715 716 invisible(self) 717} 718 719idesc_create_cmd <- function(self, private, cmd = c("new")) { 720 stopifnot(is_constructor_cmd(cmd)) 721 722 if (cmd == "!new") { 723 txt <- 724'Package: {{ Package }} 725Title: {{ Title }} 726Version: 1.0.0 727Authors@R: 728 c(person(given = "Jo", family = "Doe", email = "jodoe@dom.ain", 729 role = c("aut", "cre"))) 730Maintainer: {{ Maintainer }} 731Description: {{ Description }} 732License: {{ License }} 733URL: {{ URL }} 734BugReports: {{ BugReports }} 735Encoding: UTF-8 736' 737 txt <- sub("Authors@R:", "Authors@R: ", txt) 738 idesc_create_text(self, private, text = txt) 739 } 740 741 invisible(self) 742} 743 744idesc_create_file <- function(self, private, file) { 745 stopifnot(is_path(file)) 746 747 if (file.exists(file) && is_dir(file)) file <- find_description(file) 748 stopifnot(is_existing_file(file)) 749 750 if (is_package_archive(file)) { 751 file <- get_description_from_package(file) 752 753 } else { 754 private$path <- normalizePath(file) 755 } 756 757 tryCatch( 758 lines <- readLines(file), 759 error = function(e) stop("Cannot read ", file, ": ", e$message) 760 ) 761 762 idesc_create_text(self, private, lines) 763} 764 765idesc_create_text <- function(self, private, text) { 766 stopifnot(is.character(text)) 767 con <- textConnection(text, local = TRUE, encoding = "bytes") 768 on.exit(close(con), add = TRUE) 769 dcf <- read_dcf(con) 770 private$notws <- dcf$notws 771 private$data <- dcf$dcf 772 check_encoding(self, private, NULL) 773} 774 775idesc_create_package <- function(self, private, package) { 776 stopifnot(is_string(package)) 777 path <- system.file(package = package, "DESCRIPTION") 778 if (path == "") { 779 stop("Cannot find DESCRIPTION for installed package ", package) 780 } 781 idesc_create_file(self, private, path) 782} 783 784#' @importFrom crayon strip_style 785 786idesc_write <- function(self, private, file) { 787 if (is.null(file)) file <- private$path 788 if (is.null(file)) { 789 stop("Cannot write back DESCRIPTION. Note that it is not possible 790 to update DESCRIPTION files within package archives") 791 } 792 793 mat <- idesc_as_matrix(private$data) 794 if ("Encoding" %in% colnames(mat)) { 795 encoding <- mat[, "Encoding"] 796 mat[] <- iconv(mat[], from = "UTF-8", to = encoding) 797 } 798 # This is to avoid re-encoding 799 Encoding(mat) <- "unknown" 800 801 ## Need to write to a temp file first, to preserve absense of trailing ws 802 tmp <- tempfile() 803 on.exit(unlink(tmp), add = TRUE) 804 write.dcf(mat, file = tmp, keep.white = names(private$data)) 805 806 removed <- ! names(private$notws) %in% colnames(mat) 807 if (any(removed)) private$notws <- private$notws[! removed] 808 809 postprocess_trailing_ws(tmp, names(private$notws)) 810 if (file.exists(file) && is_dir(file)) file <- find_description(file) 811 812 ofile <- file(file, raw = TRUE, open = "wb+") 813 on.exit(close(ofile), add = TRUE) 814 writeLines(readLines(tmp), ofile) 815 816 invisible(self) 817} 818 819idesc_fields <- function(self, private) { 820 names(private$data) 821} 822 823idesc_has_fields <- function(self, private, keys) { 824 stopifnot(is.character(keys), has_no_na(keys)) 825 keys %in% self$fields() 826} 827 828idesc_as_matrix <- function(data) { 829 matrix( 830 vapply(data, "[[", "", "value"), 831 nrow = 1, 832 dimnames = list(NULL, names(data)) 833 ) 834} 835 836idesc_get <- function(self, private, keys) { 837 stopifnot(is.character(keys), has_no_na(keys)) 838 res <- lapply(private$data[keys], "[[", "value") 839 res[vapply(res, is.null, logical(1))] <- NA_character_ 840 res <- as.character(unlist(res)) 841 names(res) <- keys 842 res 843} 844 845idesc_get_field <- function(self, private, key, default, trim_ws, squish_ws) { 846 stopifnot(is_string(key)) 847 stopifnot(is_flag(trim_ws)) 848 val <- private$data[[key]]$value 849 if (!is.null(val)) { 850 if (trim_ws) val <- str_trim(val) 851 if (squish_ws) val <- str_squish(val) 852 } 853 val %||% default 854} 855 856idesc_get_or_fail <- function(self, private, keys) { 857 stopifnot(is.character(keys), has_no_na(keys)) 858 res <- self$get(keys) 859 if (any(is.na(res))) { 860 w <- is.na(res) 861 msg <- paste0( 862 "Could not find DESCRIPTION ", 863 if (sum(w) == 1) "field: " else "fields: ", 864 paste(sQuote(keys[w]), collapse = ", "), 865 "." 866 ) 867 stop(msg, call. = FALSE) 868 } 869 res 870} 871 872idesc_get_list <- function(self, private, key, default, sep, trim_ws, squish_ws) { 873 stopifnot(is_string(key), is_flag(trim_ws), is_flag(squish_ws)) 874 val <- private$data[[key]]$value %||% default 875 val <- strsplit(val, sep, fixed = TRUE)[[1]] 876 if (trim_ws) val <- str_trim(val) 877 if (squish_ws) val <- str_squish(val) 878 val 879} 880 881## ... are either 882## - two unnamed arguments, key and value, or 883## - an arbitrary number of named arguments, the names are the keys, 884## the values are the values 885 886idesc_set <- function(self, private, ...) { 887 args <- list(...) 888 889 if (is.null(names(args)) && length(args) == 2) { 890 keys <- as_string(args[[1]]) 891 values <- as_string(args[[2]]) 892 893 } else if (!is.null(names(args)) && all(names(args) != "")) { 894 keys <- names(args) 895 values <- unlist(args) 896 897 } else { 898 stop("$set needs two unnamed args, or all named args, see docs") 899 } 900 901 fields <- create_fields(keys, enc2utf8(values)) 902 lapply(fields, check_field, warn = TRUE) 903 check_encoding(self, private, lapply(fields, "[[", "value")) 904 private$data[keys] <- fields 905 906 invisible(self) 907} 908 909idesc_set_list <- function(self, private, key, list_value, sep) { 910 stopifnot(is_string(key), is.character(list_value)) 911 value <- paste(list_value, collapse = sep) 912 idesc_set(self, private, key, value) 913} 914 915idesc_del <- function(self, private, keys) { 916 stopifnot(is.character(keys), has_no_na(keys)) 917 private$data <- private$data[setdiff(names(private$data), keys)] 918 invisible(self) 919} 920