[med-svn] [r-cran-desc] 01/02: New upstream version 1.1.1
Andreas Tille
tille at debian.org
Sun Oct 1 21:59:15 UTC 2017
This is an automated email from the git hooks/post-receive script.
tille pushed a commit to branch master
in repository r-cran-desc.
commit 8f8c1600fb7612910e2fff98a2481cd195c95699
Author: Andreas Tille <tille at debian.org>
Date: Sun Oct 1 23:55:36 2017 +0200
New upstream version 1.1.1
---
DESCRIPTION | 29 +
LICENSE | 2 +
MD5 | 119 +++
NAMESPACE | 106 +++
NEWS.md | 25 +
R/assertions.R | 147 ++++
R/authors-at-r.R | 236 ++++++
R/classes.R | 69 ++
R/collate.R | 122 ++++
R/constants.R | 111 +++
R/deps.R | 131 ++++
R/description.R | 808 +++++++++++++++++++++
R/encoding.R | 37 +
R/latex.R | 82 +++
R/non-oo-api.R | 628 ++++++++++++++++
R/package-archives.R | 62 ++
R/read.R | 35 +
R/remotes.R | 49 ++
R/str.R | 139 ++++
R/syntax_checks.R | 412 +++++++++++
R/urls.R | 47 ++
R/utils.R | 109 +++
R/validate.R | 5 +
R/version.R | 56 ++
inst/DESCRIPTION | 19 +
inst/DESCRIPTION2 | 62 ++
inst/NEWS.md | 25 +
inst/README.Rmd | 161 ++++
inst/README.md | 329 +++++++++
man/check_encoding.Rd | 27 +
man/check_field.Rd | 23 +
man/cran_ascii_fields.Rd | 18 +
man/cran_valid_fields.Rd | 18 +
man/dep_types.Rd | 20 +
man/desc.Rd | 40 +
man/desc_add_author.Rd | 41 ++
man/desc_add_me.Rd | 34 +
man/desc_add_remotes.Rd | 25 +
man/desc_add_role.Rd | 43 ++
man/desc_add_to_collate.Rd | 31 +
man/desc_add_urls.Rd | 22 +
man/desc_bump_version.Rd | 44 ++
man/desc_change_maintainer.Rd | 41 ++
man/desc_clear_remotes.Rd | 19 +
man/desc_clear_urls.Rd | 19 +
man/desc_del.Rd | 26 +
man/desc_del_author.Rd | 44 ++
man/desc_del_collate.Rd | 29 +
man/desc_del_dep.Rd | 31 +
man/desc_del_deps.Rd | 24 +
man/desc_del_from_collate.Rd | 31 +
man/desc_del_remotes.Rd | 22 +
man/desc_del_role.Rd | 42 ++
man/desc_del_urls.Rd | 22 +
man/desc_fields.Rd | 24 +
man/desc_get.Rd | 28 +
man/desc_get_author.Rd | 31 +
man/desc_get_authors.Rd | 29 +
man/desc_get_collate.Rd | 28 +
man/desc_get_deps.Rd | 26 +
man/desc_get_maintainer.Rd | 29 +
man/desc_get_or_fail.Rd | 27 +
man/desc_get_remotes.Rd | 20 +
man/desc_get_urls.Rd | 20 +
man/desc_get_version.Rd | 24 +
man/desc_has_dep.Rd | 28 +
man/desc_has_fields.Rd | 27 +
man/desc_normalize.Rd | 20 +
man/desc_print.Rd | 16 +
man/desc_reformat_fields.Rd | 20 +
man/desc_reorder_fields.Rd | 20 +
man/desc_set.Rd | 34 +
man/desc_set_authors.Rd | 31 +
man/desc_set_collate.Rd | 31 +
man/desc_set_dep.Rd | 31 +
man/desc_set_deps.Rd | 27 +
man/desc_set_remotes.Rd | 22 +
man/desc_set_urls.Rd | 22 +
man/desc_set_version.Rd | 27 +
man/desc_to_latex.Rd | 16 +
man/desc_validate.Rd | 16 +
man/description.Rd | 413 +++++++++++
tests/testthat.R | 4 +
tests/testthat/D1 | 17 +
tests/testthat/D2 | 66 ++
tests/testthat/D3 | 63 ++
tests/testthat/D4 | 11 +
tests/testthat/D5 | 17 +
tests/testthat/files/DESCRIPTION | 17 +
tests/testthat/fixtures/notpkg_1.0.tar.gz | Bin 0 -> 140 bytes
tests/testthat/fixtures/pkg_1.0.0.tar.gz | Bin 0 -> 333 bytes
tests/testthat/fixtures/pkg_1.0.0.tgz | Bin 0 -> 2708 bytes
.../pkg_1.0.0_R_x86_64-pc-linux-gnu.tar.gz | Bin 0 -> 2958 bytes
tests/testthat/fixtures/xxx.gz | Bin 0 -> 28 bytes
tests/testthat/fixtures/xxx.tar.gz | Bin 0 -> 140 bytes
tests/testthat/fixtures/xxx.zip | Bin 0 -> 160 bytes
tests/testthat/helper.R | 6 +
tests/testthat/output/to_latex.tex | 17 +
tests/testthat/test-archives.R | 77 ++
tests/testthat/test-authors.R | 211 ++++++
tests/testthat/test-checks.R | 115 +++
tests/testthat/test-collate.R | 125 ++++
tests/testthat/test-create.R | 86 +++
tests/testthat/test-deps.R | 160 ++++
tests/testthat/test-desc.R | 9 +
tests/testthat/test-encoding.R | 41 ++
tests/testthat/test-idempotent.R | 30 +
tests/testthat/test-non-oo.R | 243 +++++++
tests/testthat/test-queries.R | 78 ++
tests/testthat/test-read.R | 32 +
tests/testthat/test-remotes.R | 38 +
tests/testthat/test-repair.R | 53 ++
tests/testthat/test-str.R | 68 ++
tests/testthat/test-to_latex.R | 15 +
tests/testthat/test-trailing-ws.R | 49 ++
tests/testthat/test-urls.R | 31 +
tests/testthat/test-utils.R | 65 ++
tests/testthat/test-validation.R | 11 +
tests/testthat/test-versions.R | 68 ++
tests/testthat/test-write.R | 27 +
120 files changed, 7835 insertions(+)
diff --git a/DESCRIPTION b/DESCRIPTION
new file mode 100644
index 0000000..70c835a
--- /dev/null
+++ b/DESCRIPTION
@@ -0,0 +1,29 @@
+Package: desc
+Title: Manipulate DESCRIPTION Files
+Version: 1.1.1
+Authors at R: c(
+ person("Gábor", "Csárdi",, "csardi.gabor at gmail.com", role = c("aut", "cre")),
+ person("Kirill", "Müller", role = c("aut")))
+Maintainer: Gábor Csárdi <csardi.gabor at gmail.com>
+Description: Tools to read, write, create, and manipulate DESCRIPTION files.
+ It is intended for packages that create or manipulate other packages.
+License: MIT + file LICENSE
+LazyData: true
+URL: https://github.com/r-lib/desc#readme
+BugReports: https://github.com/r-lib/desc/issues
+Depends: R (>= 3.1.0)
+Suggests: covr, testthat, whoami, withr
+Imports: assertthat, utils, R6, crayon, rprojroot
+Encoding: UTF-8
+RoxygenNote: 5.0.1.9000
+Collate: 'assertions.R' 'authors-at-r.R' 'classes.R' 'collate.R'
+ 'constants.R' 'deps.R' 'description.R' 'encoding.R' 'latex.R'
+ 'non-oo-api.R' 'package-archives.R' 'read.R' 'remotes.R'
+ 'str.R' 'syntax_checks.R' 'urls.R' 'utils.R' 'validate.R'
+ 'version.R'
+NeedsCompilation: no
+Packaged: 2017-08-03 09:45:28 UTC; gaborcsardi
+Author: Gábor Csárdi [aut, cre],
+ Kirill Müller [aut]
+Repository: CRAN
+Date/Publication: 2017-08-03 15:22:33 UTC
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..99ce77d
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,2 @@
+YEAR: 2015-2016
+COPYRIGHT HOLDER: Gábor Csárdi
diff --git a/MD5 b/MD5
new file mode 100644
index 0000000..ba605cd
--- /dev/null
+++ b/MD5
@@ -0,0 +1,119 @@
+b120a98a326be283dee5416f745a47c7 *DESCRIPTION
+e4c81207cdbd596c9e35a231e630e872 *LICENSE
+4e254b99976e579a2587cecb34a7c229 *NAMESPACE
+3056cc67c2902fb0032a72e11d495e3a *NEWS.md
+dc985b802fd20b6d0d31adaa5929cad5 *R/assertions.R
+babff9502088d0d0062c6cccbe86c3eb *R/authors-at-r.R
+e13f26e1ec730f2f133ba45efc8c4a60 *R/classes.R
+39c3291961f1119871f8b3576aa06c4d *R/collate.R
+418508ee6da0653620bfbf67f4dfdbd5 *R/constants.R
+87c6a8c69ef7a464d62b9e9c974c1a51 *R/deps.R
+6f867cf06289e8aec541b3065450b761 *R/description.R
+1327877ac0ca3f0132fb959bddc8365d *R/encoding.R
+8b53a4c108f2cba9e3b2684b30b00df0 *R/latex.R
+4236fbc124e625d4d7e866783192696b *R/non-oo-api.R
+50a6134f5a03ab1295ee9d941495eea2 *R/package-archives.R
+80205733fbcc2dd463b4679f3ae8d53c *R/read.R
+fad76c098683791fd87bee78ca016611 *R/remotes.R
+fe84270cb5fa350ff53046022c7a86b4 *R/str.R
+3167990c8ab9a520cf45e51f641eeaaf *R/syntax_checks.R
+1903e0b416ba812c5767561fae410752 *R/urls.R
+c2be1854c16b3ef1c62766a30aa30033 *R/utils.R
+7609c65315c4ccc461b4312b88e44e62 *R/validate.R
+9b3b1554e0ec41655574a6ba78f8a84a *R/version.R
+3ba0ce861688512cabe7cf512952e4cc *inst/DESCRIPTION
+dcee715ad05f22f9ea9015c7b9b4b0fc *inst/DESCRIPTION2
+3056cc67c2902fb0032a72e11d495e3a *inst/NEWS.md
+a21b37eda742851762f4920ed9d32153 *inst/README.Rmd
+0ea2e15de4e924d6e0caaa7c600df2d4 *inst/README.md
+dcb395e54a74f1a3e245107d3a7c462b *man/check_encoding.Rd
+7e5ed2e9239d833fb6dfdb79cd33b4b4 *man/check_field.Rd
+737f049bec71fdb4d033a71872687f62 *man/cran_ascii_fields.Rd
+6fc600cbc6c101d70ecbbc3e369ed83d *man/cran_valid_fields.Rd
+a3a809fc89d20c8074092db7ceb538c3 *man/dep_types.Rd
+5ed873c0a4c47152d098f34e31abb773 *man/desc.Rd
+9c7ac53396b3f2026341a95d5805ff54 *man/desc_add_author.Rd
+58582ba7155f961fdf5418ed8408b4eb *man/desc_add_me.Rd
+71706994e9b0be7f75c8b2e7eacbeedf *man/desc_add_remotes.Rd
+0cdf462feab0271295a3d43664127ac1 *man/desc_add_role.Rd
+4f68385fb64eecaf7c97bb70cca2737a *man/desc_add_to_collate.Rd
+891dc1dadafa3b454b0603d73e0d57aa *man/desc_add_urls.Rd
+2d590497f4fb0c88aedcc7e4f8a15366 *man/desc_bump_version.Rd
+e31650ac99464245228bfa6175b1dffb *man/desc_change_maintainer.Rd
+dc77c76d09b96aa30d0cca6a86debe40 *man/desc_clear_remotes.Rd
+a96c15eb21a62d796f73254995f0f44f *man/desc_clear_urls.Rd
+ec6a33b1a1cb753eb4199646cf29bcc7 *man/desc_del.Rd
+a78490e3f52365b3708e401848a6dea6 *man/desc_del_author.Rd
+8eb2495d08d694c333941485cd56d1df *man/desc_del_collate.Rd
+940ad60e62289b82e5f1f7a95ff3ebab *man/desc_del_dep.Rd
+3b837791e78755abace507762c86beea *man/desc_del_deps.Rd
+699968fdca0ddf2637903f7f598731fa *man/desc_del_from_collate.Rd
+bb28faf1c729437fb533c3fc4232178a *man/desc_del_remotes.Rd
+5013912f2ca8aec7812f5889cb70a77e *man/desc_del_role.Rd
+c5aeee74741c9a60a52b6a6af11f343d *man/desc_del_urls.Rd
+5769c2c428dd3d6fad29c5f7a748791a *man/desc_fields.Rd
+dbf2da6905930f0179fc93c7a8de8d53 *man/desc_get.Rd
+a70a7f06d61b5a870ecca401e1b1a95a *man/desc_get_author.Rd
+1aa1666b26ba947eef643803e7be8f98 *man/desc_get_authors.Rd
+b31f62168e3eee63c70685da79530cb5 *man/desc_get_collate.Rd
+04e2d3e6056320f7db40f858a1b3337c *man/desc_get_deps.Rd
+e6831e6c45a6cd6822fdb7f521faf49f *man/desc_get_maintainer.Rd
+9b2220cc3f2cb0f5bd413c8e933cd0ed *man/desc_get_or_fail.Rd
+f8a71db35581155e6a1aa702e197e06e *man/desc_get_remotes.Rd
+191429364648ee2946274f88bac6e8af *man/desc_get_urls.Rd
+e7e8ca420bc568ac84e0eaa521e48056 *man/desc_get_version.Rd
+a37df7a1e5602b98fa9c24bd4473bb8e *man/desc_has_dep.Rd
+142d818ce66c8f927f2c4d23e1de2a3d *man/desc_has_fields.Rd
+0b99f8ff7b4eafcb3024c593d890ab66 *man/desc_normalize.Rd
+6c1f784506911f67d26aa7357e9cac33 *man/desc_print.Rd
+c35d6dc412d5bb981990b6567136c1f0 *man/desc_reformat_fields.Rd
+9f3d13eaf7964187601b3ec96ac9dd4d *man/desc_reorder_fields.Rd
+7c8949e7d02fd9bfc55cf9a10632e277 *man/desc_set.Rd
+1c8464488a7ad5d7615b9c7dc877fff2 *man/desc_set_authors.Rd
+ee00f9c46b96d2ba8ae106404055608d *man/desc_set_collate.Rd
+3819e5b089cd5076328ba0b6b5902343 *man/desc_set_dep.Rd
+c4eb564530d2ad4cdc35b7120d8274ff *man/desc_set_deps.Rd
+2eb5d8e5c2de3230a6f58355706a184b *man/desc_set_remotes.Rd
+f22cccff4053b69fb690a7e0f5dca57a *man/desc_set_urls.Rd
+0e98b3e064e24ee4ff8462c107d38877 *man/desc_set_version.Rd
+644b0b378ca68574140981e51075c4b9 *man/desc_to_latex.Rd
+3796d76d4f7e600ff05a25dff40e9f9a *man/desc_validate.Rd
+cf7b13c7a12245c8775f0bc8965ca34a *man/description.Rd
+17a96b3396f1857d99adece6077e1577 *tests/testthat.R
+be96b5143b1d6266291d4f09583b9cee *tests/testthat/D1
+c7c30e1187575c55c4788e0a40d22196 *tests/testthat/D2
+0c5171c23afb3abfc6e7fb9810b44ff6 *tests/testthat/D3
+8e9f51d287512d940952ae45304d7019 *tests/testthat/D4
+2c9323b51bd0b848494819c3ee05d1e7 *tests/testthat/D5
+be96b5143b1d6266291d4f09583b9cee *tests/testthat/files/DESCRIPTION
+6dc77ce3ba4e9b5ecb2d5e53e7a129e2 *tests/testthat/fixtures/notpkg_1.0.tar.gz
+1261a114ea4b6fb4e2ab27228146c2ef *tests/testthat/fixtures/pkg_1.0.0.tar.gz
+5cdce130ef8ad8360e3cea164cfe6857 *tests/testthat/fixtures/pkg_1.0.0.tgz
+39f0cbfa42fb7e0643ef19ea07749d7e *tests/testthat/fixtures/pkg_1.0.0_R_x86_64-pc-linux-gnu.tar.gz
+aef8f02891aa60e66955e23356e6e3a4 *tests/testthat/fixtures/xxx.gz
+6dc77ce3ba4e9b5ecb2d5e53e7a129e2 *tests/testthat/fixtures/xxx.tar.gz
+267812fc7bc60f0a742afcfc33acfc62 *tests/testthat/fixtures/xxx.zip
+4a2335de80a9f8648445cba2aabd583f *tests/testthat/helper.R
+681b5cdb593bafa9ce5f35dfae530be2 *tests/testthat/output/to_latex.tex
+b7d43ebade5d8e25d0273284f39eb7b8 *tests/testthat/test-archives.R
+e1731b58f55950189d5fe2226ee3311e *tests/testthat/test-authors.R
+04d167df04fa31c835f34400d66dc7eb *tests/testthat/test-checks.R
+bb3600ff906f59f20c74ad662ba470c2 *tests/testthat/test-collate.R
+7f7769b4cdc0f3d15f849ee53f1e3d10 *tests/testthat/test-create.R
+b8839d6860a2035aec77b833504f5524 *tests/testthat/test-deps.R
+91a2fffc2a8f29c7ffe5e3993bc9be82 *tests/testthat/test-desc.R
+2e66935fe29cd96ff06b5ff2f36f6a56 *tests/testthat/test-encoding.R
+0484cad7c85958a2841b2cd9846b6a90 *tests/testthat/test-idempotent.R
+e9d7def0ebabc6180dadd4d6185f1882 *tests/testthat/test-non-oo.R
+b050b433642dc8e1353063a3d4e6bc4f *tests/testthat/test-queries.R
+23e640a4e35175373c2a876fa80067e6 *tests/testthat/test-read.R
+5336a948fb80ed8c3088b3e8c927e723 *tests/testthat/test-remotes.R
+09d6ed54453d8d642af2d07f0565a75f *tests/testthat/test-repair.R
+c2aee1218a3b5a642c96a95212fc31ee *tests/testthat/test-str.R
+6a949f92ab8c27e5254f6fd7d7180ec6 *tests/testthat/test-to_latex.R
+62fb74840dc93877d9a61ca6d68f4734 *tests/testthat/test-trailing-ws.R
+01531bec9205016b2d41b01d820a96f3 *tests/testthat/test-urls.R
+418c886aee25cdc0e088d2b1bbea79ec *tests/testthat/test-utils.R
+0f287c1718c783216568c3f0aebd0ade *tests/testthat/test-validation.R
+bbfa92ac5f36579f74e68a83e680f97c *tests/testthat/test-versions.R
+c53312942e21436c7a7b743359cbb398 *tests/testthat/test-write.R
diff --git a/NAMESPACE b/NAMESPACE
new file mode 100644
index 0000000..ab256eb
--- /dev/null
+++ b/NAMESPACE
@@ -0,0 +1,106 @@
+# Generated by roxygen2: do not edit by hand
+
+S3method(check_field,DescriptionAddedByRCMD)
+S3method(check_field,DescriptionAuthorsAtR)
+S3method(check_field,DescriptionClassification)
+S3method(check_field,DescriptionCollate)
+S3method(check_field,DescriptionCompression)
+S3method(check_field,DescriptionDate)
+S3method(check_field,DescriptionDependencyList)
+S3method(check_field,DescriptionDescription)
+S3method(check_field,DescriptionEncoding)
+S3method(check_field,DescriptionField)
+S3method(check_field,DescriptionFreeForm)
+S3method(check_field,DescriptionLanguage)
+S3method(check_field,DescriptionLicense)
+S3method(check_field,DescriptionLogical)
+S3method(check_field,DescriptionMaintainer)
+S3method(check_field,DescriptionOSType)
+S3method(check_field,DescriptionPackage)
+S3method(check_field,DescriptionPackageList)
+S3method(check_field,DescriptionPriority)
+S3method(check_field,DescriptionRepoList)
+S3method(check_field,DescriptionRepository)
+S3method(check_field,DescriptionTitle)
+S3method(check_field,DescriptionType)
+S3method(check_field,DescriptionURL)
+S3method(check_field,DescriptionURLList)
+S3method(check_field,DescriptionVersion)
+S3method(format,DescriptionAuthorsAtR)
+S3method(format,DescriptionCollate)
+S3method(format,DescriptionDependencyList)
+S3method(format,DescriptionField)
+S3method(toLatex,DescriptionAuthorsAtR)
+S3method(toLatex,DescriptionCollate)
+S3method(toLatex,DescriptionField)
+S3method(toLatex,DescriptionURL)
+S3method(toLatex,DescriptionURLList)
+S3method(toLatex,character)
+S3method(toLatex,person)
+export(check_field)
+export(cran_ascii_fields)
+export(cran_valid_fields)
+export(dep_types)
+export(desc)
+export(desc_add_author)
+export(desc_add_me)
+export(desc_add_remotes)
+export(desc_add_role)
+export(desc_add_to_collate)
+export(desc_add_urls)
+export(desc_bump_version)
+export(desc_change_maintainer)
+export(desc_clear_remotes)
+export(desc_clear_urls)
+export(desc_del)
+export(desc_del_author)
+export(desc_del_collate)
+export(desc_del_dep)
+export(desc_del_deps)
+export(desc_del_from_collate)
+export(desc_del_remotes)
+export(desc_del_role)
+export(desc_del_urls)
+export(desc_fields)
+export(desc_get)
+export(desc_get_author)
+export(desc_get_authors)
+export(desc_get_collate)
+export(desc_get_deps)
+export(desc_get_maintainer)
+export(desc_get_or_fail)
+export(desc_get_remotes)
+export(desc_get_urls)
+export(desc_get_version)
+export(desc_has_dep)
+export(desc_has_fields)
+export(desc_normalize)
+export(desc_print)
+export(desc_reformat_fields)
+export(desc_reorder_fields)
+export(desc_set)
+export(desc_set_authors)
+export(desc_set_collate)
+export(desc_set_dep)
+export(desc_set_deps)
+export(desc_set_remotes)
+export(desc_set_urls)
+export(desc_set_version)
+export(desc_to_latex)
+export(desc_validate)
+export(description)
+importFrom(R6,R6Class)
+importFrom(assertthat,"on_failure<-")
+importFrom(assertthat,assert_that)
+importFrom(crayon,blue)
+importFrom(crayon,red)
+importFrom(crayon,strip_style)
+importFrom(rprojroot,find_root)
+importFrom(rprojroot,is_r_package)
+importFrom(utils,as.person)
+importFrom(utils,packageName)
+importFrom(utils,person)
+importFrom(utils,tail)
+importFrom(utils,toLatex)
+importFrom(utils,untar)
+importFrom(utils,unzip)
diff --git a/NEWS.md b/NEWS.md
new file mode 100644
index 0000000..af32a99
--- /dev/null
+++ b/NEWS.md
@@ -0,0 +1,25 @@
+
+# 1.1.1
+
+* Relax the R >= 3.2.0 dependency, R 3.1.0 is enough now.
+
+# 1.1.0
+
+* Fix bug when adding authors and there is no `Authors at R` field
+
+* Get `DESCRIPTION` from package archives (#40)
+
+* Fix but in `del_dep()` and `has_dep()`, they only worked if the package
+ was attached.
+
+# 1.0.1
+
+* Fix formatting of `Collate` fields, they always start at a new line now.
+
+* Fix formatting of `Authors at R` fields, when changed.
+
+* Keep trailing space after the `:` character, see #14
+
+# 1.0.0
+
+First public release.
diff --git a/R/assertions.R b/R/assertions.R
new file mode 100644
index 0000000..b302cbf
--- /dev/null
+++ b/R/assertions.R
@@ -0,0 +1,147 @@
+
+as_string <- function(x) {
+ x <- as.character(x)
+ if (length(x) != 1) stop("Value must be a scalar")
+ x
+}
+
+#' @importFrom assertthat assert_that on_failure<-
+
+is_string <- function(x) {
+ is.character(x) && length(x) == 1 && ! is.na(x)
+}
+
+on_failure(is_string) <- function(call, env) {
+ paste0(deparse(call$x), " is not a string")
+}
+
+is_constructor_cmd <- function(x) {
+ is_string(x) && substring(x, 1, 1) == "!"
+}
+
+on_failure(is_constructor_cmd) <- function(call, env) {
+ paste0(deparse(call$x), " is not a string that starts with '!'")
+}
+
+is_path <- function(x) {
+ is_string(x)
+}
+
+on_failure(is_path) <- function(call, env) {
+ paste0(deparse(call$x), " is not a path")
+}
+
+is_existing_file <- function(x) {
+ is_path(x) && file.exists(x)
+}
+
+on_failure(is_existing_file) <- function(call, env) {
+ paste0("File ", deparse(call$x), " does not exist")
+}
+
+has_no_na <- function(x) {
+ !any(is.na(x))
+}
+
+on_failure(has_no_na) <- function(call, env) {
+ paste0(deparse(call$x), " must not contain NAs")
+}
+
+is_flag <- function(x) {
+ is.logical(x) && length(x) == 1 && !is.na(x)
+}
+
+on_failure(is_flag) <- function(call, env) {
+ paste0(deparse(call$x), " is not a flag (length 1 logical)")
+}
+
+is_authors <- function(x) {
+ inherits(x, "person")
+}
+
+on_failure(is_authors) <- function(call, env) {
+ paste0(deparse(call$x), " must be a person object")
+}
+
+is_string_or_null <- function(x) {
+ is_string(x) || is.null(x)
+}
+
+on_failure(is_string_or_null) <- function(call, env) {
+ paste0(deparse(call$x), " must be a string or NULL")
+}
+
+is_collate_field <- function(x) {
+ is_string(x) && x %in% names(collate_fields)
+}
+
+on_failure(is_collate_field) <- function(call, env) {
+ fields <- paste(sQuote(names(collate_fields)), collapse = ", ")
+ paste0(deparse(call$x), " must be one of ", fields)
+}
+
+is_collate_field_or_all <- function(x) {
+ is_string(x) && (x %in% names(collate_fields) || x == "all")
+}
+
+on_failure(is_collate_field) <- function(call, env) {
+ fields <- paste(sQuote(c(names(collate_fields), "all")), collapse = ", ")
+ paste0(deparse(call$x), " must be one of ", fields)
+}
+
+is_collate_field_or_all_or_default <- function(x) {
+ is_string(x) && (x %in% names(collate_fields) || x == "all" || x == "default")
+}
+
+on_failure(is_collate_field) <- function(call, env) {
+ fields <- paste(
+ sQuote(c(names(collate_fields), "all", "default")),
+ collapse = ", "
+ )
+ paste0(deparse(call$x), " must be one of ", fields)
+}
+
+is_deps_df <- function(x) {
+ is.data.frame(x) &&
+ identical(sort(names(x)), sort(c("type", "package", "version"))) &&
+ all(sapply(x, is.character) | sapply(x, is.factor))
+}
+
+on_failure(is_deps_df) <- function(call, env) {
+ cols <- paste(sQuote(c("type", "package", "version")), collapse = ", ")
+ paste0(
+ deparse(call$x),
+ " must be a data frame with character columns ",
+ cols
+ )
+}
+
+is_package_version <- function(x) {
+ tryCatch(
+ {
+ length(package_version(x)) == 1
+ },
+ error = function(e) FALSE
+ )
+}
+
+on_failure(is_package_version) <- function(call, env) {
+ paste0(deparse(call$x), " is an invalid version number")
+}
+
+is_version_component <- function(x) {
+ (is_string(x) && x %in% c("major", "minor", "patch", "dev")) ||
+ (is_count(x) && x <= 5)
+}
+
+on_failure(is_version_component) <- function(call, env) {
+ paste0(deparse(call$x), " is not a version number component (see docs)")
+}
+
+is_count <- function(x) {
+ is.numeric(x) && length(x) == 1 && !is.na(x) && as.integer(x) == x
+}
+
+on_failure(is_count) <- function(call, env) {
+ paste0(deparse(call$x), " is not a count (length 1 integer)")
+}
diff --git a/R/authors-at-r.R b/R/authors-at-r.R
new file mode 100644
index 0000000..0c089f1
--- /dev/null
+++ b/R/authors-at-r.R
@@ -0,0 +1,236 @@
+
+#' @importFrom utils as.person
+
+parse_authors_at_r <- function(x) {
+
+ if (is.null(x) || is.na(x)) return(NULL)
+
+ out <- tryCatch(
+ eval(parse(text = x)),
+ error = identity
+ )
+ if (inherits(out, "error")) NULL else out
+}
+
+
+deparse_authors_at_r <- function(x) {
+ fmt <- lapply(unclass(x), deparse_author_at_r)
+ if (length(fmt) == 1) {
+ paste0("\n", paste0(" ", fmt[[1]], collapse = "\n"))
+ } else {
+ for (i in seq_along(fmt)) {
+ fmt[[i]] <- paste0(" ", fmt[[i]])
+ fmt[[i]][[length(fmt[[i]])]] <- paste0(fmt[[i]][[length(fmt[[i]])]], ",")
+ }
+ fmt[[1]][[1]] <- sub("^ ", "c(", fmt[[1]][[1]])
+ n <- length(fmt)
+ fmt[[n]][[length(fmt[[n]])]] <- sub(",$", ")", fmt[[n]][[length(fmt[[n]])]])
+ paste0("\n", paste0(" ", unlist(fmt), collapse = "\n"))
+ }
+}
+
+deparse_author_at_r <- function(x1) {
+ x1 <- x1[! vapply(x1, is.null, TRUE)]
+ paste0(
+ c("person(", rep(" ", length(x1) - 1)),
+ names(x1), " = ", vapply(x1, deparse, ""),
+ c(rep(",", length(x1) - 1), ")")
+ )
+}
+
+set_author_field <- function(authors, which, field, value) {
+ rval <- unclass(authors)
+ for (w in which) rval[[w]][[field]] <- value
+ class(rval) <- class(authors)
+ rval
+}
+
+
+ensure_authors_at_r <- function(obj) {
+ if (! obj$has_fields("Authors at R")) {
+ stop("No 'Authors at R' field!\n",
+ "You can create one with $add_author")
+ }
+}
+
+
+## Find an author in the Authors at R field, based on a partical
+## specification. E.g. it is enough to give the first name.
+
+search_for_author <- function(authors, given = NULL, family = NULL,
+ email = NULL, role = NULL, comment = NULL) {
+
+ matching <-
+ ngrepl(given, authors$given) &
+ ngrepl(family, authors$family) &
+ ngrepl(email, authors$email) &
+ ngrepl(role, authors$role) &
+ ngrepl(comment, authors$comment)
+
+ list(index = which(matching), authors = authors[matching])
+}
+
+
+idesc_get_authors <- function(self, private, ensure = TRUE) {
+ assert_that(is_flag(ensure))
+ if (ensure) ensure_authors_at_r(self)
+ parse_authors_at_r(self$get("Authors at R"))
+}
+
+
+idesc_get_author <- function(self, private, role) {
+ assert_that(is_string(role))
+ if (self$has_fields("Authors at R")) {
+ aut <- self$get_authors()
+ roles <- aut$role
+ ## Broken person() API, vector for 1 author, list otherwise...
+ if (!is.list(roles)) roles <- list(roles)
+ selected <- vapply(roles, function(r) all(role %in% r), TRUE)
+ aut[selected]
+ } else {
+ NULL
+ }
+}
+
+idesc_set_authors <- function(self, private, authors) {
+ assert_that(is_authors(authors))
+ self$set("Authors at R", deparse_authors_at_r(authors))
+}
+
+check_author_args <- function(given = NULL, family = NULL, email = NULL,
+ role = NULL, comment = NULL) {
+ assert_that(
+ is_string_or_null(given),
+ is_string_or_null(family),
+ is_string_or_null(email),
+ is_string_or_null(role),
+ is_string_or_null(comment)
+ )
+}
+
+#' @importFrom utils person
+
+idesc_add_author <- function(self, private, given, family, email, role,
+ comment) {
+ check_author_args(given, family, email, role, comment)
+ orig <- idesc_get_authors(self, private, ensure = FALSE)
+ newp <- person(given = given, family = family, email = email,
+ role = role, comment = comment)
+ new_authors <- if (is.null(orig)) newp else c(orig, newp)
+ self$set_authors(new_authors)
+}
+
+
+idesc_add_role <- function(self, private, role, given, family, email,
+ comment) {
+
+ assert_that(is.character(role))
+ check_author_args(given, family, email, comment = comment)
+
+ orig <- idesc_get_authors(self, private, ensure = FALSE)
+ wh <- search_for_author(
+ orig, given = given, family = family, email = email, comment = comment,
+ role = NULL
+ )
+
+ for (w in wh$index) {
+ orig <- set_author_field(
+ orig,
+ w,
+ "role",
+ unique(c(orig[[w]]$role, role))
+ )
+ }
+
+ self$set_authors(orig)
+}
+
+
+idesc_del_author <- function(self, private, given, family, email, role,
+ comment) {
+
+ check_author_args(given, family, email, role, comment)
+
+ orig <- idesc_get_authors(self, private, ensure = FALSE)
+ wh <- search_for_author(
+ orig, given = given, family = family, email = email, comment = comment
+ )
+
+ if (length(wh$index) == 0) {
+ message("Could not find author to remove.")
+ } else {
+ au <- if (length(wh$index) == 1) "Author" else "Authors"
+ message(
+ au, "removed: ",
+ paste(wh$authors$given, wh$authors$family, collapse = ", "),
+ "."
+ )
+ self$set_authors(orig[-wh$index])
+ }
+
+ invisible(self)
+}
+
+
+idesc_del_role <- function(self, private, role, given, family, email,
+ comment) {
+
+ assert_that(is.character(role))
+ check_author_args(given, family, email, role = NULL, comment)
+
+ orig <- idesc_get_authors(self, private, ensure = FALSE)
+ wh <- search_for_author(
+ orig, given = given, family = family, email = email, comment = comment,
+ role = NULL
+ )
+
+ for (w in wh$index) {
+ orig <- set_author_field(
+ orig,
+ w,
+ "role",
+ setdiff(orig[[w]]$role, role)
+ )
+ }
+
+ self$set_authors(orig)
+}
+
+
+idesc_change_maintainer <- function(self, private, given, family, email,
+ comment) {
+ check_author_args(given, family, email, role = NULL, comment)
+ ensure_authors_at_r(self)
+ self$del_role(role = "cre")
+ self$add_role(role = "cre", given = given, family = family,
+ email = email, comment = comment)
+}
+
+
+#' @importFrom utils tail
+
+idesc_add_me <- function(self, private, role, comment) {
+ assert_that(is_string_or_null(role))
+ assert_that(is_string_or_null(comment))
+ check_for_package("whoami", "$add_me needs the 'whoami' package")
+ fn <- strsplit(whoami::fullname(), "[ ]+")[[1]]
+ family <- tail(fn, 1)
+ given <- paste(fn[-length(fn)], collapse = " ")
+ email <- whoami::email_address()
+ self$add_author(given = given, family = family, email = email,
+ comment = comment, role = role)
+}
+
+
+idesc_get_maintainer <- function(self, private) {
+ if (self$has_fields("Maintainer")) {
+ unname(self$get("Maintainer"))
+ } else if (self$has_fields("Authors at R")) {
+ format(
+ self$get_author(role = "cre"),
+ include = c("given", "family", "email")
+ )
+ } else {
+ NA_character_
+ }
+}
diff --git a/R/classes.R b/R/classes.R
new file mode 100644
index 0000000..8a94ed6
--- /dev/null
+++ b/R/classes.R
@@ -0,0 +1,69 @@
+
+collate_fields <- c(
+ main = "Collate",
+ windows = "Collate.windows",
+ unix = "Collate.unix"
+)
+
+field_classes <- list(
+
+ Package = "Package",
+ Version = "Version",
+ License = "License",
+ Description = "Description",
+ Title = "Title",
+ Maintainer = "Maintainer",
+ AuthorsAtR = "Authors at R",
+
+ DependencyList = c("Imports", "Suggests", "Depends", "Enhances",
+ "LinkingTo"),
+ RepoList = "Additional_repositories",
+ URL = "BugReports",
+ URLList = "URL",
+ Priority = "Priority",
+ Collate = unname(collate_fields),
+ Logical = c("LazyData", "KeepSource", "ByteCompile", "ZipData", "Biarch",
+ "BuildVignettes", "NeedsCompilation", "License_is_FOSS",
+ "License_restricts_use", "BuildKeepEmpty", "BuildManual",
+ "BuildResaveData", "LazyLoad"),
+ PackageList = c("VignetteBuilder", "RdMacros"),
+ Encoding = "Encoding",
+ OSType = "OS_type",
+ Type = "Type",
+ Classification = c("Classification/ACM", "Classification/ACM-2012",
+ "Classification/JEL", "Classification/MSC", "Classification/MSC-2010"),
+ Language = "Language",
+ Date = "Date",
+ Compression = c("LazyDataCompression", "SysDataCompression"),
+ Repository = "Repository",
+
+ FreeForm = c("Author", "SystemRequirements",
+ "Archs", "Contact", "Copyright", "MailingList", "Note", "Path",
+ "LastChangedDate", "LastChangedRevision", "Revision", "RcmdrModels",
+ "RcppModules", "Roxygen", "Acknowledgements", "Acknowledgments",
+ "biocViews"),
+
+ AddedByRCMD = c("Built", "Packaged", "MD5sum", "Date/Publication")
+)
+
+field_classes$FreeForm <- c(
+ field_classes$FreeForm,
+ paste0(unlist(field_classes), "Note")
+)
+
+create_fields <- function(keys, values) {
+ mapply(keys, values, SIMPLIFY = FALSE, FUN = create_field)
+}
+
+create_field <- function(key, value) {
+ f <- structure(list(key = key, value = value), class = "DescriptionField")
+ if (key %in% unlist(field_classes)) {
+ cl <- paste0("Description", find_field_class(key))
+ class(f) <- c(cl, class(f))
+ }
+ f
+}
+
+find_field_class <- function(k) {
+ names(which(vapply(field_classes, `%in%`, logical(1), x = k)))
+}
diff --git a/R/collate.R b/R/collate.R
new file mode 100644
index 0000000..05e4c39
--- /dev/null
+++ b/R/collate.R
@@ -0,0 +1,122 @@
+
+which_collate <- function(x) {
+ collate_fields[x]
+}
+
+
+idesc_set_collate <- function(self, private, files, which) {
+ assert_that(is.character(files), is_collate_field(which))
+ if (length(files) == 0) warning("No files in 'Collate' field")
+
+ idesc_really_set_collate(self, private, files, which_collate(which))
+}
+
+
+idesc_really_set_collate <- function(self, private, files, field) {
+ if (!identical(self$get_collate(), files)) {
+ self$set(field, deparse_collate(files))
+ }
+}
+
+
+idesc_get_collate <- function(self, private, which) {
+ assert_that(is_collate_field(which))
+ coll <- unname(self$get(which_collate(which)))
+ if (identical(coll, NA_character_)) character() else parse_collate(coll)
+}
+
+
+idesc_del_collate <- function(self, private, which) {
+ assert_that(is_collate_field_or_all(which))
+
+ if (which == "all") {
+ self$del(collate_fields)
+
+ } else {
+ self$del(collate_fields[which])
+ }
+
+ invisible(self)
+}
+
+
+idesc_add_to_collate <- function(self, private, files, which) {
+ assert_that(is.character(files), is_collate_field_or_all_or_default(which))
+
+ if (which == "default") {
+ ex_coll <- intersect(collate_fields, self$fields())
+ if (length(ex_coll) == 0) {
+ real_add_to_collate(self, private, which_collate("main"), files)
+ } else {
+ for (ex in ex_coll) real_add_to_collate(self, private, ex, files)
+ }
+
+ } else if (which == "all") {
+ for (coll in collate_fields) {
+ real_add_to_collate(self, private, coll, files)
+ }
+
+ } else {
+ real_add_to_collate(self, private, which_collate(which), files)
+ }
+
+}
+
+## TODO: better order, and support dependencies
+
+real_add_to_collate <- function(self, private, field, files) {
+ ex <- if (!self$has_fields(field)) {
+ character()
+ } else {
+ parse_collate(self$get(field))
+ }
+
+ files <- unique(c(ex, files))
+ idesc_really_set_collate(self, private, files, field)
+}
+
+
+idesc_del_from_collate <- function(self, private, files, which) {
+ assert_that(is.character(files), is_collate_field_or_all(which))
+
+ if (which == "all") {
+ for (coll in collate_fields) {
+ real_del_from_collate(self, private, coll, files)
+ }
+
+ } else {
+ real_del_from_collate(self, private, which_collate(which), files)
+ }
+}
+
+real_del_from_collate <- function(self, private, field, files) {
+ if (self$has_fields(field)) {
+ coll <- setdiff(parse_collate(self$get(field)), files)
+ idesc_really_set_collate(self, private, coll, field)
+ } else {
+ invisible(self)
+ }
+}
+
+
+parse_collate <- function(str) {
+ scan(
+ text = gsub("\n", " ", str),
+ what = "",
+ strip.white = TRUE,
+ quiet = TRUE
+ )
+}
+
+
+deparse_collate <- function(list) {
+ paste0(
+ "\n",
+ paste0(
+ " '",
+ list,
+ "'",
+ collapse = "\n"
+ )
+ )
+}
diff --git a/R/constants.R b/R/constants.R
new file mode 100644
index 0000000..f8b89d8
--- /dev/null
+++ b/R/constants.R
@@ -0,0 +1,111 @@
+
+#' DESCRIPTION fields that denote package dependencies
+#'
+#' Currently it has the following ones: Imports, Depends,
+#' Suggests, Enhances and LinkingTo. See the \emph{Writing R Extensions}
+#' manual for when to use which.
+#'
+#' @family field types
+#' @export
+
+dep_types <- c("Imports", "Depends", "Suggests", "Enhances", "LinkingTo")
+
+standard_fields <- c(
+ "Additional_repositories",
+ "Author",
+ "Authors at R",
+ "Biarch",
+ "BugReports",
+ "BuildKeepEmpty",
+ "BuildManual",
+ "BuildResaveData",
+ "BuildVignettes",
+ "Built",
+ "ByteCompile",
+ "Classification/ACM",
+ "Classification/ACM-2012",
+ "Classification/JEL",
+ "Classification/MSC",
+ "Classification/MSC-2010",
+ "Collate",
+ "Collate.unix",
+ "Collate.windows",
+ "Contact",
+ "Copyright",
+ "Date",
+ "Depends",
+ "Description",
+ "Encoding",
+ "Enhances",
+ "Imports",
+ "KeepSource",
+ "Language",
+ "LazyData",
+ "LazyDataCompression",
+ "LazyLoad",
+ "License",
+ "LinkingTo",
+ "MailingList",
+ "Maintainer",
+ "Note",
+ "OS_type",
+ "Package",
+ "Packaged",
+ "Priority",
+ "Suggests",
+ "SysDataCompression",
+ "SystemRequirements",
+ "Title",
+ "Type",
+ "URL",
+ "Version",
+ "VignetteBuilder",
+ "ZipData",
+ "Repository",
+ "Path",
+ "Date/Publication",
+ "LastChangedDate",
+ "LastChangedRevision",
+ "Revision",
+ "RcmdrModels",
+ "RcppModules",
+ "Roxygen",
+ "Acknowledgements",
+ "Acknowledgments", # USA/Canadian usage.
+ "biocViews"
+)
+
+#' A list of DESCRIPTION fields that are valid according to the CRAN checks
+#'
+#' @family field types
+#' @export
+
+cran_valid_fields <- c(
+ standard_fields,
+ "^(?:X-CRAN|Repository/R-Forge)",
+ paste0(standard_fields, "Note")
+)
+
+#' The DESCRIPTION fields that are supposed to be in plain ASCII encoding
+#'
+#' @family field types
+#' @export
+
+cran_ascii_fields <- c(
+ "Package",
+ "Version",
+ "Priority",
+ "Depends",
+ "Imports",
+ "LinkingTo",
+ "Suggests",
+ "Enhances",
+ "License",
+ "License_is_FOSS",
+ "License_restricts_use",
+ "OS_type",
+ "Archs",
+ "MD5sum",
+ "NeedsCompilation",
+ "Encoding"
+)
diff --git a/R/deps.R b/R/deps.R
new file mode 100644
index 0000000..bcd5ccc
--- /dev/null
+++ b/R/deps.R
@@ -0,0 +1,131 @@
+
+idesc_set_dep <- function(self, private, package, type, version) {
+ assert_that(is_string(package), is_string(version))
+ deps <- self$get_deps()
+ has <- which(deps$package == package & deps$type == type)
+
+ if (length(has)) {
+ deps[ has, "version" ] <- version
+
+ } else {
+ deps <- rbind(
+ deps,
+ data.frame(
+ stringsAsFactors = FALSE,
+ type = type, package = package, version = version
+ )
+ )
+ }
+
+ idesc_set_deps(self, private, deps)
+}
+
+
+idesc_set_deps <- function(self, private, deps) {
+ assert_that(is_deps_df(deps))
+ depdeps <- deparse_deps(deps)
+ for (d in names(depdeps)) {
+ if (! same_deps(depdeps[[d]], private$data[[d]]$value)) {
+ self$set(d, depdeps[[d]])
+ }
+ }
+
+ deldeps <- setdiff(dep_types, names(depdeps))
+ self$del(deldeps)
+
+ invisible(self)
+}
+
+
+same_deps <- function(d1, d2) {
+ if (is.null(d1) + is.null(d2) == 1) return(FALSE)
+
+ d1 <- parse_deps("foo", d1)
+ d2 <- parse_deps("foo", d2)
+
+ d1 <- d1[ order(d1$type, d1$package, d1$version), ]
+ d2 <- d2[ order(d2$type, d2$package, d2$version), ]
+ nrow(d1) == nrow(d2) &&
+ all(d1$type == d2$type) &&
+ all(d1$package == d2$package) &&
+ all(d1$version == d2$version)
+}
+
+
+idesc_get_deps <- function(self, private) {
+ types <- intersect(names(private$data), dep_types)
+ res <- lapply(types, function(type)
+ parse_deps(type, private$data[[type]]$value))
+ do.call(rbind, res)
+}
+
+
+parse_deps <- function(type, deps) {
+ deps <- str_trim(strsplit(deps, ",")[[1]])
+ deps <- lapply(strsplit(deps, "\\("), str_trim)
+ deps <- lapply(deps, sub, pattern = "\\)$", replacement = "")
+ res <- data.frame(
+ stringsAsFactors = FALSE,
+ type = if (length(deps)) type else character(),
+ package = vapply(deps, "[", "", 1),
+ version = vapply(deps, "[", "", 2)
+ )
+ res [ is.na(res) ] <- "*"
+ res
+}
+
+
+deparse_deps <- function(deps) {
+ tapply(seq_len(nrow(deps)), deps$type, function(x) {
+ pkgs <- paste0(
+ " ",
+ deps$package[x],
+ ifelse(
+ deps$version[x] == "*",
+ "",
+ paste0(" (", deps$version[x], ")")
+ ),
+ collapse = ",\n"
+ )
+ paste0("\n", pkgs)
+ })
+}
+
+
+idesc_del_dep <- function(self, private, package, type) {
+ assert_that(is_string(package))
+ deps <- self$get_deps()
+
+ if (type == "all") {
+ has <- which(deps$package == package)
+ } else {
+ has <- which(deps$package == package & deps$type == type)
+ }
+
+ if (length(has)) {
+ deps <- deps[-has, ]
+ idesc_set_deps(self, private, deps)
+
+ } else {
+ invisible(self)
+ }
+}
+
+
+idesc_del_deps <- function(self, private) {
+ self$del(dep_types)
+}
+
+
+idesc_has_dep <- function(self, private, package, type) {
+ assert_that(is_string(package))
+
+ deps <- self$get_deps()
+ if (type == "any") {
+ package %in% deps$package
+
+ } else {
+ package %in% deps$package &&
+ type %in% deps[match(package, deps$package), "type"]
+ }
+}
diff --git a/R/description.R b/R/description.R
new file mode 100644
index 0000000..5ab9e89
--- /dev/null
+++ b/R/description.R
@@ -0,0 +1,808 @@
+
+#' Read a DESCRIPTION file
+#'
+#' This is a convenience wrapper for \code{description$new()}.
+#' Very often you want to read an existing \code{DESCRIPTION}
+#' file, and to do this you can just supply the path to the file or its
+#' directory to \code{desc()}.
+#'
+#' @param cmd A command to create a description from scratch.
+#' Currently only \code{"!new"} is implemented. If it does not start
+#' with an exclamation mark, it will be interpreted as the \sQuote{file}
+#' argument.
+#' @param file Name of the \code{DESCRIPTION} file to load. If all of
+#' \sQuote{cmd}, \sQuote{file} and \sQuote{text} are \code{NULL} (the
+#' default), then the \code{DESCRIPTION} file in the current working
+#' directory is used. The file can also be an R package (source, or
+#' binary), in which case the DESCRIPTION file is extracted from it, but
+#' note that in this case \code{$write()} cannot write the file back in
+#' the package archive.
+#' @param text A character scalar containing the full DESCRIPTION.
+#' Character vectors are collapsed into a character scalar, with
+#' newline as the separator.
+#' @param package If not NULL, then the name of an installed package
+#' and the DESCRIPTION file of this package will be loaded.
+#'
+#' @export
+#' @examples
+#' desc(package = "desc")
+#' DESCRIPTION <- system.file("DESCRIPTION", package = "desc")
+#' desc(DESCRIPTION)
+
+desc <- function(cmd = NULL, file = NULL, text = NULL, package = NULL) {
+ description$new(cmd, file, text, package)
+}
+
+#' Read, write, update, validate DESCRIPTION files
+#'
+#' @section Constructors:
+#'
+#' There are two ways of creating a description object. The first
+#' is reading an already existing \code{DESCRIPTION} file; simply give
+#' the name of the file as an argument. The default is
+#' \code{DESCRIPTION}: \preformatted{ x <- description$new()
+#' x2 <- description$new("path/to/DESCRIPTION")}
+#'
+#' The second way is creating a description object from scratch,
+#' supply \code{"!new"} as an argument to do this.
+#' \preformatted{ x3 <- description$new("!new")}
+#'
+#' The complete API reference:
+#' \preformatted{description$new(cmd = NULL, file = NULL, text = NULL,
+#' package = NULL)}
+#' \describe{
+#' \item{cmd:}{A command to create a description from scratch.
+#' Currently only \code{"!new"} is implemented. If it does not start
+#' with an exclamation mark, it will be interpreted as a \sQuote{file}
+#' argument.}
+#' \item{file:}{Name of the \code{DESCRIPTION} file to load. If it is
+#' a directory, then we assume that it is inside an R package and
+#' conduct a search for the package root directory, i.e. the first
+#' directory up the tree that contains a \code{DESCRIPTION} file.
+#' If \sQuote{cmd}, \sQuote{file}, \sQuote{text} and \sQuote{package}
+#' are all \code{NULL} (the default), then the search is started from
+#' the working directory. The file can also be an R package (source, or
+#' binary), in which case the DESCRIPTION file is extracted from it,
+#' but note that in this case \code{$write()} cannot write the file
+#' back in the package archive.}
+#' \item{text:}{A character scalar containing the full DESCRIPTION.
+#' Character vectors are collapsed into a character scalar, with
+#' newline as the separator.}
+#' \item{package}{If not NULL, then the name of an installed package
+#' and the DESCRIPTION file of this package will be loaded.}
+#' }
+#'
+#' @section Setting and Querying fields:
+#' Set a field with \code{$set} and query it with \code{$get}:
+#' \preformatted{ x <- description$new("!new")
+#' x$get("Package)
+#' x$set("Package", "foobar")
+#' x$set(Title = "Example Package for 'description'")
+#' x$get("Package")}
+#' Note that \code{$set} has two forms. You can either give the field name
+#' and new value as two arguments; or you can use a single named argument,
+#' the argument name is the field name, the argument value is the field
+#' value.
+#'
+#' The \code{$fields} method simply lists the fields in the object:
+#' \preformatted{ x$fields()}
+#'
+#' The \code{$has_fields} method checks if one or multiple fields are
+#' present in a description object: \preformatted{ x$has_fields("Package")
+#' x$has_fields(c("Title", "foobar"))}
+#'
+#' The \code{$del} method removes the specified fields:
+#' \preformatted{ x$set(foo = "bar")
+#' x$del("foo")}
+#'
+#' \code{$get_or_fail} is similar to \code{$get}, but throws an error
+#' if a field does not exist, except of silently returning
+#' \code{NA_character}.
+#'
+#' The complete API reference:
+#' \preformatted{ description$get(keys)
+#' description$get_or_fail(keys)
+#' description$set(...)
+#' description$fields()
+#' description$has_fields(keys)
+#' description$del(keys)}
+#' \describe{
+#' \item{keys:}{A character vector of keys to query, check or delete.}
+#' \item{...:}{This must be either two unnamed arguments, the key and
+#' and the value to set; or an arbitrary number of named arguments,
+#' names are used as keys, values as values to set.}
+#' }
+#'
+#' @section Normalizing:
+#' Format DESCRIPTION in a standard way. \code{$str} formats each
+#' field in a standard way and returns them (it does not change the
+#' object itself), \code{$print} is used to print it to the
+#' screen. The \code{$normalize} function normalizes each field (i.e.
+#' it changes the object). Normalization means reformatting the fields,
+#' via \code{$reformat_fields()} and also reordering them via
+#' \code{$reorder_fields()}. The format of the various fields is
+#' opinionated and you might like it or not. Note that \code{desc} only
+#' reformats fields that it updates, and only on demand, so if your
+#' formatting preferences differ, you can still manually edit
+#' \code{DESCRIPTION} and \code{desc} will respect your edits.
+#'
+#' \preformatted{ description$str(by_field = FALSE, normalize = TRUE,
+#' mode = c("file", "screen"))
+#' description$normalize()
+#' description$reformat_fields()
+#' description$reorder_fields()
+#' description$print()
+#' }
+#' \describe{
+#' \item{by_field:}{Whether to return the normalized format
+#' by field, or collapsed into a character scalar.}
+#' \item{normalize:}{Whether to reorder and reformat the fields.}
+#' \item{mode:}{\sQuote{file} mode formats the fields as they are
+#' written to a file with the \code{write} method. \sQuote{screen}
+#' mode adds extra markup to some fields, e.g. formats the
+#' \code{Authors at R} field in a readable way.}
+#' }
+#'
+#' @section Writing it to file:
+#' The \code{$write} method writes the description to a file.
+#' By default it writes it to the file it was created from, if it was
+#' created from a file. Otherwise giving a file name is compulsary:
+#' \preformatted{ x$write(file = "DESCRIPTION")}
+#'
+#' The \code{normalize} argument controls whether the fields are
+#' reformatted according to a standard style. By default they are not.
+#'
+#' The API:
+#' \preformatted{ description$write(file = NULL, normalize = NULL)}
+#' \describe{
+#' \item{file:}{Path to write the description to. If it was created
+#' from a file in the first place, then it is written to the same
+#' file. Otherwise this argument must be specified.}
+#' \item{normalize:}{Whether to reformat the fields in a standard way.}
+#' }
+#'
+#' @section Version numbers:
+#'
+#' \preformatted{ description$get_version()
+#' description$set_version(version)
+#' description$bump_version(which = c("patch", "minor", "major", "dev"))
+#' }
+#'
+#' \describe{
+#' \item{version:}{A string or a \code{\link[base]{package_version}}
+#' object.}
+#' \item{which:}{Which component of the version number to increase.
+#' See details just below.}
+#' }
+#'
+#' These functions are simple helpers to make it easier to query, set and
+#' increase the version number of a package.
+#'
+#' \code{$get_version()} returns the version number as a
+#' \code{\link[base]{package_version}} object. It throws an error if the
+#' package does not have a \sQuote{Version} field.
+#'
+#' \code{$set_version()} takes a string or a
+#' \code{\link[base]{package_version}} object and sets the \sQuote{Version}
+#' field to it.
+#'
+#' \code{$bump_version()} increases the version number. The \code{which}
+#' parameter specifies which component to increase.
+#' It can be a string referring to a component: \sQuote{major},
+#' \sQuote{minor}, \sQuote{patch} or \sQuote{dev}, or an integer
+#' scalar, for the latter components are counted from one, and the
+#' beginning. I.e. component one is equivalent to \sQuote{major}.
+#'
+#' If a component is bumped, then the ones after it are zeroed out.
+#' Trailing zero components are omitted from the new version number,
+#' but if the old version number had at least two or three components, then
+#' the one will also have two or three.
+#'
+#' The bumping of the \sQuote{dev} version (the fourth component) is
+#' special: if the original version number had less than four components,
+#' and the \sQuote{dev} version is bumped, then it is set to \code{9000}
+#' instead of \code{1}. This is a convention often used by R developers,
+#' it was originally invented by Winston Chang.
+#'
+#' Both \code{$set_version()} and \code{$bump_version()} use dots to
+#' separate the version number components.
+#'
+#' @section Dependencies:
+#' These functions handle the fields that define how the R package
+#' uses another R packages. See \code{\link{dep_types}} for the
+#' list of fields in this group.
+#'
+#' The \code{$get_deps} method returns all declared dependencies, in a
+#' data frame with columns: \code{type}, \code{package} and \code{version}.
+#' \code{type} is the name of the dependency field, \code{package} is the
+#' name of the R package, and \code{version} is the required version. If
+#' no specific versions are required, then this is a \code{"\*"}.
+#'
+#' The \code{$set_deps} method is the opposite of \code{$get_deps} and
+#' it sets all dependencies. The input is a data frame, with the same
+#' structure as the return value of \code{$get_deps}.
+#'
+#' The \code{$has_dep} method checks if a package is included in the
+#' dependencies. It returns a logical scalar. If \code{type} is not
+#' \sQuote{any}, then it has to match as well.
+#'
+#' The \code{$del_deps} method removes all declared dependencies.
+#'
+#' The \code{$set_dep} method adds or updates a single dependency. By
+#' default it adds the package to the \code{Imports} field.
+#'
+#' The API:
+#' \preformatted{ description$set_dep(package, type = dep_types, version = "\*")
+#' description$set_deps(deps)
+#' description$get_deps()
+#' description$has_dep(package, type = c("any", dep_types))
+#' description$del_dep(package, type = c("all", dep_types))
+#' description$del_deps()
+#' }
+#' \describe{
+#' \item{package:}{Name of the package to add to or remove from the
+#' dependencies.}
+#' \item{type:}{Dependency type, see \code{\link{dep_types}}. For
+#' \code{$del_dep} it may also be \code{"all"}, and then the package
+#' will be deleted from all dependency types.}
+#' \item{version:}{Required version. Defaults to \code{"\*"}, which means
+#' no explicit version requirements.}
+#' \item{deps:}{A data frame with columns \code{type}, \code{package} and
+#' \code{version}. \code{$get_deps} returns the same format.}
+#' }
+#'
+#' @section Collate fields:
+#' Collate fields contain lists of file names with R source code,
+#' and the package has a separate API for them. In brief, you can
+#' use \code{$add_to_collate} to add one or more files to the main or
+#' other collate field. You can use \code{$del_from_collate} to remove
+#' it from there.
+#'
+#' The API:
+#' \preformatted{ description$set_collate(files, which = c("main", "windows", "unix"))
+#' description$get_collate(which = c("main", "windows", "unix"))
+#' description$del_collate(which = c("all", "main", "windows", "unix"))
+#' description$add_to_collate(files, which = c("default", "all", "main",
+#' "windows", "unix"))
+#' description$del_from_collate(files, which = c("all", "main",
+#' "windows", "unix"))
+#' }
+#' \describe{
+#' \item{files:}{The files to add or remove, in a character vector.}
+#' \item{which:}{Which collate field to manipulate. \code{"default"} for
+#' \code{$add_to_collate} means all existing collate fields, or the
+#' main one if none exist.}
+#' }
+#'
+#' @section Authors:
+#' There is a specialized API for the \code{Authors at R} field,
+#' to add and remove authors, udpate their roles, change the maintainer,
+#' etc.
+#'
+#' The API:
+#' \preformatted{ description$get_authors()
+#' description$set_authors(authors)
+#' description$get_author(role)
+#' description$get_maintainer()
+#' }
+#' \describe{
+#' \item{authors:}{A \code{person} object, a list of authors.}
+#' \item{role:}{The role to query. See \code{person} for details.}
+#' }
+#' \code{$get_authors} returns a \code{person} object, the parsed
+#' authors. See \code{\link[utils]{person}} for details.
+#'
+#' \code{$get_author} returns a \code{person} object, all authors with
+#' the specified role.
+#'
+#' \code{$get_maintainer} returns the maintainer of the package. It works
+#' with \code{Authors at R} fields and with traditional \code{Maintainer}
+#' fields as well.
+#'
+#' \preformatted{ description$add_author(given = NULL, family = NULL, email = NULL,
+#' role = NULL, comment = NULL)
+#' description$add_me(role = "ctb", comment = NULL)
+#' }
+#' Add a new author. The arguments correspond to the arguments of the
+#' \code{\link[utils]{person}} function. \code{add_me} is a convenience
+#' function, it adds the current user as an author, and it needs the
+#' \code{whoami} package to be installed.
+#'
+#' \preformatted{ description$del_author(given = NULL, family = NULL, email = NULL,
+#' role = NULL, comment = NULL)
+#' }
+#' Remove an author, or multiple authors. The author(s) to be removed
+#' can be specified via any field(s). All authors matching all
+#' specifications will be removed. E.g. if only \code{given = "Joe"}
+#' is supplied, then all authors whole given name matches \code{Joe} will
+#' be removed. The specifications can be (PCRE) regular expressions.
+#'
+#' \preformatted{ description$add_role(role, given = NULL, family = NULL, email = NULL,
+#' comment = NULL)
+#' description$del_role(role, given = NULL, family = NULL, email = NULL,
+#' comment = NULL)
+#' description$change_maintainer(given = NULL, family = NULL,
+#' email = NULL, comment = NULL)
+#' }
+#' \code{role} is the role to add or delete. The other arguments
+#' are used to select a subset of the authors, on which the operation
+#' is performed, similarly to \code{$del_author}.
+#'
+#' @section URLs:
+#'
+#' We provide helper functions for manipulating URLs in the \code{URL}
+#' field:
+#'
+#' \preformatted{ description$get_urls()
+#' description$set_urls(urls)
+#' description$add_urls(urls)
+#' description$del_urls(pattern)
+#' description$clear_urls()
+#' }
+#' \describe{
+#' \item{urls:}{Character vector of URLs to set or add.}
+#' \item{pattern:}{Perl compatible regular expression to specify the
+#' URLs to be removed.}
+#' }
+#' \code{$get_urls()} returns all urls in a character vector. If no URL
+#' fields are present, a zero length vector is returned.
+#'
+#' \code{$set_urls()} sets the URL field to the URLs specified in the
+#' character vector argument.
+#'
+#' \code{$add_urls()} appends the specified URLs to the URL field. It
+#' creates the field if it does not exists. Duplicate URLs are removed.
+#'
+#' \code{$del_urls()} deletes the URLs that match the specified pattern.
+#'
+#' \code{$clear_urls()} deletes all URLs.
+#'
+#' @section Remotes:
+#'
+#' \code{devtools}, \code{remotes} and some other packages support the
+#' non-standard \code{Remotes} field in \code{DESCRIPTION}. This field
+#' can be used to specify locations of dependent packages: GitHub or
+#' BitBucket repositories, generic git repositories, etc. Please see the
+#' \sQuote{Package remotes} vignette in the \code{devtools} package.
+#'
+#' \code{desc} has helper functions for manipulating the \code{Remotes}
+#' field:
+#'
+#' \preformatted{ description$get_remotes()
+#' description$get_remotes()
+#' description$set_remotes(remotes)
+#' description$add_remotes(remotes)
+#' description$del_remotes(pattern)
+#' description$clear_remotes()
+#' }
+#' \describe{
+#' \item{remotes:}{Character vector of remote dependency locations to
+#' set or add.}
+#' \item{pattern:}{Perl compatible regular expression to specify the
+#' remote dependency locations to remove.}
+#' }
+#' \code{$get_remotes()} returns all remotes in a character vector.
+#' If no URL fields are present, a zero length vector is returned.
+#'
+#' \code{$set_remotes()} sets the URL field to the Remotes specified in the
+#' character vector argument.
+#'
+#' \code{$add_remotes()} appends the specified remotes to the
+#' \code{Remotes} field. It creates the field if it does not exists.
+#' Duplicate remotes are removed.
+#'
+#' \code{$del_remotes()} deletes the remotes that match the specified
+#' pattern.
+#'
+#' \code{$clear_remotes()} deletes all remotes.
+#'
+#' @export
+#' @importFrom R6 R6Class
+#' @docType class
+#' @format An R6 class.
+#'
+#' @examples
+#' ## Create a template
+#' desc <- description$new("!new")
+#' desc
+#'
+#' ## Read a file
+#' desc2 <- description$new(file = system.file("DESCRIPTION",
+#' package = "desc"))
+#' desc2
+#'
+#' ## Remove a field
+#' desc2$del("LazyData")
+#'
+#' ## Add another one
+#' desc2$set(VignetteBuilder = "knitr")
+#' desc2$get("VignetteBuilder")
+#' desc2
+
+description <- R6Class("description",
+ public = list(
+
+ ## Either from a file, or from a character vector
+ initialize = function(cmd = NULL, file = NULL, text = NULL, package = NULL)
+ idesc_create(self, private, cmd, file, text, package),
+
+ write = function(file = NULL)
+ idesc_write(self, private, file),
+
+ fields = function()
+ idesc_fields(self, private),
+
+ has_fields = function(keys)
+ idesc_has_fields(self, private, keys),
+
+ get = function(keys)
+ idesc_get(self, private, keys),
+
+ get_or_fail = function(keys)
+ idesc_get_or_fail(self, private, keys),
+
+ set = function(...)
+ idesc_set(self, private, ...),
+
+ del = function(keys)
+ idesc_del(self, private, keys),
+
+ validate = function()
+ idesc_validate(self, private),
+
+ print = function()
+ idesc_print(self, private),
+
+ str = function(by_field = FALSE, normalize = TRUE,
+ mode = c("file", "screen"))
+ idesc_str(self, private, by_field, normalize, mode),
+
+ to_latex = function()
+ idesc_to_latex(self, private),
+
+ normalize = function()
+ idesc_normalize(self, private),
+
+ reformat_fields = function()
+ idesc_reformat_fields(self, private),
+
+ reorder_fields = function()
+ idesc_reorder_fields(self, private),
+
+ ## -----------------------------------------------------------------
+ ## Version numbers
+
+ get_version = function()
+ idesc_get_version(self, private),
+
+ set_version = function(version)
+ idesc_set_version(self, private, version),
+
+ bump_version = function(which)
+ idesc_bump_version(self, private, which),
+
+ ## -----------------------------------------------------------------
+ ## Package dependencies
+
+ set_dep = function(package, type = desc::dep_types, version = "*")
+ idesc_set_dep(self, private, package, match.arg(type), version),
+
+ set_deps = function(deps)
+ idesc_set_deps(self, private, deps),
+
+ get_deps = function()
+ idesc_get_deps(self, private),
+
+ del_dep = function(package, type = c("all", desc::dep_types))
+ idesc_del_dep(self, private, package, match.arg(type)),
+
+ del_deps = function()
+ idesc_del_deps(self, private),
+
+ has_dep = function(package, type = c("any", desc::dep_types))
+ idesc_has_dep(self, private, package, match.arg(type)),
+
+ ## -----------------------------------------------------------------
+ ## Collate fields
+
+ set_collate = function(files, which = c("main", "windows", "unix"))
+ idesc_set_collate(self, private, files, match.arg(which)),
+
+ get_collate = function(which = c("main", "windows", "unix"))
+ idesc_get_collate(self, private, match.arg(which)),
+
+ del_collate = function(which = c("all", "main", "windows", "unix"))
+ idesc_del_collate(self, private, match.arg(which)),
+
+ add_to_collate = function(files,
+ which = c("default", "all", "main", "windows", "unix"))
+ idesc_add_to_collate(self, private, files, match.arg(which)),
+
+ del_from_collate = function(files,
+ which = c("all", "main", "windows", "unix"))
+ idesc_del_from_collate(self, private, files, match.arg(which)),
+
+ ## -----------------------------------------------------------------
+ ## Authors at R
+
+ get_authors = function()
+ idesc_get_authors(self, private),
+
+ get_author = function(role = "cre")
+ idesc_get_author(self, private, role),
+
+ set_authors = function(authors)
+ idesc_set_authors(self, private, authors),
+
+ add_author = function(given = NULL, family = NULL, email = NULL,
+ role = NULL, comment = NULL)
+ idesc_add_author(self, private, given, family, email, role, comment),
+
+ add_role = function(role, given = NULL, family = NULL, email = NULL,
+ comment = NULL)
+ idesc_add_role(self, private, role, given, family, email, comment),
+
+ del_author = function(given = NULL, family = NULL, email = NULL,
+ role = NULL, comment = NULL)
+ idesc_del_author(self, private, given, family, email, role, comment),
+
+ del_role = function(role, given = NULL, family = NULL, email = NULL,
+ comment = NULL)
+ idesc_del_role(self, private, role, given, family, email, comment),
+
+ change_maintainer = function(given = NULL, family = NULL, email = NULL,
+ comment = NULL)
+ idesc_change_maintainer(self, private, given, family, email, comment),
+
+ add_me = function(role = "ctb", comment = NULL)
+ idesc_add_me(self, private, role, comment),
+
+ get_maintainer = function()
+ idesc_get_maintainer(self, private),
+
+ ## -----------------------------------------------------------------
+ ## URL
+
+ get_urls = function()
+ idesc_get_urls(self, private),
+
+ set_urls = function(urls)
+ idesc_set_urls(self, private, urls),
+
+ add_urls = function(urls)
+ idesc_add_urls(self, private, urls),
+
+ del_urls = function(pattern)
+ idesc_del_urls(self, private, pattern),
+
+ clear_urls = function()
+ idesc_clear_urls(self, private),
+
+ ## -----------------------------------------------------------------
+ ## Remotes
+
+ get_remotes = function()
+ idesc_get_remotes(self, private),
+
+ set_remotes = function(remotes)
+ idesc_set_remotes(self, private, remotes),
+
+ add_remotes = function(remotes)
+ idesc_add_remotes(self, private, remotes),
+
+ del_remotes = function(pattern)
+ idesc_del_remotes(self, private, pattern),
+
+ clear_remotes = function()
+ idesc_clear_remotes(self, private)
+ ),
+
+ private = list(
+ data = NULL,
+ path = "DESCRIPTION",
+ notws = character() # entries without trailing ws
+ )
+)
+
+idesc_create <- function(self, private, cmd, file, text, package) {
+
+ if (!is.null(cmd) && substring(cmd, 1, 1) != "!") {
+ file <- cmd
+ cmd <- NULL
+ }
+
+ if (!is.null(cmd)) {
+ if (!is.null(file)) warning("file argument ignored")
+ if (!is.null(text)) warning("text argument ignored")
+ if (!is.null(package)) warning("package argument ignored")
+ idesc_create_cmd(self, private, cmd)
+
+ } else if (is.null(cmd) && is.null(file) && is.null(text) &&
+ is.null(package)) {
+ idesc_create_file(self, private, ".")
+
+ } else if (!is.null(file)) {
+ if (!is.null(text)) warning("text argument ignored")
+ if (!is.null(package)) warning("package argument ignored")
+ idesc_create_file(self, private, file)
+
+ } else if (!is.null(text)) {
+ if (!is.null(package)) warning("package argument ignored")
+ idesc_create_text(self, private, text)
+
+ } else {
+ idesc_create_package(self, private, package)
+ }
+
+ invisible(self)
+}
+
+idesc_create_cmd <- function(self, private, cmd = c("new")) {
+ assert_that(is_constructor_cmd(cmd))
+
+ if (cmd == "!new") {
+ idesc_create_text(self, private, text =
+'Package: {{ Package }}
+Title: {{ Title }}
+Version: 1.0.0
+Authors at R:
+ c(person(given = "Jo", family = "Doe", email = "jodoe at dom.ain",
+ role = c("aut", "cre")))
+Maintainer: {{ Maintainer }}
+Description: {{ Description }}
+License: {{ License }}
+LazyData: true
+URL: {{ URL }}
+BugReports: {{ BugReports }}
+Encoding: UTF-8
+')
+ }
+
+ invisible(self)
+}
+
+idesc_create_file <- function(self, private, file) {
+ assert_that(is_path(file))
+
+ if (file.exists(file) && is_dir(file)) file <- find_description(file)
+ assert_that(is_existing_file(file))
+
+ if (is_package_archive(file)) {
+ file <- get_description_from_package(file)
+
+ } else {
+ private$path <- normalizePath(file)
+ }
+
+ tryCatch(
+ lines <- readLines(file),
+ error = function(e) stop("Cannot read ", file, ": ", e$message)
+ )
+
+ idesc_create_text(self, private, lines)
+}
+
+idesc_create_text <- function(self, private, text) {
+ assert_that(is.character(text))
+ con <- textConnection(text, local = TRUE)
+ on.exit(close(con), add = TRUE)
+ dcf <- read_dcf(con)
+ private$notws <- dcf$notws
+ private$data <- dcf$dcf
+ check_encoding(self, private, NULL)
+}
+
+idesc_create_package <- function(self, private, package) {
+ assert_that(is_string(package))
+ path <- system.file(package = package, "DESCRIPTION")
+ if (path == "") {
+ stop("Cannot find DESCRIPTION for installed package ", package)
+ }
+ idesc_create_file(self, private, path)
+}
+
+#' @importFrom crayon strip_style
+
+idesc_write <- function(self, private, file) {
+ if (is.null(file)) file <- private$path
+ if (is.null(file)) {
+ stop("Cannot write back DESCRIPTION. Note that it is not possible
+ to update DESCRIPTION files within package archives")
+ }
+
+ mat <- idesc_as_matrix(private$data)
+
+ ## Need to write to a temp file first, to preserve absense of trailing ws
+ tmp <- tempfile()
+ on.exit(unlink(tmp), add = TRUE)
+ write.dcf(mat, file = tmp, keep.white = names(private$data))
+
+ removed <- ! names(private$notws) %in% colnames(mat)
+ if (any(removed)) private$notws <- private$notws[! removed]
+
+ changed <- mat[, names(private$notws)] != private$notws
+ if (any(changed)) private$notws <- private$notws[! changed]
+
+ postprocess_trailing_ws(tmp, names(private$notws))
+ if (file.exists(file) && is_dir(file)) file <- find_description(file)
+ writeLines(readLines(tmp), file)
+
+ invisible(self)
+}
+
+idesc_fields <- function(self, private) {
+ names(private$data)
+}
+
+idesc_has_fields <- function(self, private, keys) {
+ assert_that(is.character(keys), has_no_na(keys))
+ keys %in% self$fields()
+}
+
+idesc_as_matrix <- function(data) {
+ matrix(
+ vapply(data, "[[", "", "value"),
+ nrow = 1,
+ dimnames = list(NULL, names(data))
+ )
+}
+
+idesc_get <- function(self, private, keys) {
+ assert_that(is.character(keys), has_no_na(keys))
+ res <- lapply(private$data[keys], "[[", "value")
+ res[vapply(res, is.null, logical(1))] <- NA_character_
+ res <- unlist(res)
+ names(res) <- keys
+ res
+}
+
+idesc_get_or_fail <- function(self, private, keys) {
+ assert_that(is.character(keys), has_no_na(keys))
+ res <- self$get(keys)
+ if (any(is.na(res))) {
+ w <- is.na(res)
+ msg <- paste0(
+ "Could not find DESCRIPTION ",
+ if (sum(w) == 1) "field: " else "fields: ",
+ paste(sQuote(keys[w]), collapse = ", "),
+ "."
+ )
+ stop(msg, call. = FALSE)
+ }
+ res
+}
+
+## ... are either
+## - two unnamed arguments, key and value, or
+## - an arbitrary number of named arguments, the names are the keys,
+## the values are the values
+
+idesc_set <- function(self, private, ...) {
+ args <- list(...)
+
+ if (is.null(names(args)) && length(args) == 2) {
+ keys <- as_string(args[[1]])
+ values <- as_string(args[[2]])
+
+ } else if (!is.null(names(args)) && all(names(args) != "")) {
+ keys <- names(args)
+ values <- unlist(args)
+
+ } else {
+ stop("$set needs two unnamed args, or all named args, see docs")
+ }
+
+ fields <- create_fields(keys, values)
+ lapply(fields, check_field, warn = TRUE)
+ check_encoding(self, private, lapply(fields, "[[", "value"))
+ private$data[keys] <- fields
+
+ invisible(self)
+}
+
+
+idesc_del <- function(self, private, keys) {
+ assert_that(is.character(keys), has_no_na(keys))
+ private$data <- private$data[setdiff(names(private$data), keys)]
+ invisible(self)
+}
diff --git a/R/encoding.R b/R/encoding.R
new file mode 100644
index 0000000..8e22ada
--- /dev/null
+++ b/R/encoding.R
@@ -0,0 +1,37 @@
+
+#' Check encoding of new or existing fields
+#'
+#' If \code{new_fields} is \code{NULL}, then the existing
+#' fields are checked. Otherwised \code{new_fields} are checked.
+#'
+#' Warnings are given for non-ascii fields, if the \code{Encoding}
+#' field is not set.
+#'
+#' @param self Object.
+#' @param private Private env.
+#' @param new_fields New fields, or \code{NULL} to check existing fields.
+#' @return Object, invisibly.
+#'
+#' @keywords internal
+
+check_encoding <- function(self, private, new_fields) {
+ if (!is.na(self$get('Encoding'))) return(invisible(self))
+
+ fields <- if (is.null(new_fields)) {
+ as.list(self$get(self$fields()))
+ } else {
+ new_fields
+ }
+
+ nonascii <- !vapply(fields, is_ascii, TRUE)
+
+ if (any(nonascii)) {
+ warning(
+ "Consider adding an Encoding field to DESCRIPTION,\n",
+ "Non-ASCII character(s) in ",
+ paste(names(fields)[nonascii], collapse = ", ")
+ )
+ }
+
+ invisible(self)
+}
diff --git a/R/latex.R b/R/latex.R
new file mode 100644
index 0000000..1483867
--- /dev/null
+++ b/R/latex.R
@@ -0,0 +1,82 @@
+idesc_to_latex <- function(self, private) {
+ cols <- field_order(names(private$data))
+ col_str <- unlist(lapply(
+ cols,
+ FUN = function(col) {
+ toLatex(private$data[[col]])
+ }))
+
+ structure(
+ c(
+ "\\begin{description}",
+ " \\raggedright{}",
+ paste0(" ", col_str, collapse = "\n"),
+ "\\end{description}"
+ ),
+ class = "Latex"
+ )
+}
+
+
+#' @importFrom utils toLatex
+NULL
+
+#' @export
+toLatex.character <- function(object, ...) {
+ object <- gsub("'([^ ']*)'", "`\\1'", object, useBytes = TRUE)
+ object <- gsub("\"([^\"]*)\"", "``\\1''", object, useBytes = TRUE)
+ object <- gsub("\\", "\\textbackslash ", object, fixed = TRUE,
+ useBytes = TRUE)
+ object <- gsub("([{}$#_^%])", "\\\\\\1", object, useBytes = TRUE)
+ object
+}
+
+#' @export
+toLatex.DescriptionField <- function(object, ...) {
+ paste0("\\item[", object$key, "] ", toLatex(object$value))
+}
+
+#' @export
+toLatex.DescriptionCollate <- function(object, ...) {
+ invisible(NULL)
+}
+
+#' @export
+toLatex.DescriptionURLList <- function(object, ...) {
+ paste0("\\item[", object$key, "] ", format_url(parse_url_list(object$value)))
+}
+
+#' @export
+toLatex.DescriptionURL <- function(object, ...) {
+ paste0("\\item[", object$key, "] ", format_url(object$value))
+}
+
+
+#' @export
+toLatex.DescriptionAuthorsAtR <- function(object, ...) {
+ xx <- parse_authors_at_r(object$value)
+ c(
+ "\\item[Authors at R] ~\\\\",
+ " \\begin{description}",
+ paste0(" ", vapply(xx, toLatex, character(1L)), collapse = "\n"),
+ " \\end{description}"
+ )
+}
+
+#' @export
+toLatex.person <- function(object, ...) {
+ paste0(
+ "\\item",
+ format(object, include = c("role")),
+ " ",
+ format(object, include = c("given", "family")),
+ " ",
+ if (!is.null(object$email))
+ paste0("<\\href{mailto:", object$email, "}{", object$email, "}>"),
+ format(object, include = c("comment"))
+ )
+}
+
+format_url <- function(object) {
+ paste0("\\url{", object, "}", collapse = ", ")
+}
diff --git a/R/non-oo-api.R b/R/non-oo-api.R
new file mode 100644
index 0000000..eef4144
--- /dev/null
+++ b/R/non-oo-api.R
@@ -0,0 +1,628 @@
+
+# No coverage calculating here, since this code
+# runs during install time only.
+# nocov start
+
+#' @include description.R
+#' @importFrom utils packageName
+
+generate_api <- function(member, self = TRUE, norm = TRUE,
+ invisible = FALSE) {
+
+ res <- function() { }
+
+ func <- description$public_methods[[member]]
+
+ ## Arguments
+ xargs <- list(file = ".")
+ if (self && norm) xargs <- c(xargs, list(normalize = FALSE))
+ formals(res) <- c(formals(func), xargs)
+
+ ## Call to member function
+ member_call <- substitute(
+ desc[[`_member`]](),
+ list(`_member` = member)
+ )
+ argnames <- names(formals(func))
+ dargs <- structure(lapply(argnames, as.name), names = argnames)
+ if (!is.null(formals(func))) {
+ member_call[1 + 1:length(formals(func))] <- dargs
+ }
+ desc_call <- substitute(
+ result <- `_member`,
+ list(`_member` = member_call)
+ )
+
+ ## Call to write, or just return the result
+ write_call <- if (self && norm) {
+ quote({
+ if (normalize) desc$normalize()
+ desc$write(file = file)
+ })
+ } else if (self) {
+ quote(desc$write(file = file))
+ }
+
+ ## Put together
+
+ body(res) <- substitute(
+ { `_read`; `_trans`; `_write`; `_return` },
+ list(
+ `_read` = quote(desc <- description$new(file = file)),
+ `_trans` = desc_call,
+ `_write` = write_call,
+ `_return` = if (invisible) quote(invisible(result)) else quote(result)
+ )
+ )
+
+ environment(res) <- asNamespace(packageName())
+
+ res
+}
+
+## -------------------------------------------------------------------
+
+#' List all fields in a DESCRIPTION file
+#'
+#' @inheritParams desc_set
+#' @return Character vector of fields.
+#'
+#' @family simple queries
+#' @export
+
+desc_fields <- generate_api("fields", self = FALSE)
+
+#' Check if some fields are present in a DESCRIPTION file
+#'
+#' @param keys Character vector of keys to check.
+#' @inheritParams desc_set
+#' @return Logical vector.
+#'
+#' @family simple queries
+#' @export
+
+desc_has_fields <- generate_api("has_fields", self = FALSE)
+
+#' Get a field from a DESCRIPTION file
+#'
+#' @param keys Character vector of fields to get.
+#' @inheritParams desc_set
+#' @return Character vector, values of the specified keys.
+#' Non-existing keys return \code{NA}.
+#'
+#' @family simple queries
+#' @export
+
+desc_get <- generate_api("get", self = FALSE)
+
+#' Get fields from a DESCRIPTION file, fail if not found
+#'
+#' @inheritParams desc_get
+#' @return Character vector, values of the specified keys.
+#' Non-existing keys return \code{NA}.
+#'
+#' @family simple queries
+#' @export
+
+desc_get_or_fail <- generate_api("get_or_fail", self = FALSE)
+
+#' Set one or more fields in a DESCRIPTION file
+#'
+#' @details
+#' \code{desc_set} supports two forms, the first is two unnamed
+#' arguments: the key and its value to set.
+#'
+#' The second form requires named arguments: names are used as keys
+#' and values as values to set.
+#'
+#' @param ... Values to set, see details below.
+#' @param file DESCRIPTION file to use. By default the DESCRIPTION
+#' file of the current package (i.e. the package the working directory
+#' is part of) is used.
+#' @param normalize Whether to normalize the write when writing back
+#' the result. See \code{\link{desc_normalize}}.
+#'
+#' @family simple queries
+#' @export
+
+desc_set <- generate_api("set")
+
+#' Remove fields from a DESCRIPTION file
+#'
+#' @param keys Character vector of keys to remove.
+#' @inheritParams desc_set
+#'
+#' @family simple queries
+#' @export
+
+desc_del <- generate_api("del")
+
+## -------------------------------------------------------------------
+
+#' Print the contents of a DESCRIPTION file to the screen
+#'
+#' @inheritParams desc_set
+#' @export
+
+desc_print <- generate_api("print", self = FALSE, invisible = TRUE)
+
+#' Converts a DESCRIPTION file to LaTeX
+#'
+#' Returns the contents of the DESCRIPTION in LaTeX format.
+#'
+#' @inheritParams desc_set
+#' @export
+
+desc_to_latex <- generate_api("to_latex", self = FALSE)
+
+#' Normalize a DESCRIPTION file
+#'
+#' Reformats and reorders fields in DESCRIPTION in a standard way.
+#'
+#' @inheritParams desc_set
+#' @family repair functions
+#' @export
+
+desc_normalize <- generate_api("normalize", self = TRUE, norm = FALSE)
+
+#' Reformat fields in a DESCRIPTION file
+#'
+#' Reformat the fields in DESCRIPTION in a standard way.
+#'
+#' @inheritParams desc_set
+#' @family repair functions
+#' @export
+
+desc_reformat_fields <- generate_api("reformat_fields", self = TRUE, norm = FALSE)
+
+#' Reorder fields in a DESCRIPTION file
+#'
+#' Reorder the fields in DESCRIPTION in a standard way.
+#'
+#' @inheritParams desc_set
+#' @family repair functions
+#' @export
+
+desc_reorder_fields <- generate_api("reorder_fields", self = TRUE, norm = FALSE)
+
+#' Validate a DESCRIPTION file
+#'
+#' This function is not implemented yet.
+#'
+#' @inheritParams desc_set
+#'
+#' @export
+
+desc_validate <- generate_api("validate", self = FALSE)
+
+## -------------------------------------------------------------------
+
+#' List all package dependencies from a DESCRIPTION file
+#'
+#' @inheritParams desc_set
+#' @return Data frame with columns: \code{type} (dependency type),
+#' \code{package}, and \code{version}. For non-versioned dependencies
+#' \code{version} is \code{*}.
+#'
+#' @family dependencies
+#' @export
+
+desc_get_deps <- generate_api("get_deps", self = FALSE)
+
+#' Add a package dependency to a DESCRIPTION file
+#'
+#' @param package Package to depend on.
+#' @param type Dependency type.
+#' @param version Version to depend on, for versioned dependencies.
+#' @inheritParams desc_set
+#'
+#' @family dependencies
+#' @export
+
+desc_set_dep <- generate_api("set_dep")
+
+#' Set all package dependencies in DESCRIPTION
+#'
+#' @param deps Package dependency data frame, in the same format
+#' returned by \code{\link{desc_get_deps}}.
+#' @inheritParams desc_set
+#'
+#' @family dependencies
+#' @export
+
+desc_set_deps <- generate_api("set_deps")
+
+#' Remove a package dependency from DESCRIPTION
+#'
+#' @param package Package dependency to remove.
+#' @param type Dependency type to remove. Sometimes a package is depended
+#' on via multiple dependency types, e.g. \code{LinkingTo} and
+#' \code{Imports}. Defaults to all types.
+#' @inheritParams desc_set
+#'
+#' @family dependencies
+#' @export
+
+desc_del_dep <- generate_api("del_dep")
+
+#' Remove all dependencies from DESCRIPTION
+#'
+#' @inheritParams desc_set
+#'
+#' @family dependencies
+#' @export
+
+desc_del_deps <- generate_api("del_deps")
+
+#' Check for a dependency
+#'
+#' @inheritParams desc_set
+#' @param package The package name.
+#' @param type A dependency type or \sQuote{any}.
+#' @return A logical scalar.
+#'
+#' @family dependencies
+#' @export
+
+desc_has_dep <- generate_api("has_dep", self = FALSE)
+
+## -------------------------------------------------------------------
+
+#' Set the Collate field in DESCRIPTION
+#'
+#' @param files Collate field to set, as a character vector.
+#' @param which Which collate field to use. Collate fields can
+#' be operating system type specific.
+#' @inheritParams desc_set
+#'
+#' @family Collate field
+#' @export
+
+desc_set_collate <- generate_api("set_collate")
+
+#' Query the Collate field in DESCRIPTION
+#'
+#' @inheritParams desc_set_collate
+#' @return Character vector of file names.
+#'
+#' @family Collate field
+#' @export
+
+desc_get_collate <- generate_api("get_collate", self = FALSE)
+
+#' Delete the Collate field from DESCRIPTION
+#'
+#' @inheritParams desc_set_collate
+#'
+#' @family Collate field
+#' @export
+
+desc_del_collate <- generate_api("del_collate")
+
+#' Add one or more files to the Collate field, in DESCRIPTION
+#'
+#' @param files Character vector, files to add.
+#' @inheritParams desc_set_collate
+#'
+#' @family Collate field
+#' @export
+
+desc_add_to_collate <- generate_api("add_to_collate")
+
+#' Remove files from the Collate field.
+#'
+#' @param files Files to remove from the Collate field.
+#' @inheritParams desc_set_collate
+#'
+#' @family Collate field
+#' @export
+
+desc_del_from_collate <- generate_api("del_from_collate")
+
+## -------------------------------------------------------------------
+
+#' Query all authors in Authors at R, in DESCRIPTION
+#'
+#' @inheritParams desc_set
+#' @return A \code{\link[utils]{person}} object.
+#'
+#' @family Authors at R
+#' @export
+
+desc_get_authors <- generate_api("get_authors", self = FALSE)
+
+#' Query authors by role in Authors at R, in DESCRIPTION
+#'
+#' @param role Role to query. Defaults to the package maintainer.
+#' @inheritParams desc_set
+#' @return A \code{\link[utils]{person}} object.
+#'
+#' @family Authors at R
+#' @export
+
+desc_get_author <- generate_api("get_author", self = FALSE)
+
+#' Set authors in Authors at R, in DESCRIPTION
+#'
+#' @param authors Authors, to set, a \code{\link[utils]{person}} object.
+#' @inheritParams desc_set
+#'
+#' @family Authors at R
+#' @export
+
+desc_set_authors <- generate_api("set_authors")
+
+#' Add an author to Authors at R in DESCRIPTION
+#'
+#' @param given Given name.
+#' @param family Family name.
+#' @param email Email address.
+#' @param role Role.
+#' @param comment Comment.
+#' @inheritParams desc_set
+#'
+#' @family Authors at R
+#' @export
+
+desc_add_author <- generate_api("add_author")
+
+#' Add a role to one or more authors in Authors at R, in DESCRIPTION
+#'
+#' The author(s) can be specified by a combination of the \code{given},
+#' \code{family}, \code{email} and \code{comment} fields. If multiple
+#' filters are specified, then all must match to identify the author(s).
+#'
+#' @param role Role to add.
+#' @param given Given name to filter on. Regular expression.
+#' @param family Family name to filter on. Regular expression.
+#' @param email Email address to filter on. Regular expression.
+#' @param comment Comment field to filter on. Regular expression.
+#' @inheritParams desc_set
+#'
+#' @family Authors at R
+#' @export
+
+desc_add_role <- generate_api("add_role")
+
+#' Remove one or more authors from DESCRIPTION.
+#'
+#' It uses the Authors at R field. The author(s) to be removed
+#' can be specified via any field(s). All authors matching all
+#' specifications will be removed. E.g. if only \code{given = "Joe"}
+#' is supplied, then all authors whole given name matches \code{Joe} will
+#' be removed. The specifications can be (PCRE) regular expressions.
+#'
+#' @param role Role to filter on. Regular expression.
+#' @inheritParams desc_add_role
+#'
+#' @family Authors at R
+#' @export
+
+desc_del_author <- generate_api("del_author")
+
+#' Delete a role of an author, in DESCRIPTION
+#'
+#' The author(s) can be specified by a combination of the \code{given},
+#' \code{family}, \code{email} and \code{comment} fields. If multiple
+#' filters are specified, then all must match to identify the author(s).
+#'
+#' @param role Role to remove.
+#' @inheritParams desc_add_role
+#'
+#' @family Authors at R
+#' @export
+
+desc_del_role <- generate_api("del_role")
+
+#' Change maintainer of the package, in DESCRIPTION
+#'
+#' Only works with the Authors at R field.
+#'
+#' The current maintainer is kept if they have at least another role.
+#'
+#' @inheritParams desc_add_author
+#'
+#' @family Authors at R
+#' @export
+
+desc_change_maintainer <- generate_api("change_maintainer")
+
+#' Add the current user as an author to DESCRIPTION
+#'
+#' Uses the Authors at R field.
+#'
+#' @param role Role to set for the user, defaults to contributor.
+#' @param comment Comment, empty by default.
+#' @inheritParams desc_set
+#'
+#' @family Authors at R
+#' @export
+
+desc_add_me <- generate_api("add_me")
+
+#' Query the package maintainer in DESCRIPTION
+#'
+#' Either from the \sQuote{Maintainer} or the \sQuote{Authors at R} field.
+#' @inheritParams desc_set
+#' @return A character scalar.
+#'
+#' @family Authors at R
+#' @export
+
+desc_get_maintainer <- generate_api("get_maintainer", self = FALSE)
+
+## -------------------------------------------------------------------
+
+#' Query the URL field in DESCRIPTION
+#'
+#' @inheritParams desc_set
+#' @return A character vectors or URLs. A length zero vector is returned
+#' if there is no URL field in the package.
+#'
+#' @export
+
+desc_get_urls <- generate_api("get_urls", self = FALSE)
+
+#' Set the URL field in DESCRIPTION
+#'
+#' The specified urls replace the current ones. The URL field is created
+#' if it does not exist currently.
+#'
+#' @param urls A character vector of urls to set.
+#' @inheritParams desc_set
+#'
+#' @export
+
+desc_set_urls <- generate_api("set_urls")
+
+#' Add URLs to the URL field in DESCRIPTION
+#'
+#' @param urls Character vector of URLs to add. Duplicate URLs are
+#' eliminated.
+#' @inheritParams desc_set
+#'
+#' @export
+
+desc_add_urls <- generate_api("add_urls")
+
+#' Delete URLs from the URL field in DESCRIPTION
+#'
+#' All URLs matching the specified pattern are deleted.
+#'
+#' @param pattern Perl-compatible regular expression, all URLs
+#' matching this expression will be deleted.
+#' @inheritParams desc_set
+#'
+#' @export
+
+desc_del_urls <- generate_api("del_urls")
+
+#' Remove all URLs from the URL field of DESCRIPTION
+#'
+#' @inheritParams desc_set
+#'
+#' @export
+
+desc_clear_urls <- generate_api("clear_urls")
+
+## -------------------------------------------------------------------
+
+#' List the locations in the Remotes field in DESCRIPTION
+#'
+#' @inheritParams desc_set
+#' @return A character vectors or remote locations. A length zero vector
+#' is returned if there is no Remotes field in the package.
+#'
+#' @export
+
+desc_get_remotes <- generate_api("get_remotes", self = FALSE)
+
+#' Set the Remotes field in DESCRIPTION
+#'
+#' The specified locations replace the current ones. The Remotes field is
+#' created if it does not exist currently.
+#'
+#' @param remotes A character vector of remote locations to set.
+#' @inheritParams desc_set
+#'
+#' @export
+
+desc_set_remotes <- generate_api("set_remotes")
+
+#' Add locations in the Remotes field in DESCRIPTION
+#'
+#' @param remotes Character vector of remote locations to add.
+#' Duplicate locations are eliminated. Note that existing locations
+#' are not updated, so if you want to \emph{change} a remote location
+#' of a package, you need to delete the old location first and then add
+#' the new one.
+#' @inheritParams desc_set
+#'
+#' @export
+
+desc_add_remotes <- generate_api("add_remotes")
+
+#' Delete locations from the Remotes field in DESCRIPTION
+#'
+#' All locations matching the specified pattern are deleted.
+#'
+#' @param pattern Perl-compatible regular expression, all locations
+#' matching this expression will be deleted.
+#' @inheritParams desc_set
+#'
+#' @export
+
+desc_del_remotes <- generate_api("del_remotes")
+
+#' Remove all locations from the Remotes field of DESCRIPTION
+#'
+#' This simply means that the field is deleted.
+#'
+#' @inheritParams desc_set
+#'
+#' @export
+
+desc_clear_remotes <- generate_api("clear_remotes")
+
+## -------------------------------------------------------------------
+
+#' Query the package version in DESCRIPTION
+#'
+#' If the file has no \code{Version} field, or it is an invalid
+#' version string, then it throws an error.
+#'
+#' @inheritParams desc_set
+#' @return A \code{\link[base]{package_version}} object.
+#'
+#' @export
+#' @family version numbers
+
+desc_get_version <- generate_api("get_version", self = FALSE)
+
+#' Set the package version in DESCRIPTION
+#'
+#' Both \code{$set_version()} and \code{$bump_version()} use dots to
+#' separate the version number components.
+#'
+#' @param version A string or a \code{\link[base]{package_version}}
+#' object.
+#' @inheritParams desc_set
+#'
+#' @export
+#' @family version numbers
+
+desc_set_version <- generate_api("set_version")
+
+#' Increase the version number in DESCRIPTION
+#'
+#' The \code{which} parameter specifies which component to increase.
+#' It can be a string referring to a component: \sQuote{major},
+#' \sQuote{minor}, \sQuote{patch} or \sQuote{dev}, or an integer
+#' scalar, for the latter components are counted from one, and the
+#' beginning. I.e. component one is equivalent to \sQuote{major}.
+#'
+#' If a component is bumped, then the ones after it are zeroed out.
+#' Trailing zero components are omitted from the new version number,
+#' but if the old version number had at least two or three components, then
+#' the one will also have two or three.
+#'
+#' The bumping of the \sQuote{dev} version (the fourth component) is
+#' special: if the original version number had less than four components,
+#' and the \sQuote{dev} version is bumped, then it is set to \code{9000}
+#' instead of \code{1}. This is a convention often used by R developers,
+#' it was originally invented by Winston Chang.
+#'
+#' Both \code{$set_version()} and \code{$bump_version()} use dots to
+#' separate the version number components.
+#'
+#' @param which Which component to increase. See details below.
+#' @inheritParams desc_set
+#'
+#' @export
+#' @family version numbers
+
+desc_bump_version <- generate_api("bump_version")
+
+## -------------------------------------------------------------------
+
+# nocov end
diff --git a/R/package-archives.R b/R/package-archives.R
new file mode 100644
index 0000000..3cb83d5
--- /dev/null
+++ b/R/package-archives.R
@@ -0,0 +1,62 @@
+
+is_package_archive <- function(file) {
+ (is_zip_file(file) || is_tar_gz_file(file)) &&
+ is_valid_package_file_name(file)
+}
+
+is_zip_file <- function(file) {
+ buf <- readBin(file, what = "raw", n = 4)
+ length(buf) == 4 &&
+ buf[1] == 0x50 &&
+ buf[2] == 0x4b &&
+ (buf[3] == 0x03 || buf[3] == 0x05 || buf[5] == 0x07) &&
+ (buf[4] == 0x04 || buf[4] == 0x06 || buf[4] == 0x08)
+}
+
+is_gz_file <- function(file) {
+ buf <- readBin(file, what = "raw", n = 3)
+ length(buf) == 3 &&
+ buf[1] == 0x1f &&
+ buf[2] == 0x8b &&
+ buf[3] == 0x08
+}
+
+is_tar_gz_file <- function(file) {
+ if (!is_gz_file(file)) return(FALSE)
+ con <- gzfile(file, open = "rb")
+ on.exit(close(con))
+ buf <- readBin(con, what = "raw", n = 262)
+ length(buf) == 262 &&
+ buf[258] == 0x75 &&
+ buf[259] == 0x73 &&
+ buf[260] == 0x74 &&
+ buf[261] == 0x61 &&
+ buf[262] == 0x72
+}
+
+is_valid_package_file_name <- function(filename) {
+ grepl(valid_package_archive_name, basename(filename))
+}
+
+#' @importFrom utils untar unzip
+
+get_description_from_package <- function(file) {
+ uncompress <- if (is_zip_file(file)) unzip else untar
+ package_name <- sub("_.*$", "", basename(file))
+
+ tmp <- tempfile()
+
+ suppressWarnings(uncompress(
+ file,
+ files = paste(package_name, sep = "/", "DESCRIPTION"),
+ exdir = tmp
+ ))
+
+ desc <- file.path(tmp, package_name, "DESCRIPTION")
+
+ if (!file.exists(desc)) {
+ stop("Cannot extract DESCRIPTION from ", sQuote(file))
+ }
+
+ desc
+}
diff --git a/R/read.R b/R/read.R
new file mode 100644
index 0000000..739ec58
--- /dev/null
+++ b/R/read.R
@@ -0,0 +1,35 @@
+
+## TODO: handle empty files
+
+read_dcf <- function(file) {
+ lines <- readLines(file)
+ no_tws_fields <- sub(
+ ":$",
+ "",
+ grep("^[^\\s]+:$", lines, perl = TRUE, value = TRUE)
+ )
+
+ con <- textConnection(lines, local = TRUE)
+ fields <- colnames(read.dcf(con))
+ close(con)
+
+ con <- textConnection(lines, local = TRUE)
+ res <- read.dcf(con, keep.white = fields)
+ close(con)
+
+ con <- textConnection(lines, local = TRUE)
+ res2 <- read.dcf(con, keep.white = fields, all = TRUE)
+ close(con)
+
+ if (any(mismatch <- res != res2)) {
+ stop("Duplicate DESCRIPTION fields: ",
+ paste(sQuote(colnames(res)[mismatch]), collapse = ", "))
+ }
+
+ notws <- res[1, match(no_tws_fields, fields)]
+
+ list(
+ dcf = create_fields(fields, res[1, ]),
+ notws = notws
+ )
+}
diff --git a/R/remotes.R b/R/remotes.R
new file mode 100644
index 0000000..e4ecc18
--- /dev/null
+++ b/R/remotes.R
@@ -0,0 +1,49 @@
+
+parse_remotes <- function(remotes) {
+ str_trim(strsplit(remotes, "\\s*,\\s*", perl = TRUE)[[1]])
+}
+
+deparse_remotes <- function(remotes) {
+ paste(str_trim(remotes), collapse = ",\n ")
+}
+
+idesc_get_remotes <- function(self, private) {
+ remotes <- self$get("Remotes")
+ if (is.na(remotes)) {
+ character()
+ } else {
+ parse_remotes(remotes)
+ }
+}
+
+idesc_set_remotes <- function(self, private, remotes) {
+ assert_that(is.character(remotes))
+ self$set(Remotes = deparse_remotes(remotes))
+ invisible(self)
+}
+
+idesc_add_remotes <- function(self, private, remotes) {
+ assert_that(is.character(remotes))
+ remotes <- unique(c(self$get_remotes(), remotes))
+ self$set(Remotes = deparse_remotes(remotes))
+ invisible(self)
+}
+
+idesc_del_remotes <- function(self, private, pattern) {
+ assert_that(is_string(pattern))
+ remotes <- self$get_remotes()
+ if (length(remotes) == 0) return(invisible(self))
+
+ filt <- grep(pattern, remotes, invert = TRUE, value = TRUE, perl = TRUE)
+ if (length(filt) > 0) {
+ self$set(Remotes = deparse_remotes(filt))
+ } else {
+ self$del("Remotes")
+ }
+ invisible(self)
+}
+
+idesc_clear_remotes <- function(self, private) {
+ self$del("Remotes")
+ invisible(self)
+}
diff --git a/R/str.R b/R/str.R
new file mode 100644
index 0000000..8066fcf
--- /dev/null
+++ b/R/str.R
@@ -0,0 +1,139 @@
+
+## TODO: continuation lines
+
+idesc_str <- function(self, private, by_field, normalize = TRUE,
+ mode = c("file", "screen")) {
+
+ assert_that(is_flag(by_field))
+ mode <- match.arg(mode)
+ cols <- names(private$data)
+ if (normalize) cols <- field_order(cols)
+ col_str <- vapply(
+ cols, FUN.VALUE = "",
+ FUN = function(col) {
+ if (normalize) {
+ format(private$data[[col]], mode = mode)
+ } else {
+ paste0(private$data[[col]]$key, ": ", private$data[[col]]$value)
+ }
+ })
+
+ if (by_field) col_str else paste(col_str, collapse = "\n")
+}
+
+field_order <- function(fields) {
+ first <- c(
+ "Type", "Package", "Title", "Version", "Date",
+ "Authors at R", "Author", "Maintainer",
+ "Description", "License", "URL", "BugReports",
+ "Depends", setdiff(dep_types, "Depends"), "VignetteBuilder"
+ )
+
+ last <- collate_fields
+
+ c(
+ intersect(first, fields),
+ sort(setdiff(fields, c(first, last))),
+ intersect(last, fields)
+ )
+}
+
+#' @importFrom crayon red
+
+color_bad <- function(x) {
+ if (identical(check_field(x), TRUE)) x$value else red(x$value)
+}
+
+#' @export
+#' @importFrom crayon blue
+#' @method format DescriptionField
+
+format.DescriptionField <- function(x, ..., width = 75) {
+ paste(
+ strwrap(paste0(blue(x$key), ": ", color_bad(x)), exdent = 4, width = width),
+ collapse = "\n"
+ )
+}
+
+#' @export
+#' @importFrom crayon blue
+#' @method format DescriptionDependencyList
+
+format.DescriptionDependencyList <- function(x, ...) {
+ paste0(
+ blue(x$key), ":\n",
+ paste0(
+ " ",
+ str_trim(strsplit(color_bad(x), ",", fixed = TRUE)[[1]]),
+ collapse = ",\n"
+ )
+ )
+}
+
+#' @export
+#' @importFrom crayon blue
+#' @method format DescriptionCollate
+
+format.DescriptionCollate <- function(x, ...) {
+ paste0(
+ blue(x$key), ":",
+ deparse_collate(parse_collate(color_bad(x)))
+ )
+}
+
+#' @export
+#' @importFrom crayon blue red
+#' @method format DescriptionAuthorsAtR
+
+format.DescriptionAuthorsAtR <- function(x, mode = c("file", "screen"),
+ ...) {
+ xx <- parse_authors_at_r(x$value)
+
+ if (mode == "screen") {
+ good <- check_field(x)
+ xxx <- if (good) xx else red(xx)
+ paste0(
+ blue(x$key), " (parsed):\n",
+ paste0(" * ", format(xxx), collapse = "\n")
+ )
+
+ } else {
+ paste0(
+ x$key, ":",
+ sub("\n$", "", deparse_authors_at_r(xx))
+ )
+ }
+}
+
+
+idesc_print <- function(self, private) {
+ cat(
+ idesc_str(self, private, by_field = FALSE, mode = "screen"),
+ sep = "",
+ "\n"
+ )
+ invisible(self)
+}
+
+
+idesc_normalize <- function(self, private) {
+ self$reformat_fields()
+ self$reorder_fields()
+ invisible(self)
+}
+
+#' @importFrom crayon strip_style
+
+idesc_reformat_fields <- function(self, private) {
+ norm_fields <- strip_style(idesc_str(self, private, by_field = TRUE))
+ for (f in names(norm_fields)) {
+ private$data[[f]]$value <-
+ sub(paste0(f, ":[ ]?"), "", norm_fields[[f]])
+ }
+ invisible(self)
+}
+
+idesc_reorder_fields <- function(self, private) {
+ private$data <- private$data[field_order(names(private$data))]
+ invisible(self)
+}
diff --git a/R/syntax_checks.R b/R/syntax_checks.R
new file mode 100644
index 0000000..7687967
--- /dev/null
+++ b/R/syntax_checks.R
@@ -0,0 +1,412 @@
+
+chk <- function(msg, check) {
+ if (check) TRUE else msg
+}
+
+chks <- function(..., x, warn) {
+ results <- list(...)
+ results <- unlist(setdiff(results, TRUE))
+ results <- if (length(results) == 0) TRUE else results
+
+ if (! identical(results, TRUE) && warn) {
+ warning(
+ call. = FALSE,
+ "'", x$key, "'",
+ paste0(
+ if (length(results) == 1) " " else "\n * ",
+ strwrap(results, indent = 0, exdent = 6)
+ )
+ )
+ }
+
+ results
+}
+
+
+#' Syntactical check of a DESCRIPTION field
+#'
+#' @param x The field.
+#' @param warn Whether to generate a warning if the syntax check fails.
+#' @param ... Additional arguments, they might be used in the future.
+#' @return \code{TRUE} if the field is syntactically correct,
+#' otherwise a character vector, containing one or multiple
+#' error messages.
+#'
+#' @export
+
+check_field <- function(x, warn = FALSE, ...)
+ UseMethod("check_field")
+
+#' @export
+#' @method check_field DescriptionField
+
+check_field.DescriptionField <- function(x, warn = FALSE, ...) TRUE
+
+##' @export
+##' @method check_field DescriptionPackage
+
+check_field.DescriptionPackage <- function(x, warn = FALSE, R = FALSE, ...) {
+
+ ## In Depends, we can depend on certain 'R' versions
+ if (R && x$value == "R") return(TRUE)
+
+ chks(
+ x = x, warn = warn,
+ chk("must only contain ASCII letters, numbers, dots",
+ grepl("^[a-zA-Z0-9\\.]*$", x$value)),
+ chk("must be at least two characters long",
+ nchar(x$value) >= 2),
+ chk("must start with a letter",
+ grepl("^[a-zA-Z]", x$value)),
+ chk("must not end with a dot",
+ !grepl("\\.$", x$value))
+ )
+}
+
+valid_packagename_regexp <- "[[:alpha:]][[:alnum:].]*[[:alnum:]]"
+valid_version_regexp <- "[0-9]+[-\\.][0-9]+([-\\.][0-9]+)*"
+valid_package_archive_name <- paste0(
+ "^",
+ valid_packagename_regexp,
+ "_",
+ valid_version_regexp,
+ "(.*)?",
+ "(\\.tar\\.gz|\\.tgz|\\.zip)",
+ "$"
+)
+
+##' @export
+##' @method check_field DescriptionVersion
+
+check_field.DescriptionVersion <- function(x, warn = FALSE, ...) {
+
+ chks(
+ x = x, warn = warn,
+ chk(paste("must be a sequence of at least two (usually three)",
+ " non-negative integers separated by a single dot or dash",
+ " character"),
+ grepl(paste0("^", valid_version_regexp, "$"), x$value))
+ )
+}
+
+## TODO: It also must be a license R CMD check recognizes
+##
+##' @export
+##' @method check_field DescriptionLicense
+
+check_field.DescriptionLicense <- function(x, warn = FALSE, ...) {
+
+ chks(
+ x = x, warn = warn,
+ chk("must contain only ASCII characters",
+ is_ascii(x$value)),
+ chk("must not be empty",
+ str_trim(x$value) != "")
+ )
+}
+
+##' @export
+##' @method check_field DescriptionDescription
+
+check_field.DescriptionDescription <- function(x, warn = FALSE, ...) {
+
+ chks(
+ x = x, warn = warn,
+ chk("must not be empty",
+ str_trim(x$value) != ""),
+ chk("must contain one or more complete sentences",
+ grepl("[.!?]['\")]?$", str_trim(x$value))),
+ chk("must not start with 'The package', 'This Package, 'A package'",
+ !grepl("^(The|This|A|In this|In the) package", x$value)),
+ chk("must start with a capital letter",
+ grepl("^['\"]?[[:upper:]]", x$value))
+ )
+}
+
+##' @export
+##' @method check_field DescriptionTitle
+
+check_field.DescriptionTitle <- function(x, warn = FALSE, ...) {
+
+ chks(
+ x = x, warn = warn,
+ chk("must not be empty",
+ str_trim(x$value) != ""),
+ chk("must not end with a period",
+ !grepl("[.]$", str_trim(x$value)) ||
+ grepl("[[:space:]][.][.][.]|et[[:space:]]al[.]", str_trim(x$value)))
+ )
+}
+
+##' @export
+##' @method check_field DescriptionMaintainer
+
+check_field.DescriptionMaintainer <- function(x, warn = FALSE, ...) {
+
+ re_maint <- paste0(
+ "^[[:space:]]*(.*<",
+ RFC_2822_email_regexp,
+ ">|ORPHANED)[[:space:]]*$"
+ )
+
+ chks(
+ x = x, warn = warn,
+ chk("must not be empty",
+ str_trim(x$value) != ""),
+ chk("must contain an email address",
+ grepl(re_maint, x$value))
+ )
+}
+
+## TODO
+##' @export
+##' @method check_field DescriptionAuthorsAtR
+
+check_field.DescriptionAuthorsAtR <- function(x, warn = FALSE, ...) {
+ TRUE
+}
+
+##' @export
+##' @method check_field DescriptionDependencyList
+
+check_field.DescriptionDependencyList <- function(x, warn = FALSE, ...) {
+
+ deps <- parse_deps(x$key, x$value)
+
+ is_package_list <- function(xx) {
+ p <- lapply(xx, function(pc)
+ check_field.DescriptionPackage(
+ list(key = "Package", value = pc),
+ R = x$key[1] == "Depends"
+ )
+ )
+ all_true(p)
+ }
+
+ is_version_req <- function(x) {
+
+ x <- str_trim(x)
+ if (x == "*") return(TRUE)
+
+ re <- paste0(
+ "^(<=|>=|<|>|==|!=)\\s*",
+ valid_version_regexp,
+ "$"
+ )
+ grepl(re, x)
+ }
+
+ is_version_req_list <- function(x) {
+ all_true(vapply(x, is_version_req, TRUE))
+ }
+
+ chks(
+ x = x, warn = warn,
+ chk("must contain valid package names",
+ is_package_list(deps$package)),
+ chk("must contain valid version requirements",
+ is_version_req_list(deps$version))
+ )
+}
+
+##' @export
+##' @method check_field DescriptionRepoList
+
+check_field.DescriptionRepoList <- function(x, warn = FALSE, ...) {
+
+ chks(
+ x = x, warn = warn,
+ chk("must be a comma separated list repository URLs",
+ is_url_list(x$value))
+ )
+}
+
+##' @export
+##' @method check_field DescriptionURL
+
+check_field.DescriptionURL <- function(x, warn = FALSE, ...) {
+
+ chks(
+ x = x, warn = warn,
+ chk("must be a http, https or ftp URL",
+ is_url(x$value))
+ )
+}
+
+##' @export
+##' @method check_field DescriptionURLList
+
+check_field.DescriptionURLList <- function(x, warn = FALSE, ...) {
+
+ chks(
+ x = x, warn = warn,
+ chk("must be a comma separated list of http, https or ftp URLs",
+ is_url_list(x$value))
+ )
+}
+
+##' @export
+##' @method check_field DescriptionPriority
+
+check_field.DescriptionPriority <- function(x, warn = FALSE, ...) {
+
+ chks(
+ x = x, warn = warn,
+ chk("must be one of 'base', 'recommended' or 'defunct-base'",
+ str_trim(x$value) %in% c("base", "recommended", "defunct-base"))
+ )
+}
+
+##' @export
+##' @method check_field DescriptionCollate
+
+check_field.DescriptionCollate <- function(x, warn = FALSE, ...) {
+
+ coll <- tolower(parse_collate(x$value))
+
+ chks(
+ x = x, warn = warn,
+ chk("must contain a list of .R files",
+ all(grepl("[.]r$", coll)))
+ )
+}
+
+##' @export
+##' @method check_field DescriptionLogical
+
+check_field.DescriptionLogical <- function(x, warn = FALSE, ...) {
+
+ chks(
+ x = x, warn = warn,
+ chk("must be one of 'true', 'false', 'yes' or 'no' (case insensitive)",
+ str_trim(tolower(x$value)) %in% c("true", "false", "yes", "no"))
+ )
+}
+
+##' @export
+##' @method check_field DescriptionPackageList
+
+check_field.DescriptionPackageList <- function(x, warn = FALSE, ...) {
+
+ is_package_list <- function(x) {
+ xx <- str_trim(strsplit(x, ",", fixed = TRUE)[[1]])
+ p <- lapply(xx, function(pc)
+ check_field.DescriptionPackage(list(key = "Package", value = pc)))
+ all_true(p)
+ }
+
+ chks(
+ x = x, warn = warn,
+ chk("must be a comma separated list of package names",
+ is_package_list(x$value))
+ )
+}
+
+##' @export
+##' @method check_field DescriptionEncoding
+
+check_field.DescriptionEncoding <- function(x, warn = FALSE, ...) {
+
+ chks(
+ x = x, warn = warn,
+ chk("must be one of 'latin1', 'latin2' and 'UTF-8'",
+ x$value %in% c("latin1", "latin2", "UTF-8"))
+ )
+}
+
+##' @export
+##' @method check_field DescriptionOSType
+
+check_field.DescriptionOSType <- function(x, warn = FALSE, ...) {
+
+ chks(
+ x = x, warn = warn,
+ chk("must be one of 'unix' and 'windows'",
+ x$value %in% c("unix", "windows"))
+ )
+}
+
+##' @export
+##' @method check_field DescriptionType
+
+check_field.DescriptionType <- function(x, warn = FALSE, ...) {
+
+ chks(
+ x = x, warn = warn,
+ chk("must be either 'Package' or 'Translation'",
+ x$value %in% c("Package", "Translation"))
+ )
+}
+
+##' @export
+##' @method check_field DescriptionClassification
+
+check_field.DescriptionClassification <- function(x, warn = FALSE, ...) {
+ TRUE
+}
+
+##' @export
+##' @method check_field DescriptionLanguage
+
+check_field.DescriptionLanguage <- function(x, warn = FALSE, ...) {
+
+ is_language_list <- function(x) {
+ x <- str_trim(strsplit(x, ",", fixed = TRUE)[[1]])
+ all(grepl("^[a-z][a-z][a-z]?$", x))
+ }
+
+ chks(
+ x = x, warn = warn,
+ chk("must be a list of IETF language codes defined by defined by RFC 5646",
+ is_language_list(x$value))
+ )
+}
+
+##' @export
+##' @method check_field DescriptionDate
+
+check_field.DescriptionDate <- function(x, warn = FALSE, ...) {
+
+ chks(
+ x = x, warn = warn,
+ chk(
+ paste0(
+ "must be an ISO date: yyyy-mm-dd, but it is actually better\n",
+ "to leave this field out completely. It is not required."),
+ grepl("^[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]$", x$value)
+ )
+ )
+}
+
+##' @export
+##' @method check_field DescriptionCompression
+
+check_field.DescriptionCompression <- function(x, warn = FALSE, ...) {
+
+ chks(
+ x = x, warn = warn,
+ chk("must be one of 'bzip2', 'xz', 'gzip'",
+ x$value %in% c("bzip2", "xz", "gzip"))
+ )
+}
+
+##' @export
+##' @method check_field DescriptionRepository
+
+check_field.DescriptionRepository <- function(x, warn = FALSE, ...) {
+ TRUE
+}
+
+##' @export
+##' @method check_field DescriptionFreeForm
+
+check_field.DescriptionFreeForm <- function(x, warn = FALSE, ...) {
+ TRUE
+}
+
+##' @export
+##' @method check_field DescriptionAddedByRCMD
+
+check_field.DescriptionAddedByRCMD <- function(x, warn = FALSE, ...) {
+ TRUE
+}
diff --git a/R/urls.R b/R/urls.R
new file mode 100644
index 0000000..ecb1a9a
--- /dev/null
+++ b/R/urls.R
@@ -0,0 +1,47 @@
+
+parse_urls <- function(urls) {
+ str_trim(strsplit(urls, "[,\\s]+", perl = TRUE)[[1]])
+}
+
+deparse_urls <- function(urls) {
+ paste(urls, collapse = ",\n ")
+}
+
+idesc_get_urls <- function(self, private) {
+ urls <- self$get("URL")
+ if (is.na(urls)) {
+ character()
+ } else {
+ parse_urls(urls)
+ }
+}
+
+idesc_set_urls <- function(self, private, urls) {
+ assert_that(is.character(urls))
+ self$set(URL = deparse_urls(urls))
+ invisible(self)
+}
+
+idesc_add_urls <- function(self, private, urls) {
+ assert_that(is.character(urls))
+ urls <- unique(c(self$get_urls(), urls))
+ self$set(URL = deparse_urls(urls))
+ invisible(self)
+}
+
+idesc_del_urls <- function(self, private, pattern) {
+ assert_that(is_string(pattern))
+ urls <- self$get_urls()
+ filt <- grep(pattern, urls, invert = TRUE, value = TRUE, perl = TRUE)
+ if (length(filt) > 0) {
+ self$set(URL = deparse_urls(filt))
+ } else {
+ self$del("URL")
+ }
+ invisible(self)
+}
+
+idesc_clear_urls <- function(self, private) {
+ self$del("URL")
+ invisible(self)
+}
diff --git a/R/utils.R b/R/utils.R
new file mode 100644
index 0000000..7e2a66c
--- /dev/null
+++ b/R/utils.R
@@ -0,0 +1,109 @@
+
+str_trim <- function(x) {
+ sub("^\\s+", "", sub("\\s+$", "", x))
+}
+
+
+is_ascii <- function(x) {
+ vapply(
+ as.character(x),
+ function(txt) all(charToRaw(txt) <= as.raw(127)),
+ TRUE,
+ USE.NAMES = FALSE
+ )
+}
+
+
+## This is from tools/R/QC.R
+## We do not calculate code coverage for this, as
+## it is run at install time
+##
+## nocov start
+RFC_2822_email_regexp <- (function() {
+
+ ## Local part consists of ASCII letters and digits, the characters
+ ## ! # $ % * / ? | ^ { } ` ~ & ' + = _ -
+ ## and . provided it is not leading or trailing or repeated, or must
+ ## be a quoted string.
+ ## Domain part consists of dot-separated elements consisting of
+ ## ASCII letters, digits and hyphen.
+ ## We could also check that the local and domain parts are no longer
+ ## than 64 and 255 characters, respectively.
+ ## See http://en.wikipedia.org/wiki/Email_address.
+
+ ASCII_letters_and_digits <-
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
+ l <- sprintf("[%s%s]", ASCII_letters_and_digits, "!#$%*/?|^{}`~&'+=_-")
+ d <- sprintf("[%s%s]", ASCII_letters_and_digits, "-")
+ ## Be careful to arrange the hyphens to come last in the range spec.
+ sprintf("(\\\".+\\\"|(%s+\\.)*%s+)@(%s+\\.)*%s+", l, l, d, d)
+})()
+## nocov end
+
+
+is_url <- function(x) {
+ grepl("^(https?|ftp)://\\S+$", str_trim(x))
+}
+
+
+is_url_list <- function(x) {
+ xx <- parse_url_list(x)
+ all(vapply(xx, is_url, TRUE))
+}
+
+
+parse_url_list <- function(x) {
+ xx <- strsplit(x, ",", fixed = TRUE)[[1]]
+ str_trim(xx)
+}
+
+
+all_true <- function(x) {
+ all(vapply(x, identical, TRUE, TRUE))
+}
+
+
+flatten <- function(x) {
+ if (is.list(x)) {
+ x <- lapply(
+ x,
+ function(e) if (is.null(e)) "" else paste(e, collapse = ",")
+ )
+ x <- unlist(x)
+ }
+ x
+}
+
+ngrepl <- function(pattern, x, ...) {
+ if (is.null(pattern)) pattern <- ""
+ x <- flatten(x)
+ grepl(pattern, x, ...)
+}
+
+check_for_package <- function(pkg, msg = paste0("Package '", pkg,
+ "' is needed.")) {
+
+ has <- requireNamespace(pkg, quietly = TRUE)
+ if (!has) stop(msg, call. = FALSE)
+ has
+}
+
+is_dir <- function(path) {
+ file.info(path)$isdir
+}
+
+postprocess_trailing_ws <- function(file, notws) {
+ lines <- readLines(file)
+
+ for (n in notws) {
+ lines <- sub(paste0("^", n, ": "), paste0(n, ":"), lines)
+ }
+ writeLines(lines, file)
+}
+
+#' @importFrom rprojroot find_root is_r_package
+
+find_description <- function(dir) {
+ pkg_root <- find_root(is_r_package, dir)
+ file.path(pkg_root, "DESCRIPTION")
+}
diff --git a/R/validate.R b/R/validate.R
new file mode 100644
index 0000000..861699f
--- /dev/null
+++ b/R/validate.R
@@ -0,0 +1,5 @@
+
+idesc_validate <- function(self, private) {
+ warning("Validation is not implemented yet")
+ TRUE
+}
diff --git a/R/version.R b/R/version.R
new file mode 100644
index 0000000..21a617f
--- /dev/null
+++ b/R/version.R
@@ -0,0 +1,56 @@
+
+idesc_get_version <- function(self, private) {
+ ver <- unname(self$get("Version"))
+ if (is.na(ver)) stop("No ", sQuote('Version'), " field found")
+ package_version(ver)
+}
+
+idesc_set_version <- function(self, private, version) {
+ assert_that(is_package_version(version))
+ self$set(Version = as.character(version))
+}
+
+idesc_bump_version <- function(self, private, which) {
+ assert_that(is_version_component(which))
+ if (is.character(which)) {
+ which <- match(which, c("major", "minor", "patch", "dev"))
+ }
+
+ ver_str <- self$get_version()
+ ver <- get_version_components(ver_str)
+
+ ## Special dev version
+ inc <- if (which == 4 && length(ver) < 4) 9000 else 1
+
+ ## Missing components are equivalent to zero
+ if (which > length(ver)) ver <- c(ver, rep(0, which - length(ver)))
+
+ ## Bump selected component
+ ver[which] <- ver[which] + inc
+
+ ## Zero out everything after
+ if (which < length(ver)) ver[(which+1):length(ver)] <- 0
+
+ ## Keep at most three components if they are zero
+ if (length(ver) > 3 && all(ver[4:length(ver)] == 0)) {
+ ver <- ver[1:3]
+ }
+
+ ## Set the new version
+ new_ver <- package_version(paste(ver, collapse = "."))
+ self$set_version(new_ver)
+
+ ## Give a message
+ message(
+ "Package version bumped from ", sQuote(ver_str),
+ " to ", sQuote(new_ver)
+ )
+
+ invisible(self)
+}
+
+## Note that this is not vectorized
+
+get_version_components <- function(x) {
+ as.numeric(strsplit(format(x), "[-\\.]")[[1]])
+}
diff --git a/inst/DESCRIPTION b/inst/DESCRIPTION
new file mode 100644
index 0000000..084dc61
--- /dev/null
+++ b/inst/DESCRIPTION
@@ -0,0 +1,19 @@
+Package: desc
+Title: Manipulate DESCRIPTION Files
+Version: 1.0.0
+Author: Gábor Csárdi
+Maintainer: Gábor Csárdi <csardi.gabor at gmail.com>
+Description: Tools to read, write, create, and manipulate DESCRIPTION files.
+ It is intented for packages that create or manipulate other packages.
+License: MIT + file LICENSE
+LazyData: true
+URL: https://github.com/r-lib/desc
+BugReports: https://github.com/r-lib/desc/issues
+Suggests:
+ testthat,
+ whoami,
+ newpackage
+Imports:
+ R6
+RoxygenNote: 5.0.0
+Encoding: UTF-8
diff --git a/inst/DESCRIPTION2 b/inst/DESCRIPTION2
new file mode 100644
index 0000000..189cf90
--- /dev/null
+++ b/inst/DESCRIPTION2
@@ -0,0 +1,62 @@
+Package: roxygen2
+Title: In-Source Documentation for R
+Description: A 'Doxygen'-like in-source documentation system
+ for Rd, collation, and 'NAMESPACE' files.
+URL: https://github.com/klutometis/roxygen
+Version: 4.1.1.9000
+License: GPL (>= 2)
+Authors at R: c(
+ person("Hadley", "Wickham",, "h.wickham at gmail.com", c("aut", "cre", "cph")),
+ person("Peter", "Danenberg",, "pcd at roxygen.org", c("aut", "cph")),
+ person("Manuel", "Eugster", role = c("aut", "cph")),
+ person("RStudio", role = "cph")
+ )
+Depends:
+ R (>= 3.0.2)
+Imports:
+ stringr (>= 0.5),
+ brew,
+ digest,
+ methods,
+ Rcpp (>= 0.11.0)
+Suggests:
+ testthat (>= 0.8.0),
+ knitr
+VignetteBuilder: knitr
+LinkingTo: Rcpp
+Collate:
+ 'RcppExports.R'
+ 'alias.R'
+ 'description.R'
+ 'family.R'
+ 'inherit-params.R'
+ 'minidesc.R'
+ 'object-defaults.R'
+ 'object-from-call.R'
+ 'object.R'
+ 'order-params.R'
+ 'parse-preref.R'
+ 'parse-registry.R'
+ 'parse.R'
+ 'rc.R'
+ 'rd-escape.R'
+ 'rd-file-api.R'
+ 'rd-parse.R'
+ 'rd-tag-api.R'
+ 'roclet-collate.R'
+ 'roclet-namespace.R'
+ 'roclet-rd.R'
+ 'roclet-vignette.R'
+ 'roclet.R'
+ 'roxygen.R'
+ 'roxygenize.R'
+ 's3.R'
+ 'safety.R'
+ 'source.R'
+ 'template.R'
+ 'topic-name.R'
+ 'topo-sort.R'
+ 'usage.R'
+ 'util-locale.R'
+ 'utils.R'
+RoxygenNote: 4.1.1.9000
diff --git a/inst/NEWS.md b/inst/NEWS.md
new file mode 100644
index 0000000..af32a99
--- /dev/null
+++ b/inst/NEWS.md
@@ -0,0 +1,25 @@
+
+# 1.1.1
+
+* Relax the R >= 3.2.0 dependency, R 3.1.0 is enough now.
+
+# 1.1.0
+
+* Fix bug when adding authors and there is no `Authors at R` field
+
+* Get `DESCRIPTION` from package archives (#40)
+
+* Fix but in `del_dep()` and `has_dep()`, they only worked if the package
+ was attached.
+
+# 1.0.1
+
+* Fix formatting of `Collate` fields, they always start at a new line now.
+
+* Fix formatting of `Authors at R` fields, when changed.
+
+* Keep trailing space after the `:` character, see #14
+
+# 1.0.0
+
+First public release.
diff --git a/inst/README.Rmd b/inst/README.Rmd
new file mode 100644
index 0000000..83dad2a
--- /dev/null
+++ b/inst/README.Rmd
@@ -0,0 +1,161 @@
+
+```{r, setup, echo = FALSE, message = FALSE}
+knitr::opts_chunk$set(
+ comment = "#>",
+ tidy = FALSE,
+ error = FALSE,
+ fig.width = 8,
+ fig.height = 8)
+```
+
+# desc
+
+> Parse DESCRIPTION files
+
+[![Linux Build Status](https://travis-ci.org/r-lib/desc.svg?branch=master)](https://travis-ci.org/r-lib/desc)
+[![Windows Build status](https://ci.appveyor.com/api/projects/status/github/r-lib/desc?svg=true)](https://ci.appveyor.com/project/gaborcsardi/desc)
+[![](http://www.r-pkg.org/badges/version/desc)](http://www.r-pkg.org/pkg/desc)
+[![CRAN RStudio mirror downloads](http://cranlogs.r-pkg.org/badges/desc)](http://www.r-pkg.org/pkg/desc)
+[![Coverage Status](https://img.shields.io/codecov/c/github/r-lib/desc/master.svg)](https://codecov.io/github/r-lib/desc?branch=master)
+
+Parse, manipulate and reformat DESCRIPTION files. The package
+provides two APIs, one is object oriented, the other one is
+procedural and manipulates the files *in place*.
+
+---
+
+ - [Installation](#installation)
+ - [The object oriented API](#the-oo-api)
+ - [Introduction](#introduction)
+ - [Loading or creating new `DESCRIPTION` files](#loading-or-creating-new-description-files)
+ - [Normalizing `DESCRIPTION` files](#normalizing-description-files)
+ - [Querying, changing and removing fields](#querying-changing-and-removing-fields)
+ - [Dependencies](#dependencies)
+ - [Collate fields](#collate-fields)
+ - [Authors](#authors)
+ - [The procedural API](#the-procedural-api)
+ - [License](#license)
+
+## Installation
+
+```{r eval = FALSE}
+source("https://install-github.me/r-lib/desc")
+```
+
+## The object oriented API
+
+```{r}
+library(desc)
+```
+
+### Introduction
+
+The object oriented API uses [R6](https://github.com/wch/R6) classes.
+
+### Loading or creating new `DESCRIPTION` files
+
+A new `description` object can be created by reading a `DESCRPTION`
+file form the disk. By default the `DESCRIPTION` file in the current
+directory is read:
+
+```{r}
+desc <- description$new()
+desc
+```
+
+A new object can also be created from scratch:
+
+```{r}
+desc2 <- description$new("!new")
+desc2
+```
+
+### Normalizing `DESCRIPTION` files
+
+Most `DESCRIPTION` fields may be formatted in multiple equivalent
+ways. `desc` does not reformat fields, unless they are
+updated or reformatting is explicitly requested via a call to
+the `normalize()` method or using the `normalize` argument of the
+`write()` method.
+
+### Querying, changing and removing fields
+
+`get()` and `set()` queries or updates a field:
+
+```{r}
+desc$set("Package", "foo")
+desc$get("Package")
+```
+
+They work with multiple fields as well:
+
+```{r}
+desc$set(Package = "bar", Title = "Bar Package")
+desc$get(c("Package", "Title"))
+```
+
+### Dependencies
+
+Package dependencies can be set and updated via an easier API:
+
+```{r}
+desc$get_deps()
+desc$set_dep("mvtnorm")
+desc$set_dep("Rcpp", "LinkingTo")
+desc$get_deps()
+desc
+```
+
+### Collate fields
+
+Collate fields can be queried and set using simple character
+vectors of file names:
+
+```{r}
+desc$set_collate(list.files("../R"))
+desc$get_collate()
+```
+
+### Authors
+
+Authors information, when specified via the `Authors at R` field,
+also has a simplified API:
+
+```{r}
+desc <- description$new("DESCRIPTION2")
+desc$get_authors()
+desc$add_author("Bugs", "Bunny", email = "bb at acme.com")
+desc$add_me()
+desc$get_authors()
+```
+
+## The procedural API
+
+The procedural API is simpler to use for one-off `DESCRIPTION`
+manipulation, since it does not require dealing with
+`description` objects. Each object oriented method has a
+procedural counterpart that works on a file, and potentially
+writes its result back to the same file.
+
+For example, adding a new dependency to `DESCRIPTION` in the
+current working directory can be done with
+
+```{r}
+desc_set_dep("newpackage", "Suggests")
+```
+
+This added `newpackage` to the `Suggests` field:
+
+```{r}
+desc_get("Suggests")
+```
+
+So the full list of dependencies are now
+
+```{r}
+desc_get_deps()
+```
+
+## License
+
+MIT © [Gábor Csárdi](https://github.com/gaborcsardi).
diff --git a/inst/README.md b/inst/README.md
new file mode 100644
index 0000000..eeed32b
--- /dev/null
+++ b/inst/README.md
@@ -0,0 +1,329 @@
+
+
+
+# desc
+
+> Parse DESCRIPTION files
+
+[![Linux Build Status](https://travis-ci.org/r-lib/desc.svg?branch=master)](https://travis-ci.org/r-lib/desc)
+[![Windows Build status](https://ci.appveyor.com/api/projects/status/github/r-lib/desc?svg=true)](https://ci.appveyor.com/project/gaborcsardi/desc)
+[![](http://www.r-pkg.org/badges/version/desc)](http://www.r-pkg.org/pkg/desc)
+[![CRAN RStudio mirror downloads](http://cranlogs.r-pkg.org/badges/desc)](http://www.r-pkg.org/pkg/desc)
+[![Coverage Status](https://img.shields.io/codecov/c/github/r-lib/desc/master.svg)](https://codecov.io/github/r-lib/desc?branch=master)
+
+Parse, manipulate and reformat DESCRIPTION files. The package
+provides two APIs, one is object oriented, the other one is
+procedural and manipulates the files *in place*.
+
+---
+
+ - [Installation](#installation)
+ - [The object oriented API](#the-oo-api)
+ - [Introduction](#introduction)
+ - [Loading or creating new `DESCRIPTION` files](#loading-or-creating-new-description-files)
+ - [Normalizing `DESCRIPTION` files](#normalizing-description-files)
+ - [Querying, changing and removing fields](#querying-changing-and-removing-fields)
+ - [Dependencies](#dependencies)
+ - [Collate fields](#collate-fields)
+ - [Authors](#authors)
+ - [The procedural API](#the-procedural-api)
+ - [License](#license)
+
+## Installation
+
+
+```r
+source("https://install-github.me/r-lib/desc")
+```
+
+## The object oriented API
+
+
+```r
+library(desc)
+```
+
+### Introduction
+
+The object oriented API uses [R6](https://github.com/wch/R6) classes.
+
+### Loading or creating new `DESCRIPTION` files
+
+A new `description` object can be created by reading a `DESCRPTION`
+file form the disk. By default the `DESCRIPTION` file in the current
+directory is read:
+
+
+```r
+desc <- description$new()
+desc
+```
+
+```
+#> Package: desc
+#> Title: Manipulate DESCRIPTION Files
+#> Version: 1.0.0
+#> Author: Gábor Csárdi
+#> Maintainer: Gábor Csárdi <csardi.gabor at gmail.com>
+#> Description: Tools to read, write, create, and manipulate DESCRIPTION
+#> files. It is intented for packages that create or manipulate other
+#> packages.
+#> License: MIT + file LICENSE
+#> URL: https://github.com/r-lib/desc
+#> BugReports: https://github.com/r-lib/desc/issues
+#> Imports:
+#> R6
+#> Suggests:
+#> testthat,
+#> whoami,
+#> newpackage
+#> Encoding: UTF-8
+#> LazyData: true
+#> RoxygenNote: 5.0.0
+```
+
+A new object can also be created from scratch:
+
+
+```r
+desc2 <- description$new("!new")
+desc2
+```
+
+```
+#> Package: {{ Package }}
+#> Title: {{ Title }}
+#> Version: 1.0.0
+#> Authors at R (parsed):
+#> * Jo Doe <jodoe at dom.ain> [aut, cre]
+#> Maintainer: {{ Maintainer }}
+#> Description: {{ Description }}
+#> License: {{ License }}
+#> URL: {{ URL }}
+#> BugReports: {{ BugReports }}
+#> Encoding: UTF-8
+#> LazyData: true
+```
+
+### Normalizing `DESCRIPTION` files
+
+Most `DESCRIPTION` fields may be formatted in multiple equivalent
+ways. `desc` does not reformat fields, unless they are
+updated or reformatting is explicitly requested via a call to
+the `normalize()` method or using the `normalize` argument of the
+`write()` method.
+
+### Querying, changing and removing fields
+
+`get()` and `set()` queries or updates a field:
+
+
+```r
+desc$set("Package", "foo")
+desc$get("Package")
+```
+
+```
+#> Package
+#> "foo"
+```
+
+They work with multiple fields as well:
+
+
+```r
+desc$set(Package = "bar", Title = "Bar Package")
+desc$get(c("Package", "Title"))
+```
+
+```
+#> Package Title
+#> "bar" "Bar Package"
+```
+
+### Dependencies
+
+Package dependencies can be set and updated via an easier API:
+
+
+```r
+desc$get_deps()
+```
+
+```
+#> type package version
+#> 1 Suggests testthat *
+#> 2 Suggests whoami *
+#> 3 Suggests newpackage *
+#> 4 Imports R6 *
+```
+
+```r
+desc$set_dep("mvtnorm")
+desc$set_dep("Rcpp", "LinkingTo")
+desc$get_deps()
+```
+
+```
+#> type package version
+#> 1 Suggests testthat *
+#> 2 Suggests whoami *
+#> 3 Suggests newpackage *
+#> 4 Imports R6 *
+#> 5 Imports mvtnorm *
+#> 6 LinkingTo Rcpp *
+```
+
+```r
+desc
+```
+
+```
+#> Package: bar
+#> Title: Bar Package
+#> Version: 1.0.0
+#> Author: Gábor Csárdi
+#> Maintainer: Gábor Csárdi <csardi.gabor at gmail.com>
+#> Description: Tools to read, write, create, and manipulate DESCRIPTION
+#> files. It is intented for packages that create or manipulate other
+#> packages.
+#> License: MIT + file LICENSE
+#> URL: https://github.com/r-lib/desc
+#> BugReports: https://github.com/r-lib/desc/issues
+#> Imports:
+#> R6,
+#> mvtnorm
+#> Suggests:
+#> testthat,
+#> whoami,
+#> newpackage
+#> LinkingTo:
+#> Rcpp
+#> Encoding: UTF-8
+#> LazyData: true
+#> RoxygenNote: 5.0.0
+```
+
+### Collate fields
+
+Collate fields can be queried and set using simple character
+vectors of file names:
+
+
+```r
+desc$set_collate(list.files("../R"))
+desc$get_collate()
+```
+
+```
+#> [1] "assertions.R" "authors-at-r.R" "classes.R"
+#> [4] "collate.R" "constants.R" "deps.R"
+#> [7] "description.R" "encoding.R" "latex.R"
+#> [10] "non-oo-api.R" "package-archives.R" "read.R"
+#> [13] "remotes.R" "str.R" "syntax_checks.R"
+#> [16] "urls.R" "utils.R" "validate.R"
+#> [19] "version.R"
+```
+
+### Authors
+
+Authors information, when specified via the `Authors at R` field,
+also has a simplified API:
+
+
+```r
+desc <- description$new("DESCRIPTION2")
+desc$get_authors()
+```
+
+```
+#> [1] "Hadley Wickham <h.wickham at gmail.com> [aut, cre, cph]"
+#> [2] "Peter Danenberg <pcd at roxygen.org> [aut, cph]"
+#> [3] "Manuel Eugster [aut, cph]"
+#> [4] "RStudio [cph]"
+```
+
+```r
+desc$add_author("Bugs", "Bunny", email = "bb at acme.com")
+desc$add_me()
+desc$get_authors()
+```
+
+```
+#> [1] "Hadley Wickham <h.wickham at gmail.com> [aut, cre, cph]"
+#> [2] "Peter Danenberg <pcd at roxygen.org> [aut, cph]"
+#> [3] "Manuel Eugster [aut, cph]"
+#> [4] "RStudio [cph]"
+#> [5] "Bugs Bunny <bb at acme.com>"
+#> [6] "Gabor Csardi <csardi.gabor at gmail.com> [ctb]"
+```
+
+## The procedural API
+
+The procedural API is simpler to use for one-off `DESCRIPTION`
+manipulation, since it does not require dealing with
+`description` objects. Each object oriented method has a
+procedural counterpart that works on a file, and potentially
+writes its result back to the same file.
+
+For example, adding a new dependency to `DESCRIPTION` in the
+current working directory can be done with
+
+
+```r
+desc_set_dep("newpackage", "Suggests")
+```
+
+```
+#> Package: desc
+#> Title: Manipulate DESCRIPTION Files
+#> Version: 1.0.0
+#> Author: Gábor Csárdi
+#> Maintainer: Gábor Csárdi <csardi.gabor at gmail.com>
+#> Description: Tools to read, write, create, and manipulate DESCRIPTION
+#> files. It is intented for packages that create or manipulate other
+#> packages.
+#> License: MIT + file LICENSE
+#> URL: https://github.com/r-lib/desc
+#> BugReports: https://github.com/r-lib/desc/issues
+#> Imports:
+#> R6
+#> Suggests:
+#> testthat,
+#> whoami,
+#> newpackage
+#> Encoding: UTF-8
+#> LazyData: true
+#> RoxygenNote: 5.0.0
+```
+
+This added `newpackage` to the `Suggests` field:
+
+
+```r
+desc_get("Suggests")
+```
+
+```
+#> Suggests
+#> "\n testthat,\n whoami,\n newpackage"
+```
+
+So the full list of dependencies are now
+
+
+```r
+desc_get_deps()
+```
+
+```
+#> type package version
+#> 1 Suggests testthat *
+#> 2 Suggests whoami *
+#> 3 Suggests newpackage *
+#> 4 Imports R6 *
+```
+
+## License
+
+MIT © [Gábor Csárdi](https://github.com/gaborcsardi).
diff --git a/man/check_encoding.Rd b/man/check_encoding.Rd
new file mode 100644
index 0000000..5eeb4a7
--- /dev/null
+++ b/man/check_encoding.Rd
@@ -0,0 +1,27 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/encoding.R
+\name{check_encoding}
+\alias{check_encoding}
+\title{Check encoding of new or existing fields}
+\usage{
+check_encoding(self, private, new_fields)
+}
+\arguments{
+\item{self}{Object.}
+
+\item{private}{Private env.}
+
+\item{new_fields}{New fields, or \code{NULL} to check existing fields.}
+}
+\value{
+Object, invisibly.
+}
+\description{
+If \code{new_fields} is \code{NULL}, then the existing
+fields are checked. Otherwised \code{new_fields} are checked.
+}
+\details{
+Warnings are given for non-ascii fields, if the \code{Encoding}
+field is not set.
+}
+\keyword{internal}
diff --git a/man/check_field.Rd b/man/check_field.Rd
new file mode 100644
index 0000000..c240640
--- /dev/null
+++ b/man/check_field.Rd
@@ -0,0 +1,23 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/syntax_checks.R
+\name{check_field}
+\alias{check_field}
+\title{Syntactical check of a DESCRIPTION field}
+\usage{
+check_field(x, warn = FALSE, ...)
+}
+\arguments{
+\item{x}{The field.}
+
+\item{warn}{Whether to generate a warning if the syntax check fails.}
+
+\item{...}{Additional arguments, they might be used in the future.}
+}
+\value{
+\code{TRUE} if the field is syntactically correct,
+ otherwise a character vector, containing one or multiple
+ error messages.
+}
+\description{
+Syntactical check of a DESCRIPTION field
+}
diff --git a/man/cran_ascii_fields.Rd b/man/cran_ascii_fields.Rd
new file mode 100644
index 0000000..5fdf70a
--- /dev/null
+++ b/man/cran_ascii_fields.Rd
@@ -0,0 +1,18 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/constants.R
+\docType{data}
+\name{cran_ascii_fields}
+\alias{cran_ascii_fields}
+\title{The DESCRIPTION fields that are supposed to be in plain ASCII encoding}
+\format{An object of class \code{character} of length 16.}
+\usage{
+cran_ascii_fields
+}
+\description{
+The DESCRIPTION fields that are supposed to be in plain ASCII encoding
+}
+\seealso{
+Other field types: \code{\link{cran_valid_fields}},
+ \code{\link{dep_types}}
+}
+\keyword{datasets}
diff --git a/man/cran_valid_fields.Rd b/man/cran_valid_fields.Rd
new file mode 100644
index 0000000..0b055f5
--- /dev/null
+++ b/man/cran_valid_fields.Rd
@@ -0,0 +1,18 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/constants.R
+\docType{data}
+\name{cran_valid_fields}
+\alias{cran_valid_fields}
+\title{A list of DESCRIPTION fields that are valid according to the CRAN checks}
+\format{An object of class \code{character} of length 125.}
+\usage{
+cran_valid_fields
+}
+\description{
+A list of DESCRIPTION fields that are valid according to the CRAN checks
+}
+\seealso{
+Other field types: \code{\link{cran_ascii_fields}},
+ \code{\link{dep_types}}
+}
+\keyword{datasets}
diff --git a/man/dep_types.Rd b/man/dep_types.Rd
new file mode 100644
index 0000000..96275fc
--- /dev/null
+++ b/man/dep_types.Rd
@@ -0,0 +1,20 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/constants.R
+\docType{data}
+\name{dep_types}
+\alias{dep_types}
+\title{DESCRIPTION fields that denote package dependencies}
+\format{An object of class \code{character} of length 5.}
+\usage{
+dep_types
+}
+\description{
+Currently it has the following ones: Imports, Depends,
+Suggests, Enhances and LinkingTo. See the \emph{Writing R Extensions}
+manual for when to use which.
+}
+\seealso{
+Other field types: \code{\link{cran_ascii_fields}},
+ \code{\link{cran_valid_fields}}
+}
+\keyword{datasets}
diff --git a/man/desc.Rd b/man/desc.Rd
new file mode 100644
index 0000000..7a2009d
--- /dev/null
+++ b/man/desc.Rd
@@ -0,0 +1,40 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/description.R
+\name{desc}
+\alias{desc}
+\title{Read a DESCRIPTION file}
+\usage{
+desc(cmd = NULL, file = NULL, text = NULL, package = NULL)
+}
+\arguments{
+\item{cmd}{A command to create a description from scratch.
+Currently only \code{"!new"} is implemented. If it does not start
+with an exclamation mark, it will be interpreted as the \sQuote{file}
+argument.}
+
+\item{file}{Name of the \code{DESCRIPTION} file to load. If all of
+\sQuote{cmd}, \sQuote{file} and \sQuote{text} are \code{NULL} (the
+default), then the \code{DESCRIPTION} file in the current working
+directory is used. The file can also be an R package (source, or
+binary), in which case the DESCRIPTION file is extracted from it, but
+note that in this case \code{$write()} cannot write the file back in
+the package archive.}
+
+\item{text}{A character scalar containing the full DESCRIPTION.
+Character vectors are collapsed into a character scalar, with
+newline as the separator.}
+
+\item{package}{If not NULL, then the name of an installed package
+and the DESCRIPTION file of this package will be loaded.}
+}
+\description{
+This is a convenience wrapper for \code{description$new()}.
+Very often you want to read an existing \code{DESCRIPTION}
+file, and to do this you can just supply the path to the file or its
+directory to \code{desc()}.
+}
+\examples{
+desc(package = "desc")
+DESCRIPTION <- system.file("DESCRIPTION", package = "desc")
+desc(DESCRIPTION)
+}
diff --git a/man/desc_add_author.Rd b/man/desc_add_author.Rd
new file mode 100644
index 0000000..4817a6c
--- /dev/null
+++ b/man/desc_add_author.Rd
@@ -0,0 +1,41 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/non-oo-api.R
+\name{desc_add_author}
+\alias{desc_add_author}
+\title{Add an author to Authors at R in DESCRIPTION}
+\usage{
+desc_add_author(given = NULL, family = NULL, email = NULL, role = NULL,
+ comment = NULL, file = ".", normalize = FALSE)
+}
+\arguments{
+\item{given}{Given name.}
+
+\item{family}{Family name.}
+
+\item{email}{Email address.}
+
+\item{role}{Role.}
+
+\item{comment}{Comment.}
+
+\item{file}{DESCRIPTION file to use. By default the DESCRIPTION
+file of the current package (i.e. the package the working directory
+is part of) is used.}
+
+\item{normalize}{Whether to normalize the write when writing back
+the result. See \code{\link{desc_normalize}}.}
+}
+\description{
+Add an author to Authors at R in DESCRIPTION
+}
+\seealso{
+Other Authors at R: \code{\link{desc_add_me}},
+ \code{\link{desc_add_role}},
+ \code{\link{desc_change_maintainer}},
+ \code{\link{desc_del_author}},
+ \code{\link{desc_del_role}},
+ \code{\link{desc_get_authors}},
+ \code{\link{desc_get_author}},
+ \code{\link{desc_get_maintainer}},
+ \code{\link{desc_set_authors}}
+}
diff --git a/man/desc_add_me.Rd b/man/desc_add_me.Rd
new file mode 100644
index 0000000..b2d9524
--- /dev/null
+++ b/man/desc_add_me.Rd
@@ -0,0 +1,34 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/non-oo-api.R
+\name{desc_add_me}
+\alias{desc_add_me}
+\title{Add the current user as an author to DESCRIPTION}
+\usage{
+desc_add_me(role = "ctb", comment = NULL, file = ".", normalize = FALSE)
+}
+\arguments{
+\item{role}{Role to set for the user, defaults to contributor.}
+
+\item{comment}{Comment, empty by default.}
+
+\item{file}{DESCRIPTION file to use. By default the DESCRIPTION
+file of the current package (i.e. the package the working directory
+is part of) is used.}
+
+\item{normalize}{Whether to normalize the write when writing back
+the result. See \code{\link{desc_normalize}}.}
+}
+\description{
+Uses the Authors at R field.
+}
+\seealso{
+Other Authors at R: \code{\link{desc_add_author}},
+ \code{\link{desc_add_role}},
+ \code{\link{desc_change_maintainer}},
+ \code{\link{desc_del_author}},
+ \code{\link{desc_del_role}},
+ \code{\link{desc_get_authors}},
+ \code{\link{desc_get_author}},
+ \code{\link{desc_get_maintainer}},
+ \code{\link{desc_set_authors}}
+}
diff --git a/man/desc_add_remotes.Rd b/man/desc_add_remotes.Rd
new file mode 100644
index 0000000..2666c9b
--- /dev/null
+++ b/man/desc_add_remotes.Rd
@@ -0,0 +1,25 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/non-oo-api.R
+\name{desc_add_remotes}
+\alias{desc_add_remotes}
+\title{Add locations in the Remotes field in DESCRIPTION}
+\usage{
+desc_add_remotes(remotes, file = ".", normalize = FALSE)
+}
+\arguments{
+\item{remotes}{Character vector of remote locations to add.
+Duplicate locations are eliminated. Note that existing locations
+are not updated, so if you want to \emph{change} a remote location
+of a package, you need to delete the old location first and then add
+the new one.}
+
+\item{file}{DESCRIPTION file to use. By default the DESCRIPTION
+file of the current package (i.e. the package the working directory
+is part of) is used.}
+
+\item{normalize}{Whether to normalize the write when writing back
+the result. See \code{\link{desc_normalize}}.}
+}
+\description{
+Add locations in the Remotes field in DESCRIPTION
+}
diff --git a/man/desc_add_role.Rd b/man/desc_add_role.Rd
new file mode 100644
index 0000000..a0cf0fd
--- /dev/null
+++ b/man/desc_add_role.Rd
@@ -0,0 +1,43 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/non-oo-api.R
+\name{desc_add_role}
+\alias{desc_add_role}
+\title{Add a role to one or more authors in Authors at R, in DESCRIPTION}
+\usage{
+desc_add_role(role, given = NULL, family = NULL, email = NULL,
+ comment = NULL, file = ".", normalize = FALSE)
+}
+\arguments{
+\item{role}{Role to add.}
+
+\item{given}{Given name to filter on. Regular expression.}
+
+\item{family}{Family name to filter on. Regular expression.}
+
+\item{email}{Email address to filter on. Regular expression.}
+
+\item{comment}{Comment field to filter on. Regular expression.}
+
+\item{file}{DESCRIPTION file to use. By default the DESCRIPTION
+file of the current package (i.e. the package the working directory
+is part of) is used.}
+
+\item{normalize}{Whether to normalize the write when writing back
+the result. See \code{\link{desc_normalize}}.}
+}
+\description{
+The author(s) can be specified by a combination of the \code{given},
+\code{family}, \code{email} and \code{comment} fields. If multiple
+filters are specified, then all must match to identify the author(s).
+}
+\seealso{
+Other Authors at R: \code{\link{desc_add_author}},
+ \code{\link{desc_add_me}},
+ \code{\link{desc_change_maintainer}},
+ \code{\link{desc_del_author}},
+ \code{\link{desc_del_role}},
+ \code{\link{desc_get_authors}},
+ \code{\link{desc_get_author}},
+ \code{\link{desc_get_maintainer}},
+ \code{\link{desc_set_authors}}
+}
diff --git a/man/desc_add_to_collate.Rd b/man/desc_add_to_collate.Rd
new file mode 100644
index 0000000..d9f723b
--- /dev/null
+++ b/man/desc_add_to_collate.Rd
@@ -0,0 +1,31 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/non-oo-api.R
+\name{desc_add_to_collate}
+\alias{desc_add_to_collate}
+\title{Add one or more files to the Collate field, in DESCRIPTION}
+\usage{
+desc_add_to_collate(files, which = c("default", "all", "main", "windows",
+ "unix"), file = ".", normalize = FALSE)
+}
+\arguments{
+\item{files}{Character vector, files to add.}
+
+\item{which}{Which collate field to use. Collate fields can
+be operating system type specific.}
+
+\item{file}{DESCRIPTION file to use. By default the DESCRIPTION
+file of the current package (i.e. the package the working directory
+is part of) is used.}
+
+\item{normalize}{Whether to normalize the write when writing back
+the result. See \code{\link{desc_normalize}}.}
+}
+\description{
+Add one or more files to the Collate field, in DESCRIPTION
+}
+\seealso{
+Other Collate field: \code{\link{desc_del_collate}},
+ \code{\link{desc_del_from_collate}},
+ \code{\link{desc_get_collate}},
+ \code{\link{desc_set_collate}}
+}
diff --git a/man/desc_add_urls.Rd b/man/desc_add_urls.Rd
new file mode 100644
index 0000000..019be0c
--- /dev/null
+++ b/man/desc_add_urls.Rd
@@ -0,0 +1,22 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/non-oo-api.R
+\name{desc_add_urls}
+\alias{desc_add_urls}
+\title{Add URLs to the URL field in DESCRIPTION}
+\usage{
+desc_add_urls(urls, file = ".", normalize = FALSE)
+}
+\arguments{
+\item{urls}{Character vector of URLs to add. Duplicate URLs are
+eliminated.}
+
+\item{file}{DESCRIPTION file to use. By default the DESCRIPTION
+file of the current package (i.e. the package the working directory
+is part of) is used.}
+
+\item{normalize}{Whether to normalize the write when writing back
+the result. See \code{\link{desc_normalize}}.}
+}
+\description{
+Add URLs to the URL field in DESCRIPTION
+}
diff --git a/man/desc_bump_version.Rd b/man/desc_bump_version.Rd
new file mode 100644
index 0000000..6fa2bdd
--- /dev/null
+++ b/man/desc_bump_version.Rd
@@ -0,0 +1,44 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/non-oo-api.R
+\name{desc_bump_version}
+\alias{desc_bump_version}
+\title{Increase the version number in DESCRIPTION}
+\usage{
+desc_bump_version(which, file = ".", normalize = FALSE)
+}
+\arguments{
+\item{which}{Which component to increase. See details below.}
+
+\item{file}{DESCRIPTION file to use. By default the DESCRIPTION
+file of the current package (i.e. the package the working directory
+is part of) is used.}
+
+\item{normalize}{Whether to normalize the write when writing back
+the result. See \code{\link{desc_normalize}}.}
+}
+\description{
+The \code{which} parameter specifies which component to increase.
+It can be a string referring to a component: \sQuote{major},
+\sQuote{minor}, \sQuote{patch} or \sQuote{dev}, or an integer
+scalar, for the latter components are counted from one, and the
+beginning. I.e. component one is equivalent to \sQuote{major}.
+}
+\details{
+If a component is bumped, then the ones after it are zeroed out.
+Trailing zero components are omitted from the new version number,
+but if the old version number had at least two or three components, then
+the one will also have two or three.
+
+The bumping of the \sQuote{dev} version (the fourth component) is
+special: if the original version number had less than four components,
+and the \sQuote{dev} version is bumped, then it is set to \code{9000}
+instead of \code{1}. This is a convention often used by R developers,
+it was originally invented by Winston Chang.
+
+Both \code{$set_version()} and \code{$bump_version()} use dots to
+separate the version number components.
+}
+\seealso{
+Other version numbers: \code{\link{desc_get_version}},
+ \code{\link{desc_set_version}}
+}
diff --git a/man/desc_change_maintainer.Rd b/man/desc_change_maintainer.Rd
new file mode 100644
index 0000000..2d96f97
--- /dev/null
+++ b/man/desc_change_maintainer.Rd
@@ -0,0 +1,41 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/non-oo-api.R
+\name{desc_change_maintainer}
+\alias{desc_change_maintainer}
+\title{Change maintainer of the package, in DESCRIPTION}
+\usage{
+desc_change_maintainer(given = NULL, family = NULL, email = NULL,
+ comment = NULL, file = ".", normalize = FALSE)
+}
+\arguments{
+\item{given}{Given name.}
+
+\item{family}{Family name.}
+
+\item{email}{Email address.}
+
+\item{comment}{Comment.}
+
+\item{file}{DESCRIPTION file to use. By default the DESCRIPTION
+file of the current package (i.e. the package the working directory
+is part of) is used.}
+
+\item{normalize}{Whether to normalize the write when writing back
+the result. See \code{\link{desc_normalize}}.}
+}
+\description{
+Only works with the Authors at R field.
+}
+\details{
+The current maintainer is kept if they have at least another role.
+}
+\seealso{
+Other Authors at R: \code{\link{desc_add_author}},
+ \code{\link{desc_add_me}}, \code{\link{desc_add_role}},
+ \code{\link{desc_del_author}},
+ \code{\link{desc_del_role}},
+ \code{\link{desc_get_authors}},
+ \code{\link{desc_get_author}},
+ \code{\link{desc_get_maintainer}},
+ \code{\link{desc_set_authors}}
+}
diff --git a/man/desc_clear_remotes.Rd b/man/desc_clear_remotes.Rd
new file mode 100644
index 0000000..d2e989d
--- /dev/null
+++ b/man/desc_clear_remotes.Rd
@@ -0,0 +1,19 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/non-oo-api.R
+\name{desc_clear_remotes}
+\alias{desc_clear_remotes}
+\title{Remove all locations from the Remotes field of DESCRIPTION}
+\usage{
+desc_clear_remotes(file = ".", normalize = FALSE)
+}
+\arguments{
+\item{file}{DESCRIPTION file to use. By default the DESCRIPTION
+file of the current package (i.e. the package the working directory
+is part of) is used.}
+
+\item{normalize}{Whether to normalize the write when writing back
+the result. See \code{\link{desc_normalize}}.}
+}
+\description{
+This simply means that the field is deleted.
+}
diff --git a/man/desc_clear_urls.Rd b/man/desc_clear_urls.Rd
new file mode 100644
index 0000000..22fb2a1
--- /dev/null
+++ b/man/desc_clear_urls.Rd
@@ -0,0 +1,19 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/non-oo-api.R
+\name{desc_clear_urls}
+\alias{desc_clear_urls}
+\title{Remove all URLs from the URL field of DESCRIPTION}
+\usage{
+desc_clear_urls(file = ".", normalize = FALSE)
+}
+\arguments{
+\item{file}{DESCRIPTION file to use. By default the DESCRIPTION
+file of the current package (i.e. the package the working directory
+is part of) is used.}
+
+\item{normalize}{Whether to normalize the write when writing back
+the result. See \code{\link{desc_normalize}}.}
+}
+\description{
+Remove all URLs from the URL field of DESCRIPTION
+}
diff --git a/man/desc_del.Rd b/man/desc_del.Rd
new file mode 100644
index 0000000..c2805e3
--- /dev/null
+++ b/man/desc_del.Rd
@@ -0,0 +1,26 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/non-oo-api.R
+\name{desc_del}
+\alias{desc_del}
+\title{Remove fields from a DESCRIPTION file}
+\usage{
+desc_del(keys, file = ".", normalize = FALSE)
+}
+\arguments{
+\item{keys}{Character vector of keys to remove.}
+
+\item{file}{DESCRIPTION file to use. By default the DESCRIPTION
+file of the current package (i.e. the package the working directory
+is part of) is used.}
+
+\item{normalize}{Whether to normalize the write when writing back
+the result. See \code{\link{desc_normalize}}.}
+}
+\description{
+Remove fields from a DESCRIPTION file
+}
+\seealso{
+Other simple queries: \code{\link{desc_fields}},
+ \code{\link{desc_get_or_fail}}, \code{\link{desc_get}},
+ \code{\link{desc_has_fields}}, \code{\link{desc_set}}
+}
diff --git a/man/desc_del_author.Rd b/man/desc_del_author.Rd
new file mode 100644
index 0000000..de6d1a9
--- /dev/null
+++ b/man/desc_del_author.Rd
@@ -0,0 +1,44 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/non-oo-api.R
+\name{desc_del_author}
+\alias{desc_del_author}
+\title{Remove one or more authors from DESCRIPTION.}
+\usage{
+desc_del_author(given = NULL, family = NULL, email = NULL, role = NULL,
+ comment = NULL, file = ".", normalize = FALSE)
+}
+\arguments{
+\item{given}{Given name to filter on. Regular expression.}
+
+\item{family}{Family name to filter on. Regular expression.}
+
+\item{email}{Email address to filter on. Regular expression.}
+
+\item{role}{Role to filter on. Regular expression.}
+
+\item{comment}{Comment field to filter on. Regular expression.}
+
+\item{file}{DESCRIPTION file to use. By default the DESCRIPTION
+file of the current package (i.e. the package the working directory
+is part of) is used.}
+
+\item{normalize}{Whether to normalize the write when writing back
+the result. See \code{\link{desc_normalize}}.}
+}
+\description{
+It uses the Authors at R field. The author(s) to be removed
+can be specified via any field(s). All authors matching all
+specifications will be removed. E.g. if only \code{given = "Joe"}
+is supplied, then all authors whole given name matches \code{Joe} will
+be removed. The specifications can be (PCRE) regular expressions.
+}
+\seealso{
+Other Authors at R: \code{\link{desc_add_author}},
+ \code{\link{desc_add_me}}, \code{\link{desc_add_role}},
+ \code{\link{desc_change_maintainer}},
+ \code{\link{desc_del_role}},
+ \code{\link{desc_get_authors}},
+ \code{\link{desc_get_author}},
+ \code{\link{desc_get_maintainer}},
+ \code{\link{desc_set_authors}}
+}
diff --git a/man/desc_del_collate.Rd b/man/desc_del_collate.Rd
new file mode 100644
index 0000000..ebe14bd
--- /dev/null
+++ b/man/desc_del_collate.Rd
@@ -0,0 +1,29 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/non-oo-api.R
+\name{desc_del_collate}
+\alias{desc_del_collate}
+\title{Delete the Collate field from DESCRIPTION}
+\usage{
+desc_del_collate(which = c("all", "main", "windows", "unix"), file = ".",
+ normalize = FALSE)
+}
+\arguments{
+\item{which}{Which collate field to use. Collate fields can
+be operating system type specific.}
+
+\item{file}{DESCRIPTION file to use. By default the DESCRIPTION
+file of the current package (i.e. the package the working directory
+is part of) is used.}
+
+\item{normalize}{Whether to normalize the write when writing back
+the result. See \code{\link{desc_normalize}}.}
+}
+\description{
+Delete the Collate field from DESCRIPTION
+}
+\seealso{
+Other Collate field: \code{\link{desc_add_to_collate}},
+ \code{\link{desc_del_from_collate}},
+ \code{\link{desc_get_collate}},
+ \code{\link{desc_set_collate}}
+}
diff --git a/man/desc_del_dep.Rd b/man/desc_del_dep.Rd
new file mode 100644
index 0000000..a7e26b0
--- /dev/null
+++ b/man/desc_del_dep.Rd
@@ -0,0 +1,31 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/non-oo-api.R
+\name{desc_del_dep}
+\alias{desc_del_dep}
+\title{Remove a package dependency from DESCRIPTION}
+\usage{
+desc_del_dep(package, type = c("all", desc::dep_types), file = ".",
+ normalize = FALSE)
+}
+\arguments{
+\item{package}{Package dependency to remove.}
+
+\item{type}{Dependency type to remove. Sometimes a package is depended
+on via multiple dependency types, e.g. \code{LinkingTo} and
+\code{Imports}. Defaults to all types.}
+
+\item{file}{DESCRIPTION file to use. By default the DESCRIPTION
+file of the current package (i.e. the package the working directory
+is part of) is used.}
+
+\item{normalize}{Whether to normalize the write when writing back
+the result. See \code{\link{desc_normalize}}.}
+}
+\description{
+Remove a package dependency from DESCRIPTION
+}
+\seealso{
+Other dependencies: \code{\link{desc_del_deps}},
+ \code{\link{desc_get_deps}}, \code{\link{desc_has_dep}},
+ \code{\link{desc_set_deps}}, \code{\link{desc_set_dep}}
+}
diff --git a/man/desc_del_deps.Rd b/man/desc_del_deps.Rd
new file mode 100644
index 0000000..ca820eb
--- /dev/null
+++ b/man/desc_del_deps.Rd
@@ -0,0 +1,24 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/non-oo-api.R
+\name{desc_del_deps}
+\alias{desc_del_deps}
+\title{Remove all dependencies from DESCRIPTION}
+\usage{
+desc_del_deps(file = ".", normalize = FALSE)
+}
+\arguments{
+\item{file}{DESCRIPTION file to use. By default the DESCRIPTION
+file of the current package (i.e. the package the working directory
+is part of) is used.}
+
+\item{normalize}{Whether to normalize the write when writing back
+the result. See \code{\link{desc_normalize}}.}
+}
+\description{
+Remove all dependencies from DESCRIPTION
+}
+\seealso{
+Other dependencies: \code{\link{desc_del_dep}},
+ \code{\link{desc_get_deps}}, \code{\link{desc_has_dep}},
+ \code{\link{desc_set_deps}}, \code{\link{desc_set_dep}}
+}
diff --git a/man/desc_del_from_collate.Rd b/man/desc_del_from_collate.Rd
new file mode 100644
index 0000000..0c0095e
--- /dev/null
+++ b/man/desc_del_from_collate.Rd
@@ -0,0 +1,31 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/non-oo-api.R
+\name{desc_del_from_collate}
+\alias{desc_del_from_collate}
+\title{Remove files from the Collate field.}
+\usage{
+desc_del_from_collate(files, which = c("all", "main", "windows", "unix"),
+ file = ".", normalize = FALSE)
+}
+\arguments{
+\item{files}{Files to remove from the Collate field.}
+
+\item{which}{Which collate field to use. Collate fields can
+be operating system type specific.}
+
+\item{file}{DESCRIPTION file to use. By default the DESCRIPTION
+file of the current package (i.e. the package the working directory
+is part of) is used.}
+
+\item{normalize}{Whether to normalize the write when writing back
+the result. See \code{\link{desc_normalize}}.}
+}
+\description{
+Remove files from the Collate field.
+}
+\seealso{
+Other Collate field: \code{\link{desc_add_to_collate}},
+ \code{\link{desc_del_collate}},
+ \code{\link{desc_get_collate}},
+ \code{\link{desc_set_collate}}
+}
diff --git a/man/desc_del_remotes.Rd b/man/desc_del_remotes.Rd
new file mode 100644
index 0000000..b713bf7
--- /dev/null
+++ b/man/desc_del_remotes.Rd
@@ -0,0 +1,22 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/non-oo-api.R
+\name{desc_del_remotes}
+\alias{desc_del_remotes}
+\title{Delete locations from the Remotes field in DESCRIPTION}
+\usage{
+desc_del_remotes(pattern, file = ".", normalize = FALSE)
+}
+\arguments{
+\item{pattern}{Perl-compatible regular expression, all locations
+matching this expression will be deleted.}
+
+\item{file}{DESCRIPTION file to use. By default the DESCRIPTION
+file of the current package (i.e. the package the working directory
+is part of) is used.}
+
+\item{normalize}{Whether to normalize the write when writing back
+the result. See \code{\link{desc_normalize}}.}
+}
+\description{
+All locations matching the specified pattern are deleted.
+}
diff --git a/man/desc_del_role.Rd b/man/desc_del_role.Rd
new file mode 100644
index 0000000..244261d
--- /dev/null
+++ b/man/desc_del_role.Rd
@@ -0,0 +1,42 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/non-oo-api.R
+\name{desc_del_role}
+\alias{desc_del_role}
+\title{Delete a role of an author, in DESCRIPTION}
+\usage{
+desc_del_role(role, given = NULL, family = NULL, email = NULL,
+ comment = NULL, file = ".", normalize = FALSE)
+}
+\arguments{
+\item{role}{Role to remove.}
+
+\item{given}{Given name to filter on. Regular expression.}
+
+\item{family}{Family name to filter on. Regular expression.}
+
+\item{email}{Email address to filter on. Regular expression.}
+
+\item{comment}{Comment field to filter on. Regular expression.}
+
+\item{file}{DESCRIPTION file to use. By default the DESCRIPTION
+file of the current package (i.e. the package the working directory
+is part of) is used.}
+
+\item{normalize}{Whether to normalize the write when writing back
+the result. See \code{\link{desc_normalize}}.}
+}
+\description{
+The author(s) can be specified by a combination of the \code{given},
+\code{family}, \code{email} and \code{comment} fields. If multiple
+filters are specified, then all must match to identify the author(s).
+}
+\seealso{
+Other Authors at R: \code{\link{desc_add_author}},
+ \code{\link{desc_add_me}}, \code{\link{desc_add_role}},
+ \code{\link{desc_change_maintainer}},
+ \code{\link{desc_del_author}},
+ \code{\link{desc_get_authors}},
+ \code{\link{desc_get_author}},
+ \code{\link{desc_get_maintainer}},
+ \code{\link{desc_set_authors}}
+}
diff --git a/man/desc_del_urls.Rd b/man/desc_del_urls.Rd
new file mode 100644
index 0000000..2e59a69
--- /dev/null
+++ b/man/desc_del_urls.Rd
@@ -0,0 +1,22 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/non-oo-api.R
+\name{desc_del_urls}
+\alias{desc_del_urls}
+\title{Delete URLs from the URL field in DESCRIPTION}
+\usage{
+desc_del_urls(pattern, file = ".", normalize = FALSE)
+}
+\arguments{
+\item{pattern}{Perl-compatible regular expression, all URLs
+matching this expression will be deleted.}
+
+\item{file}{DESCRIPTION file to use. By default the DESCRIPTION
+file of the current package (i.e. the package the working directory
+is part of) is used.}
+
+\item{normalize}{Whether to normalize the write when writing back
+the result. See \code{\link{desc_normalize}}.}
+}
+\description{
+All URLs matching the specified pattern are deleted.
+}
diff --git a/man/desc_fields.Rd b/man/desc_fields.Rd
new file mode 100644
index 0000000..1201575
--- /dev/null
+++ b/man/desc_fields.Rd
@@ -0,0 +1,24 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/non-oo-api.R
+\name{desc_fields}
+\alias{desc_fields}
+\title{List all fields in a DESCRIPTION file}
+\usage{
+desc_fields(file = ".")
+}
+\arguments{
+\item{file}{DESCRIPTION file to use. By default the DESCRIPTION
+file of the current package (i.e. the package the working directory
+is part of) is used.}
+}
+\value{
+Character vector of fields.
+}
+\description{
+List all fields in a DESCRIPTION file
+}
+\seealso{
+Other simple queries: \code{\link{desc_del}},
+ \code{\link{desc_get_or_fail}}, \code{\link{desc_get}},
+ \code{\link{desc_has_fields}}, \code{\link{desc_set}}
+}
diff --git a/man/desc_get.Rd b/man/desc_get.Rd
new file mode 100644
index 0000000..d75fb2d
--- /dev/null
+++ b/man/desc_get.Rd
@@ -0,0 +1,28 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/non-oo-api.R
+\name{desc_get}
+\alias{desc_get}
+\title{Get a field from a DESCRIPTION file}
+\usage{
+desc_get(keys, file = ".")
+}
+\arguments{
+\item{keys}{Character vector of fields to get.}
+
+\item{file}{DESCRIPTION file to use. By default the DESCRIPTION
+file of the current package (i.e. the package the working directory
+is part of) is used.}
+}
+\value{
+Character vector, values of the specified keys.
+ Non-existing keys return \code{NA}.
+}
+\description{
+Get a field from a DESCRIPTION file
+}
+\seealso{
+Other simple queries: \code{\link{desc_del}},
+ \code{\link{desc_fields}},
+ \code{\link{desc_get_or_fail}},
+ \code{\link{desc_has_fields}}, \code{\link{desc_set}}
+}
diff --git a/man/desc_get_author.Rd b/man/desc_get_author.Rd
new file mode 100644
index 0000000..7de47e0
--- /dev/null
+++ b/man/desc_get_author.Rd
@@ -0,0 +1,31 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/non-oo-api.R
+\name{desc_get_author}
+\alias{desc_get_author}
+\title{Query authors by role in Authors at R, in DESCRIPTION}
+\usage{
+desc_get_author(role = "cre", file = ".")
+}
+\arguments{
+\item{role}{Role to query. Defaults to the package maintainer.}
+
+\item{file}{DESCRIPTION file to use. By default the DESCRIPTION
+file of the current package (i.e. the package the working directory
+is part of) is used.}
+}
+\value{
+A \code{\link[utils]{person}} object.
+}
+\description{
+Query authors by role in Authors at R, in DESCRIPTION
+}
+\seealso{
+Other Authors at R: \code{\link{desc_add_author}},
+ \code{\link{desc_add_me}}, \code{\link{desc_add_role}},
+ \code{\link{desc_change_maintainer}},
+ \code{\link{desc_del_author}},
+ \code{\link{desc_del_role}},
+ \code{\link{desc_get_authors}},
+ \code{\link{desc_get_maintainer}},
+ \code{\link{desc_set_authors}}
+}
diff --git a/man/desc_get_authors.Rd b/man/desc_get_authors.Rd
new file mode 100644
index 0000000..9b2a863
--- /dev/null
+++ b/man/desc_get_authors.Rd
@@ -0,0 +1,29 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/non-oo-api.R
+\name{desc_get_authors}
+\alias{desc_get_authors}
+\title{Query all authors in Authors at R, in DESCRIPTION}
+\usage{
+desc_get_authors(file = ".")
+}
+\arguments{
+\item{file}{DESCRIPTION file to use. By default the DESCRIPTION
+file of the current package (i.e. the package the working directory
+is part of) is used.}
+}
+\value{
+A \code{\link[utils]{person}} object.
+}
+\description{
+Query all authors in Authors at R, in DESCRIPTION
+}
+\seealso{
+Other Authors at R: \code{\link{desc_add_author}},
+ \code{\link{desc_add_me}}, \code{\link{desc_add_role}},
+ \code{\link{desc_change_maintainer}},
+ \code{\link{desc_del_author}},
+ \code{\link{desc_del_role}},
+ \code{\link{desc_get_author}},
+ \code{\link{desc_get_maintainer}},
+ \code{\link{desc_set_authors}}
+}
diff --git a/man/desc_get_collate.Rd b/man/desc_get_collate.Rd
new file mode 100644
index 0000000..90a60fa
--- /dev/null
+++ b/man/desc_get_collate.Rd
@@ -0,0 +1,28 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/non-oo-api.R
+\name{desc_get_collate}
+\alias{desc_get_collate}
+\title{Query the Collate field in DESCRIPTION}
+\usage{
+desc_get_collate(which = c("main", "windows", "unix"), file = ".")
+}
+\arguments{
+\item{which}{Which collate field to use. Collate fields can
+be operating system type specific.}
+
+\item{file}{DESCRIPTION file to use. By default the DESCRIPTION
+file of the current package (i.e. the package the working directory
+is part of) is used.}
+}
+\value{
+Character vector of file names.
+}
+\description{
+Query the Collate field in DESCRIPTION
+}
+\seealso{
+Other Collate field: \code{\link{desc_add_to_collate}},
+ \code{\link{desc_del_collate}},
+ \code{\link{desc_del_from_collate}},
+ \code{\link{desc_set_collate}}
+}
diff --git a/man/desc_get_deps.Rd b/man/desc_get_deps.Rd
new file mode 100644
index 0000000..f749405
--- /dev/null
+++ b/man/desc_get_deps.Rd
@@ -0,0 +1,26 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/non-oo-api.R
+\name{desc_get_deps}
+\alias{desc_get_deps}
+\title{List all package dependencies from a DESCRIPTION file}
+\usage{
+desc_get_deps(file = ".")
+}
+\arguments{
+\item{file}{DESCRIPTION file to use. By default the DESCRIPTION
+file of the current package (i.e. the package the working directory
+is part of) is used.}
+}
+\value{
+Data frame with columns: \code{type} (dependency type),
+ \code{package}, and \code{version}. For non-versioned dependencies
+ \code{version} is \code{*}.
+}
+\description{
+List all package dependencies from a DESCRIPTION file
+}
+\seealso{
+Other dependencies: \code{\link{desc_del_deps}},
+ \code{\link{desc_del_dep}}, \code{\link{desc_has_dep}},
+ \code{\link{desc_set_deps}}, \code{\link{desc_set_dep}}
+}
diff --git a/man/desc_get_maintainer.Rd b/man/desc_get_maintainer.Rd
new file mode 100644
index 0000000..7f130f7
--- /dev/null
+++ b/man/desc_get_maintainer.Rd
@@ -0,0 +1,29 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/non-oo-api.R
+\name{desc_get_maintainer}
+\alias{desc_get_maintainer}
+\title{Query the package maintainer in DESCRIPTION}
+\usage{
+desc_get_maintainer(file = ".")
+}
+\arguments{
+\item{file}{DESCRIPTION file to use. By default the DESCRIPTION
+file of the current package (i.e. the package the working directory
+is part of) is used.}
+}
+\value{
+A character scalar.
+}
+\description{
+Either from the \sQuote{Maintainer} or the \sQuote{Authors at R} field.
+}
+\seealso{
+Other Authors at R: \code{\link{desc_add_author}},
+ \code{\link{desc_add_me}}, \code{\link{desc_add_role}},
+ \code{\link{desc_change_maintainer}},
+ \code{\link{desc_del_author}},
+ \code{\link{desc_del_role}},
+ \code{\link{desc_get_authors}},
+ \code{\link{desc_get_author}},
+ \code{\link{desc_set_authors}}
+}
diff --git a/man/desc_get_or_fail.Rd b/man/desc_get_or_fail.Rd
new file mode 100644
index 0000000..fe7152e
--- /dev/null
+++ b/man/desc_get_or_fail.Rd
@@ -0,0 +1,27 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/non-oo-api.R
+\name{desc_get_or_fail}
+\alias{desc_get_or_fail}
+\title{Get fields from a DESCRIPTION file, fail if not found}
+\usage{
+desc_get_or_fail(keys, file = ".")
+}
+\arguments{
+\item{keys}{Character vector of fields to get.}
+
+\item{file}{DESCRIPTION file to use. By default the DESCRIPTION
+file of the current package (i.e. the package the working directory
+is part of) is used.}
+}
+\value{
+Character vector, values of the specified keys.
+ Non-existing keys return \code{NA}.
+}
+\description{
+Get fields from a DESCRIPTION file, fail if not found
+}
+\seealso{
+Other simple queries: \code{\link{desc_del}},
+ \code{\link{desc_fields}}, \code{\link{desc_get}},
+ \code{\link{desc_has_fields}}, \code{\link{desc_set}}
+}
diff --git a/man/desc_get_remotes.Rd b/man/desc_get_remotes.Rd
new file mode 100644
index 0000000..e72c3cd
--- /dev/null
+++ b/man/desc_get_remotes.Rd
@@ -0,0 +1,20 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/non-oo-api.R
+\name{desc_get_remotes}
+\alias{desc_get_remotes}
+\title{List the locations in the Remotes field in DESCRIPTION}
+\usage{
+desc_get_remotes(file = ".")
+}
+\arguments{
+\item{file}{DESCRIPTION file to use. By default the DESCRIPTION
+file of the current package (i.e. the package the working directory
+is part of) is used.}
+}
+\value{
+A character vectors or remote locations. A length zero vector
+ is returned if there is no Remotes field in the package.
+}
+\description{
+List the locations in the Remotes field in DESCRIPTION
+}
diff --git a/man/desc_get_urls.Rd b/man/desc_get_urls.Rd
new file mode 100644
index 0000000..b6ed272
--- /dev/null
+++ b/man/desc_get_urls.Rd
@@ -0,0 +1,20 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/non-oo-api.R
+\name{desc_get_urls}
+\alias{desc_get_urls}
+\title{Query the URL field in DESCRIPTION}
+\usage{
+desc_get_urls(file = ".")
+}
+\arguments{
+\item{file}{DESCRIPTION file to use. By default the DESCRIPTION
+file of the current package (i.e. the package the working directory
+is part of) is used.}
+}
+\value{
+A character vectors or URLs. A length zero vector is returned
+ if there is no URL field in the package.
+}
+\description{
+Query the URL field in DESCRIPTION
+}
diff --git a/man/desc_get_version.Rd b/man/desc_get_version.Rd
new file mode 100644
index 0000000..e6d2e5d
--- /dev/null
+++ b/man/desc_get_version.Rd
@@ -0,0 +1,24 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/non-oo-api.R
+\name{desc_get_version}
+\alias{desc_get_version}
+\title{Query the package version in DESCRIPTION}
+\usage{
+desc_get_version(file = ".")
+}
+\arguments{
+\item{file}{DESCRIPTION file to use. By default the DESCRIPTION
+file of the current package (i.e. the package the working directory
+is part of) is used.}
+}
+\value{
+A \code{\link[base]{package_version}} object.
+}
+\description{
+If the file has no \code{Version} field, or it is an invalid
+version string, then it throws an error.
+}
+\seealso{
+Other version numbers: \code{\link{desc_bump_version}},
+ \code{\link{desc_set_version}}
+}
diff --git a/man/desc_has_dep.Rd b/man/desc_has_dep.Rd
new file mode 100644
index 0000000..44e4e48
--- /dev/null
+++ b/man/desc_has_dep.Rd
@@ -0,0 +1,28 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/non-oo-api.R
+\name{desc_has_dep}
+\alias{desc_has_dep}
+\title{Check for a dependency}
+\usage{
+desc_has_dep(package, type = c("any", desc::dep_types), file = ".")
+}
+\arguments{
+\item{package}{The package name.}
+
+\item{type}{A dependency type or \sQuote{any}.}
+
+\item{file}{DESCRIPTION file to use. By default the DESCRIPTION
+file of the current package (i.e. the package the working directory
+is part of) is used.}
+}
+\value{
+A logical scalar.
+}
+\description{
+Check for a dependency
+}
+\seealso{
+Other dependencies: \code{\link{desc_del_deps}},
+ \code{\link{desc_del_dep}}, \code{\link{desc_get_deps}},
+ \code{\link{desc_set_deps}}, \code{\link{desc_set_dep}}
+}
diff --git a/man/desc_has_fields.Rd b/man/desc_has_fields.Rd
new file mode 100644
index 0000000..abf2a2e
--- /dev/null
+++ b/man/desc_has_fields.Rd
@@ -0,0 +1,27 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/non-oo-api.R
+\name{desc_has_fields}
+\alias{desc_has_fields}
+\title{Check if some fields are present in a DESCRIPTION file}
+\usage{
+desc_has_fields(keys, file = ".")
+}
+\arguments{
+\item{keys}{Character vector of keys to check.}
+
+\item{file}{DESCRIPTION file to use. By default the DESCRIPTION
+file of the current package (i.e. the package the working directory
+is part of) is used.}
+}
+\value{
+Logical vector.
+}
+\description{
+Check if some fields are present in a DESCRIPTION file
+}
+\seealso{
+Other simple queries: \code{\link{desc_del}},
+ \code{\link{desc_fields}},
+ \code{\link{desc_get_or_fail}}, \code{\link{desc_get}},
+ \code{\link{desc_set}}
+}
diff --git a/man/desc_normalize.Rd b/man/desc_normalize.Rd
new file mode 100644
index 0000000..0f1de90
--- /dev/null
+++ b/man/desc_normalize.Rd
@@ -0,0 +1,20 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/non-oo-api.R
+\name{desc_normalize}
+\alias{desc_normalize}
+\title{Normalize a DESCRIPTION file}
+\usage{
+desc_normalize(file = ".")
+}
+\arguments{
+\item{file}{DESCRIPTION file to use. By default the DESCRIPTION
+file of the current package (i.e. the package the working directory
+is part of) is used.}
+}
+\description{
+Reformats and reorders fields in DESCRIPTION in a standard way.
+}
+\seealso{
+Other repair functions: \code{\link{desc_reformat_fields}},
+ \code{\link{desc_reorder_fields}}
+}
diff --git a/man/desc_print.Rd b/man/desc_print.Rd
new file mode 100644
index 0000000..a5d04c9
--- /dev/null
+++ b/man/desc_print.Rd
@@ -0,0 +1,16 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/non-oo-api.R
+\name{desc_print}
+\alias{desc_print}
+\title{Print the contents of a DESCRIPTION file to the screen}
+\usage{
+desc_print(file = ".")
+}
+\arguments{
+\item{file}{DESCRIPTION file to use. By default the DESCRIPTION
+file of the current package (i.e. the package the working directory
+is part of) is used.}
+}
+\description{
+Print the contents of a DESCRIPTION file to the screen
+}
diff --git a/man/desc_reformat_fields.Rd b/man/desc_reformat_fields.Rd
new file mode 100644
index 0000000..000653f
--- /dev/null
+++ b/man/desc_reformat_fields.Rd
@@ -0,0 +1,20 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/non-oo-api.R
+\name{desc_reformat_fields}
+\alias{desc_reformat_fields}
+\title{Reformat fields in a DESCRIPTION file}
+\usage{
+desc_reformat_fields(file = ".")
+}
+\arguments{
+\item{file}{DESCRIPTION file to use. By default the DESCRIPTION
+file of the current package (i.e. the package the working directory
+is part of) is used.}
+}
+\description{
+Reformat the fields in DESCRIPTION in a standard way.
+}
+\seealso{
+Other repair functions: \code{\link{desc_normalize}},
+ \code{\link{desc_reorder_fields}}
+}
diff --git a/man/desc_reorder_fields.Rd b/man/desc_reorder_fields.Rd
new file mode 100644
index 0000000..4083d5f
--- /dev/null
+++ b/man/desc_reorder_fields.Rd
@@ -0,0 +1,20 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/non-oo-api.R
+\name{desc_reorder_fields}
+\alias{desc_reorder_fields}
+\title{Reorder fields in a DESCRIPTION file}
+\usage{
+desc_reorder_fields(file = ".")
+}
+\arguments{
+\item{file}{DESCRIPTION file to use. By default the DESCRIPTION
+file of the current package (i.e. the package the working directory
+is part of) is used.}
+}
+\description{
+Reorder the fields in DESCRIPTION in a standard way.
+}
+\seealso{
+Other repair functions: \code{\link{desc_normalize}},
+ \code{\link{desc_reformat_fields}}
+}
diff --git a/man/desc_set.Rd b/man/desc_set.Rd
new file mode 100644
index 0000000..71c99b7
--- /dev/null
+++ b/man/desc_set.Rd
@@ -0,0 +1,34 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/non-oo-api.R
+\name{desc_set}
+\alias{desc_set}
+\title{Set one or more fields in a DESCRIPTION file}
+\usage{
+desc_set(..., file = ".", normalize = FALSE)
+}
+\arguments{
+\item{...}{Values to set, see details below.}
+
+\item{file}{DESCRIPTION file to use. By default the DESCRIPTION
+file of the current package (i.e. the package the working directory
+is part of) is used.}
+
+\item{normalize}{Whether to normalize the write when writing back
+the result. See \code{\link{desc_normalize}}.}
+}
+\description{
+Set one or more fields in a DESCRIPTION file
+}
+\details{
+\code{desc_set} supports two forms, the first is two unnamed
+arguments: the key and its value to set.
+
+The second form requires named arguments: names are used as keys
+and values as values to set.
+}
+\seealso{
+Other simple queries: \code{\link{desc_del}},
+ \code{\link{desc_fields}},
+ \code{\link{desc_get_or_fail}}, \code{\link{desc_get}},
+ \code{\link{desc_has_fields}}
+}
diff --git a/man/desc_set_authors.Rd b/man/desc_set_authors.Rd
new file mode 100644
index 0000000..27e64dd
--- /dev/null
+++ b/man/desc_set_authors.Rd
@@ -0,0 +1,31 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/non-oo-api.R
+\name{desc_set_authors}
+\alias{desc_set_authors}
+\title{Set authors in Authors at R, in DESCRIPTION}
+\usage{
+desc_set_authors(authors, file = ".", normalize = FALSE)
+}
+\arguments{
+\item{authors}{Authors, to set, a \code{\link[utils]{person}} object.}
+
+\item{file}{DESCRIPTION file to use. By default the DESCRIPTION
+file of the current package (i.e. the package the working directory
+is part of) is used.}
+
+\item{normalize}{Whether to normalize the write when writing back
+the result. See \code{\link{desc_normalize}}.}
+}
+\description{
+Set authors in Authors at R, in DESCRIPTION
+}
+\seealso{
+Other Authors at R: \code{\link{desc_add_author}},
+ \code{\link{desc_add_me}}, \code{\link{desc_add_role}},
+ \code{\link{desc_change_maintainer}},
+ \code{\link{desc_del_author}},
+ \code{\link{desc_del_role}},
+ \code{\link{desc_get_authors}},
+ \code{\link{desc_get_author}},
+ \code{\link{desc_get_maintainer}}
+}
diff --git a/man/desc_set_collate.Rd b/man/desc_set_collate.Rd
new file mode 100644
index 0000000..2640003
--- /dev/null
+++ b/man/desc_set_collate.Rd
@@ -0,0 +1,31 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/non-oo-api.R
+\name{desc_set_collate}
+\alias{desc_set_collate}
+\title{Set the Collate field in DESCRIPTION}
+\usage{
+desc_set_collate(files, which = c("main", "windows", "unix"), file = ".",
+ normalize = FALSE)
+}
+\arguments{
+\item{files}{Collate field to set, as a character vector.}
+
+\item{which}{Which collate field to use. Collate fields can
+be operating system type specific.}
+
+\item{file}{DESCRIPTION file to use. By default the DESCRIPTION
+file of the current package (i.e. the package the working directory
+is part of) is used.}
+
+\item{normalize}{Whether to normalize the write when writing back
+the result. See \code{\link{desc_normalize}}.}
+}
+\description{
+Set the Collate field in DESCRIPTION
+}
+\seealso{
+Other Collate field: \code{\link{desc_add_to_collate}},
+ \code{\link{desc_del_collate}},
+ \code{\link{desc_del_from_collate}},
+ \code{\link{desc_get_collate}}
+}
diff --git a/man/desc_set_dep.Rd b/man/desc_set_dep.Rd
new file mode 100644
index 0000000..6762ac1
--- /dev/null
+++ b/man/desc_set_dep.Rd
@@ -0,0 +1,31 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/non-oo-api.R
+\name{desc_set_dep}
+\alias{desc_set_dep}
+\title{Add a package dependency to a DESCRIPTION file}
+\usage{
+desc_set_dep(package, type = desc::dep_types, version = "*", file = ".",
+ normalize = FALSE)
+}
+\arguments{
+\item{package}{Package to depend on.}
+
+\item{type}{Dependency type.}
+
+\item{version}{Version to depend on, for versioned dependencies.}
+
+\item{file}{DESCRIPTION file to use. By default the DESCRIPTION
+file of the current package (i.e. the package the working directory
+is part of) is used.}
+
+\item{normalize}{Whether to normalize the write when writing back
+the result. See \code{\link{desc_normalize}}.}
+}
+\description{
+Add a package dependency to a DESCRIPTION file
+}
+\seealso{
+Other dependencies: \code{\link{desc_del_deps}},
+ \code{\link{desc_del_dep}}, \code{\link{desc_get_deps}},
+ \code{\link{desc_has_dep}}, \code{\link{desc_set_deps}}
+}
diff --git a/man/desc_set_deps.Rd b/man/desc_set_deps.Rd
new file mode 100644
index 0000000..9d7a884
--- /dev/null
+++ b/man/desc_set_deps.Rd
@@ -0,0 +1,27 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/non-oo-api.R
+\name{desc_set_deps}
+\alias{desc_set_deps}
+\title{Set all package dependencies in DESCRIPTION}
+\usage{
+desc_set_deps(deps, file = ".", normalize = FALSE)
+}
+\arguments{
+\item{deps}{Package dependency data frame, in the same format
+returned by \code{\link{desc_get_deps}}.}
+
+\item{file}{DESCRIPTION file to use. By default the DESCRIPTION
+file of the current package (i.e. the package the working directory
+is part of) is used.}
+
+\item{normalize}{Whether to normalize the write when writing back
+the result. See \code{\link{desc_normalize}}.}
+}
+\description{
+Set all package dependencies in DESCRIPTION
+}
+\seealso{
+Other dependencies: \code{\link{desc_del_deps}},
+ \code{\link{desc_del_dep}}, \code{\link{desc_get_deps}},
+ \code{\link{desc_has_dep}}, \code{\link{desc_set_dep}}
+}
diff --git a/man/desc_set_remotes.Rd b/man/desc_set_remotes.Rd
new file mode 100644
index 0000000..4825748
--- /dev/null
+++ b/man/desc_set_remotes.Rd
@@ -0,0 +1,22 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/non-oo-api.R
+\name{desc_set_remotes}
+\alias{desc_set_remotes}
+\title{Set the Remotes field in DESCRIPTION}
+\usage{
+desc_set_remotes(remotes, file = ".", normalize = FALSE)
+}
+\arguments{
+\item{remotes}{A character vector of remote locations to set.}
+
+\item{file}{DESCRIPTION file to use. By default the DESCRIPTION
+file of the current package (i.e. the package the working directory
+is part of) is used.}
+
+\item{normalize}{Whether to normalize the write when writing back
+the result. See \code{\link{desc_normalize}}.}
+}
+\description{
+The specified locations replace the current ones. The Remotes field is
+created if it does not exist currently.
+}
diff --git a/man/desc_set_urls.Rd b/man/desc_set_urls.Rd
new file mode 100644
index 0000000..df4f94c
--- /dev/null
+++ b/man/desc_set_urls.Rd
@@ -0,0 +1,22 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/non-oo-api.R
+\name{desc_set_urls}
+\alias{desc_set_urls}
+\title{Set the URL field in DESCRIPTION}
+\usage{
+desc_set_urls(urls, file = ".", normalize = FALSE)
+}
+\arguments{
+\item{urls}{A character vector of urls to set.}
+
+\item{file}{DESCRIPTION file to use. By default the DESCRIPTION
+file of the current package (i.e. the package the working directory
+is part of) is used.}
+
+\item{normalize}{Whether to normalize the write when writing back
+the result. See \code{\link{desc_normalize}}.}
+}
+\description{
+The specified urls replace the current ones. The URL field is created
+if it does not exist currently.
+}
diff --git a/man/desc_set_version.Rd b/man/desc_set_version.Rd
new file mode 100644
index 0000000..4d2fbd9
--- /dev/null
+++ b/man/desc_set_version.Rd
@@ -0,0 +1,27 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/non-oo-api.R
+\name{desc_set_version}
+\alias{desc_set_version}
+\title{Set the package version in DESCRIPTION}
+\usage{
+desc_set_version(version, file = ".", normalize = FALSE)
+}
+\arguments{
+\item{version}{A string or a \code{\link[base]{package_version}}
+object.}
+
+\item{file}{DESCRIPTION file to use. By default the DESCRIPTION
+file of the current package (i.e. the package the working directory
+is part of) is used.}
+
+\item{normalize}{Whether to normalize the write when writing back
+the result. See \code{\link{desc_normalize}}.}
+}
+\description{
+Both \code{$set_version()} and \code{$bump_version()} use dots to
+separate the version number components.
+}
+\seealso{
+Other version numbers: \code{\link{desc_bump_version}},
+ \code{\link{desc_get_version}}
+}
diff --git a/man/desc_to_latex.Rd b/man/desc_to_latex.Rd
new file mode 100644
index 0000000..6f98101
--- /dev/null
+++ b/man/desc_to_latex.Rd
@@ -0,0 +1,16 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/non-oo-api.R
+\name{desc_to_latex}
+\alias{desc_to_latex}
+\title{Converts a DESCRIPTION file to LaTeX}
+\usage{
+desc_to_latex(file = ".")
+}
+\arguments{
+\item{file}{DESCRIPTION file to use. By default the DESCRIPTION
+file of the current package (i.e. the package the working directory
+is part of) is used.}
+}
+\description{
+Returns the contents of the DESCRIPTION in LaTeX format.
+}
diff --git a/man/desc_validate.Rd b/man/desc_validate.Rd
new file mode 100644
index 0000000..8dfc5e7
--- /dev/null
+++ b/man/desc_validate.Rd
@@ -0,0 +1,16 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/non-oo-api.R
+\name{desc_validate}
+\alias{desc_validate}
+\title{Validate a DESCRIPTION file}
+\usage{
+desc_validate(file = ".")
+}
+\arguments{
+\item{file}{DESCRIPTION file to use. By default the DESCRIPTION
+file of the current package (i.e. the package the working directory
+is part of) is used.}
+}
+\description{
+This function is not implemented yet.
+}
diff --git a/man/description.Rd b/man/description.Rd
new file mode 100644
index 0000000..bd5b545
--- /dev/null
+++ b/man/description.Rd
@@ -0,0 +1,413 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/description.R
+\docType{class}
+\name{description}
+\alias{description}
+\title{Read, write, update, validate DESCRIPTION files}
+\format{An R6 class.}
+\usage{
+description
+}
+\description{
+Read, write, update, validate DESCRIPTION files
+}
+\section{Constructors}{
+
+
+There are two ways of creating a description object. The first
+is reading an already existing \code{DESCRIPTION} file; simply give
+the name of the file as an argument. The default is
+\code{DESCRIPTION}: \preformatted{ x <- description$new()
+ x2 <- description$new("path/to/DESCRIPTION")}
+
+The second way is creating a description object from scratch,
+supply \code{"!new"} as an argument to do this.
+\preformatted{ x3 <- description$new("!new")}
+
+The complete API reference:
+\preformatted{description$new(cmd = NULL, file = NULL, text = NULL,
+ package = NULL)}
+\describe{
+ \item{cmd:}{A command to create a description from scratch.
+ Currently only \code{"!new"} is implemented. If it does not start
+ with an exclamation mark, it will be interpreted as a \sQuote{file}
+ argument.}
+ \item{file:}{Name of the \code{DESCRIPTION} file to load. If it is
+ a directory, then we assume that it is inside an R package and
+ conduct a search for the package root directory, i.e. the first
+ directory up the tree that contains a \code{DESCRIPTION} file.
+ If \sQuote{cmd}, \sQuote{file}, \sQuote{text} and \sQuote{package}
+ are all \code{NULL} (the default), then the search is started from
+ the working directory. The file can also be an R package (source, or
+ binary), in which case the DESCRIPTION file is extracted from it,
+ but note that in this case \code{$write()} cannot write the file
+ back in the package archive.}
+ \item{text:}{A character scalar containing the full DESCRIPTION.
+ Character vectors are collapsed into a character scalar, with
+ newline as the separator.}
+ \item{package}{If not NULL, then the name of an installed package
+ and the DESCRIPTION file of this package will be loaded.}
+}
+}
+
+\section{Setting and Querying fields}{
+
+Set a field with \code{$set} and query it with \code{$get}:
+\preformatted{ x <- description$new("!new")
+ x$get("Package)
+ x$set("Package", "foobar")
+ x$set(Title = "Example Package for 'description'")
+ x$get("Package")}
+Note that \code{$set} has two forms. You can either give the field name
+and new value as two arguments; or you can use a single named argument,
+the argument name is the field name, the argument value is the field
+value.
+
+The \code{$fields} method simply lists the fields in the object:
+\preformatted{ x$fields()}
+
+The \code{$has_fields} method checks if one or multiple fields are
+present in a description object: \preformatted{ x$has_fields("Package")
+ x$has_fields(c("Title", "foobar"))}
+
+The \code{$del} method removes the specified fields:
+\preformatted{ x$set(foo = "bar")
+ x$del("foo")}
+
+\code{$get_or_fail} is similar to \code{$get}, but throws an error
+if a field does not exist, except of silently returning
+\code{NA_character}.
+
+The complete API reference:
+\preformatted{ description$get(keys)
+ description$get_or_fail(keys)
+ description$set(...)
+ description$fields()
+ description$has_fields(keys)
+ description$del(keys)}
+\describe{
+ \item{keys:}{A character vector of keys to query, check or delete.}
+ \item{...:}{This must be either two unnamed arguments, the key and
+ and the value to set; or an arbitrary number of named arguments,
+ names are used as keys, values as values to set.}
+}
+}
+
+\section{Normalizing}{
+
+Format DESCRIPTION in a standard way. \code{$str} formats each
+field in a standard way and returns them (it does not change the
+object itself), \code{$print} is used to print it to the
+screen. The \code{$normalize} function normalizes each field (i.e.
+it changes the object). Normalization means reformatting the fields,
+via \code{$reformat_fields()} and also reordering them via
+\code{$reorder_fields()}. The format of the various fields is
+opinionated and you might like it or not. Note that \code{desc} only
+reformats fields that it updates, and only on demand, so if your
+formatting preferences differ, you can still manually edit
+\code{DESCRIPTION} and \code{desc} will respect your edits.
+
+\preformatted{ description$str(by_field = FALSE, normalize = TRUE,
+ mode = c("file", "screen"))
+ description$normalize()
+ description$reformat_fields()
+ description$reorder_fields()
+ description$print()
+}
+\describe{
+ \item{by_field:}{Whether to return the normalized format
+ by field, or collapsed into a character scalar.}
+ \item{normalize:}{Whether to reorder and reformat the fields.}
+ \item{mode:}{\sQuote{file} mode formats the fields as they are
+ written to a file with the \code{write} method. \sQuote{screen}
+ mode adds extra markup to some fields, e.g. formats the
+ \code{Authors at R} field in a readable way.}
+}
+}
+
+\section{Writing it to file}{
+
+The \code{$write} method writes the description to a file.
+By default it writes it to the file it was created from, if it was
+created from a file. Otherwise giving a file name is compulsary:
+\preformatted{ x$write(file = "DESCRIPTION")}
+
+The \code{normalize} argument controls whether the fields are
+reformatted according to a standard style. By default they are not.
+
+The API:
+\preformatted{ description$write(file = NULL, normalize = NULL)}
+\describe{
+ \item{file:}{Path to write the description to. If it was created
+ from a file in the first place, then it is written to the same
+ file. Otherwise this argument must be specified.}
+ \item{normalize:}{Whether to reformat the fields in a standard way.}
+}
+}
+
+\section{Version numbers}{
+
+
+\preformatted{ description$get_version()
+ description$set_version(version)
+ description$bump_version(which = c("patch", "minor", "major", "dev"))
+}
+
+\describe{
+ \item{version:}{A string or a \code{\link[base]{package_version}}
+ object.}
+ \item{which:}{Which component of the version number to increase.
+ See details just below.}
+}
+
+These functions are simple helpers to make it easier to query, set and
+increase the version number of a package.
+
+\code{$get_version()} returns the version number as a
+\code{\link[base]{package_version}} object. It throws an error if the
+package does not have a \sQuote{Version} field.
+
+\code{$set_version()} takes a string or a
+\code{\link[base]{package_version}} object and sets the \sQuote{Version}
+field to it.
+
+\code{$bump_version()} increases the version number. The \code{which}
+parameter specifies which component to increase.
+It can be a string referring to a component: \sQuote{major},
+\sQuote{minor}, \sQuote{patch} or \sQuote{dev}, or an integer
+scalar, for the latter components are counted from one, and the
+beginning. I.e. component one is equivalent to \sQuote{major}.
+
+If a component is bumped, then the ones after it are zeroed out.
+Trailing zero components are omitted from the new version number,
+but if the old version number had at least two or three components, then
+the one will also have two or three.
+
+The bumping of the \sQuote{dev} version (the fourth component) is
+special: if the original version number had less than four components,
+and the \sQuote{dev} version is bumped, then it is set to \code{9000}
+instead of \code{1}. This is a convention often used by R developers,
+it was originally invented by Winston Chang.
+
+Both \code{$set_version()} and \code{$bump_version()} use dots to
+separate the version number components.
+}
+
+\section{Dependencies}{
+
+These functions handle the fields that define how the R package
+uses another R packages. See \code{\link{dep_types}} for the
+list of fields in this group.
+
+The \code{$get_deps} method returns all declared dependencies, in a
+data frame with columns: \code{type}, \code{package} and \code{version}.
+\code{type} is the name of the dependency field, \code{package} is the
+name of the R package, and \code{version} is the required version. If
+no specific versions are required, then this is a \code{"\*"}.
+
+The \code{$set_deps} method is the opposite of \code{$get_deps} and
+it sets all dependencies. The input is a data frame, with the same
+structure as the return value of \code{$get_deps}.
+
+The \code{$has_dep} method checks if a package is included in the
+dependencies. It returns a logical scalar. If \code{type} is not
+\sQuote{any}, then it has to match as well.
+
+The \code{$del_deps} method removes all declared dependencies.
+
+The \code{$set_dep} method adds or updates a single dependency. By
+default it adds the package to the \code{Imports} field.
+
+The API:
+\preformatted{ description$set_dep(package, type = dep_types, version = "\*")
+ description$set_deps(deps)
+ description$get_deps()
+ description$has_dep(package, type = c("any", dep_types))
+ description$del_dep(package, type = c("all", dep_types))
+ description$del_deps()
+}
+\describe{
+ \item{package:}{Name of the package to add to or remove from the
+ dependencies.}
+ \item{type:}{Dependency type, see \code{\link{dep_types}}. For
+ \code{$del_dep} it may also be \code{"all"}, and then the package
+ will be deleted from all dependency types.}
+ \item{version:}{Required version. Defaults to \code{"\*"}, which means
+ no explicit version requirements.}
+ \item{deps:}{A data frame with columns \code{type}, \code{package} and
+ \code{version}. \code{$get_deps} returns the same format.}
+}
+}
+
+\section{Collate fields}{
+
+Collate fields contain lists of file names with R source code,
+and the package has a separate API for them. In brief, you can
+use \code{$add_to_collate} to add one or more files to the main or
+other collate field. You can use \code{$del_from_collate} to remove
+it from there.
+
+The API:
+\preformatted{ description$set_collate(files, which = c("main", "windows", "unix"))
+ description$get_collate(which = c("main", "windows", "unix"))
+ description$del_collate(which = c("all", "main", "windows", "unix"))
+ description$add_to_collate(files, which = c("default", "all", "main",
+ "windows", "unix"))
+ description$del_from_collate(files, which = c("all", "main",
+ "windows", "unix"))
+}
+\describe{
+ \item{files:}{The files to add or remove, in a character vector.}
+ \item{which:}{Which collate field to manipulate. \code{"default"} for
+ \code{$add_to_collate} means all existing collate fields, or the
+ main one if none exist.}
+}
+}
+
+\section{Authors}{
+
+There is a specialized API for the \code{Authors at R} field,
+to add and remove authors, udpate their roles, change the maintainer,
+etc.
+
+The API:
+\preformatted{ description$get_authors()
+ description$set_authors(authors)
+ description$get_author(role)
+ description$get_maintainer()
+}
+\describe{
+ \item{authors:}{A \code{person} object, a list of authors.}
+ \item{role:}{The role to query. See \code{person} for details.}
+}
+\code{$get_authors} returns a \code{person} object, the parsed
+authors. See \code{\link[utils]{person}} for details.
+
+\code{$get_author} returns a \code{person} object, all authors with
+the specified role.
+
+\code{$get_maintainer} returns the maintainer of the package. It works
+with \code{Authors at R} fields and with traditional \code{Maintainer}
+fields as well.
+
+\preformatted{ description$add_author(given = NULL, family = NULL, email = NULL,
+ role = NULL, comment = NULL)
+ description$add_me(role = "ctb", comment = NULL)
+}
+Add a new author. The arguments correspond to the arguments of the
+\code{\link[utils]{person}} function. \code{add_me} is a convenience
+function, it adds the current user as an author, and it needs the
+\code{whoami} package to be installed.
+
+\preformatted{ description$del_author(given = NULL, family = NULL, email = NULL,
+ role = NULL, comment = NULL)
+}
+Remove an author, or multiple authors. The author(s) to be removed
+can be specified via any field(s). All authors matching all
+specifications will be removed. E.g. if only \code{given = "Joe"}
+is supplied, then all authors whole given name matches \code{Joe} will
+be removed. The specifications can be (PCRE) regular expressions.
+
+\preformatted{ description$add_role(role, given = NULL, family = NULL, email = NULL,
+ comment = NULL)
+ description$del_role(role, given = NULL, family = NULL, email = NULL,
+ comment = NULL)
+ description$change_maintainer(given = NULL, family = NULL,
+ email = NULL, comment = NULL)
+}
+\code{role} is the role to add or delete. The other arguments
+are used to select a subset of the authors, on which the operation
+is performed, similarly to \code{$del_author}.
+}
+
+\section{URLs}{
+
+
+We provide helper functions for manipulating URLs in the \code{URL}
+field:
+
+\preformatted{ description$get_urls()
+ description$set_urls(urls)
+ description$add_urls(urls)
+ description$del_urls(pattern)
+ description$clear_urls()
+}
+\describe{
+ \item{urls:}{Character vector of URLs to set or add.}
+ \item{pattern:}{Perl compatible regular expression to specify the
+ URLs to be removed.}
+}
+\code{$get_urls()} returns all urls in a character vector. If no URL
+fields are present, a zero length vector is returned.
+
+\code{$set_urls()} sets the URL field to the URLs specified in the
+character vector argument.
+
+\code{$add_urls()} appends the specified URLs to the URL field. It
+creates the field if it does not exists. Duplicate URLs are removed.
+
+\code{$del_urls()} deletes the URLs that match the specified pattern.
+
+\code{$clear_urls()} deletes all URLs.
+}
+
+\section{Remotes}{
+
+
+\code{devtools}, \code{remotes} and some other packages support the
+non-standard \code{Remotes} field in \code{DESCRIPTION}. This field
+can be used to specify locations of dependent packages: GitHub or
+BitBucket repositories, generic git repositories, etc. Please see the
+\sQuote{Package remotes} vignette in the \code{devtools} package.
+
+\code{desc} has helper functions for manipulating the \code{Remotes}
+field:
+
+\preformatted{ description$get_remotes()
+ description$get_remotes()
+ description$set_remotes(remotes)
+ description$add_remotes(remotes)
+ description$del_remotes(pattern)
+ description$clear_remotes()
+}
+\describe{
+ \item{remotes:}{Character vector of remote dependency locations to
+ set or add.}
+ \item{pattern:}{Perl compatible regular expression to specify the
+ remote dependency locations to remove.}
+}
+\code{$get_remotes()} returns all remotes in a character vector.
+If no URL fields are present, a zero length vector is returned.
+
+\code{$set_remotes()} sets the URL field to the Remotes specified in the
+character vector argument.
+
+\code{$add_remotes()} appends the specified remotes to the
+\code{Remotes} field. It creates the field if it does not exists.
+Duplicate remotes are removed.
+
+\code{$del_remotes()} deletes the remotes that match the specified
+pattern.
+
+\code{$clear_remotes()} deletes all remotes.
+}
+
+\examples{
+## Create a template
+desc <- description$new("!new")
+desc
+
+## Read a file
+desc2 <- description$new(file = system.file("DESCRIPTION",
+ package = "desc"))
+desc2
+
+## Remove a field
+desc2$del("LazyData")
+
+## Add another one
+desc2$set(VignetteBuilder = "knitr")
+desc2$get("VignetteBuilder")
+desc2
+}
+\keyword{datasets}
diff --git a/tests/testthat.R b/tests/testthat.R
new file mode 100644
index 0000000..6cc4693
--- /dev/null
+++ b/tests/testthat.R
@@ -0,0 +1,4 @@
+library(testthat)
+library(desc)
+
+test_check("desc")
diff --git a/tests/testthat/D1 b/tests/testthat/D1
new file mode 100644
index 0000000..9fd64c4
--- /dev/null
+++ b/tests/testthat/D1
@@ -0,0 +1,17 @@
+Package: desc
+Title: Manipulate DESCRIPTION Files
+Version: 1.0.0
+Author: Gábor Csárdi
+Maintainer: Gábor Csárdi <csardi.gabor at gmail.com>
+Description: Tools to read, write, create, and manipulate DESCRIPTION
+ files. It is intented for packages that create or manipulate other
+ packages.
+License: MIT + file LICENSE
+URL: https://github.com/r-pkgs/desc
+BugReports: https://github.com/r-pkgs/desc/issues
+Imports:
+ R6
+Suggests:
+ testthat
+VignetteBuilder: knitr
+Encoding: UTF-8
diff --git a/tests/testthat/D2 b/tests/testthat/D2
new file mode 100644
index 0000000..c827af5
--- /dev/null
+++ b/tests/testthat/D2
@@ -0,0 +1,66 @@
+Package: roxygen2
+Title: In-Source Documentation for R
+Description: A 'Doxygen'-like in-source documentation system
+ for Rd, collation, and 'NAMESPACE' files.
+URL: https://github.com/klutometis/roxygen
+Version: 4.1.1.9000
+License: GPL (>= 2)
+Authors at R: c(
+ person("Hadley", "Wickham",, "h.wickham at gmail.com", c("aut", "cre", "cph")),
+ person("Peter", "Danenberg",, "pcd at roxygen.org", c("aut", "cph")),
+ person("Manuel", "Eugster", role = c("aut", "cph")),
+ person("RStudio", role = "cph")
+ )
+Depends:
+ R (>= 3.0.2)
+Imports:
+ stringr (>= 0.5),
+ brew,
+ digest,
+ methods,
+ Rcpp (>= 0.11.0)
+Suggests:
+ testthat (>= 0.8.0),
+ knitr
+VignetteBuilder: knitr
+LinkingTo: Rcpp
+Collate:
+ 'RcppExports.R'
+ 'alias.R'
+ 'description.R'
+ 'family.R'
+ 'inherit-params.R'
+ 'minidesc.R'
+ 'object-defaults.R'
+ 'object-from-call.R'
+ 'object.R'
+ 'order-params.R'
+ 'parse-preref.R'
+ 'parse-registry.R'
+ 'parse.R'
+ 'rc.R'
+ 'rd-escape.R'
+ 'rd-file-api.R'
+ 'rd-parse.R'
+ 'rd-tag-api.R'
+ 'roclet-collate.R'
+ 'roclet-namespace.R'
+ 'roclet-rd.R'
+ 'roclet-vignette.R'
+ 'roclet.R'
+ 'roxygen.R'
+ 'roxygenize.R'
+ 's3.R'
+ 'safety.R'
+ 'source.R'
+ 'template.R'
+ 'topic-name.R'
+ 'topo-sort.R'
+ 'usage.R'
+ 'util-locale.R'
+ 'utils.R'
+RoxygenNote: 4.1.1.9000
+Encoding: UTF-8
+Remotes: foo/digest, svn::https://github.com/hadley/stringr,
+ local::/pkgs/testthat
+
diff --git a/tests/testthat/D3 b/tests/testthat/D3
new file mode 100644
index 0000000..50f8f30
--- /dev/null
+++ b/tests/testthat/D3
@@ -0,0 +1,63 @@
+Package: roxygen2
+Title: In-Source Documentation for R
+Description: A 'Doxygen'-like in-source documentation system
+ for Rd, collation, and 'NAMESPACE' files.
+URL: https://github.com/klutometis/roxygen
+Version: 4.1.1.9000
+License: GPL (>= 2)
+Authors at R: c(
+ person("Hadley", "Wickham",, "h.wickham at gmail.com", c("aut", "cre", "cph")),
+ person("Peter", "Danenberg",, "pcd at roxygen.org", c("aut", "cph")),
+ person("Manuel", "Eugster", role = c("aut", "cph")),
+ person("RStudio", role = "cph")
+ )
+Depends:
+ R (>= 3.0.2)
+Imports:
+ stringr (>= 0.5),
+ brew,
+ digest,
+ methods,
+ Rcpp (>= 0.11.0)
+Suggests:
+ testthat (>= 0.8.0),
+ knitr
+VignetteBuilder: knitr
+LinkingTo: Rcpp
+Collate:
+ 'RcppExports.R'
+ 'alias.R'
+ 'description.R'
+ 'family.R'
+ 'inherit-params.R'
+ 'minidesc.R'
+ 'object-defaults.R'
+ 'object-from-call.R'
+ 'object.R'
+ 'order-params.R'
+ 'parse-preref.R'
+ 'parse-registry.R'
+ 'parse.R'
+ 'rc.R'
+ 'rd-escape.R'
+ 'rd-file-api.R'
+ 'rd-parse.R'
+ 'rd-tag-api.R'
+ 'roclet-collate.R'
+ 'roclet-namespace.R'
+ 'roclet-rd.R'
+ 'roclet-vignette.R'
+ 'roclet.R'
+ 'roxygen.R'
+ 'roxygenize.R'
+ 's3.R'
+ 'safety.R'
+ 'source.R'
+ 'template.R'
+ 'topic-name.R'
+ 'topo-sort.R'
+ 'usage.R'
+ 'util-locale.R'
+ 'utils.R'
+RoxygenNote: 4.1.1.9000
+Encoding: UTF-8
diff --git a/tests/testthat/D4 b/tests/testthat/D4
new file mode 100644
index 0000000..4cee3da
--- /dev/null
+++ b/tests/testthat/D4
@@ -0,0 +1,11 @@
+Package: desc
+Title: Manipulate DESCRIPTION Files
+Version: 1.0.0
+Author: Gábor Csárdi
+Maintainer: Gábor Csárdi <csardi.gabor at gmail.com>
+Description: Tools to read, write, create, and manipulate DESCRIPTION
+ files. It is intented for packages that create or manipulate other
+ packages.
+License: MIT + file LICENSE
+Depends:
+Encoding: UTF-8
diff --git a/tests/testthat/D5 b/tests/testthat/D5
new file mode 100644
index 0000000..64a205d
--- /dev/null
+++ b/tests/testthat/D5
@@ -0,0 +1,17 @@
+Package: desc
+Title: Manipulate DESCRIPTION Files
+Version: 1.0.0
+Author: Gábor Csárdi
+Maintainer: Gábor Csárdi <csardi.gabor at gmail.com>
+Description: Tools to read, write, create, and manipulate DESCRIPTION
+ files. It is intented for packages that create or manipulate other
+ packages.
+License: MIT + file LICENSE
+Imports:
+ httrmock,
+ lintr
+Remotes:
+ jimhester/lintr
+Encoding: UTF-8
+Remotes:
+ gaborcsardi/httrmock
diff --git a/tests/testthat/files/DESCRIPTION b/tests/testthat/files/DESCRIPTION
new file mode 100644
index 0000000..9fd64c4
--- /dev/null
+++ b/tests/testthat/files/DESCRIPTION
@@ -0,0 +1,17 @@
+Package: desc
+Title: Manipulate DESCRIPTION Files
+Version: 1.0.0
+Author: Gábor Csárdi
+Maintainer: Gábor Csárdi <csardi.gabor at gmail.com>
+Description: Tools to read, write, create, and manipulate DESCRIPTION
+ files. It is intented for packages that create or manipulate other
+ packages.
+License: MIT + file LICENSE
+URL: https://github.com/r-pkgs/desc
+BugReports: https://github.com/r-pkgs/desc/issues
+Imports:
+ R6
+Suggests:
+ testthat
+VignetteBuilder: knitr
+Encoding: UTF-8
diff --git a/tests/testthat/fixtures/notpkg_1.0.tar.gz b/tests/testthat/fixtures/notpkg_1.0.tar.gz
new file mode 100644
index 0000000..e3b6373
Binary files /dev/null and b/tests/testthat/fixtures/notpkg_1.0.tar.gz differ
diff --git a/tests/testthat/fixtures/pkg_1.0.0.tar.gz b/tests/testthat/fixtures/pkg_1.0.0.tar.gz
new file mode 100644
index 0000000..e31f475
Binary files /dev/null and b/tests/testthat/fixtures/pkg_1.0.0.tar.gz differ
diff --git a/tests/testthat/fixtures/pkg_1.0.0.tgz b/tests/testthat/fixtures/pkg_1.0.0.tgz
new file mode 100644
index 0000000..a82fed0
Binary files /dev/null and b/tests/testthat/fixtures/pkg_1.0.0.tgz differ
diff --git a/tests/testthat/fixtures/pkg_1.0.0_R_x86_64-pc-linux-gnu.tar.gz b/tests/testthat/fixtures/pkg_1.0.0_R_x86_64-pc-linux-gnu.tar.gz
new file mode 100644
index 0000000..7e96bd6
Binary files /dev/null and b/tests/testthat/fixtures/pkg_1.0.0_R_x86_64-pc-linux-gnu.tar.gz differ
diff --git a/tests/testthat/fixtures/xxx.gz b/tests/testthat/fixtures/xxx.gz
new file mode 100644
index 0000000..b374e82
Binary files /dev/null and b/tests/testthat/fixtures/xxx.gz differ
diff --git a/tests/testthat/fixtures/xxx.tar.gz b/tests/testthat/fixtures/xxx.tar.gz
new file mode 100644
index 0000000..e3b6373
Binary files /dev/null and b/tests/testthat/fixtures/xxx.tar.gz differ
diff --git a/tests/testthat/fixtures/xxx.zip b/tests/testthat/fixtures/xxx.zip
new file mode 100644
index 0000000..c32822a
Binary files /dev/null and b/tests/testthat/fixtures/xxx.zip differ
diff --git a/tests/testthat/helper.R b/tests/testthat/helper.R
new file mode 100644
index 0000000..8307721
--- /dev/null
+++ b/tests/testthat/helper.R
@@ -0,0 +1,6 @@
+
+temp_desc <- function(file = "D2") {
+ tmp <- tempfile()
+ file.copy(file, tmp)
+ tmp
+}
diff --git a/tests/testthat/output/to_latex.tex b/tests/testthat/output/to_latex.tex
new file mode 100644
index 0000000..b0fb9ce
--- /dev/null
+++ b/tests/testthat/output/to_latex.tex
@@ -0,0 +1,17 @@
+\begin{description}
+ \raggedright{}
+ \item[Package] Test.Package
+ \item[Title] Must be in Title Case
+ \item[Version] 1.0.0
+ \item[Authors at R] ~\\
+ \begin{description}
+ \item[cre] Kirill Müller <\href{mailto:aaa at bbb.xx}{aaa at bbb.xx}>
+ \end{description}
+ \item[Maintainer] \{\{ Maintainer \}\}
+ \item[Description] Words not in the dictionary must be `quoted'. ``Double quotes'' can also be used, as well as \textbackslash \_\{\}[]\^\$\#\%. Must end with a full stop.
+ \item[License] CC-BY-SA
+ \item[URL] \url{http://somewhere.io}, \url{http://somewhereel.se}
+ \item[BugReports] \url{http://somewhere.io/trac}
+ \item[Encoding] UTF-8
+ \item[LazyData] true
+\end{description}
diff --git a/tests/testthat/test-archives.R b/tests/testthat/test-archives.R
new file mode 100644
index 0000000..44983c1
--- /dev/null
+++ b/tests/testthat/test-archives.R
@@ -0,0 +1,77 @@
+
+context("Package archives")
+
+test_that("is_zip_file", {
+ expect_true(is_zip_file(file.path("fixtures", "xxx.zip")))
+ expect_false(is_zip_file(file.path("fixtures", "xxx.gz")))
+ expect_false(is_zip_file(file.path("fixtures", "xxx.tar.gz")))
+})
+
+test_that("is_gz_file", {
+ expect_false(is_gz_file(file.path("fixtures", "xxx.zip")))
+ expect_true(is_gz_file(file.path("fixtures", "xxx.gz")))
+ expect_true(is_gz_file(file.path("fixtures", "xxx.tar.gz")))
+})
+
+test_that("is_tar_gz_file", {
+ expect_false(is_tar_gz_file(file.path("fixtures", "xxx.zip")))
+ expect_false(is_tar_gz_file(file.path("fixtures", "xxx.gz")))
+ expect_true(is_tar_gz_file(file.path("fixtures", "xxx.tar.gz")))
+})
+
+test_that("is_valid_package_file_name", {
+ pos <- c(
+ "foo_1.0.tar.gz",
+ "a1_0.2.tar.gz",
+ "A0_1.2-4.tar.gz",
+ "foo_1.0.tgz",
+ "a1_0.2.tgz",
+ "A0_1.2-4.tgz",
+ "foo_1.0.zip",
+ "a1_0.2.zip",
+ "A0_1.2-4.zip",
+ "R6_2.2.0_R_x86_64-pc-linux-gnu.tar.gz"
+ )
+
+ neg <- c(
+ "1foo_1.0.tar.gz", # cannot start with number
+ "x_1.5.tar.gz", # must be at least two characters
+ "xx-1.5.tar.gz", # dash separator is invalid
+ "foo_1.tar.gz", # version number must have 2 comps
+ "foo_1.0.tar.gzx", # invalid file extension
+ "foo_1.0.zipfile" # invalid file extension
+ )
+
+ for (x in pos) expect_true(is_valid_package_file_name(x), info = x)
+ for (x in neg) expect_false(is_valid_package_file_name(x), info = x)
+})
+
+test_that("is_package_archive", {
+ pos <- file.path("fixtures", c(
+ "pkg_1.0.0.tar.gz",
+ "pkg_1.0.0.tgz",
+ "pkg_1.0.0_R_x86_64-pc-linux-gnu.tar.gz"
+ ))
+ neg <- file.path("fixtures", c("xxx.zip", "xxx.gz", "xxx.tar.gz"))
+
+ for (x in pos) expect_true(is_package_archive(x), info = x)
+ for (x in neg) expect_false(is_package_archive(x), info = x)
+})
+
+test_that("get_description_from_package", {
+ d1 <- description$new(file.path("fixtures", "pkg_1.0.0.tar.gz"))
+ d2 <- description$new(file.path("fixtures", "pkg_1.0.0.tgz"))
+ d3 <- description$new(file.path(
+ "fixtures",
+ "pkg_1.0.0_R_x86_64-pc-linux-gnu.tar.gz"
+ ))
+
+ expect_equal(d1$get("Package"), c(Package = "pkg"))
+ expect_equal(d2$get("Package"), c(Package = "pkg"))
+ expect_equal(d3$get("Package"), c(Package = "pkg"))
+
+ expect_error(
+ d4 <- description$new(file.path("fixtures", "notpkg_1.0.tar.gz")),
+ "Cannot extract DESCRIPTION"
+ )
+})
diff --git a/tests/testthat/test-authors.R b/tests/testthat/test-authors.R
new file mode 100644
index 0000000..57b3c1d
--- /dev/null
+++ b/tests/testthat/test-authors.R
@@ -0,0 +1,211 @@
+
+context("Authors")
+
+test_that("we can get the authors", {
+ desc <- description$new("D2")
+
+ ans <- c(
+ person("Hadley", "Wickham", email = "h.wickham at gmail.com",
+ role = c("aut", "cre", "cph")),
+ person("Peter", "Danenberg", email = "pcd at roxygen.org",
+ role = c("aut", "cph")),
+ person("Manuel", "Eugster", role = c("aut", "cph")),
+ person("RStudio", role = "cph")
+ )
+
+ expect_identical(desc$get_authors(), ans)
+})
+
+test_that("we can set the authors", {
+
+ desc1 <- description$new("D1")
+ desc2 <- description$new("D2")
+
+ desc1$set_authors(desc2$get_authors())
+
+ expect_identical(desc1$get_authors(), desc2$get_authors())
+})
+
+test_that("we can add an author", {
+ desc <- description$new("D2")
+
+ desc$add_author("Gábor", "Csárdi", email = "csardi.gabor at gmail.com",
+ role = "ctb", comment = "Really?")
+
+ expect_identical(
+ format(desc$get_authors()[5]),
+ "Gábor Csárdi <csardi.gabor at gmail.com> [ctb] (Really?)"
+ )
+})
+
+test_that("we can search for authors", {
+ desc <- description$new("D2")
+ authors <- desc$get_authors()
+
+ expect_equal(
+ search_for_author(authors, given = "Hadley")$index,
+ 1L
+ )
+
+})
+
+test_that("we can add a role to an author", {
+ desc <- description$new("D2")
+
+ desc$add_author("Gábor", "Csárdi", email = "csardi.gabor at gmail.com",
+ role = "ctb", comment = "Really?")
+ desc$add_role(given = "Gábor", role = "cph")
+
+ expect_identical(
+ format(desc$get_authors()[5]),
+ "Gábor Csárdi <csardi.gabor at gmail.com> [ctb, cph] (Really?)"
+ )
+})
+
+test_that("we can delete an author", {
+ desc <- description$new("D2")
+
+ desc$del_author(given = "Hadley")
+ desc$del_author(family = "Danenberg")
+
+ ans <- c(
+ person("Manuel", "Eugster", role = c("aut", "cph")),
+ person("RStudio", role = "cph")
+ )
+
+ expect_identical(desc$get_authors(), ans)
+})
+
+test_that("we can delete a role", {
+ desc <- description$new("D2")
+
+ desc$add_author("Gábor", "Csárdi", email = "csardi.gabor at gmail.com",
+ role = "ctb", comment = "Really?")
+ desc$add_role(given = "Gábor", role = "cph")
+ desc$del_role(family = "Csárdi", role = "ctb")
+
+ expect_identical(
+ format(desc$get_authors()[5]),
+ "Gábor Csárdi <csardi.gabor at gmail.com> [cph] (Really?)"
+ )
+})
+
+test_that("we can change the maintainer", {
+ desc <- description$new("D2")
+
+ desc$change_maintainer(given = "Peter")
+
+ ans <- c(
+ person("Hadley", "Wickham", email = "h.wickham at gmail.com",
+ role = c("aut", "cph")),
+ person("Peter", "Danenberg", email = "pcd at roxygen.org",
+ role = c("aut", "cph", "cre")),
+ person("Manuel", "Eugster", role = c("aut", "cph")),
+ person("RStudio", role = "cph")
+ )
+
+ expect_identical(desc$get_authors(), ans)
+
+})
+
+test_that("add_me works", {
+ desc <- description$new("D2")
+ with_mock(
+ `desc:::check_for_package` = function(...) TRUE,
+ `whoami::fullname` = function() "Bugs Bunny",
+ `whoami::email_address` = function() "bugs.bunny at acme.com",
+ desc$add_me(comment = "Yikes!")
+ )
+
+ expect_identical(
+ format(desc$get_authors()[5]),
+ "Bugs Bunny <bugs.bunny at acme.com> [ctb] (Yikes!)"
+ )
+})
+
+test_that("error if not Authors at R field", {
+
+ desc <- description$new("D1")
+ expect_error(
+ desc$get_authors(),
+ "No 'Authors at R' field"
+ )
+})
+
+test_that("message if not author to delete does not exist", {
+
+ desc <- description$new("D2")
+ expect_message(
+ desc$del_author(given = "Gábor"),
+ "Could not find author to remove"
+ )
+})
+
+test_that("get_author is OK", {
+
+ D2 <- description$new("D2")
+
+ expect_equal(
+ D2$get_author(role = "cre"),
+ person(
+ given = "Hadley",
+ family = "Wickham",
+ email = "h.wickham at gmail.com",
+ role = c("aut", "cre", "cph"),
+ comment = NULL
+ )
+ )
+
+ expect_equal(
+ D2$get_author(role = "aut"),
+ D2$get_authors()[1:3]
+ )
+
+ D1 <- description$new("D1")
+ expect_null(D1$get_author(role = "cre"))
+})
+
+test_that("get_maintainer is OK, too", {
+
+ D1 <- description$new("D1")
+ expect_equal(
+ D1$get_maintainer(),
+ "Gábor Csárdi <csardi.gabor at gmail.com>"
+ )
+
+ D2 <- description$new("D2")
+ expect_equal(
+ D2$get_maintainer(),
+ "Hadley Wickham <h.wickham at gmail.com>"
+ )
+
+ D1$del("Maintainer")
+ expect_equal(
+ D1$get_maintainer(),
+ NA_character_
+ )
+})
+
+test_that("add_author if there is no Authors at R field", {
+ D1 <- description$new("D1")
+ D1$add_author("Gabor", "Csardi", "csardi.gabor at gmail.com", role = "ctb")
+ expect_identical(
+ format(D1$get_authors()[1]),
+ "Gabor Csardi <csardi.gabor at gmail.com> [ctb]"
+ )
+})
+
+test_that("add myself if there is no Authors at R field", {
+ D1 <- description$new("D1")
+ with_mock(
+ `desc:::check_for_package` = function(...) TRUE,
+ `whoami::fullname` = function() "Bugs Bunny",
+ `whoami::email_address` = function() "bugs.bunny at acme.com",
+ D1$add_me(comment = "Yikes!")
+ )
+
+ expect_identical(
+ format(D1$get_authors()[1]),
+ "Bugs Bunny <bugs.bunny at acme.com> [ctb] (Yikes!)"
+ )
+})
diff --git a/tests/testthat/test-checks.R b/tests/testthat/test-checks.R
new file mode 100644
index 0000000..4c5905b
--- /dev/null
+++ b/tests/testthat/test-checks.R
@@ -0,0 +1,115 @@
+
+context("Syntax checks")
+
+
+test_that("checking other fields is always ok", {
+
+ desc <- description$new("D1")
+ desc$set("Note", "Blah-blah")
+
+ expect_equal(desc$get("Note"), c(Note = "Blah-blah"))
+})
+
+
+test_that("we catch syntax errors", {
+
+ desc <- description$new("D1")
+
+ expect_warning(
+ desc$set("Package", "444!!! not a valid package name"),
+ "must only contain ASCII letters.*must start with a letter"
+ )
+
+ expect_warning(
+ desc$set("Package", "4aaaa"),
+ "must start with a letter"
+ )
+})
+
+
+test_that("generic field check is always true", {
+ field <- list(key = "XY", value = "foobar")
+ class(field) <- "DescriptionField"
+ expect_silent(check_field(field, warn = TRUE))
+ expect_true(check_field(field))
+})
+
+
+test_chk <- function(field, value, warn_msg, warn = TRUE, class = field) {
+ rec <- list(key = field, value = value)
+ class(rec) <- c(paste0("Description", class), "DescriptionField")
+
+ test_that(paste(field, "syntax"), {
+ if (warn) {
+ expect_warning(check_field(rec, warn = TRUE), warn_msg)
+ } else {
+ expect_silent(check_field(rec, warn = TRUE))
+ }
+ })
+}
+
+test_chk("License", "GPL", warn = FALSE)
+test_chk("License", "", "must not be empty")
+test_chk("License", "almost good but \x80", "only ASCII characters")
+
+test_chk("Title", "This is a Good Title", warn = FALSE)
+test_chk("Title", "", "must not be empty")
+test_chk("Title", "Not a good title.", "must not end with a period")
+test_chk("Title", "Good title by Bugs Bunny et al.", warn = FALSE)
+
+test_chk("Maintainer", "Bugs Bunny <bb at acme.com>", warn = FALSE)
+test_chk("Maintainer", "ORPHANED", warn = FALSE)
+test_chk("Maintainer", "", "must not be empty")
+test_chk("Maintainer", "foobar", "must contain an email address")
+
+test_chk("RepoList", "", warn = FALSE)
+test_chk("RepoList", "http://cran.rstudio.com", warn = FALSE)
+test_chk("RepoList", "http://cran.rstudio.com, https://cran.r-project.org",
+ warn = FALSE)
+test_chk("RepoList", "foobar", "must be a comma .* repository URLs")
+test_chk("RepoList", "http://foo.bar http://fobar", "must be a comma")
+
+test_chk("URL", "http://acme.com", warn = FALSE)
+test_chk("URL", "not this one", "must be a http, https or ftp URL")
+
+test_chk("URLList", "", warn = FALSE)
+test_chk("URLList", "http://cran.rstudio.com", warn = FALSE)
+test_chk("URLList", "http://cran.rstudio.com, https://cran.r-project.org",
+ warn = FALSE)
+test_chk("URLList", "foobar", "must be a comma .* URLs")
+test_chk("URLList", "http://foo.bar http://fobar", "must be a comma")
+
+test_chk("Priority", "recommended", warn = FALSE)
+test_chk("Priority", "foobar", "must be one of")
+
+test_chk("LazyLoad", "yes", warn = FALSE, class = "Logical")
+test_chk("LazyLoad", "foobar", "must be one of", class = "Logical")
+
+test_chk("VignetteBuilder", "devtools, knitr", warn = FALSE,
+ class = "PackageList")
+test_chk("VignetteBuilder", "this is not", "must be a comma",
+ class = "PackageList")
+
+test_chk("Encoding", "UTF-8", warn = FALSE)
+test_chk("Encoding", "foobar", "must be one of")
+
+test_chk("OSType", "unix", warn = FALSE)
+test_chk("OSType", "foobar", "must be one of")
+
+test_chk("Type", "Translation", warn = FALSE)
+test_chk("Type", "foobar", "must be either")
+
+test_chk("Classification", "", warn = FALSE)
+
+test_chk("Language", "hun, deu", warn = FALSE)
+test_chk("Language", "this is not", "must be a list of IETF language")
+
+test_chk("Date", "2015-01-21", warn = FALSE)
+test_chk("Date", "foobar", "must be an ISO date")
+
+test_chk("Compression", "gzip", warn = FALSE)
+test_chk("Compression", "foobar", "must be one of")
+
+test_chk("Repository", "", warn = FALSE)
+
+test_chk("AddedByRCMD", "", warn = FALSE)
diff --git a/tests/testthat/test-collate.R b/tests/testthat/test-collate.R
new file mode 100644
index 0000000..739d49b
--- /dev/null
+++ b/tests/testthat/test-collate.R
@@ -0,0 +1,125 @@
+
+context("Collate API")
+
+test_that("set_collate and get_collate work", {
+ desc <- description$new("D1")
+
+ files <- c("foo.R", "bar.R", "foobar.R")
+ desc$set_collate(files)
+
+ expect_equal(desc$get_collate(), files)
+ expect_equal(desc$get_collate(which = "windows"), character())
+ expect_equal(desc$get_collate(which = "unix"), character())
+
+ files2 <- c(files, "foo-win.R")
+ desc$set_collate(files2, which = "windows")
+
+ expect_equal(desc$get_collate("windows"), files2)
+ expect_equal(desc$get_collate(which = "main"), files)
+ expect_equal(desc$get_collate(which = "unix"), character())
+
+ files3 <- c(files, "foo-unix.R")
+ desc$set_collate(files3, which = "unix")
+
+ expect_equal(desc$get_collate(which = "unix"), files3)
+ expect_equal(desc$get_collate(which = "windows"), files2)
+ expect_equal(desc$get_collate(which = "main"), files)
+
+})
+
+test_that("del_collate works", {
+ desc <- description$new("D1")
+
+ files <- c("foo.R", "bar.R", "foobar.R")
+ files2 <- c(files, "foo-win.R")
+ files3 <- c(files, "foo-unix.R")
+
+ desc$set_collate(files, which = "main")
+ desc$set_collate(files2, which = "windows")
+ desc$set_collate(files3, which = "unix")
+
+ desc$del_collate(which = "windows")
+
+ expect_equal(desc$get_collate(which = "windows"), character())
+ expect_equal(desc$get_collate(which = "main"), files)
+ expect_equal(desc$get_collate(which = "unix"), files3)
+
+ desc$del_collate()
+
+ expect_equal(desc$get_collate(which = "windows"), character())
+ expect_equal(desc$get_collate(which = "main"), character())
+ expect_equal(desc$get_collate(which = "unix"), character())
+
+})
+
+test_that("add_to_collate works", {
+ desc <- description$new("D1")
+
+ desc$add_to_collate("bar.R")
+ expect_equal(desc$get_collate(), "bar.R")
+
+ desc$add_to_collate("foo.R")
+ expect_equal(desc$get_collate(), c("bar.R", "foo.R"))
+
+ desc$add_to_collate("foobar.R", which = "windows")
+ expect_equal(desc$get_collate(), c("bar.R", "foo.R"))
+ expect_equal(desc$get_collate(which = "windows"), "foobar.R")
+
+ desc$add_to_collate("foobar2.R")
+ expect_equal(desc$get_collate(), c("bar.R", "foo.R", "foobar2.R"))
+ expect_equal(desc$get_collate(which = "windows"),
+ c("foobar.R", "foobar2.R"))
+
+})
+
+test_that("del_from_collate works", {
+ desc <- description$new("D1")
+
+ files <- c("foo.R", "bar.R", "foobar.R")
+ files2 <- c(files, "foo-win.R")
+ files3 <- c(files, "foo-unix.R")
+
+ desc$set_collate(files, which = "main")
+ desc$set_collate(files2, which = "windows")
+ desc$set_collate(files3, which = "unix")
+
+ desc$del_from_collate("foo.R")
+
+ expect_equal(desc$get_collate(which = "main"), c("bar.R", "foobar.R"))
+ expect_equal(desc$get_collate(which = "windows"),
+ c("bar.R", "foobar.R", "foo-win.R"))
+ expect_equal(desc$get_collate(which = "unix"),
+ c("bar.R", "foobar.R", "foo-unix.R"))
+
+ desc$del_from_collate("bar.R", which = "windows")
+
+ expect_equal(desc$get_collate(which = "main"), c("bar.R", "foobar.R"))
+ expect_equal(desc$get_collate(which = "windows"),
+ c("foobar.R", "foo-win.R"))
+ expect_equal(desc$get_collate(which = "unix"),
+ c("bar.R", "foobar.R", "foo-unix.R"))
+
+})
+
+test_that("add to all collate fields", {
+
+ desc <- description$new("D1")
+ desc$set_collate(c("f1.R", "f2.R"), which = "main")
+ desc$set_collate(c("f3.R", "f4.R"), which = "win")
+ desc$add_to_collate("f5.R", which = "all")
+
+ expect_equal(
+ desc$get_collate(which = "main"),
+ c("f1.R", "f2.R", "f5.R")
+ )
+ expect_equal(
+ desc$get_collate(which = "win"),
+ c("f3.R", "f4.R", "f5.R")
+ )
+})
+
+test_that("deleting from non-existing collate does nothing", {
+
+ desc <- description$new("D1")
+ expect_silent(desc$del_from_collate('foo.R'))
+})
diff --git a/tests/testthat/test-create.R b/tests/testthat/test-create.R
new file mode 100644
index 0000000..86bd68f
--- /dev/null
+++ b/tests/testthat/test-create.R
@@ -0,0 +1,86 @@
+
+context("Constructors")
+
+test_that("can create new object", {
+ desc <- description$new("!new")
+
+ expect_true(!is.na(desc$get("Package")))
+ expect_true(!is.na(desc$get("Title")))
+ expect_true(!is.na(desc$get("Version")))
+ expect_true(!is.na(desc$get("Authors at R")))
+ expect_true(!is.na(desc$get("Maintainer")))
+ expect_true(!is.na(desc$get("Description")))
+ expect_true(!is.na(desc$get("License")))
+ expect_true(!is.na(desc$get("LazyData")))
+ expect_true(!is.na(desc$get("URL")))
+ expect_true(!is.na(desc$get("BugReports")))
+})
+
+test_that("can read object from file", {
+ desc <- description$new("D1")
+
+ expect_true(!is.na(desc$get("Package")))
+ expect_true(!is.na(desc$get("Title")))
+ expect_true(!is.na(desc$get("Version")))
+ expect_true(!is.na(desc$get("Author")))
+ expect_true(!is.na(desc$get("Maintainer")))
+ expect_true(!is.na(desc$get("Description")))
+ expect_true(!is.na(desc$get("License")))
+ expect_true(!is.na(desc$get("URL")))
+ expect_true(!is.na(desc$get("BugReports")))
+
+})
+
+test_that("can read object from character vector", {
+ lines <- readLines("D1")
+ desc <- description$new(text = lines)
+
+ expect_true(!is.na(desc$get("Package")))
+ expect_true(!is.na(desc$get("Title")))
+ expect_true(!is.na(desc$get("Version")))
+ expect_true(!is.na(desc$get("Author")))
+ expect_true(!is.na(desc$get("Maintainer")))
+ expect_true(!is.na(desc$get("Description")))
+ expect_true(!is.na(desc$get("License")))
+ expect_true(!is.na(desc$get("URL")))
+ expect_true(!is.na(desc$get("BugReports")))
+
+})
+
+test_that("DESCRPTION is read by default", {
+
+ wd <- getwd()
+ on.exit(setwd(wd), add = TRUE)
+ setwd("files")
+
+ d1 <- description$new()
+ d2 <- description$new("DESCRIPTION")
+
+ expect_equal(d1, d2)
+})
+
+test_that("From installed package", {
+
+ desc <- description$new(package = "utils")
+ expect_match(desc$get("Author"), "Core Team")
+
+ expect_error(
+ description$new(package = "fgsdgsdhldsknfglkedsfgsdf"),
+ "Cannot find DESCRIPTION for installed package"
+ )
+})
+
+test_that("Package root is found", {
+
+ wd <- getwd()
+ on.exit(setwd(wd), add = TRUE)
+ setwd("files")
+
+ d1 <- description$new()
+
+ dir.create("subdir", showWarnings = FALSE)
+ setwd("subdir")
+ d2 <- description$new()
+
+ expect_equal(d1, d2)
+})
diff --git a/tests/testthat/test-deps.R b/tests/testthat/test-deps.R
new file mode 100644
index 0000000..2845043
--- /dev/null
+++ b/tests/testthat/test-deps.R
@@ -0,0 +1,160 @@
+
+context("Dependencies")
+
+test_that("get_deps", {
+ desc <- description$new("D1")
+
+ res <- data.frame(
+ stringsAsFactors = FALSE,
+ type = c("Imports", "Suggests"),
+ package = c("R6", "testthat"),
+ version = c("*", "*")
+ )
+
+ expect_equal(desc$get_deps(), res)
+})
+
+
+test_that("set_dep", {
+ desc <- description$new("D1")
+
+ desc$set_dep("igraph")
+
+ res <- data.frame(
+ stringsAsFactors = FALSE,
+ type = c("Imports", "Imports", "Suggests"),
+ package = c("R6", "igraph", "testthat"),
+ version = c("*", "*", "*")
+ )
+ expect_equal(desc$get_deps(), res)
+
+ desc$set_dep("igraph", version = ">= 1.0.0")
+
+ res <- data.frame(
+ stringsAsFactors = FALSE,
+ type = c("Imports", "Imports", "Suggests"),
+ package = c("R6", "igraph", "testthat"),
+ version = c("*", ">= 1.0.0", "*")
+ )
+
+ expect_equal(desc$get_deps(), res)
+
+ desc$set_dep("igraph", type = "Depends", version = ">= 1.0.0")
+
+ res <- data.frame(
+ stringsAsFactors = FALSE,
+ type = c("Imports", "Imports", "Suggests", "Depends"),
+ package = c("R6", "igraph", "testthat", "igraph"),
+ version = c("*", ">= 1.0.0", "*", ">= 1.0.0")
+ )
+
+ expect_equal(desc$get_deps(), res)
+
+})
+
+test_that("set_dep for the first dependency", {
+
+ desc <- description$new("!new")
+
+ expect_equal(eval(formals(desc$set_dep)[["type"]])[[1L]], "Imports")
+
+ desc$set_dep("igraph")
+
+ res <- data.frame(
+ stringsAsFactors = FALSE,
+ type = c("Imports"),
+ package = c("igraph"),
+ version = c("*")
+ )
+ expect_equal(desc$get_deps(), res)
+})
+
+test_that("del_dep", {
+ desc <- description$new("D1")
+
+ desc$set_dep("igraph")
+ desc$set_dep("igraph", type = "Depends", version = ">= 1.0.0")
+ desc$del_dep("igraph")
+
+ res <- data.frame(
+ stringsAsFactors = FALSE,
+ type = c("Imports", "Suggests"),
+ package = c("R6", "testthat"),
+ version = c("*", "*")
+ )
+
+ expect_equal(desc$get_deps(), res)
+
+ desc <- description$new("D1")
+
+ desc$set_dep("igraph")
+ desc$set_dep("igraph", type = "Depends", version = ">= 1.0.0")
+ desc$del_dep("igraph", type = "Imports")
+
+ res <- data.frame(
+ stringsAsFactors = FALSE,
+ type = c("Imports", "Suggests", "Depends"),
+ package = c("R6", "testthat", "igraph"),
+ version = c("*", "*", ">= 1.0.0")
+ )
+
+ expect_equal(desc$get_deps(), res)
+
+})
+
+test_that("deleting all dependencies", {
+
+ desc <- description$new("D1")
+ desc$del_deps()
+ expect_equal(desc$get("Imports"), c(Imports = NA_character_))
+ expect_equal(desc$get("Suggests"), c(Suggests = NA_character_))
+
+})
+
+test_that("deleting a non-dependency is OK", {
+
+ desc <- description$new("D1")
+ before <- desc$get("Imports")
+ desc$del_dep("foobar", "Imports")
+ after <- desc$get("Imports")
+ expect_equal(before, after)
+})
+
+test_that("has_dep", {
+
+ desc <- description$new("D1")
+ expect_true(desc$has_dep("R6"))
+ expect_true(desc$has_dep("testthat"))
+
+ expect_true(desc$has_dep("R6", "Imports"))
+ expect_true(desc$has_dep("testthat", "Suggests"))
+
+ expect_false(desc$has_dep("foobar"))
+
+ expect_false(desc$has_dep("testthat", "Imports"))
+
+ expect_error(desc$has_dep(123))
+ expect_error(desc$has_dep("testthat", "xxx"))
+})
+
+test_that("issue #34 is fine (empty dep fields)", {
+
+ empty_deps <- data.frame(
+ stringsAsFactors = FALSE,
+ type = character(),
+ package = character(),
+ version = character()
+ )
+
+ desc <- description$new("D4")
+ expect_silent(deps <- desc$get_deps())
+ expect_equal(deps, empty_deps)
+
+ desc$set(Imports = "")
+ expect_silent(deps <- desc$get_deps())
+ expect_equal(deps, empty_deps)
+
+ expect_silent(desc$set_deps(deps))
+ expect_silent(deps <- desc$get_deps())
+ expect_null(deps)
+})
diff --git a/tests/testthat/test-desc.R b/tests/testthat/test-desc.R
new file mode 100644
index 0000000..dc32b63
--- /dev/null
+++ b/tests/testthat/test-desc.R
@@ -0,0 +1,9 @@
+
+context("desc")
+
+test_that("desc wrapper works", {
+ expect_equal(
+ desc("D2"),
+ description$new("D2")
+ )
+})
diff --git a/tests/testthat/test-encoding.R b/tests/testthat/test-encoding.R
new file mode 100644
index 0000000..b9a3129
--- /dev/null
+++ b/tests/testthat/test-encoding.R
@@ -0,0 +1,41 @@
+
+context("Encoding")
+
+test_that("Encoding field is observed", {
+
+ expect_silent(
+ desc <- description$new(
+ text = paste0(
+ "Package: foo\n",
+ "Title: Foo Package\n",
+ "Author: Package Author\n",
+ "Maintainer: Package Author <author at here.net>\n",
+ "Description: The great foo package.\n",
+ "License: GPL\n",
+ "Encoding: UTF-8\n"
+ )
+ )
+ )
+
+ expect_silent(desc$set(Author = "Gábor Csárdi"))
+})
+
+test_that("Unset Encoding results warning", {
+
+ expect_warning(
+ desc <- description$new(
+ text = paste0(
+ "Package: foo\n",
+ "Title: Foo Package\n",
+ "Author: Package Author\n",
+ "Maintainer: Gábor Csárdi <author at here.net>\n",
+ "Description: The great foo package.\n",
+ "License: GPL\n"
+ )
+ ),
+ "Encoding"
+ )
+
+ expect_warning(desc$set(Author = "Gábor Csárdi"), "Encoding")
+
+})
diff --git a/tests/testthat/test-idempotent.R b/tests/testthat/test-idempotent.R
new file mode 100644
index 0000000..57e9074
--- /dev/null
+++ b/tests/testthat/test-idempotent.R
@@ -0,0 +1,30 @@
+
+context("Nice behavior")
+
+test_that("dependencies are not reformatted if new value is the same", {
+
+ desc <- description$new("D1")
+ desc$set("Imports", "R6")
+ before <- desc$get("Imports")
+ desc$set_dep("R6")
+ after <- desc$get("Imports")
+ expect_equal(before, after)
+
+ desc <- description$new("D1")
+ desc$set("Imports", "R6")
+ before <- desc$get("Imports")
+ desc$set_deps(data.frame(package = "R6", type = "Imports", version = "*"))
+ after <- desc$get("Imports")
+ expect_equal(before, after)
+})
+
+
+test_that("collate fields are not reformatted if new value is the same", {
+
+ desc <- description$new("D1")
+ desc$set("Collate", "'foo.R' 'bar.R' foobar.R")
+ before <- desc$get("Collate")
+ desc$set_collate(desc$get_collate())
+ after <- desc$get("Collate")
+ expect_equal(before, after)
+})
diff --git a/tests/testthat/test-non-oo.R b/tests/testthat/test-non-oo.R
new file mode 100644
index 0000000..5aa75cf
--- /dev/null
+++ b/tests/testthat/test-non-oo.R
@@ -0,0 +1,243 @@
+
+context("Non OO API")
+
+test_that("desc_add_author", {
+ d <- temp_desc()
+ on.exit(unlink(d))
+ x <- desc_add_author(file = d, "Bunny", "Bugs", "bugs.bunny at acme.com")
+ expect_match(desc_get(file = d, "Authors at R"), "Bunny")
+})
+
+test_that("desc_add_me", {
+ d <- temp_desc()
+ on.exit(unlink(d))
+ with_mock(
+ `whoami::fullname` = function() "First Last",
+ `whoami::email_address` = function() "first.last at dom.com",
+ x <- desc_add_me(file = d)
+ )
+ expect_match(desc_get(file = d, "Authors at R"), "First")
+ expect_match(desc_get(file = d, "Authors at R"), "first.last at dom.com")
+})
+
+test_that("desc_add_role", {
+ d <- temp_desc()
+ on.exit(unlink(d))
+ x <- desc_add_role(file = d, "ctb", given = "Manuel")
+ expect_match(
+ as.character(desc_get_author(file = d, role = "ctb")),
+ "Manuel"
+ )
+})
+
+test_that("desc_add_to_collate et al.", {
+ d <- temp_desc()
+ on.exit(unlink(d))
+ expect_true("roxygenize.R" %in% desc_get_collate(file = d))
+ x <- desc_del_from_collate(file = d, "roxygenize.R")
+ expect_false("roxygenize.R" %in% desc_get_collate(file = d))
+ x <- desc_add_to_collate(file = d, "roxygenize.R")
+ expect_true("roxygenize.R" %in% desc_get_collate(file = d))
+})
+
+test_that("desc_get_maintainer, desc_change_maintainer", {
+ d <- temp_desc()
+ on.exit(unlink(d))
+ expect_match(
+ as.character(desc_get_maintainer(file = d)),
+ "Hadley"
+ )
+ x <- desc_change_maintainer(file = d, given = "Peter")
+ expect_match(
+ as.character(desc_get_maintainer(file = d)),
+ "Peter"
+ )
+})
+
+test_that("desc_set, desc_get, desc_del", {
+ d <- temp_desc()
+ on.exit(unlink(d))
+ expect_equal(
+ desc_get(file = d, c("Package", "Version")),
+ c(Package = "roxygen2", Version = "4.1.1.9000")
+ )
+ desc_set(file = d, Package = "foobar")
+ expect_equal(
+ desc_get(file = d, c("Package", "Version")),
+ c(Package = "foobar", Version = "4.1.1.9000")
+ )
+})
+
+test_that("desc_del_author", {
+ d <- temp_desc()
+ on.exit(unlink(d))
+ desc_del_author(file = d, given = "Peter")
+ expect_false(
+ any(grepl("Peter", as.character(desc_get_authors(file = d))))
+ )
+})
+
+test_that("desc_del_collate", {
+ d <- temp_desc()
+ on.exit(unlink(d))
+ desc_del_collate(file = d)
+ expect_equal(
+ desc_get(file = d, "Collate"),
+ c(Collate = NA_character_)
+ )
+})
+
+test_that("desc_del_dep", {
+ d <- temp_desc()
+ on.exit(unlink(d))
+ expect_true(
+ "testthat" %in% desc_get_deps(file = d)$package
+ )
+ desc_del_dep(file = d, "testthat")
+ expect_false(
+ "testthat" %in% desc_get_deps(file = d)$package
+ )
+})
+
+test_that("desc_del_deps", {
+ d <- temp_desc()
+ on.exit(unlink(d))
+ desc_del_deps(file = d)
+ expect_null(desc_get_deps(file = d))
+})
+
+test_that("desc_del_role", {
+ d <- temp_desc()
+ on.exit(unlink(d))
+ desc_del_role(file = d, "aut", given = "Manuel")
+ expect_false(
+ any(grepl("Manuel", desc_get_author(file = d, role = "aut")))
+ )
+})
+
+test_that("desc_fields", {
+ d <- temp_desc()
+ on.exit(unlink(d))
+ expect_true(
+ all(c("Package", "Title", "Version", "URL", "Collate") %in%
+ desc_fields(file = d))
+ )
+})
+
+test_that("desc_has_fields", {
+ d <- temp_desc()
+ on.exit(unlink(d))
+ expect_equal(
+ desc_has_fields(file = d, c("Package", "foobar", "Collate")),
+ c(TRUE, FALSE, TRUE)
+ )
+})
+
+test_that("desc_normalize", {
+ d <- temp_desc()
+ on.exit(unlink(d))
+ desc <- description$new(d)
+ desc_normalize(file = d)
+ desc$normalize()
+
+ d2 <- tempfile()
+ on.exit(unlink(d2), add = TRUE)
+ desc$write(d2)
+ expect_equal(readLines(d), readLines(d2))
+})
+
+test_that("desc_print", {
+ d <- temp_desc()
+ on.exit(unlink(d))
+ capt <- capture.output(description$new("D2")$print())
+ expect_output(
+ desc_print(file = d),
+ paste(capt, collapse = "\n"),
+ fixed = TRUE
+ )
+})
+
+test_that("desc_reformat_fields", {
+ d <- temp_desc()
+ on.exit(unlink(d))
+ desc_reformat_fields(file = d)
+ expect_equal(
+ desc_get(file = d, "Description"),
+ description$new("D2")$reformat_fields()$get("Description")
+ )
+})
+
+test_that("desc_reorder_fields", {
+ d <- temp_desc()
+ on.exit(unlink(d))
+ desc_reorder_fields(file = d)
+ expect_equal(
+ desc_fields(file = d),
+ c("Package", "Title", "Version", "Authors at R", "Description",
+ "License", "URL", "Depends", "Imports", "Suggests", "LinkingTo",
+ "VignetteBuilder", "Encoding", "Remotes", "RoxygenNote", "Collate")
+ )
+})
+
+test_that("desc_set_authors", {
+ d <- temp_desc("D1")
+ on.exit(unlink(d))
+ desc_set_authors(file = d, desc_get_authors(file = "D2"))
+ expect_equal(
+ desc_get_authors(file = d),
+ desc_get_authors("D2")
+ )
+})
+
+test_that("desc_set_collate", {
+ d <- temp_desc()
+ on.exit(unlink(d))
+ desc_set_collate(file = d, c("foo.R", "bar.R"))
+ expect_equal(
+ desc_get_collate(file = d),
+ c("foo.R", "bar.R")
+ )
+})
+
+test_that("desc_set_dep", {
+ d <- temp_desc()
+ on.exit(unlink(d))
+ desc_set_dep(file = d, "foobar", type = "Suggests")
+ expect_true(
+ "foobar" %in% desc_get_deps(file = d)$package
+ )
+})
+
+test_that("desc_set_deps", {
+ d <- temp_desc()
+ on.exit(unlink(d))
+ desc_set_deps(file = d, desc_get_deps(file = "D1"))
+ expect_equal(
+ desc_get_deps(file = d),
+ desc_get_deps(file = "D1")
+ )
+})
+
+test_that("desc_to_latex", {
+ expect_equal(
+ desc_to_latex(file = "D2"),
+ description$new("D2")$to_latex()
+ )
+})
+
+test_that("desc_validate", {
+ expect_warning(
+ desc_validate(file = "D1"),
+ "not implemented"
+ )
+})
+
+test_that("can write back automatically found DESCRIPTION file", {
+ dir.create(tmp <- tempfile())
+ file.copy(file.path("files", "DESCRIPTION"), tmp)
+ withr::with_dir(
+ tmp,
+ desc_set_dep("somepackage", "Suggests")
+ )
+ expect_true("somepackage" %in% desc_get_deps(file = tmp)$package)
+})
diff --git a/tests/testthat/test-queries.R b/tests/testthat/test-queries.R
new file mode 100644
index 0000000..0ee9b7f
--- /dev/null
+++ b/tests/testthat/test-queries.R
@@ -0,0 +1,78 @@
+
+context("Queries")
+
+test_that("get works", {
+ desc <- description$new("D1")
+
+ expect_equal(desc$get("Package"), c(Package = "desc"))
+ expect_equal(desc$get("Version"), c(Version = "1.0.0"))
+ expect_equal(desc$get("Author"), c(Author = "Gábor Csárdi"))
+ expect_equal(desc$get("Imports"), c(Imports = "\n R6"))
+})
+
+test_that("get_or_fail works", {
+ desc <- description$new("D1")
+
+ expect_equal(desc$get_or_fail("Package"), c(Package = "desc"))
+ expect_equal(desc$get_or_fail("Version"), c(Version = "1.0.0"))
+ expect_equal(desc$get_or_fail("Author"), c(Author = "Gábor Csárdi"))
+ expect_equal(desc$get_or_fail("Imports"), c(Imports = "\n R6"))
+
+ expect_equal(
+ desc$get_or_fail(c("Package", "Version")),
+ c(Package = "desc", Version = "1.0.0")
+ )
+
+ expect_error(
+ desc$get_or_fail("package"),
+ "Could not find DESCRIPTION field.*package"
+ )
+ expect_error(
+ desc$get_or_fail(c("Package", "versionx")),
+ "Could not find DESCRIPTION field.*versionx"
+ )
+ expect_error(
+ desc$get_or_fail(c("Package", "versionx", "foobar")),
+ "Could not find DESCRIPTION fields.*versionx.*foobar"
+ )
+})
+
+test_that("set works", {
+ desc <- description$new("D1")
+ expect_equal(desc$get("Package"), c(Package = "desc"))
+
+ desc$set(Package = "foobar")
+ expect_equal(desc$get("Package"), c(Package = "foobar"))
+
+ desc$set("Package", "foo")
+ expect_equal(desc$get("Package"), c(Package = "foo"))
+
+ desc$set(Version = "100.0", Author = "Bugs Bunny")
+ expect_equal(desc$get("Version"), c(Version = "100.0"))
+ expect_equal(desc$get("Author"), c(Author = "Bugs Bunny"))
+
+ desc$set("Description", "Foo\n foobar\n foobar2.")
+ expect_equal(desc$get("Description"),
+ c(Description = "Foo\n foobar\n foobar2."))
+})
+
+test_that("get with non-exixting fields", {
+ desc <- description$new("D1")
+ expect_equal(desc$get("foobar"), c(foobar = NA_character_))
+})
+
+test_that("del works", {
+ desc <- description$new("D1")
+ desc$del("Package")
+ expect_equal(desc$get("Package"), c(Package = NA_character_))
+ expect_false(is.na(desc$get("Title")))
+})
+
+test_that("set errors on invalid input", {
+
+ desc <- description$new("D1")
+ expect_error(
+ desc$set("foobar"),
+ "needs two unnamed args"
+ )
+})
diff --git a/tests/testthat/test-read.R b/tests/testthat/test-read.R
new file mode 100644
index 0000000..424d416
--- /dev/null
+++ b/tests/testthat/test-read.R
@@ -0,0 +1,32 @@
+
+context("DCF reader")
+
+test_that("DCF reader works", {
+ desc <- description$new("D1")
+
+ expect_equal(desc$get("Package"), c(Package = "desc"))
+ expect_equal(desc$get("Version"), c(Version = "1.0.0"))
+ expect_equal(desc$get("Author"), c(Author = "Gábor Csárdi"))
+ expect_equal(desc$get("Imports"), c(Imports = "\n R6"))
+})
+
+test_that("DCF reader keeps whitespace", {
+ desc <- description$new("D1")
+
+ expect_equal(desc$get("Suggests"), c(Suggests = "\n testthat"))
+ expect_equal(desc$get("Description"), c(
+ Description = paste0(
+ "Tools to read, write, create, and manipulate DESCRIPTION\n",
+ " files. It is intented for packages that create or manipulate other\n",
+ " packages."
+ )
+ ))
+
+})
+
+test_that("duplicate fields, #43", {
+ expect_error(
+ description$new("D5"),
+ "Duplicate DESCRIPTION fields.*Remotes"
+ )
+})
diff --git a/tests/testthat/test-remotes.R b/tests/testthat/test-remotes.R
new file mode 100644
index 0000000..c906f7e
--- /dev/null
+++ b/tests/testthat/test-remotes.R
@@ -0,0 +1,38 @@
+
+context("REMOTE")
+
+test_that("get, set, etc. remotes", {
+ desc <- description$new("D2")
+ expect_identical(
+ desc$get_remotes(),
+ c("foo/digest",
+ "svn::https://github.com/hadley/stringr",
+ "local::/pkgs/testthat"
+ )
+ )
+
+ desc$set_remotes(c("bar/knitr", "local::/pkgs/Rcpp"))
+ expect_identical(desc$get_remotes(), c("bar/knitr", "local::/pkgs/Rcpp"))
+
+ desc$add_remotes("github::brewer/brew")
+ expect_identical(
+ desc$get_remotes(),
+ c("bar/knitr", "local::/pkgs/Rcpp", "github::brewer/brew")
+ )
+
+ desc$del_remotes("^local::")
+ expect_identical(
+ desc$get_remotes(),
+ c("bar/knitr", "github::brewer/brew")
+ )
+
+ desc$clear_remotes()
+ expect_identical(desc$get_remotes(), character())
+
+ desc$add_remotes("hadley/stringr")
+ expect_identical(desc$get_remotes(), "hadley/stringr")
+
+ desc$del_remotes("stringr")
+ expect_identical(desc$get_remotes(), character())
+ expect_identical(desc$get("Remotes"), c(Remotes = NA_character_))
+})
diff --git a/tests/testthat/test-repair.R b/tests/testthat/test-repair.R
new file mode 100644
index 0000000..58c032c
--- /dev/null
+++ b/tests/testthat/test-repair.R
@@ -0,0 +1,53 @@
+context("repair")
+
+test_that("normalization", {
+
+ desc <- description$new("!new")
+ desc$set("Imports", "foo, bar, foobar")
+
+ before_fields <- desc$fields()
+ before_data <- desc$get(before_fields)
+ desc$normalize()
+ after_fields <- desc$fields()
+ after_data <- desc$get(after_fields)
+
+ expect_equal(sort(before_fields), sort(after_fields))
+ expect_lt(match("Imports", after_fields), match("Imports", before_fields))
+ expect_gt(match("LazyData", after_fields), match("LazyData", before_fields))
+ expect_equal(after_data[["Imports"]], "\n foo,\n bar,\n foobar")
+
+})
+
+test_that("reformatting", {
+
+ desc <- description$new("!new")
+ desc$set("Imports", "foo, bar, foobar")
+
+ before_fields <- desc$fields()
+ before_data <- desc$get(before_fields)
+ desc$reformat_fields()
+ after_fields <- desc$fields()
+ after_data <- desc$get(after_fields)
+
+ expect_equal(before_fields, after_fields)
+ expect_equal(after_data[["Imports"]], "\n foo,\n bar,\n foobar")
+
+})
+
+test_that("reordering", {
+
+ desc <- description$new("!new")
+ desc$set("Imports", "foo, bar, foobar")
+
+ before_fields <- desc$fields()
+ before_data <- desc$get(before_fields)
+ desc$reorder_fields()
+ after_fields <- desc$fields()
+ after_data <- desc$get(after_fields)
+
+ expect_equal(sort(before_fields), sort(after_fields))
+ expect_lt(match("Imports", after_fields), match("Imports", before_fields))
+ expect_gt(match("LazyData", after_fields), match("LazyData", before_fields))
+ expect_identical(before_data[after_fields], after_data)
+
+})
diff --git a/tests/testthat/test-str.R b/tests/testthat/test-str.R
new file mode 100644
index 0000000..9793f68
--- /dev/null
+++ b/tests/testthat/test-str.R
@@ -0,0 +1,68 @@
+
+context("Formatting")
+
+test_that("str orders fields", {
+ desc <- description$new("!new")
+
+ desc$del("Package")
+ desc$set("Package", "foobar")
+
+ expect_match(crayon::strip_style(desc$str()), "^Package:")
+})
+
+test_that("str formats some fields specially", {
+ desc <- description$new("!new")
+
+ desc$set("Imports", "pkg1, pkg2, \n pkg3, pkg4")
+ expect_match(
+ crayon::strip_style(desc$str()),
+ "Imports:\n pkg1,\n pkg2,\n pkg3,\n pkg4"
+ )
+
+ desc$set("Collate", "file1.R 'file2.R' 'file with spaces.R' file4.R")
+ expect_match(
+ crayon::strip_style(desc$str()),
+ "Collate:\n 'file1.R'\n 'file2.R'\n 'file with spaces.R'\n 'file4.R'"
+ )
+})
+
+test_that("str formats authors properly", {
+
+ desc <- description$new("D2")
+
+ expect_equal(
+ crayon::strip_style(desc$str(by_field = TRUE)[["Authors at R"]]),
+ paste0(
+ "Authors at R:\n ",
+ "c(person(given = \"Hadley\",\n ",
+ "family = \"Wickham\",\n ",
+ "role = c(\"aut\", \"cre\", \"cph\"),\n ",
+ "email = \"h.wickham at gmail.com\"),\n ",
+ "person(given = \"Peter\",\n ",
+ "family = \"Danenberg\",\n ",
+ "role = c(\"aut\", \"cph\"),\n ",
+ "email = \"pcd at roxygen.org\"),\n ",
+ "person(given = \"Manuel\",\n ",
+ "family = \"Eugster\",\n ",
+ "role = c(\"aut\", \"cph\")),\n ",
+ "person(given = \"RStudio\",\n ",
+ "role = \"cph\"))"
+ )
+ )
+})
+
+test_that("authors are printed to the screen properly", {
+
+ desc <- description$new("D2")
+
+ expect_output(
+ print(desc),
+ "Authors at R (parsed):
+ * Hadley Wickham <h.wickham at gmail.com> [aut, cre, cph]
+ * Peter Danenberg <pcd at roxygen.org> [aut, cph]
+ * Manuel Eugster [aut, cph]
+ * RStudio [cph]",
+ fixed = TRUE
+ )
+
+})
diff --git a/tests/testthat/test-to_latex.R b/tests/testthat/test-to_latex.R
new file mode 100644
index 0000000..199fae4
--- /dev/null
+++ b/tests/testthat/test-to_latex.R
@@ -0,0 +1,15 @@
+context("to_latex")
+
+test_that("Test expected LaTeX output", {
+ desc <- description$new("!new")
+
+ desc$set_authors(person("Kirill", "Müller", email = "aaa at bbb.xx", role = "cre"))
+ desc$set(Package = "Test.Package",
+ Title = "Must be in Title Case",
+ Description = "Words not in the dictionary must be 'quoted'. \"Double quotes\" can also be used, as well as \\_{}[]^$#%. Must end with a full stop.",
+ License = "CC-BY-SA",
+ URL = "http://somewhere.io, http://somewhereel.se",
+ BugReports = "http://somewhere.io/trac")
+
+ expect_output_file(print(desc$to_latex()), "output/to_latex.tex", update = TRUE)
+})
diff --git a/tests/testthat/test-trailing-ws.R b/tests/testthat/test-trailing-ws.R
new file mode 100644
index 0000000..ee32bd9
--- /dev/null
+++ b/tests/testthat/test-trailing-ws.R
@@ -0,0 +1,49 @@
+
+context("Absense of trailing WS is kept")
+
+test_that("No WS is kept if field is not modified", {
+ d <- description$new("D3")
+ tmp <- tempfile()
+ on.exit(unlink(tmp), add = TRUE)
+ d$write(tmp)
+ expect_equal(readLines("D3"), readLines(tmp))
+})
+
+test_that("WS is present in newly created fields", {
+ d <- description$new("D3")
+ tmp <- tempfile()
+ on.exit(unlink(tmp), add = TRUE)
+ d$set("Foobar" = "\n TRAZ")
+ d$write(tmp)
+ contents <- paste0(paste0(readLines(tmp), collapse = "\n"), "\n")
+ expect_match(contents, "\nFoobar: \n TRAZ\n")
+})
+
+test_that("WS is present in newly created files", {
+ desc <- description$new("!new")
+ tmp <- tempfile()
+ on.exit(unlink(tmp), add = TRUE)
+ desc$write(tmp)
+ contents <- paste0(readLines(tmp), collapse = "\n")
+ expect_match(contents, "\nAuthors at R: \n")
+})
+
+test_that("WS is added if field is changed", {
+ d <- description$new("D3")
+ tmp <- tempfile()
+ on.exit(unlink(tmp), add = TRUE)
+ d$add_author("Gabor", "Csardi", "foo at bar.com")
+ d$write(tmp)
+ contents <- paste0(readLines(tmp), collapse = "\n")
+ expect_match(contents, "Authors at R: \n")
+})
+
+test_that("No WS is added if an other field is changed", {
+ d <- description$new("D3")
+ tmp <- tempfile()
+ on.exit(unlink(tmp), add = TRUE)
+ d$add_author("Gabor", "Csardi", "foo at bar.com")
+ d$write(tmp)
+ contents <- paste0(readLines(tmp), collapse = "\n")
+ expect_match(contents, "Imports:\n")
+})
diff --git a/tests/testthat/test-urls.R b/tests/testthat/test-urls.R
new file mode 100644
index 0000000..e9f5ce5
--- /dev/null
+++ b/tests/testthat/test-urls.R
@@ -0,0 +1,31 @@
+
+context("URL")
+
+test_that("get, set, etc. urls", {
+ desc <- description$new("D2")
+ expect_identical(
+ desc$get_urls(), "https://github.com/klutometis/roxygen"
+ )
+
+ desc$set_urls(c("https://foo.bar", "http://x.y"))
+ expect_identical(desc$get_urls(), c("https://foo.bar", "http://x.y"))
+
+ desc$add_urls("http://another.one")
+ expect_identical(
+ desc$get_urls(),
+ c("https://foo.bar", "http://x.y", "http://another.one")
+ )
+
+ desc$del_urls("^http://")
+ expect_identical(desc$get_urls(), "https://foo.bar")
+
+ desc$clear_urls()
+ expect_identical(desc$get_urls(), character())
+
+ desc$add_urls("http://another.one")
+ expect_identical(desc$get_urls(), "http://another.one")
+
+ desc$del_urls("another.one")
+ expect_identical(desc$get_urls(), character())
+ expect_identical(desc$get("URL"), c(URL = NA_character_))
+})
diff --git a/tests/testthat/test-utils.R b/tests/testthat/test-utils.R
new file mode 100644
index 0000000..b65c715
--- /dev/null
+++ b/tests/testthat/test-utils.R
@@ -0,0 +1,65 @@
+
+context("Utility functions")
+
+test_that("check_for_package works", {
+
+ expect_true(check_for_package("utils"))
+
+ expect_error(
+ check_for_package("foobarfoobarfoobar"),
+ "Package 'foobarfoobarfoobar' is needed"
+ )
+
+})
+
+test_that("is_ascii", {
+
+ expect_true(is_ascii(""))
+ expect_true(is_ascii("a"))
+ expect_true(is_ascii(rawToChar(as.raw(127))))
+
+ expect_equal(
+ is_ascii(character()),
+ logical()
+ )
+
+ expect_equal(
+ is_ascii(c("a", "b")),
+ c(TRUE, TRUE)
+ )
+
+ expect_false(is_ascii(rawToChar(as.raw(128))))
+})
+
+test_that("is_url", {
+
+ expect_true(is_url("http://acme.com"))
+ expect_true(is_url("https://acme.com"))
+ expect_true(is_url("ftp://this.is.it"))
+
+ expect_equal(is_url(character()), logical())
+
+ expect_false(is_url(""))
+ expect_false(is_url("this.is.not"))
+ expect_false(is_url("http://"))
+ expect_false(is_url("http:/this.is.no"))
+
+ expect_equal(
+ is_url(c("http://acme.com", "http://index.me")),
+ c(TRUE, TRUE)
+ )
+
+ expect_equal(
+ is_url(c("foo", "https://foo.bar")),
+ c(FALSE, TRUE)
+ )
+})
+
+test_that("is_url_list", {
+
+ expect_true(is_url_list(""))
+ expect_true(is_url_list("http://foo.bar"))
+
+ expect_false(is_url_list("this is not it"))
+ expect_false(is_url_list("http://so.far.so.good, oh, no!"))
+})
diff --git a/tests/testthat/test-validation.R b/tests/testthat/test-validation.R
new file mode 100644
index 0000000..b88bb18
--- /dev/null
+++ b/tests/testthat/test-validation.R
@@ -0,0 +1,11 @@
+
+context("Validation")
+
+test_that("validation is not implemented", {
+
+ desc <- description$new("!new")
+ expect_warning(
+ desc$validate(),
+ "not implemented"
+ )
+})
diff --git a/tests/testthat/test-versions.R b/tests/testthat/test-versions.R
new file mode 100644
index 0000000..fec92cb
--- /dev/null
+++ b/tests/testthat/test-versions.R
@@ -0,0 +1,68 @@
+
+context("Version")
+
+test_that("get_version", {
+ desc <- description$new("D1")
+ v <- desc$get_version()
+ expect_true(inherits(v, "package_version"))
+ expect_equal(as.character(v), "1.0.0")
+
+ desc$del("Version")
+ expect_error(desc$get_version())
+})
+
+test_that("set_version", {
+ desc <- description$new("D1")
+
+ desc$set_version("2.1.3")$set_version("2.1.4")
+ expect_equal(desc$get_version(), package_version("2.1.4"))
+
+ desc$set_version(package_version("1.9.10.100"))
+ expect_equal(desc$get_version(), package_version("1.9.10.100"))
+
+ expect_error(desc$set_version("1"))
+ expect_error(desc$set_version("1.0.0-dev"))
+})
+
+test_that("bump_version", {
+ desc <- description$new("D1")
+
+ cases <- list(
+ c("1.2.3", "major", "2.0.0"),
+ c("1.2.3", "minor", "1.3.0"),
+ c("1.2.3", "patch", "1.2.4"),
+ c("1.2.3", "dev", "1.2.3.9000"),
+
+ c("1.5", "major", "2.0"),
+ c("1.5", "minor", "1.6"),
+ c("1.5", "patch", "1.5.1"),
+ c("1.5", "dev", "1.5.0.9000"),
+
+ c("1.2.3.9000", "major", "2.0.0"),
+ c("1.2.3.9000", "minor", "1.3.0"),
+ c("1.2.3.9000", "patch", "1.2.4"),
+ c("1.2.3.9000", "dev", "1.2.3.9001"),
+
+ list("1.2.3", 1, "2.0.0"),
+ list("1.2.3", 2, "1.3.0"),
+ list("1.2.3", 3, "1.2.4"),
+ list("1.2.3", 4, "1.2.3.9000"),
+
+ list("1.5", 1, "2.0"),
+ list("1.5", 2, "1.6"),
+ list("1.5", 3, "1.5.1"),
+ list("1.5", 4, "1.5.0.9000"),
+
+ list("1.2.3.9000", 1, "2.0.0"),
+ list("1.2.3.9000", 2, "1.3.0"),
+ list("1.2.3.9000", 3, "1.2.4"),
+ list("1.2.3.9000", 4, "1.2.3.9001")
+ )
+
+ for (c in cases) {
+ expect_equal(
+ desc$set_version(c[[1]])$bump_version(c[[2]])$get_version(),
+ package_version(c[[3]])
+ )
+ }
+})
diff --git a/tests/testthat/test-write.R b/tests/testthat/test-write.R
new file mode 100644
index 0000000..9d9a22e
--- /dev/null
+++ b/tests/testthat/test-write.R
@@ -0,0 +1,27 @@
+
+context("Write")
+
+test_that("can write to file", {
+
+ desc <- description$new("!new")
+ tmp <- tempfile()
+ desc$write(tmp)
+
+ desc2 <- description$new(tmp)
+ expect_equal(desc$str(), desc2$str())
+
+})
+
+test_that("normalization while writing to file", {
+
+ desc <- description$new("!new")
+ desc$set("Imports", "foo, bar, foobar")
+
+ tmp <- tempfile()
+ desc$write(tmp)
+
+ desc$normalize()
+
+ desc2 <- description$new(tmp)
+ expect_equal(desc$str(), desc2$str())
+})
--
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/debian-med/r-cran-desc.git
More information about the debian-med-commit
mailing list