[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