From 977be55ae082610b842710518a6a296946a995e3 Mon Sep 17 00:00:00 2001 From: Thierry Onkelinx Date: Fri, 21 May 2021 10:09:27 +0200 Subject: [PATCH 01/18] update checklist settings --- .Rbuildignore | 26 ++++++------ .github/workflows/check_on_branch.yml | 1 + .github/workflows/check_on_different_r_os.yml | 9 ++++- ...{check_on_master.yml => check_on_main.yml} | 5 ++- .github/workflows/release.yml | 30 ++++++++++++++ .gitignore | 11 +++--- man/figures/background-pattern.png | Bin 0 -> 53317 bytes man/figures/flanders.woff | Bin 0 -> 23320 bytes man/figures/flanders.woff2 | Bin 0 -> 17376 bytes pkgdown/extra.css | 37 +++++------------- 10 files changed, 71 insertions(+), 48 deletions(-) rename .github/workflows/{check_on_master.yml => check_on_main.yml} (83%) create mode 100644 .github/workflows/release.yml create mode 100644 man/figures/background-pattern.png create mode 100644 man/figures/flanders.woff create mode 100644 man/figures/flanders.woff2 diff --git a/.Rbuildignore b/.Rbuildignore index b9532f6a..b525ceae 100644 --- a/.Rbuildignore +++ b/.Rbuildignore @@ -1,17 +1,21 @@ +# checklist +^_pkgdown.yml$ ^.*\.Rproj$ -^\.Rproj\.user$ -^\.github$ -^codemeta\.json$ ^.zenodo\.json$ -^man-roxygen$ -^pkgdown$ -^_pkgdown.yml$ -^docs$ -^cran-comments\.md$ -# checklist +^\.github$ +^\.httr-oauth$ +^\.Rproj\.user$ +^\.zenodo\.json$ ^checklist.yml$ ^codecov.yml$ -^LICENSE.md$ -^\.httr-oauth$ +^codecov\.yml$ +^codemeta\.json$ +^cran-comments\.md$ +^data-raw$ ^doc$ +^docs$ +^LICENSE.md$ +^man-roxygen$ ^Meta$ +^pkgdown$ +^README\.Rmd$ diff --git a/.github/workflows/check_on_branch.yml b/.github/workflows/check_on_branch.yml index 7b4f0f02..7e61c527 100644 --- a/.github/workflows/check_on_branch.yml +++ b/.github/workflows/check_on_branch.yml @@ -1,6 +1,7 @@ on: push: branches-ignore: + - main - master - ghpages diff --git a/.github/workflows/check_on_different_r_os.yml b/.github/workflows/check_on_different_r_os.yml index aa2d2bea..ada27336 100644 --- a/.github/workflows/check_on_different_r_os.yml +++ b/.github/workflows/check_on_different_r_os.yml @@ -1,12 +1,14 @@ on: push: branches: + - main - master pull_request: branches: + - main - master -name: R-CMD-check +name: R-CMD-check-OS jobs: R-CMD-check: @@ -21,10 +23,11 @@ jobs: - {os: macOS-latest, r: 'release'} - {os: windows-latest, r: 'release'} - {os: ubuntu-20.04, r: 'devel', rspm: "https://packagemanager.rstudio.com/cran/__linux__/focal/latest"} - - {os: ubuntu-16.04, r: 'oldrel', rspm: "https://packagemanager.rstudio.com/cran/__linux__/xenial/latest"} + - {os: ubuntu-18.04, r: 'oldrel', rspm: "https://packagemanager.rstudio.com/cran/__linux__/xenial/latest"} env: R_REMOTES_NO_ERRORS_FROM_WARNINGS: true + _R_CHECK_SYSTEM_CLOCK_: false RSPM: ${{ matrix.config.rspm }} GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} ORCID_TOKEN: ${{ secrets.ORCID_TOKEN }} @@ -61,6 +64,7 @@ jobs: Rscript -e "remotes::install_github('r-hub/sysreqs')" sysreqs=$(Rscript -e "cat(sysreqs::sysreq_commands('DESCRIPTION'))") sudo -s eval "$sysreqs" + - name: Install dependencies run: | remotes::install_deps(dependencies = TRUE) @@ -91,3 +95,4 @@ jobs: with: name: ${{ runner.os }}-r${{ matrix.config.r }}-results path: check + retention-days: 14 diff --git a/.github/workflows/check_on_master.yml b/.github/workflows/check_on_main.yml similarity index 83% rename from .github/workflows/check_on_master.yml rename to .github/workflows/check_on_main.yml index 9d1cb838..f9b22e9a 100644 --- a/.github/workflows/check_on_master.yml +++ b/.github/workflows/check_on_main.yml @@ -1,11 +1,12 @@ on: push: branches: + - main - master schedule: - - cron: '6 0 * * 1' + - cron: '6 0 15 * *' -name: "check package on master" +name: "check package on main" jobs: check-package: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 00000000..c44a7601 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,30 @@ +on: + push: + tags: + - 'v*' + +name: Create Release + +jobs: + build: + name: Create Release + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v2 + - name: Get tag message + run: | + TAG_BODY=$(git tag --contains ${{ github.sha }} -n100 | awk '(NR>1)') + echo "::set-output name=TAG_BODY::$TAG_BODY" + id: tag-body + - name: Create Release + id: create_release + uses: actions/create-release@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + tag_name: ${{ github.ref }} + release_name: Release ${{ github.ref }} + body: ${{ steps.tag-body.outputs.TAG_BODY }} + draft: false + prerelease: false diff --git a/.gitignore b/.gitignore index cde4424a..fa45244d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,9 +1,10 @@ -.Rproj.user -.Rhistory +.httr-oauth .RData +.Rhistory +.Rproj.user .Ruserdata -inst/doc -docs -.httr-oauth +*.html doc +docs +inst/doc Meta diff --git a/man/figures/background-pattern.png b/man/figures/background-pattern.png new file mode 100644 index 0000000000000000000000000000000000000000..a2d9426ff66d58522d578a87ee933124c3620377 GIT binary patch literal 53317 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�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 z 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�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 XULXKs5DxeGTTp1r^MW!XDHs3%JK&q! literal 0 HcmV?d00001 diff --git a/pkgdown/extra.css b/pkgdown/extra.css index fb1e7e6b..42934f7e 100644 --- a/pkgdown/extra.css +++ b/pkgdown/extra.css @@ -1,7 +1,7 @@ body { background-color: #efefef; color: #5e5e5e; - background-image: url('https://www.inbo.be/sites/all/themes/bootstrap_inbo/img/inbo/background-pattern.png'); + background-image: url('reference/figures/background-pattern.png'); font-family: FlandersArtSans-Light, Verdana, Arial, sans-serif; } @@ -41,33 +41,14 @@ a:hover { color: #5e5e5e; } -@font-face { - font-family: FlandersArtSans-Light; - src: url('https://www.inbo.be/sites/all/themes/bootstrap_inbo/fonts/FlandersArtSans-Light.eot'); - src: url('https://www.inbo.be/sites/all/themes/bootstrap_inbo/fonts/FlandersArtSans-Light.eot?#iefix') format('embedded-opentype'), url('https://www.inbo.be/sites/all/themes/bootstrap_inbo/fonts/FlandersArtSans-Light.woff') format('woff'), url('https://www.inbo.be/sites/all/themes/bootstrap_inbo/fonts/FlandersArtSans-Light.ttf') format('truetype'), url('https://www.inbo.be/sites/all/themes/bootstrap_inbo/fonts/FlandersArtSans-Light.svg#FlandersArtSans-Light') format('svg'); - font-weight: normal; - font-style: normal; -} -@font-face { - font-family: FlandersArtSans-Regular; - src: url('https://www.inbo.be/sites/all/themes/bootstrap_inbo/fonts/FlandersArtSans-Regular.eot'); - src: url('https://www.inbo.be/sites/all/themes/bootstrap_inbo/fonts/FlandersArtSans-Regular.eot?#iefix') format('embedded-opentype'), url('https://www.inbo.be/sites/all/themes/bootstrap_inbo/fonts/FlandersArtSans-Regular.woff') format('woff'), url('https://www.inbo.be/sites/all/themes/bootstrap_inbo/fonts/FlandersArtSans-Regular.ttf') format('truetype'), url('https://www.inbo.be/sites/all/themes/bootstrap_inbo/fonts/FlandersArtSans-Regular.svg#FlandersArtSans-Regular') format('svg'); - font-weight: normal; - font-style: normal; -} -@font-face { - font-family: FlandersArtSans-Medium; - src: url('https://www.inbo.be/sites/all/themes/bootstrap_inbo/fonts/FlandersArtSans-Medium.eot'); - src: url('https://www.inbo.be/sites/all/themes/bootstrap_inbo/fonts/FlandersArtSans-Medium.eot?#iefix') format('embedded-opentype'), url('https://www.inbo.be/sites/all/themes/bootstrap_inbo/fonts/FlandersArtSans-Medium.woff') format('woff'), url('https://www.inbo.be/sites/all/themes/bootstrap_inbo/fonts/FlandersArtSans-Medium.ttf') format('truetype'), url('https://www.inbo.be/sites/all/themes/bootstrap_inbo/fonts/FlandersArtSans-Medium.svg#FlandersArtSans-Medium') format('svg'); - font-weight: normal; - font-style: normal; -} -@font-face { - font-family: FlandersArtSans-Bold; - src: url('https://www.inbo.be/sites/all/themes/bootstrap_inbo/fonts/FlandersArtSans-Bold.eot'); - src: url('https://www.inbo.be/sites/all/themes/bootstrap_inbo/fonts/FlandersArtSans-Bold.eot?#iefix') format('embedded-opentype'), url('https://www.inbo.be/sites/all/themes/bootstrap_inbo/fonts/FlandersArtSans-Bold.woff') format('woff'), url('https://www.inbo.be/sites/all/themes/bootstrap_inbo/fonts/FlandersArtSans-Bold.ttf') format('truetype'), url('https://www.inbo.be/sites/all/themes/bootstrap_inbo/fonts/FlandersArtSans-Bold.svg#FlandersArtSans-Bold') format('svg'); - font-weight: normal; - font-style: normal; +@font-face{ + font-family: inbo; + src: + url('figures/flanders.woff2') format('woff2'), + url('reference/figures/flanders.woff') format('woff'); +; + font-weight:normal; + font-style:normal; } code.sourceCode.diff span.st { From bd1021958825ca31227d6f7e01543cb076ed9fae Mon Sep 17 00:00:00 2001 From: Thierry Onkelinx Date: Wed, 3 Nov 2021 09:48:15 +0100 Subject: [PATCH 02/18] upgrade to 7.1.2 --- DESCRIPTION | 2 +- man/git2rdata-package.Rd | 22 +--------------------- 2 files changed, 2 insertions(+), 22 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index e53e9b1d..e2da906a 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -69,7 +69,7 @@ Encoding: UTF-8 Language: en-GB LazyData: true Roxygen: list(markdown = TRUE) -RoxygenNote: 7.1.1 +RoxygenNote: 7.1.2 Collate: 'clean_data_path.R' 'datahash.R' diff --git a/man/git2rdata-package.Rd b/man/git2rdata-package.Rd index 26720033..2bd448fb 100644 --- a/man/git2rdata-package.Rd +++ b/man/git2rdata-package.Rd @@ -6,27 +6,7 @@ \alias{git2rdata-package} \title{git2rdata: Store and Retrieve Data.frames in a Git Repository} \description{ -The git2rdata package is an R package for writing and reading - dataframes as plain text files. A metadata file stores important - information. 1) Storing metadata allows to maintain the classes of - variables. By default, git2rdata optimizes the data for file storage. - The optimization is most effective on data containing factors. The - optimization makes the data less human readable. The user can turn - this off when they prefer a human readable format over smaller files. - Details on the implementation are available in vignette("plain_text", - package = "git2rdata"). 2) Storing metadata also allows smaller row - based diffs between two consecutive commits. This is a useful feature - when storing data as plain text files under version control. Details - on this part of the implementation are available in - vignette("version_control", package = "git2rdata"). Although we - envisioned git2rdata with a git workflow in mind, you can use it in - combination with other version control systems like subversion or - mercurial. 3) git2rdata is a useful tool in a reproducible and - traceable workflow. vignette("workflow", package = "git2rdata") gives - a toy example. 4) vignette("efficiency", package = "git2rdata") - provides some insight into the efficiency of file storage, git - repository size and speed for writing and reading. Please cite using - . +The git2rdata package is an R package for writing and reading dataframes as plain text files. A metadata file stores important information. 1) Storing metadata allows to maintain the classes of variables. By default, git2rdata optimizes the data for file storage. The optimization is most effective on data containing factors. The optimization makes the data less human readable. The user can turn this off when they prefer a human readable format over smaller files. Details on the implementation are available in vignette("plain_text", package = "git2rdata"). 2) Storing metadata also allows smaller row based diffs between two consecutive commits. This is a useful feature when storing data as plain text files under version control. Details on this part of the implementation are available in vignette("version_control", package = "git2rdata"). Although we envisioned git2rdata with a git workflow in mind, you can use it in combination with other version control systems like subversion or mercurial. 3) git2rdata is a useful tool in a reproducible and traceable workflow. vignette("workflow", package = "git2rdata") gives a toy example. 4) vignette("efficiency", package = "git2rdata") provides some insight into the efficiency of file storage, git repository size and speed for writing and reading. Please cite using . } \seealso{ Useful links: From 804ee7dd92c8a3626c531addc47d83b5cb28545d Mon Sep 17 00:00:00 2001 From: Thierry Onkelinx Date: Wed, 3 Nov 2021 10:46:01 +0100 Subject: [PATCH 03/18] add CITATION information --- .Rbuildignore | 1 + .zenodo.json | 32 ++++++++++++++++++++++++++++++++ CITATION.cff | 35 +++++++++++++++++++++++++++++++++++ DESCRIPTION | 37 ++++++++++++------------------------- inst/CITATION | 12 ++++++++++++ 5 files changed, 92 insertions(+), 25 deletions(-) create mode 100644 .zenodo.json create mode 100644 CITATION.cff create mode 100644 inst/CITATION diff --git a/.Rbuildignore b/.Rbuildignore index b525ceae..aa57a161 100644 --- a/.Rbuildignore +++ b/.Rbuildignore @@ -7,6 +7,7 @@ ^\.Rproj\.user$ ^\.zenodo\.json$ ^checklist.yml$ +^CITATION\.cff$ ^codecov.yml$ ^codecov\.yml$ ^codemeta\.json$ diff --git a/.zenodo.json b/.zenodo.json new file mode 100644 index 00000000..5334b715 --- /dev/null +++ b/.zenodo.json @@ -0,0 +1,32 @@ +{ + "title": ["git2rdata: Store and Retrieve Data.frames in a Git Repository"], + "version": ["0.4.0"], + "description": ["The git2rdata package is an R package for writing and reading dataframes as plain text files. A metadata file stores important information. 1) Storing metadata allows to maintain the classes of variables. By default, git2rdata optimizes the data for file storage. The optimization is most effective on data containing factors. The optimization makes the data less human readable. The user can turn this off when they prefer a human readable format over smaller files. Details on the implementation are available in vignette(\"plain_text\", package = \"git2rdata\"). 2) Storing metadata also allows smaller row based diffs between two consecutive commits. This is a useful feature when storing data as plain text files under version control. Details on this part of the implementation are available in vignette(\"version_control\", package = \"git2rdata\"). Although we envisioned git2rdata with a git workflow in mind, you can use it in combination with other version control systems like subversion or mercurial. 3) git2rdata is a useful tool in a reproducible and traceable workflow. vignette(\"workflow\", package = \"git2rdata\") gives a toy example. 4) vignette(\"efficiency\", package = \"git2rdata\") provides some insight into the efficiency of file storage, git repository size and speed for writing and reading. Please cite using ."], + "creators": [ + { + "name": ["Onkelinx, Thierry"], + "orcid": ["https://orcid.org/0000-0001-8804-4216"] + } + ], + "contributors": [ + { + "name": ["Vanderhaeghe, Floris"], + "orcid": ["https://orcid.org/0000-0002-6378-6229"] + }, + { + "name": ["Desmet, Peter"], + "orcid": ["https://orcid.org/0000-0002-8442-8025"] + }, + { + "name": ["Lommelen, Els"], + "orcid": ["https://orcid.org/0000-0002-3481-5684"] + } + ], + "upload_type": ["software"], + "access_rights": ["open"], + "license": ["GPL-3.0"], + "communities": { + "identifier": ["inbo"] + }, + "language": ["en"] +} diff --git a/CITATION.cff b/CITATION.cff new file mode 100644 index 00000000..fcbd5efa --- /dev/null +++ b/CITATION.cff @@ -0,0 +1,35 @@ +cff-version: 1.2.0 +message: If you use this software, please cite it as below. +authors: +- family-names: Onkelinx + given-names: Thierry + orcid: https://orcid.org/0000-0001-8804-4216 +contact: +- email: thierry.onkelinx@inbo.be + family-names: Onkelinx + given-names: Thierry +- email: info@inbo.be + name: Research Institute for Nature and Forest +title: 'git2rdata: Store and Retrieve Data.frames in a Git Repository' +version: 0.4.0 +abstract: The git2rdata package is an R package for writing and reading dataframes + as plain text files. A metadata file stores important information. 1) Storing metadata + allows to maintain the classes of variables. By default, git2rdata optimizes the + data for file storage. The optimization is most effective on data containing factors. + The optimization makes the data less human readable. The user can turn this off + when they prefer a human readable format over smaller files. Details on the implementation + are available in vignette("plain_text", package = "git2rdata"). 2) Storing metadata + also allows smaller row based diffs between two consecutive commits. This is a useful + feature when storing data as plain text files under version control. Details on + this part of the implementation are available in vignette("version_control", package + = "git2rdata"). Although we envisioned git2rdata with a git workflow in mind, you + can use it in combination with other version control systems like subversion or + mercurial. 3) git2rdata is a useful tool in a reproducible and traceable workflow. + vignette("workflow", package = "git2rdata") gives a toy example. 4) vignette("efficiency", + package = "git2rdata") provides some insight into the efficiency of file storage, + git repository size and speed for writing and reading. Please cite using . +license: GPL-3.0 +type: software +identifiers: +- type: url + value: https://ropensci.github.io/git2rdata/ diff --git a/DESCRIPTION b/DESCRIPTION index e2da906a..cdf3b7cf 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,30 +1,17 @@ Package: git2rdata Title: Store and Retrieve Data.frames in a Git Repository -Version: 0.3.1 -Authors@R: - c(person(given = "Thierry", - family = "Onkelinx", - role = c("aut", "cre"), - email = "thierry.onkelinx@inbo.be", - comment = c(ORCID = "0000-0001-8804-4216")), - person(given = "Floris", - family = "Vanderhaeghe", - role = "ctb", - email = "floris.vanderhaeghe@inbo.be", - comment = c(ORCID = "0000-0002-6378-6229")), - person(given = "Peter", - family = "Desmet", - role = "ctb", - email = "peter.desmet@inbo.be", - comment = c(ORCID = "0000-0002-8442-8025")), - person(given = "Els", - family = "Lommelen", - role = "ctb", - email = "els.lommelen@inbo.be", - comment = c(ORCID = "0000-0002-3481-5684")), - person(given = "Research Institute for Nature and Forest", - role = c("cph", "fnd"), - email = "info@inbo.be")) +Version: 0.4.0 +Authors@R: c( + person("Thierry", "Onkelinx", , "thierry.onkelinx@inbo.be", role = c("aut", "cre"), + comment = c(ORCID = "0000-0001-8804-4216")), + person("Floris", "Vanderhaeghe", , "floris.vanderhaeghe@inbo.be", role = "ctb", + comment = c(ORCID = "0000-0002-6378-6229")), + person("Peter", "Desmet", , "peter.desmet@inbo.be", role = "ctb", + comment = c(ORCID = "0000-0002-8442-8025")), + person("Els", "Lommelen", , "els.lommelen@inbo.be", role = "ctb", + comment = c(ORCID = "0000-0002-3481-5684")), + person("Research Institute for Nature and Forest", , , "info@inbo.be", role = c("cph", "fnd")) + ) Description: The git2rdata package is an R package for writing and reading dataframes as plain text files. A metadata file stores important information. 1) Storing metadata allows to maintain the classes of diff --git a/inst/CITATION b/inst/CITATION new file mode 100644 index 00000000..7a219d15 --- /dev/null +++ b/inst/CITATION @@ -0,0 +1,12 @@ +citHeader("To cite `git2rdata` in publications please use:") +# begin checklist entry +citEntry( + entry = "Manual", + title = "git2rdata: Store and Retrieve Data.frames in a Git Repository. Version 0.4.0", + author = c(person(given = "Thierry", family = "Onkelinx")), + year = 2021, + url = "https://ropensci.github.io/git2rdata/", + abstract = "The git2rdata package is an R package for writing and reading dataframes as plain text files. A metadata file stores important information. 1) Storing metadata allows to maintain the classes of variables. By default, git2rdata optimizes the data for file storage. The optimization is most effective on data containing factors. The optimization makes the data less human readable. The user can turn this off when they prefer a human readable format over smaller files. Details on the implementation are available in vignette(\"plain_text\", package = \"git2rdata\"). 2) Storing metadata also allows smaller row based diffs between two consecutive commits. This is a useful feature when storing data as plain text files under version control. Details on this part of the implementation are available in vignette(\"version_control\", package = \"git2rdata\"). Although we envisioned git2rdata with a git workflow in mind, you can use it in combination with other version control systems like subversion or mercurial. 3) git2rdata is a useful tool in a reproducible and traceable workflow. vignette(\"workflow\", package = \"git2rdata\") gives a toy example. 4) vignette(\"efficiency\", package = \"git2rdata\") provides some insight into the efficiency of file storage, git repository size and speed for writing and reading. Please cite using .", + textVersion = "Onkelinx, Thierry (2021) git2rdata: Store and Retrieve Data.frames in a Git Repository. Version 0.4.0. https://ropensci.github.io/git2rdata/", +) +# end checklist entry From c598ad8808b39f48dea9a189ff776e85a4c8f669 Mon Sep 17 00:00:00 2001 From: Thierry Onkelinx Date: Wed, 3 Nov 2021 11:16:25 +0100 Subject: [PATCH 04/18] fix URL in README --- README.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 9130cdd4..afbd0279 100644 --- a/README.md +++ b/README.md @@ -2,14 +2,13 @@ [![CRAN status](https://www.r-pkg.org/badges/version/git2rdata)](https://cran.r-project.org/package=git2rdata) -[![Rdoc](https://www.rdocumentation.org/badges/version/git2rdata)](https://www.rdocumentation.org/packages/git2rdata) [![Project Status: Active – The project has reached a stable, usable state and is being actively developed.](https://www.repostatus.org/badges/latest/active.svg)](https://www.repostatus.org/#active) -[![lifecycle](https://img.shields.io/badge/lifecycle-maturing-blue.svg)](https://www.tidyverse.org/lifecycle/#maturing) +[![lifecycle](https://img.shields.io/badge/lifecycle-stable-green.svg)](https://lifecycle.r-lib.org/articles/stages.html#stable) [![](https://badges.ropensci.org/263_status.svg)](https://github.com/ropensci/software-review/issues/263) [![Licence](https://img.shields.io/badge/licence-GPL--3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0.en.html) [![minimal R version](https://img.shields.io/badge/R%3E%3D-3.5.0-6666ff.svg)](https://cran.r-project.org/) [![DOI](https://zenodo.org/badge/147685405.svg)](https://zenodo.org/badge/latestdoi/147685405) -[![codecov](https://codecov.io/gh/ropensci/git2rdata/branch/master/graph/badge.svg)](https://codecov.io/gh/ropensci/git2rdata) +[![codecov](https://codecov.io/gh/ropensci/git2rdata/branch/master/graph/badge.svg)](https://app.codecov.io/gh/ropensci/git2rdata) ![GitHub forks](https://img.shields.io/github/forks/ropensci/git2rdata.svg?style=social) ![GitHub stars](https://img.shields.io/github/stars/ropensci/git2rdata.svg?style=social) ![GitHub code size in bytes](https://img.shields.io/github/languages/code-size/ropensci/git2rdata.svg) From d631c73cb9c909153a690bc9bb24cf23d0d1bfb0 Mon Sep 17 00:00:00 2001 From: Thierry Onkelinx Date: Thu, 4 Nov 2021 14:20:46 +0100 Subject: [PATCH 05/18] write_vc() stores non optimized files as comma separated values instead of tab separated values --- R/clean_data_path.R | 6 +- R/is_git2rdata.R | 24 +- R/is_git2rmeta.R | 13 +- R/list_data.R | 25 +- R/meta.R | 5 +- R/prune.R | 28 +- R/read_vc.R | 10 +- R/recent_commit.R | 7 + R/relabel.R | 12 +- R/rename_variable.R | 5 + R/upgrade_data.R | 16 +- R/write_vc.R | 10 +- man/meta.Rd | 2 +- tests/testthat/test_a_basics.R | 407 +++++++++++++++--------------- tests/testthat/test_b_prune.R | 136 +++++----- tests/testthat/test_b_special.R | 157 ++++++------ tests/testthat/test_c_git.R | 3 +- tests/testthat/test_e_non_ascii.R | 1 - 18 files changed, 467 insertions(+), 400 deletions(-) diff --git a/R/clean_data_path.R b/R/clean_data_path.R index 2f83d6e0..2a5ea67b 100644 --- a/R/clean_data_path.R +++ b/R/clean_data_path.R @@ -11,9 +11,9 @@ clean_data_path <- function(root, file, normalize = TRUE) { assert_that(is.flag(normalize), noNA(normalize)) dir_name <- dirname(file) - if (length(grep("\\.\\.", dir_name))) { - stop("file should not contain '..'") - } + assert_that( + length(grep("\\.\\.", dir_name)) == 0, msg = "file should not contain '..'" + ) file <- gsub("\\..*$", "", basename(file)) if (dir_name == ".") { diff --git a/R/is_git2rdata.R b/R/is_git2rdata.R index 15f9f23b..0c48b86c 100644 --- a/R/is_git2rdata.R +++ b/R/is_git2rdata.R @@ -9,8 +9,9 @@ #' @export #' @family internal #' @template example_isgit2r -is_git2rdata <- function(file, root = ".", - message = c("none", "warning", "error")) { +is_git2rdata <- function( + file, root = ".", message = c("none", "warning", "error") +) { UseMethod("is_git2rdata", root) } @@ -23,8 +24,9 @@ is_git2rdata.default <- function(file, root, message) { #' @importFrom assertthat assert_that is.string #' @importFrom yaml read_yaml as.yaml #' @importFrom utils packageVersion -is_git2rdata.character <- function(file, root = ".", - message = c("none", "warning", "error")) { +is_git2rdata.character <- function( + file, root = ".", message = c("none", "warning", "error") +) { assert_that(is.string(file), is.string(root)) message <- match.arg(message) root <- normalizePath(root, winslash = "/", mustWork = TRUE) @@ -34,6 +36,13 @@ is_git2rdata.character <- function(file, root = ".", } file <- clean_data_path(root = root, file = file) + # read the metadata + meta_data <- read_yaml(file["meta_file"]) + file["raw_file"] <- ifelse( + meta_data[["..generic"]][["optimize"]], + file["raw_file"], + gsub("\\.tsv$", ".csv", file["raw_file"]) + ) if (!file.exists(file["raw_file"])) { msg <- "Data file missing." switch(message, error = stop(msg, call. = FALSE), @@ -41,8 +50,6 @@ is_git2rdata.character <- function(file, root = ".", return(FALSE) } - # read the metadata - meta_data <- read_yaml(file["meta_file"]) if (has_name(meta_data[["..generic"]], "split_by")) { header <- readLines( file.path(file["raw_file"], "index.tsv"), n = 1, encoding = "UTF-8" @@ -79,7 +86,10 @@ is_git2rdata.character <- function(file, root = ".", } } else { correct <- names(meta_data) - correct <- paste(correct[correct != "..generic"], collapse = "\t") + correct <- paste( + correct[correct != "..generic"], + collapse = ifelse(meta_data[["..generic"]][["optimize"]], "\t", ",") + ) header <- readLines(file["raw_file"], n = 1, encoding = "UTF-8") if (correct != header) { msg <- paste("Corrupt data, incorrect header. Expecting:", correct) diff --git a/R/is_git2rmeta.R b/R/is_git2rmeta.R index f4ed0b27..868b2ad4 100644 --- a/R/is_git2rmeta.R +++ b/R/is_git2rmeta.R @@ -69,8 +69,17 @@ See `?upgrade_data()`." warning = warning(msg, call. = FALSE)) return(FALSE) } - if (package_version(meta_data[["..generic"]][["git2rdata"]]) < - package_version("0.1.0.9001")) { + if (!has_name(meta_data[["..generic"]], "optimize")) { + msg <- "Corrupt metadata, optimize flag not found." + switch(message, error = stop(msg, call. = FALSE), + warning = warning(msg, call. = FALSE)) + return(FALSE) + } + used_version <- package_version(meta_data[["..generic"]][["git2rdata"]]) + if (used_version < package_version("0.1.0.9001") || ( + used_version < package_version("0.4.0") & + !meta_data[["..generic"]][["optimize"]] + )) { msg <- "Data stored using an older version of `git2rdata`. See `?upgrade_data()`." switch(message, error = stop(msg, call. = FALSE), diff --git a/R/list_data.R b/R/list_data.R index 5dab7ae2..2e3a6793 100644 --- a/R/list_data.R +++ b/R/list_data.R @@ -28,13 +28,19 @@ list_data.character <- function(root = ".", path = ".", recursive = TRUE) { root <- normalizePath(root, winslash = "/", mustWork = TRUE) path <- normalizePath(file.path(root, path), winslash = "/", mustWork = TRUE) - data_files <- list.files(path, pattern = "\\.tsv$", recursive = recursive, + tsv_files <- list.files(path, pattern = "\\.tsv$", recursive = recursive, + full.names = TRUE) + csv_files <- list.files(path, pattern = "\\.csv$", recursive = recursive, full.names = TRUE) meta_files <- list.files(path, pattern = "\\.yml$", recursive = recursive, full.names = TRUE) - data_files <- gsub("\\.tsv$", "", data_files) + tsv_files <- gsub("\\.tsv$", "", tsv_files) + csv_files <- gsub("\\.csv$", "", csv_files) meta_files <- gsub("\\.yml$", "", meta_files) - meta_files <- meta_files[meta_files %in% data_files] + meta_files <- meta_files[meta_files %in% c(tsv_files, csv_files)] + if (length(meta_files) == 0) { + return(character(0)) + } meta_files_base <- remove_root(file = meta_files, root = root) check <- vapply(X = meta_files_base, FUN = is_git2rmeta, FUN.VALUE = NA, root = root, message = "none") @@ -43,8 +49,17 @@ list_data.character <- function(root = ".", path = ".", recursive = TRUE) { paste(meta_files_base[!check], collapse = "\n"), call. = FALSE) } meta_files <- meta_files[check] - data_files <- data_files[data_files %in% meta_files] - remove_root(file = data_files, root = root) + optimize <- vapply( + sprintf("%s.yml", meta_files), FUN.VALUE = logical(1), + FUN = function(x) { + read_yaml(x)[["..generic"]][["optimize"]] + } + ) + tsv_files <- sprintf("%s.tsv", tsv_files[tsv_files %in% meta_files[optimize]]) + csv_files <- sprintf( + "%s.csv", csv_files[csv_files %in% meta_files[!optimize]] + ) + remove_root(file = sort(c(tsv_files, csv_files)), root = root) } #' @export diff --git a/R/meta.R b/R/meta.R index b213e1c6..72993f63 100644 --- a/R/meta.R +++ b/R/meta.R @@ -36,15 +36,16 @@ meta <- function(x, ...) { #' @export #' @rdname meta #' @importFrom assertthat assert_that is.string noNA -meta.character <- function(x, na = "NA", ...) { +meta.character <- function(x, na = "NA", optimize = TRUE, ...) { assert_that(is.string(na), noNA(na), no_whitespace(na)) + assert_that(is.flag(optimize), noNA(optimize)) x <- enc2utf8(x) if (na %in% x) { stop("one of the strings matches the NA string ('", na, "') Please use a different NA string or consider using a factor.", call. = FALSE) } x <- gsub("\\\"", "\\\"\\\"", x) - to_escape <- grepl("(\"|\t|\n)", x) + to_escape <- grepl(ifelse(optimize, "(\"|\t|\n)", "(\"|,|\n)"), x) x[to_escape] <- paste0("\"", x[to_escape], "\"") x[is.na(x)] <- na m <- list(class = "character", na_string = na) diff --git a/R/prune.R b/R/prune.R index f99d2723..eeadecbf 100644 --- a/R/prune.R +++ b/R/prune.R @@ -42,9 +42,9 @@ rm_data.character <- function( if (length(to_do) == 0) { return(to_do) } - file.remove(sprintf("%s/%s.tsv", root, to_do)) + file.remove(file.path(root, to_do)) - return(invisible(paste0(to_do, ".tsv"))) + return(invisible(to_do)) } #' @export @@ -69,7 +69,6 @@ rm_data.git_repository <- function( if (length(to_do) == 0) { return(to_do) } - to_do <- paste0(to_do, ".tsv") keep <- unlist(switch(type, unmodified = status( @@ -148,6 +147,10 @@ prune_meta.character <- function( full.names = TRUE) keep <- gsub("\\.tsv$", ".yml", keep) to_do <- to_do[!to_do %in% keep] + keep <- list.files(path = path, pattern = "\\.csv$", recursive = recursive, + full.names = TRUE) + keep <- gsub("\\.csv$", ".yml", keep) + to_do <- to_do[!to_do %in% keep] to_do_base <- remove_root(file = to_do, root = root) check <- vapply(X = gsub(".yml$", "", to_do_base), FUN = is_git2rmeta, FUN.VALUE = NA, root = root, message = "none") @@ -183,18 +186,13 @@ prune_meta.git_repository <- function( assert_that(is.flag(stage)) to_do <- list.files( - path = path, - pattern = "\\.yml$", - recursive = recursive, - full.names = TRUE + path = path, pattern = "\\.yml$", recursive = recursive, full.names = TRUE ) keep <- list.files( - path = path, - pattern = "\\.tsv$", - recursive = recursive, + path = path, pattern = "\\.[ct]sv$", recursive = recursive, full.names = TRUE ) - keep <- gsub("\\.tsv$", ".yml", keep) + keep <- gsub("\\.[ct]sv$", ".yml", keep) to_do <- to_do[!to_do %in% keep] if (length(to_do) == 0) { return(invisible(NULL)) @@ -204,7 +202,9 @@ prune_meta.git_repository <- function( changed <- unlist(status( root, staged = FALSE, unstaged = TRUE, untracked = FALSE, ignored = FALSE )) - changed <- gsub("\\.tsv$", ".yml", file.path(root_wd, changed, fsep = "/")) + changed <- gsub( + "\\.[ct]sv$", ".yml", file.path(root_wd, changed, fsep = "/") + ) if (any(to_do %in% changed)) { stop( call. = FALSE, @@ -215,7 +215,9 @@ prune_meta.git_repository <- function( changed <- unlist(status( root, staged = TRUE, unstaged = FALSE, untracked = FALSE, ignored = FALSE )) - changed <- gsub("\\.tsv$", ".yml", file.path(root_wd, changed, fsep = "/")) + changed <- gsub( + "\\.[ct]sv$", ".yml", file.path(root_wd, changed, fsep = "/") + ) if (any(to_do %in% changed)) { warning("data removed and staged, metadata removed but unstaged", call. = FALSE) diff --git a/R/read_vc.R b/R/read_vc.R index 657a5d05..5cabdae3 100644 --- a/R/read_vc.R +++ b/R/read_vc.R @@ -42,14 +42,13 @@ read_vc.character <- function(file, root = ".") { stop(e$message, call. = FALSE) } ) - assert_that( - all(file.exists(file)), - msg = "raw file and/or meta file missing" - ) # read the metadata meta_data <- read_yaml(file["meta_file"]) optimize <- meta_data[["..generic"]][["optimize"]] + file["raw_file"] <- ifelse( + optimize, file["raw_file"], gsub("\\.tsv$", ".csv", file["raw_file"]) + ) col_type <- list( c( character = "character", factor = "character", integer = "integer", @@ -107,7 +106,8 @@ read_vc.character <- function(file, root = ".") { raw_data <- do.call(rbind, raw_data)[, col_names] } else { raw_data <- read.table( - file = file["raw_file"], header = TRUE, sep = "\t", quote = "\"", + file = file["raw_file"], header = TRUE, sep = ifelse(optimize, "\t", ","), + quote = "\"", dec = ".", numerals = "warn.loss", na.strings = na_string, colClasses = setNames(col_type[col_classes], col_names), comment.char = "", diff --git a/R/recent_commit.R b/R/recent_commit.R index 6cb06029..b95587a0 100644 --- a/R/recent_commit.R +++ b/R/recent_commit.R @@ -94,7 +94,14 @@ recent_commit.git_repository <- function(file, root, data = FALSE) { path <- "" } if (data) { + is_git2rdata(file = file, root = root, message = "error") file <- clean_data_path(root = workdir(root), file, normalize = FALSE) + meta_data <- read_yaml(file["meta_file"]) + file["raw_file"] <- ifelse( + meta_data[["..generic"]][["optimize"]], + file["raw_file"], + gsub("\\.tsv$", ".csv", file["raw_file"]) + ) } name <- basename(file) blobs <- odb_blobs(root) diff --git a/R/relabel.R b/R/relabel.R index 2107c561..66b4f716 100644 --- a/R/relabel.R +++ b/R/relabel.R @@ -90,18 +90,12 @@ relabel.list <- function(file, root = ".", change) { assert_that(is.string(root), is.string(file)) assert_that(!is.null(names(change)), msg = "'change' has no names") root <- normalizePath(root, winslash = "/", mustWork = TRUE) - is_git2rmeta(file = file, root = root, message = "error") + is_git2rdata(file = file, root = root, message = "error") file <- clean_data_path(root = root, file = file) - assert_that( - all(file.exists(file)), - msg = "raw file and/or meta file missing" - ) meta_data <- read_yaml(file["meta_file"]) optimize <- meta_data[["..generic"]][["optimize"]] - if (!optimize) { - stop("relabelling factors on verbose data leads to large diffs. -Use write_vc() instead.", call. = FALSE) - } + stopifnot("relabelling factors on verbose data leads to large diffs. +Use write_vc() instead." = optimize) assert_that( all(names(change) %in% names(meta_data)), msg = "every name in 'change' must match an exisiting variable" diff --git a/R/rename_variable.R b/R/rename_variable.R index b4eb1211..3833aab6 100644 --- a/R/rename_variable.R +++ b/R/rename_variable.R @@ -69,6 +69,11 @@ rename_variable.character <- function(file, change, root = ".", ...) { is_git2rdata(file = file, root = root, message = "error") file <- clean_data_path(root = root, file = file) yaml <- read_yaml(file[["meta_file"]]) + file["raw_file"] <- ifelse( + yaml[["..generic"]][["optimize"]], + file["raw_file"], + gsub("\\.tsv$", ".csv", file["raw_file"]) + ) assert_that( all(change %in% names(yaml)), msg = "Not every old name in `change` present in the `git2rdata` object." diff --git a/R/upgrade_data.R b/R/upgrade_data.R index df8ec30a..32f79de5 100644 --- a/R/upgrade_data.R +++ b/R/upgrade_data.R @@ -72,9 +72,19 @@ upgrade_data.character <- function( msg = paste(target, "has corrupt metadata, no hash found.") ) if (has_name(meta_data[["..generic"]], "git2rdata")) { - if (package_version(meta_data[["..generic"]][["git2rdata"]]) >= - package_version("0.1.0.9001") - ) { + current <- package_version(meta_data[["..generic"]][["git2rdata"]]) + if (current >= package_version("0.4.0")) { + if (verbose) { + message(target, " already up to date") + } + return(target) + } + if (current >= package_version("0.1.0.9001")) { + assert_that( + has_name(meta_data[["..generic"]], "optimize"), + msg = paste(target, "has corrupt metadata, optimize flag not found.") + ) + stopifnot(meta_data[["..generic"]][["optimize"]]) if (verbose) { message(target, " already up to date") } diff --git a/R/write_vc.R b/R/write_vc.R index 146e8007..c1bfd213 100644 --- a/R/write_vc.R +++ b/R/write_vc.R @@ -106,6 +106,11 @@ write_vc.character <- function( } } } + file["raw_file"] <- ifelse( + attr(raw_data, "meta")[["..generic"]][["optimize"]], + file["raw_file"], + gsub("\\.tsv$", ".csv", file["raw_file"]) + ) assert_that( unlink(file["raw_file"], recursive = TRUE) == 0, msg = "Failed to remove existing files." @@ -113,7 +118,10 @@ write_vc.character <- function( if (length(split_by) == 0) { write.table( x = raw_data, file = file["raw_file"], append = FALSE, quote = FALSE, - sep = "\t", eol = "\n", na = na, dec = ".", row.names = FALSE, + sep = ifelse( + attr(raw_data, "meta")[["..generic"]][["optimize"]], "\t", "," + ), + eol = "\n", na = na, dec = ".", row.names = FALSE, col.names = TRUE, fileEncoding = "UTF-8" ) } else { diff --git a/man/meta.Rd b/man/meta.Rd index c7190b95..db797216 100644 --- a/man/meta.Rd +++ b/man/meta.Rd @@ -13,7 +13,7 @@ \usage{ meta(x, ...) -\method{meta}{character}(x, na = "NA", ...) +\method{meta}{character}(x, na = "NA", optimize = TRUE, ...) \method{meta}{factor}(x, optimize = TRUE, na = "NA", index, strict = TRUE, ...) diff --git a/tests/testthat/test_a_basics.R b/tests/testthat/test_a_basics.R index 1df75e3f..495c52ec 100644 --- a/tests/testthat/test_a_basics.R +++ b/tests/testthat/test_a_basics.R @@ -1,217 +1,220 @@ -context("write_vc() and read_vc() on a file system") -expect_error(meta("NA"), "one of the strings matches the NA string") -expect_error(meta("NA", na = "abc def"), "na contains whitespace characters") -expect_error(meta("NA", na = "abc\tdef"), "na contains whitespace characters") -expect_error(meta("NA", na = "abc\ndef"), "na contains whitespace characters") -expect_error( - meta(factor("NA"), optimize = FALSE), - "one of the levels matches the NA string" -) -expect_error(write_vc(root = 1), "a 'root' of class numeric is not supported") -expect_error(read_vc(root = 1), "a 'root' of class numeric is not supported") -root <- tempfile(pattern = "git2rdata-basic") -dir.create(root) -expect_false(any(file.exists(git2rdata:::clean_data_path(root, "test")))) -expect_error( - git2rdata:::clean_data_path(root, "../wrong_location"), - "file should not contain '..'" -) -expect_error( - git2rdata:::clean_data_path(root, "./../wrong_location"), - "file should not contain '..'" -) -expect_is( - suppressWarnings( - output <- write_vc( - x = test_data, file = "test.txt", root = root, sorting = "test_Date" - ) - ), - "character" -) -expect_identical(length(output), 2L) -expect_identical(unname(output), c("test.tsv", "test.yml")) -expect_true(all(file.exists(git2rdata:::clean_data_path(root, "test")))) -expect_equal( - stored <- read_vc(file = "test.xls", root = root), - sorted_test_data, - check.attributes = FALSE -) -for (i in colnames(stored)) { +test_that("write_vc() and read_vc() on a file system", { + expect_error(meta("NA"), "one of the strings matches the NA string") + expect_error(meta("NA", na = "abc def"), "na contains whitespace characters") + expect_error(meta("NA", na = "abc\tdef"), "na contains whitespace characters") + expect_error(meta("NA", na = "abc\ndef"), "na contains whitespace characters") + expect_error( + meta(factor("NA"), optimize = FALSE), + "one of the levels matches the NA string" + ) + expect_error(write_vc(root = 1), "a 'root' of class numeric is not supported") + expect_error(read_vc(root = 1), "a 'root' of class numeric is not supported") + root <- tempfile(pattern = "git2rdata-basic") + dir.create(root) + expect_false(any(file.exists(git2rdata:::clean_data_path(root, "test")))) + expect_error( + git2rdata:::clean_data_path(root, "../wrong_location"), + "file should not contain '..'" + ) + expect_error( + git2rdata:::clean_data_path(root, "./../wrong_location"), + "file should not contain '..'" + ) + expect_is( + suppressWarnings( + output <- write_vc( + x = test_data, file = "test.txt", root = root, sorting = "test_Date" + ) + ), + "character" + ) + expect_identical(length(output), 2L) + expect_identical(unname(output), c("test.tsv", "test.yml")) + expect_true(all(file.exists(git2rdata:::clean_data_path(root, "test")))) expect_equal( - stored[[i]], - sorted_test_data[[i]], - label = paste0("stored$", i), - expected.label = paste0("sorted_test_data$", i) - ) -} -expect_identical( - suppressWarnings(write_vc(x = test_data, file = "test.xls", root = root)), - output -) -expect_error( - write_vc(data.frame(junk = 5), file = "test", root = root, sorting = "junk"), - "The data was not overwritten because of the issues below." -) -expect_error( - suppressWarnings( - write_vc(x = test_data, file = "test", root = root, optimize = FALSE) - ), - "New data is verbose, whereas old data was optimized" -) -expect_warning( - write_vc(x = test_data, file = "test", root = root, optimize = FALSE, - strict = FALSE), - "New data is verbose, whereas old data was optimized" -) -expect_error( - write_vc( - x = test_data[, colnames(test_data) != "test_Date"], - file = "test", root = root - ), - "All sorting variables must be available" -) - -expect_false(any(file.exists(git2rdata:::clean_data_path(root, "a/verbose")))) -expect_is( - output <- + stored <- read_vc(file = "test.xls", root = root), + sorted_test_data, + check.attributes = FALSE + ) + for (i in colnames(stored)) { + expect_equal( + stored[[i]], + sorted_test_data[[i]], + label = paste0("stored$", i), + expected.label = paste0("sorted_test_data$", i) + ) + } + expect_identical( + suppressWarnings(write_vc(x = test_data, file = "test.xls", root = root)), + output + ) + expect_error( + write_vc(data.frame(junk = 5), file = "test", root = root, sorting = "junk"), + "The data was not overwritten because of the issues below." + ) + expect_error( + suppressWarnings( + write_vc(x = test_data, file = "test", root = root, optimize = FALSE) + ), + "New data is verbose, whereas old data was optimized" + ) + expect_warning( + write_vc(x = test_data, file = "test", root = root, optimize = FALSE, + strict = FALSE), + "New data is verbose, whereas old data was optimized" + ) + expect_error( write_vc( - x = test_data, file = "a/verbose", root = root, sorting = "test_Date", - optimize = FALSE + x = test_data[, colnames(test_data) != "test_Date"], + file = "test", root = root ), - "character" -) -expect_true(all(file.exists(git2rdata:::clean_data_path(root, "a/verbose")))) -expect_equal( - stored <- read_vc(file = "a/verbose", root = root), - sorted_test_data, - check.attributes = FALSE -) -for (i in colnames(stored)) { + "All sorting variables must be available" + ) + + expect_false(any(file.exists(git2rdata:::clean_data_path(root, "a/verbose")))) + expect_is( + output <- + write_vc( + x = test_data, file = "a/verbose", root = root, sorting = "test_Date", + optimize = FALSE + ), + "character" + ) + expect_true( + all(file.exists(file.path(root, "a", c("verbose.csv", "verbose.yml")))) + ) expect_equal( - stored[[i]], - sorted_test_data[[i]], - label = paste0("stored$", i), - expected.label = paste0("sorted_test_data$", i) - ) -} -expect_error( - write_vc(x = test_data, file = "a/verbose", root = root), - "New data is optimized, whereas old data was verbose" -) + stored <- read_vc(file = "a/verbose", root = root), + sorted_test_data, + check.attributes = FALSE + ) + for (i in colnames(stored)) { + expect_equal( + stored[[i]], + sorted_test_data[[i]], + label = paste0("stored$", i), + expected.label = paste0("sorted_test_data$", i) + ) + } + expect_error( + write_vc(x = test_data, file = "a/verbose", root = root), + "New data is optimized, whereas old data was verbose" + ) -expect_is( - output <- write_vc( - test_na, file = "na", root = root, - sorting = c("test_Date", "test_integer", "test_numeric") - ), - "character" -) -expect_equal( - stored <- read_vc(file = "na", root = root), - sorted_test_na, - check.attributes = FALSE -) -for (i in colnames(stored)) { + expect_is( + output <- write_vc( + test_na, file = "na", root = root, + sorting = c("test_Date", "test_integer", "test_numeric") + ), + "character" + ) expect_equal( - stored[[i]], - sorted_test_na[[i]], - label = paste0("stored$", i), - expected.label = paste0("sorted_test_na$", i) + stored <- read_vc(file = "na", root = root), + sorted_test_na, + check.attributes = FALSE ) -} + for (i in colnames(stored)) { + expect_equal( + stored[[i]], + sorted_test_na[[i]], + label = paste0("stored$", i), + expected.label = paste0("sorted_test_na$", i) + ) + } -expect_error( - write_vc(test_data, file = "error", root = root, sorting = 1), - "sorting is not a character vector" -) -expect_error( - write_vc(test_data, file = "error", root = root, sorting = "junk"), - "All sorting variables must be available" -) -expect_false(any(file.exists(git2rdata:::clean_data_path(root, "sorting")))) -expect_warning( - write_vc(test_data, file = "error", root = root, sorting = character(0)), - "No sorting applied" -) -expect_warning( - output <- - write_vc(test_data, file = "sorting", root = root, sorting = "test_factor"), - "Sorting on 'test_factor' results in ties" -) -expect_is(output, "character") -expect_true(all(file.exists(git2rdata:::clean_data_path(root, "sorting")))) -expect_warning( - write_vc(test_data, file = "sorting", root = root, - sorting = c("test_factor", "test_Date"), strict = FALSE), - "The sorting variables changed" -) -expect_error( - suppressWarnings( - write_vc(test_data, file = "sorting", root = root, sorting = "test_factor") - ), - "The sorting variables changed" -) -test_changed <- test_data -test_changed$junk <- test_changed$test_character -expect_error( - suppressWarnings(write_vc(test_changed, file = "sorting", root = root)), - "New data has a different number of variables" -) -test_changed$test_character <- NULL -expect_error( - suppressWarnings(write_vc(test_changed, file = "sorting", root = root)), - "New variables: junk" -) -test_changed <- test_data -test_changed$test_character <- factor(test_changed$test_character) -expect_error( - suppressWarnings(write_vc(test_changed, file = "sorting", root = root - )), - "Change in class: 'test_character' from character to factor" -) -expect_error( - suppressWarnings( - write_vc(test_data, file = "sorting", root = root, sorting = "test_logical") - ), - "The sorting variables changed" -) -test_changed <- test_data -test_changed$test_ordered <- factor( - test_changed$test_ordered, - levels = levels(test_changed$test_ordered), - ordered = FALSE -) -expect_error( - suppressWarnings(write_vc(test_changed, file = "sorting", root = root - )), - "'test_ordered' changes from ordinal to nominal" -) + expect_error( + write_vc(test_data, file = "error", root = root, sorting = 1), + "sorting is not a character vector" + ) + expect_error( + write_vc(test_data, file = "error", root = root, sorting = "junk"), + "All sorting variables must be available" + ) + expect_false(any(file.exists(git2rdata:::clean_data_path(root, "sorting")))) + expect_warning( + write_vc(test_data, file = "error", root = root, sorting = character(0)), + "No sorting applied" + ) + expect_warning( + output <- + write_vc(test_data, file = "sorting", root = root, sorting = "test_factor"), + "Sorting on 'test_factor' results in ties" + ) + expect_is(output, "character") + expect_true(all(file.exists(git2rdata:::clean_data_path(root, "sorting")))) + expect_warning( + write_vc(test_data, file = "sorting", root = root, + sorting = c("test_factor", "test_Date"), strict = FALSE), + "The sorting variables changed" + ) + expect_error( + suppressWarnings( + write_vc(test_data, file = "sorting", root = root, sorting = "test_factor") + ), + "The sorting variables changed" + ) + test_changed <- test_data + test_changed$junk <- test_changed$test_character + expect_error( + suppressWarnings(write_vc(test_changed, file = "sorting", root = root)), + "New data has a different number of variables" + ) + test_changed$test_character <- NULL + expect_error( + suppressWarnings(write_vc(test_changed, file = "sorting", root = root)), + "New variables: junk" + ) + test_changed <- test_data + test_changed$test_character <- factor(test_changed$test_character) + expect_error( + suppressWarnings(write_vc(test_changed, file = "sorting", root = root + )), + "Change in class: 'test_character' from character to factor" + ) + expect_error( + suppressWarnings( + write_vc(test_data, file = "sorting", root = root, sorting = "test_logical") + ), + "The sorting variables changed" + ) + test_changed <- test_data + test_changed$test_ordered <- factor( + test_changed$test_ordered, + levels = levels(test_changed$test_ordered), + ordered = FALSE + ) + expect_error( + suppressWarnings(write_vc(test_changed, file = "sorting", root = root + )), + "'test_ordered' changes from ordinal to nominal" + ) -test_no <- test_data -test_no$test_ordered <- NULL -expect_is( - output <- write_vc( - x = test_no, file = "no_ordered", root = root, sorting = "test_Date" - ), - "character" -) -sorted_test_no <- sorted_test_data -sorted_test_no$test_ordered <- NULL -expect_equal( - stored <- read_vc(file = "no_ordered", root = root), - sorted_test_no, - check.attributes = FALSE -) -for (i in colnames(stored)) { + test_no <- test_data + test_no$test_ordered <- NULL + expect_is( + output <- write_vc( + x = test_no, file = "no_ordered", root = root, sorting = "test_Date" + ), + "character" + ) + sorted_test_no <- sorted_test_data + sorted_test_no$test_ordered <- NULL expect_equal( - stored[[i]], - sorted_test_no[[i]], - label = paste0("stored$", i), - expected.label = paste0("sorted_test_data$", i) + stored <- read_vc(file = "no_ordered", root = root), + sorted_test_no, + check.attributes = FALSE ) -} + for (i in colnames(stored)) { + expect_equal( + stored[[i]], + sorted_test_no[[i]], + label = paste0("stored$", i), + expected.label = paste0("sorted_test_data$", i) + ) + } -file.remove(list.files(root, recursive = TRUE, full.names = TRUE)) + file.remove(list.files(root, recursive = TRUE, full.names = TRUE)) +}) test_that( "meta() works on complex", { diff --git a/tests/testthat/test_b_prune.R b/tests/testthat/test_b_prune.R index 27173b4e..f76fc047 100644 --- a/tests/testthat/test_b_prune.R +++ b/tests/testthat/test_b_prune.R @@ -1,74 +1,74 @@ -context("rm_data & prune_meta") +test_that("rm_data & prune_meta", { + expect_error(rm_data(root = 1), "a 'root' of class numeric is not supported") + expect_error(prune_meta(root = 1), "a 'root' of class numeric is not supported") + expect_error(list_data(root = 1), "a 'root' of class numeric is not supported") -expect_error(rm_data(root = 1), "a 'root' of class numeric is not supported") -expect_error(prune_meta(root = 1), "a 'root' of class numeric is not supported") -expect_error(list_data(root = 1), "a 'root' of class numeric is not supported") + root <- tempfile(pattern = "git2rdata-prune") + root <- normalizePath(root, winslash = "/", mustWork = FALSE) + expect_error(rm_data(root, "."), root) + expect_error(prune_meta(root), root) + dir.create(root) + expect_null(prune_meta(root, path = "junk")) + write_vc(test_data, file = "test", root = root, sorting = "test_Date") + write_vc( + test_data, file = "a/verbose", root = root, sorting = "test_Date", + optimize = FALSE + ) -root <- tempfile(pattern = "git2rdata-prune") -root <- normalizePath(root, winslash = "/", mustWork = FALSE) -expect_error(rm_data(root, "."), root) -expect_error(prune_meta(root), root) -dir.create(root) -expect_null(prune_meta(root, path = "junk")) -write_vc(test_data, file = "test", root = root, sorting = "test_Date") -write_vc( - test_data, file = "a/verbose", root = root, sorting = "test_Date", - optimize = FALSE -) + current <- list.files(root, recursive = TRUE) + expect_identical(rm_data(root = root, path = "a"), "a/verbose.csv") + expect_identical( + list.files(root, recursive = TRUE), + current[-grep("^.*/.*\\.csv", current)] + ) -current <- list.files(root, recursive = TRUE) -expect_identical(rm_data(root = root, path = "a"), "a/verbose.tsv") -expect_identical( - list.files(root, recursive = TRUE), - current[-grep("^.*/.*\\.tsv", current)] -) + current <- list.files(root, recursive = TRUE) + expect_identical(prune_meta(root = root, path = "."), "a/verbose.yml") + expect_identical( + list.files(root, recursive = TRUE), + current[-grep("^.*/.*", current)] + ) -current <- list.files(root, recursive = TRUE) -expect_identical(prune_meta(root = root, path = "."), "a/verbose.yml") -expect_identical( - list.files(root, recursive = TRUE), - current[-grep("^.*/.*", current)] -) + file.remove(file.path(root, "test.yml")) + current <- list.files(root, recursive = TRUE) + expect_identical(rm_data(root, path = "."), character(0)) + expect_identical(list.files(root, recursive = TRUE), current) -file.remove(file.path(root, "test.yml")) -current <- list.files(root, recursive = TRUE) -expect_identical(rm_data(root, path = "."), character(0)) -expect_identical(list.files(root, recursive = TRUE), current) + write_vc(test_data, file = "test1", root = root, sorting = "test_Date") + junk <- write_vc(test_data, file = "test2", root = root, sorting = "test_Date") + write_vc(test_data, file = "a/test2", root = root, sorting = "test_Date") + meta_data <- yaml::read_yaml(file.path(root, junk[2])) + meta_data[["..generic"]] <- NULL + yaml::write_yaml(meta_data, file = file.path(root, junk[2])) + yaml::write_yaml(meta_data, file = file.path(root, "a", junk[2])) + expect_warning( + list_data(root = root, path = ".", recursive = FALSE), + "Invalid metadata files found.*:\ntest2" + ) + expect_warning( + list_data(root = root, path = ".", recursive = TRUE), + "Invalid metadata files found.*:\na/test2\ntest2" + ) + current <- list.files(root, recursive = TRUE) + expect_warning( + rm_data(root = root, path = "."), + "Invalid metadata files found.*:\na/test2\ntest2" + ) + expect_identical(current[current != "test1.tsv"], + list.files(root, recursive = TRUE)) + file.remove(file.path(root, "test2.tsv")) + current <- list.files(root, recursive = TRUE) + expect_warning( + prune_meta(root = root, path = "."), + "Invalid metadata files found.*:\ntest2" + ) + expect_identical(current[current != "test1.yml"], + list.files(root, recursive = TRUE)) -write_vc(test_data, file = "test1", root = root, sorting = "test_Date") -junk <- write_vc(test_data, file = "test2", root = root, sorting = "test_Date") -write_vc(test_data, file = "a/test2", root = root, sorting = "test_Date") -meta_data <- yaml::read_yaml(file.path(root, junk[2])) -meta_data[["..generic"]] <- NULL -yaml::write_yaml(meta_data, file = file.path(root, junk[2])) -yaml::write_yaml(meta_data, file = file.path(root, "a", junk[2])) -expect_warning( - list_data(root = root, path = ".", recursive = FALSE), - "Invalid metadata files found.*:\ntest2" -) -expect_warning( - list_data(root = root, path = ".", recursive = TRUE), - "Invalid metadata files found.*:\na/test2\ntest2" -) -current <- list.files(root, recursive = TRUE) -expect_warning( - rm_data(root = root, path = "."), - "Invalid metadata files found.*:\na/test2\ntest2" -) -expect_identical(current[current != "test1.tsv"], - list.files(root, recursive = TRUE)) -file.remove(file.path(root, "test2.tsv")) -current <- list.files(root, recursive = TRUE) -expect_warning( - prune_meta(root = root, path = "."), - "Invalid metadata files found.*:\ntest2" -) -expect_identical(current[current != "test1.yml"], - list.files(root, recursive = TRUE)) - -file.remove( - list.files(root, recursive = TRUE, full.names = TRUE) -) -file.remove( - list.files(root, recursive = TRUE, include.dirs = TRUE, full.names = TRUE) -) + file.remove( + list.files(root, recursive = TRUE, full.names = TRUE) + ) + file.remove( + list.files(root, recursive = TRUE, include.dirs = TRUE, full.names = TRUE) + ) +}) diff --git a/tests/testthat/test_b_special.R b/tests/testthat/test_b_special.R index 61d0608b..8a99c43c 100644 --- a/tests/testthat/test_b_special.R +++ b/tests/testthat/test_b_special.R @@ -1,81 +1,84 @@ -context("handle special characters") -root <- tempfile(pattern = "git2rdata-special") -dir.create(root) -ds <- data.frame( - a = c( - "a", "a b", - "a\tb", "a\tb\tc", "\ta", "a\t", - "a\nb", "a\nb\nc", "\na", "a\n", - "a\"b", "a\"b\"c", "\"b", "a\"", "\"b\"", - "a'b", "a'b'c", "'b", "a'", "'b'", - "a b c", "\"NA\"", "'NA'", NA, - "\U00E9", "&", "\U00E0", "\U00B5", "\U00E7", "€", "|", "#", "@", "$" - ), - stringsAsFactors = FALSE -) -expect_is( - output <- write_vc(ds, "character", root, sorting = "a"), - "character" -) -expect_equal( - names(output)[1], - "1d135a85dc9beff3223d6c79f0d8975b559afca7" -) -old_locale <- git2rdata:::set_c_locale() -dso <- ds[order(ds$a), , drop = FALSE] # nolint -git2rdata:::set_local_locale(old_locale) -expect_equal( - junk <- read_vc("character", root), dso, check.attributes = FALSE -) -expect_identical( - names(output), - names(attr(junk, "source")) -) -expect_is( - write_vc(ds, "character2", root, sorting = "a", optimize = FALSE), - "character" -) -expect_equal( - junk <- read_vc("character2", root), dso, check.attributes = FALSE -) -z <- rbind(ds, "NA") -z$a <- factor(z$a) -expect_is( - suppressWarnings(write_vc(z, "factor", root, sorting = "a")), - "character" -) -expect_equal( - read_vc("factor", root), - z[order(z$a), , drop = FALSE], # nolint - check.attributes = FALSE -) +test_that("handle special characters", { + root <- tempfile(pattern = "git2rdata-special") + dir.create(root) + ds <- data.frame( + a = c( + "a", "a b", + "a\tb", "a\tb\tc", "\ta", "a\t", + "a,b", "a,b,c", ",a", "a,", + "a;b", "a;b;c", ";a", "a;", + "a\nb", "a\nb\nc", "\na", "a\n", + "a\"b", "a\"b\"c", "\"b", "a\"", "\"b\"", + "a'b", "a'b'c", "'b", "a'", "'b'", + "a b c", "\"NA\"", "'NA'", NA, + "\U00E9", "&", "\U00E0", "\U00B5", "\U00E7", "€", "|", "#", "@", "$" + ), + stringsAsFactors = FALSE + ) + expect_is( + output <- write_vc(ds, "character", root, sorting = "a"), + "character" + ) + expect_equal( + names(output)[1], + "e8a6734d740941f347bbc21e3227b4a6392b6562" + ) + old_locale <- git2rdata:::set_c_locale() + dso <- ds[order(ds$a), , drop = FALSE] # nolint + git2rdata:::set_local_locale(old_locale) + expect_equal( + junk <- read_vc("character", root), dso, check.attributes = FALSE + ) + expect_identical( + names(output), + names(attr(junk, "source")) + ) + expect_is( + write_vc(ds, "character2", root, sorting = "a", optimize = FALSE), + "character" + ) + expect_equal( + junk <- read_vc("character2", root), dso, check.attributes = FALSE + ) + z <- rbind(ds, "NA") + z$a <- factor(z$a) + expect_is( + suppressWarnings(write_vc(z, "factor", root, sorting = "a")), + "character" + ) + expect_equal( + read_vc("factor", root), + z[order(z$a), , drop = FALSE], # nolint + check.attributes = FALSE + ) -old_locale <- git2rdata:::set_c_locale() -ds$a <- factor(ds$a) -git2rdata:::set_local_locale(old_locale) -expect_is( - output <- write_vc(ds, "factor2", root, sorting = "a", optimize = FALSE), - "character" -) -expect_equal( - junk <- read_vc("factor2", root), - ds[order(ds$a), , drop = FALSE], # nolint - check.attributes = FALSE -) -expect_equal( - names(output)[1], - "1d135a85dc9beff3223d6c79f0d8975b559afca7" -) -expect_identical( - names(output), - names(attr(junk, "source")) -) + old_locale <- git2rdata:::set_c_locale() + ds$a <- factor(ds$a) + git2rdata:::set_local_locale(old_locale) + expect_is( + output <- write_vc(ds, "factor2", root, sorting = "a", optimize = FALSE), + "character" + ) + expect_equal( + junk <- read_vc("factor2", root), + ds[order(ds$a), , drop = FALSE], # nolint + check.attributes = FALSE + ) + expect_equal( + names(output)[1], + "5fd788c095d847d8e1a8386f621ee11fc69cd9a5" + ) + expect_identical( + names(output), + names(attr(junk, "source")) + ) -yaml_file <- yaml::read_yaml(file.path(root, "factor2.yml")) -yaml_file[["..generic"]][["data_hash"]] <- "zzz" -yaml::write_yaml(yaml_file, file.path(root, "factor2.yml")) -expect_warning(read_vc("factor2", root = root), - "Mismatching data hash. Data altered outside of git2rdata.") + yaml_file <- yaml::read_yaml(file.path(root, "factor2.yml")) + yaml_file[["..generic"]][["data_hash"]] <- "zzz" + yaml::write_yaml(yaml_file, file.path(root, "factor2.yml")) + expect_warning(read_vc("factor2", root = root), + "Mismatching data hash. Data altered outside of git2rdata.") -file.remove(list.files(root, recursive = TRUE, full.names = TRUE)) + file.remove(list.files(root, recursive = TRUE, full.names = TRUE)) +}) diff --git a/tests/testthat/test_c_git.R b/tests/testthat/test_c_git.R index efa45d89..e06158f5 100644 --- a/tests/testthat/test_c_git.R +++ b/tests/testthat/test_c_git.R @@ -1,4 +1,4 @@ -context("write_vc() and read_vc() on a git-repository") +test_that("write_vc() and read_vc() on a git-repository", { root <- tempfile(pattern = "git2rdata-git") dir.create(root) root <- git2r::init(root) @@ -270,3 +270,4 @@ expect_identical( "staged.tsv", "staged.yml", "untracked.tsv", "untracked.yml") ) git2r::reset(git2r::last_commit(root), reset_type = "hard", path = ".") +}) diff --git a/tests/testthat/test_e_non_ascii.R b/tests/testthat/test_e_non_ascii.R index 4d9c7b33..c10ae3d1 100644 --- a/tests/testthat/test_e_non_ascii.R +++ b/tests/testthat/test_e_non_ascii.R @@ -1,4 +1,3 @@ -context("check writing non ASCII characters") root <- tempfile("git2rdata-empty-label") dir.create(root) characters <- data.frame(a = c("€$£ @&#§µ^ ()[]{}|²³<>/\\*+- ,;:.?!~", From a8a2d3a4ed8c3d23357c15704ad17d890e546439 Mon Sep 17 00:00:00 2001 From: Thierry Onkelinx Date: Thu, 4 Nov 2021 15:57:26 +0100 Subject: [PATCH 06/18] update vignettes --- inst/efficiency/file_timings.rds | Bin 3600 -> 3522 bytes inst/efficiency/git_size.rds | Bin 1497 -> 1497 bytes inst/efficiency/read_timings.rds | Bin 3597 -> 3550 bytes vignettes/plain_text.Rmd | 12 ++++++++++-- 4 files changed, 10 insertions(+), 2 deletions(-) diff --git a/inst/efficiency/file_timings.rds b/inst/efficiency/file_timings.rds index 48bb68a128736b22c8b03d105a376be318810229..e4afcd8f486b448e7ee3197bdb7246ab26aa5b34 100644 GIT binary patch literal 3522 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 literal 3600 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 Wn3u6A#4F(I1@1q7YYbrK6aWAn)F=u7 diff --git a/inst/efficiency/git_size.rds b/inst/efficiency/git_size.rds index 0c14df9d3d41327c3aa9b0c545fe2037bddfcda1..57506227ef14b59424f0d239b637900abec0e085 100644 GIT binary patch literal 1497 zcmV;~1t$6*iwFP!0000016`JPaFkUP#Sf{Q>~6BXWOtKYk^sUGkR}L`UW}A6AwUEZ z60$HWDUhrIArrv}f|Q{O5-Bo>2qG$?h=_&3${```)|n z+;i@?Q_>|RDyc()5|bG!xpd;F?){|%v((`SVgd19o?Sc(cs58$lfhGXZzR8u@_a9) zw1Q1#?n8e6!FYmXU@qYRb2ktN@$8qFzeMJ&*Rwx4niKiWen|nz&YGkWDPt$tLdNqY=Lf_)B+C%6BaB~?JZ|Ez?C~xb zF}a77bs2FSd&J%wz<7n^pGDlk9=`Zq$sbACw-LFc>tV3N+-Im1e1SNMeXj=lnK5f} zNiJox=dq@EWOS8_gkr5uDua_@Bw*gKqG3ich?JgE;gOzSOm z`h|UYCyhJwdzH9U>ar1RF7@dy4X}cNckK&C?bDY@1Bb`wy1fkc4P!VitR*g%x;JnZ z_cX%yVDyoGR_c)nRu9%D4LwfmOrGRsKo3Uv8vY{@-h07^;Va2R97R2ir={NCaz1C7 z;AITBDJb<_0|syARH=x&n7I2T)G%!ySd}!EdYev4{htT>3=AEOe~svsE{)~c6^(eL zD>J~@XOIaj0>(ZiXvqwZg?qvFfU!p@c!r((i~;)uJ@WqK+riMs5NcxV0=7gdI|YWu zhA}oWmkH-n|0Lp{5&2-d(INYn$JS4mMxFrUeaj|k`XsRXrP0;o-U$qiRpAv2PQ0AQ z1a2KKRX+%Z=ZdJGiN|`EG!vg$$aU;!u=gYzo``Ti%kR=S@->IR(B^FFW<}!@ZU*Cg z8~Uwn0ox82aBL>pxo16hx1y8c=fK_qtCQxUKN}vOh?X*nz^Gd^0gT)yp~H;jl7sp* zp>^A*@Dt5nc2MStPm9-A(s^ z(YvbAxP>~q=~c_g!O8lW_{wq~j61J{2UoT<>l|DZ;%)li>S18`BGLqg4?XLpHT-sw z*KG2#J_1HhTSr~o4ylHET7RPlpqmZ&z&%r{qZiuntd}0O2_D?kpq@M0=r3Nhax0$n z7~vZq+8&}Wl}T-N@!ryihHUhatOum6XxxK;qWH;k<9_;JsjoJFmh(XoShKm6{6n=JZU z3to3{egLoU=j<$ey9jSOhyi-%0cz@_4vVRg6Kw_1`F*jR;H;JWocJyqJ`dsvANs{I za_)=w*F&7)hwr7a-upQi^*anFe)e4!t1bS^nJ68>Oa3>hANjfPZw~n!rTzhW!3uQk zLW4oHd2B5h`L4qME@~a5cOECN0GeJ6$8P)6fPc7@}GqjO*0DW=R_m5kp&kPS}x2qg&V5s8!3#=$DY3dLggwgum}JE+6wL7 literal 1497 zcmV;~1t$6*iwFP!0000016`JRY}8d0g-@Aw_UY_vXQl;2wonLyveg2XvPeLnP*4QA zj4(nM+ftxdpkkqdRD>V`B8Z3>L?jqQf+#^mlprDojZx#i#of3@^c#i{^^ceL-g)nL z-@W&oduOIvB^hKeX2_7mQdyHrihB)^Iyj_^pP8>_-pAO^sA057nYV(Q_-;7+1B`Q$ z@d2>uoPEUp7uFpm^Xts}IlGH_Afr~YId$nJb0_w9GLA})0dAMe z*LVwGjl^Q=E9H|Lb4=>g8EiS&5~&aInX7p}WeXoouS$LAfDwBa@@F!z4oLmBkz*I{ z!=0%JtX#VGG+3F`4gXBoGw+oK;=cu6dyE8o3G8=i@F2;WiBIIg%xoDU71V)EmU^YW z!=B}SsSrEX$EDun&rDt|?@7bXgZ+k|C&5mz?k*LX;dT^$&gKs9SsSEc;;?PPPgB~y zm0XXi0{cqpk4+2qY{WkXuI=2tZVVXqY-nRFd9tsO25{GkJsWq%%vOD!yh}^D18N;T}3?XZREy*Hl|$! zYm|l)x2+8fZA^#TZ0rtw+e2y&(=WsHM`_9{?k@#mH4R(`M4rk6I z(rD_=z6uP@SN?#OsmC$!YlqwHFQnO=yJiwM{5mLNXFn-91&n$dhkeJ5VCZ))IdP-I z@lS%S27~V^;&h|^3FObg-i2Q^@Z;VoP5cu3#*tmRVu+o_98I2Y#=@lyiLgm zJIpy;Hf{k!!==<>_8(yLrG?bK=WFsqd``6EhRY^&xUlEtd=+}|x`?tm8$_5#%T7IYh=Pu0)DUwU1D?;rNjZzf3ddCyDT2Tn=b3&D240k!Q#J3+s+ zV;&g2dm&o)Vk1bM?`)UC#JuQjVi=l!Zx@_|H%g0%+lN+yaJTzgF!a@&1IBrXZ}!k@ zB5=5b`26&y(0XYv97WL6vT0!S&=CIaqYp&TNXt)P@DlDJ?LSEG>kU`*20y(i%v%SD zIfwUJ!2`q+20uuSbEvl!i~zkP!tcZ(-j4Ei8+|5lHyAZ^nBSmiJ^VS+{W$WNbmS2* z`qb)F9xf_jc~ zcO4oJ(MzIv(n<0a=l5;>SulE2l)iL|T*m2_8}KPa4Mz`2r|CIyINXT;A$n5`f6iPZ z<)ViN(RkPhMxCFfujKNZx_MNZml)bO2Zy=*c5Ok!Ve}q@hx2dH|LOf((NGwD#IbdO z-kApn+jd4-x!}qm6gbc1LP>2Qf$RFBffO(hHQKOj%Mt{FO<< z5UErOPW=I>{NEKbSE|dZ5~N(-%lBND?xxRe3+j>$iTsAL@_C8Nkmp}eetk_{L!$EX ziK)JPnPKl zedsb23PpksM$s#pCo#(Cc@oWun8J*NV zXgxomXFtHlPY}W_%E&4iAaW%Y!YSzxZr37eKz2QLLBsl$uppI*>E=Li0`>2uoCttMOZV=COAj!+z+!8&{5kLnpRU#oSqSFpIc+_RVesk_wugU6 z2QgQ6Tl9$&;N6}gu-A(Kwcy4z#(^auY+uZ_nid4kR;y@zBN-U)pMI*_BpD{~G1L}%L%Py`q2j9Z0-%@TCJhEif-kqxhazLDL*~FI{{+f5HQJyCp5O z3Fri2UwF76(12Yv4L1jQw-gYiLe)G(Q?q0_wwWa-2l%=0ue@ z@VFV)H>5rc>MX_Hk1`VA-Y+*?G2I`;c?0DqHSVDLE@l^alz?~DI;K#642e&cCwK}EvSa-4}*^u zgZ}$6v3@5h&{SQO&BtXyQ@(5auEG?sl(vrFO(*kpv5B`{^$h3+?^=5*tw20k>9y$o zCGeW2oD6m$K&)*t%`5r{-eK(<+%r)mepWSgyDbEDW_Go+WDIDu{_?P-OQ7uz>J3WW z4eFI9pCYM$z>E8BTA@i0n8~*{1UQ>~&mK_68iWkwBaR+c&@PVn_nmS5-r28jc5h zPyV#|-rC?_IaKob!Uu3?NuIUcy9*rV&3;$21n^{{tYxa?z&`k4Quk_k(A#^xWF04h ziHhMTd__=sMKw29 z#>RlItFp4=SSvVAHzze}B|#%&sebg8O3<}qb~dV+fxh2==@+xR@F*zo(T+v+HVJT^^WkW_I}VRGM1#9@`U)l=Q{QI0nlm+u`3IWq?={f?`a zOtP+hBYt^N#{}o~4jq&1Jg}D@X`j~936010TGqu{VA{IRoN4GlKA{QAu{sPNoe`#h`ZmK3~GdS#qj`N&;@7B%J#p3=8T#d zbQ%-%yM|2#Da~N6=#}cbG6wY8={pZf&_N50TC*s(AN1P;K3i5Kf^}H0HvW_z+2`hJ zl=t6ppc-i5}$65URLv*w!YptKEG3e#*q`c8%BFzZi)=tJQC?PlU~)DMJ< zDeIx?8-ysH=g)MmfzJk$Sq91P5sY<~I=>DgqHBiME9c438k~60-C_f%#IwosxNqTV z{Bk5w2nm;0zdA7p)YY^@MSqcXd8;@0uJw6%&G3u6rLYC;L*>_}D{6q~R_)mO+WE!~qpRI1E;OK5u2O2?P_$cBihG2%ggxeVOHBK&@7f?Ay}@Uch`V+Gcf&zso`UxTmpYuS@zanQ}L4EUdu12xU|`BAlOaE1pSO22;(&5Q3NGAJ?d zKAh8d_V@|VzP2w5up9~l{)!p|FE4l5%M4X8Pula#l%rs@VCtu(cFhQ@pHjO+{ylsp zD{P%=bRcMHDw%H925n=z=+k%i!HfCyN%k!=f9)-wT{hH%Z@Y$aM{7c^QiwJ&PZsb3}0O4y{cDdOVT-`IBYn1jP zxL|D6CY1s(UN=6cOH@Pi*=*Sx+ep9j*ik1v_d(Na>)Fed`$5z`uiZy*LDbkOrG4Uq zAG+--|5-RR@174#9gv2mW7cqWmNK|2)@SKFZvYXgqi<)T2?ntv$UJ)?xEP;E%pM}U>evhfR|mc@C?|2s{C3_D zX_r&u3|c{@8z+jFoJPDBJ4zo1kEuR zK1(Uex}gNL-)idz_uPkI&^?MbE(3z<%)pm6=?GI9QxI>{4qk{!Y4zZ3@D<&fr`wY8 z=p^!Nw%ft8McU6zAs773t7gchiy_$MjoUx5bnv_S4CBm}!fMV)^~oVD+MBY|{2Fv3 z%Cc=$mVxLja!#PhLJ;cuw=|Ul-ko>$Vml`yO#kH5Z1-oFE1PaRRF;6qjnTSOb&DZ5 z^>}mTr9t>A(G`4G+=J)#+}C~=e+O^PQ|UW?9w4IiE;z0#NAS5M;^>N6u!3gNGA{@~ z)3coMqU0cW55i*_cAJCNCFXo>N;qhyL)q)jnuF=|X?D@uEHK>Wl0PaYpyjWv(D_LI zA31azMQc=prhP3yYoiSW3;o|U|1A$eMDBoMttrBCvwXKR4nueS2&}mX^Gd#q`#KLtSd#VnhOwYk{v=|#qR|Bv`p&B`u+un-1`Em4k9m*CrKuC4a)3c`-1NGP}# z!fWG3StZ5>XoaiKJ<}D5c{zR&$5JMMIuI$FNjC&PEa|#KfDAlXUayjTJHayATs*r( z6`Ve~2FuG+;aTxO?1HH#h(F`!zLj+YH#w(OUWEYdWJ|-rsw~lKORx{u|HrjO{5w<9 zKa$7)L6bKi(YuZ`KP1BNhoJn3;1=>VJp6_CZK3$Eh{EyQ^?v*iU;kjgbzC1l`^PE}07|10EdT%j literal 3597 zcmV+o4)XCIiwFP!000001GQLrIMi$VpD~u`MJc=?%PgctNsFZ86WUcoWf{z13bTEU zB}&q!D2evB6>Ug$QaPj}3Xv2lTC|)JCDmy=BjWv*I@i44oZo-HxvsgM=eg%u?)&q( zm**Kz4H}KcpfObGG=>Va?w-{ie*cdtx|?9;b?m(#x;Os@HRkKXYQneum;vceq2`O4qNbcM09 z!W@Re8pXAA%2&CMz7p+!_3CRs#jm0+bcMcRt3>-*UnSblx-tG?9fNWp4Q|AAZzc480 zQf;B;Icm%);zPAbk#9wv6wjsNL*=e7-uj9IDo5YQyF~4)WP!8$_o3$3p?_4Mm6V7G5q}qI{}4tAWC7A|rR;0z`dY%2;%PrV zUyo<>{68J|Kenj^iumC_O#9p5CH%k$sqDLMT3<^leCThk-CooW-U;i}o4ErJ>S&={ zWDy8Lcw*_k3Ky_e#N9HCJO)4ZfwzB3ogr+N2yBDDK&+Rxd!sx6gjan^Pth|-4@>qO zwWvU=ZfXgWUtfVmMOebZArle$Fsb66aT%lu zuDYe_Y;d>jU|2sr4ld`ts!>cH{G5#~&!0I3@2d9X31_cDqGYhF`DG5c2jn$*i|)ZM zXm7Uez{imM;c?ROfChMaB_(%mtOqk^irUpVevn-A(VLue9IOH6*;~#A!oSO6UDk|b z5V!t-^~_}my`5ZGe^dpdDtoMEwjP6Q7QN?e{85;1E7DMX(E}^})fI!gT42G~YrA!$ z7HqlPILYcVWFtf;YS)!QluvUqe7F|;`DJBcTMEGa&--x>8M%<;l%4edY!Ba#S-*ZR zkV5!Y(@k#R34W~lh0w!=2zBp2wkA;kvnt~KMrI6za*215)+BIKT6#R~JiuN#UFtgI z1Bm|bOk<2(!1Q+auzUF%%p$iZml!)i7``L*x=|d$Hjj^;H`*5d7ftQP4*v*Yxpu=d zPi2U9rY{cOmjQt<>*Av-8pIqk=Z)o>V5+nPSyYrEbSZ07($k?34pL+1OT`e;cP@JU z*%%hGnxze^PQrX$RC4E?qX?MbvB*lj2VsfoS-YkjB<-7(Zc|bNKm9`6$TW9|hdw^J zGyXBeTA?BXDVb4ekUC4md!Jngziq(c+UQXTJ2BMl zZ1-}o32t_0NiNtX>I+haXo3@4QF~#QJ|x^$j(*EZ2-9pES&nQFi6$4^mQRQ8YxP?@ z{yhSOb)1gz^GI;kT#xCiU#jc?7@!FBM_DnkXduI6hbB< zf)=*Hra|c|!3f(RE57eiiZCxXH@@Lva88BWB{?Kv?8T`OzwG=4%L2?-ZN1xokQx0L zj7zKFosaC7pU=UxAtrFo$j$JxwAL(E2?Fcv)=byBc(8}Bvj2??1j|&8t%o zr{>%BoSqNn7+nLWXbz${>-0CMIKsRB$hk2!w-Lg&&ufmfgLIPmi{$HhFmGwD96IFw}0S$M2&>S_p&Y~?a0=#qR~6G}AMV3z9c)-Ujenb&kfvC~Zu z-2L^1Uv`0bsoKu=u>{LTBh@{o6QK#lCU%S4;D3AR{a;1K@T0AlgoIjvGd4Lv@OU)B z;umgkEhzwpp207kSOC9^TX^x|q@Q1^x%I5l2`neiQ&-|h)1Sk?}0f>Mn5Vg`CKm8Jf|Ma2g~(8=xm0h zFs8fmpdpBT?_X6<-+_>hO(WO7N(9e?9yv$lH;Av~d^uaq0k7*}Z1NBxd!t%+9RuMvEo|C~8z6dKW^$!u2~0bcIm1py!kh1N%H6>c ze(_(^jfl)Mw}%FoZ-eM^wDto%JxGh*)S0|%278p#Y!jRhTceZy~hgm-3kENQAGdi4YYm1xp}4q_@czq0a5{5hlUlSy~TP&j9@H z9(CnU4Mf14>)Ne3t_V{}blQM_9e%ci)8)feye0be)O4?X(qsE-e4)cW2aJRqer|o2% zP1C6E-vp*z)ZG?r zf#23tGtQv%5S`jGOY-nOSkYQHj_!Q|vAffmrio;|lw2;aT$zl}$-Vx}3+*akW$c4U zXVAIgP%SWByxcuSSup)_+%sm%UGQeq%nXq7LF}^5IDB6ZlF;!?(;{0C0p)je%1=Pl z*7%=mS4&{FVZEECZ4Jy`3TBP|KPcrl39^&iqLMv-+z$1^RM%!`#CeyQtkfU_#e#kgo4 znXlENRxjBPt_ew#e?ADPrhRI)1m|Jxw>-g09=lefzY&B}CpUE+?0~PWxB%TX;O%>! zvzJBs>)UiJ?#BUPRg=7HWFwd_iQ(fC-a_h0-ezRvJz;g{Obzer;JT8xxarv-_zT9% z)l(r|v}}~0nh~rslp;CP`;+z7^SbG&Oh|Xilr6nQ@MnGOz5k8=9CyCh8KTB*y8~^{ zf}cCW^4aZ1NCub9ZF$K<=;lwI>ZW#JU+G;h>YOoW?dE}yR9h7DvmGEhmN42Yk-T^I zxBcy;bpyD~@`A#2H?lsf<_vyi3PM}OpDgI`Q+K$eE+zBujT7_ZE_;9(c;7d1<{$_k ziN4*BnGOaAe~+pJ&j>dBMO=tK7>~2jjDU4|%I#}01IT>;E`0u)kMK(gDwR(>2i|>q z=VBKUKVrez#=@x(8N1|VcyU3@U9M`H6NXW#Z7;4S=wX@JiPAe+We6MO@U8xa+Bj9d zzSap*mzm3}sZH>omSt9bHvr7S?3L}pEO1rlee)m5{Hog78rmSvzRDT?Hyf|1 z)xtV)L@#nQ95D9t5Gv&L%pCE7IOE*9~PM)7$>PvmLNcdvGk7tY0kN*Y* z(S+v^tLjq;e0D&YhSj?9Rf4swj_z`@o TB{IJF+w1MWK$-92;1mD=x|1O^ diff --git a/vignettes/plain_text.Rmd b/vignettes/plain_text.Rmd index e6f6b4ce..379efeab 100644 --- a/vignettes/plain_text.Rmd +++ b/vignettes/plain_text.Rmd @@ -132,14 +132,22 @@ print_file("first_test.yml", path) ## Storing Verbose -Adding `optimize = FALSE` to `write_vc()` will keep the raw data in a human readable format. The metadata file is slightly different. The most obvious is the `optimize: no` tag and the different hash. Another difference is the metadata for POSIXct and Date classes. They will no longer have an origin tag but a format tag. +Adding `optimize = FALSE` to `write_vc()` will keep the raw data in a human readable format. +The metadata file is slightly different. +The most obvious is the `optimize: no` tag and the different hash. +Another difference is the metadata for POSIXct and Date classes. +They will no longer have an origin tag but a format tag. + +Another important difference is that we store the data file as comma separated values instead of tab separated values. +We noticed that the csv file format is more easily recognised by a larger audience as a data file. + ```{r write_verbose} write_vc(x = x, file = "verbose", root = path, optimize = FALSE, strict = FALSE) ``` ```{r manual_verbose_data} -print_file("verbose.tsv", path, 10) +print_file("verbose.csv", path, 10) print_file("verbose.yml", path) ``` From 88024bd3be79fb609eb2bb4b2a60ce8830e99076 Mon Sep 17 00:00:00 2001 From: Thierry Onkelinx Date: Fri, 5 Nov 2021 15:12:15 +0100 Subject: [PATCH 07/18] upgrade_data() handles the new format for verbose data --- R/upgrade_data.R | 48 ++++++- inst/testthat/optimized_0_0_4.tsv | 48 +++++++ inst/testthat/optimized_0_0_4.yml | 11 ++ inst/testthat/optimized_0_3_1.tsv | 48 +++++++ inst/testthat/optimized_0_3_1.yml | 11 ++ inst/testthat/optimized_0_4_0.tsv | 48 +++++++ inst/testthat/optimized_0_4_0.yml | 11 ++ inst/testthat/verbose_0_0_4.tsv | 48 +++++++ inst/testthat/verbose_0_0_4.yml | 11 ++ inst/testthat/verbose_0_3_1.tsv | 48 +++++++ inst/testthat/verbose_0_3_1.yml | 11 ++ inst/testthat/verbose_0_4_0.csv | 48 +++++++ inst/testthat/verbose_0_4_0.yml | 11 ++ tests/testthat/test_e_upgrade.R | 230 +++++++++++++++--------------- 14 files changed, 510 insertions(+), 122 deletions(-) create mode 100644 inst/testthat/optimized_0_0_4.tsv create mode 100644 inst/testthat/optimized_0_0_4.yml create mode 100644 inst/testthat/optimized_0_3_1.tsv create mode 100644 inst/testthat/optimized_0_3_1.yml create mode 100644 inst/testthat/optimized_0_4_0.tsv create mode 100644 inst/testthat/optimized_0_4_0.yml create mode 100644 inst/testthat/verbose_0_0_4.tsv create mode 100644 inst/testthat/verbose_0_0_4.yml create mode 100644 inst/testthat/verbose_0_3_1.tsv create mode 100644 inst/testthat/verbose_0_3_1.yml create mode 100644 inst/testthat/verbose_0_4_0.csv create mode 100644 inst/testthat/verbose_0_4_0.yml diff --git a/R/upgrade_data.R b/R/upgrade_data.R index 32f79de5..3e942be4 100644 --- a/R/upgrade_data.R +++ b/R/upgrade_data.R @@ -46,8 +46,6 @@ upgrade_data.character <- function( assert_that(is.string(root), is.flag(verbose), noNA(verbose)) root <- normalizePath(root, winslash = "/", mustWork = TRUE) if (missing(file)) { - assert_that(missing(file), - msg = "specify either 'file' or 'path'") assert_that(is.string(path)) full_path <- normalizePath(file.path(root, path), winslash = "/", mustWork = TRUE) @@ -64,7 +62,7 @@ upgrade_data.character <- function( target <- remove_root(file = file["meta_file"], root = root) target <- gsub(".yml", "", target) if (!has_name(meta_data, "..generic")) { - message(target, "is not a git2rdata object") + message(target, " is not a git2rdata object") return(target) } assert_that( @@ -79,17 +77,51 @@ upgrade_data.character <- function( } return(target) } - if (current >= package_version("0.1.0.9001")) { + if ( + current >= package_version("0.1.0.9001") && + meta_data[["..generic"]][["optimize"]] + ) { assert_that( has_name(meta_data[["..generic"]], "optimize"), msg = paste(target, "has corrupt metadata, optimize flag not found.") ) - stopifnot(meta_data[["..generic"]][["optimize"]]) if (verbose) { message(target, " already up to date") } return(target) } + if (!meta_data[["..generic"]][["optimize"]]) { + na_string <- meta_data[["..generic"]][["NA string"]] + details <- meta_data[names(meta_data) != "..generic"] + col_names <- names(details) + col_classes <- vapply(details, "[[", character(1), "class") + col_type <- c( + character = "character", factor = "character", integer = "integer", + numeric = "numeric", logical = "logical", Date = "Date", + POSIXct = "character", complex = "complex" + ) + old <- read.table( + file = file["raw_file"], header = TRUE, sep = "\t", quote = "\"", + dec = ".", numerals = "warn.loss", na.strings = na_string, + colClasses = setNames(col_type[col_classes], col_names), + comment.char = "", + stringsAsFactors = FALSE, fileEncoding = "UTF-8" + ) + file.remove(file["raw_file"]) + file["raw_file"] <- gsub("\\.tsv$", ".csv", file["raw_file"]) + for (i in which(col_type[col_classes] == "character")) { + x <- gsub("\\\"", "\\\"\\\"", old[[i]]) + to_escape <- grepl("(\"|,|\n)", x) + x[to_escape] <- paste0("\"", x[to_escape], "\"") + x[is.na(x)] <- na_string + old[[i]] <- x + } + write.table( + x = old, file = file["raw_file"], + append = FALSE, quote = FALSE, sep = ",", eol = "\n", na = na_string, + dec = ".", row.names = FALSE, col.names = TRUE, fileEncoding = "UTF-8" + ) + } meta_data[["..generic"]][["git2rdata"]] <- NULL meta_data[["..generic"]][["data_hash"]] <- NULL } @@ -124,6 +156,10 @@ upgrade_data.git_repository <- function( if (!stage) { return(file) } - add(root, path = paste0(file, ".yml"), force = force) + + file <- gsub("^\\./", "", file) + add(root, path = sprintf("%s.csv", file), force = force) + add(root, path = sprintf("%s.tsv", file), force = force) + add(root, path = sprintf("%s.yml", file), force = force) return(file) } diff --git a/inst/testthat/optimized_0_0_4.tsv b/inst/testthat/optimized_0_0_4.tsv new file mode 100644 index 00000000..b6e8a277 --- /dev/null +++ b/inst/testthat/optimized_0_0_4.tsv @@ -0,0 +1,48 @@ +a letters +" a" x +" +a" x +,a f +;a z +'b z +'b' w +'NA' t +"""b" p +"""b""" t +"""NA""" k +@ d +& y +# j +| r +$ p +€ a +a y +à l +"a " o +"a b" c +"a b c" g +"a +" w +"a +b" k +"a +b +c" g +a b x +a b c f +a, b +a,b e +a,b,c z +a; j +a;b w +a;b;c v +a' r +a'b t +a'b'c g +"a""" d +"a""b" h +"a""b""c" l +ç i +é m +µ v +NA w diff --git a/inst/testthat/optimized_0_0_4.yml b/inst/testthat/optimized_0_0_4.yml new file mode 100644 index 00000000..510b349e --- /dev/null +++ b/inst/testthat/optimized_0_0_4.yml @@ -0,0 +1,11 @@ +..generic: + git2rdata: 0.0.4 + optimize: yes + NA string: NA + sorting: a + hash: b2ebf427feaafc5f4dea639777f199ffcf9f85b0 + data_hash: b6e8a277ab11f2ca500cbd3ee20427a94af4d594 +a: + class: character +letters: + class: character diff --git a/inst/testthat/optimized_0_3_1.tsv b/inst/testthat/optimized_0_3_1.tsv new file mode 100644 index 00000000..8a29fe63 --- /dev/null +++ b/inst/testthat/optimized_0_3_1.tsv @@ -0,0 +1,48 @@ +a letters +" a" x +" +a" m +,a t +;a w +'b f +'b' l +'NA' y +"""b" a +"""b""" z +"""NA""" y +@ z +& f +# c +| p +$ s +€ f +a j +à r +"a " b +"a b" l +"a b c" v +"a +" i +"a +b" n +"a +b +c" f +a b i +a b c o +a, c +a,b g +a,b,c y +a; p +a;b x +a;b;c l +a' u +a'b e +a'b'c t +"a""" c +"a""b" r +"a""b""c" v +ç w +é q +µ a +NA m diff --git a/inst/testthat/optimized_0_3_1.yml b/inst/testthat/optimized_0_3_1.yml new file mode 100644 index 00000000..f8129821 --- /dev/null +++ b/inst/testthat/optimized_0_3_1.yml @@ -0,0 +1,11 @@ +..generic: + git2rdata: 0.3.1 + optimize: yes + NA string: NA + sorting: a + hash: b2ebf427feaafc5f4dea639777f199ffcf9f85b0 + data_hash: edfd19ec22a7172eb5f7201197bc56da7fade50f +a: + class: character +letters: + class: character diff --git a/inst/testthat/optimized_0_4_0.tsv b/inst/testthat/optimized_0_4_0.tsv new file mode 100644 index 00000000..8a29fe63 --- /dev/null +++ b/inst/testthat/optimized_0_4_0.tsv @@ -0,0 +1,48 @@ +a letters +" a" x +" +a" m +,a t +;a w +'b f +'b' l +'NA' y +"""b" a +"""b""" z +"""NA""" y +@ z +& f +# c +| p +$ s +€ f +a j +à r +"a " b +"a b" l +"a b c" v +"a +" i +"a +b" n +"a +b +c" f +a b i +a b c o +a, c +a,b g +a,b,c y +a; p +a;b x +a;b;c l +a' u +a'b e +a'b'c t +"a""" c +"a""b" r +"a""b""c" v +ç w +é q +µ a +NA m diff --git a/inst/testthat/optimized_0_4_0.yml b/inst/testthat/optimized_0_4_0.yml new file mode 100644 index 00000000..03bddc9f --- /dev/null +++ b/inst/testthat/optimized_0_4_0.yml @@ -0,0 +1,11 @@ +..generic: + git2rdata: 0.4.0 + optimize: yes + NA string: NA + sorting: a + hash: b2ebf427feaafc5f4dea639777f199ffcf9f85b0 + data_hash: edfd19ec22a7172eb5f7201197bc56da7fade50f +a: + class: character +letters: + class: character diff --git a/inst/testthat/verbose_0_0_4.tsv b/inst/testthat/verbose_0_0_4.tsv new file mode 100644 index 00000000..b6e8a277 --- /dev/null +++ b/inst/testthat/verbose_0_0_4.tsv @@ -0,0 +1,48 @@ +a letters +" a" x +" +a" x +,a f +;a z +'b z +'b' w +'NA' t +"""b" p +"""b""" t +"""NA""" k +@ d +& y +# j +| r +$ p +€ a +a y +à l +"a " o +"a b" c +"a b c" g +"a +" w +"a +b" k +"a +b +c" g +a b x +a b c f +a, b +a,b e +a,b,c z +a; j +a;b w +a;b;c v +a' r +a'b t +a'b'c g +"a""" d +"a""b" h +"a""b""c" l +ç i +é m +µ v +NA w diff --git a/inst/testthat/verbose_0_0_4.yml b/inst/testthat/verbose_0_0_4.yml new file mode 100644 index 00000000..f49a3268 --- /dev/null +++ b/inst/testthat/verbose_0_0_4.yml @@ -0,0 +1,11 @@ +..generic: + git2rdata: 0.0.4 + optimize: no + NA string: NA + sorting: a + hash: 3c02145886aac5a9eae5f1d2700a29f9a71b9829 + data_hash: b6e8a277ab11f2ca500cbd3ee20427a94af4d594 +a: + class: character +letters: + class: character diff --git a/inst/testthat/verbose_0_3_1.tsv b/inst/testthat/verbose_0_3_1.tsv new file mode 100644 index 00000000..8a29fe63 --- /dev/null +++ b/inst/testthat/verbose_0_3_1.tsv @@ -0,0 +1,48 @@ +a letters +" a" x +" +a" m +,a t +;a w +'b f +'b' l +'NA' y +"""b" a +"""b""" z +"""NA""" y +@ z +& f +# c +| p +$ s +€ f +a j +à r +"a " b +"a b" l +"a b c" v +"a +" i +"a +b" n +"a +b +c" f +a b i +a b c o +a, c +a,b g +a,b,c y +a; p +a;b x +a;b;c l +a' u +a'b e +a'b'c t +"a""" c +"a""b" r +"a""b""c" v +ç w +é q +µ a +NA m diff --git a/inst/testthat/verbose_0_3_1.yml b/inst/testthat/verbose_0_3_1.yml new file mode 100644 index 00000000..0a3b917c --- /dev/null +++ b/inst/testthat/verbose_0_3_1.yml @@ -0,0 +1,11 @@ +..generic: + git2rdata: 0.3.1 + optimize: no + NA string: NA + sorting: a + hash: 3c02145886aac5a9eae5f1d2700a29f9a71b9829 + data_hash: edfd19ec22a7172eb5f7201197bc56da7fade50f +a: + class: character +letters: + class: character diff --git a/inst/testthat/verbose_0_4_0.csv b/inst/testthat/verbose_0_4_0.csv new file mode 100644 index 00000000..55b7f6f2 --- /dev/null +++ b/inst/testthat/verbose_0_4_0.csv @@ -0,0 +1,48 @@ +a,letters + a,x +" +a",m +",a",t +;a,w +'b,f +'b',l +'NA',y +"""b",a +"""b""",z +"""NA""",y +@,z +&,f +#,c +|,p +$,s +€,f +a,j +à,r +a ,b +a b,l +a b c,v +"a +",i +"a +b",n +"a +b +c",f +a b,i +a b c,o +"a,",c +"a,b",g +"a,b,c",y +a;,p +a;b,x +a;b;c,l +a',u +a'b,e +a'b'c,t +"a""",c +"a""b",r +"a""b""c",v +ç,w +é,q +µ,a +NA,m diff --git a/inst/testthat/verbose_0_4_0.yml b/inst/testthat/verbose_0_4_0.yml new file mode 100644 index 00000000..a4d4c3b4 --- /dev/null +++ b/inst/testthat/verbose_0_4_0.yml @@ -0,0 +1,11 @@ +..generic: + git2rdata: 0.4.0 + optimize: no + NA string: NA + sorting: a + hash: 3c02145886aac5a9eae5f1d2700a29f9a71b9829 + data_hash: f9abaee65ed6bbf187817a94a6305f4d1e577bf3 +a: + class: character +letters: + class: character diff --git a/tests/testthat/test_e_upgrade.R b/tests/testthat/test_e_upgrade.R index 6df7bf1d..89a21c40 100644 --- a/tests/testthat/test_e_upgrade.R +++ b/tests/testthat/test_e_upgrade.R @@ -1,150 +1,148 @@ -context("upgrade to new version") root <- tempfile("git2rdata-upgrade") dir.create(root) +origin <- system.file("testthat", package = "git2rdata") +file.copy(origin, root, recursive = TRUE) +path <- file.path(root, "testthat") + test_that("read_vc() checks version", { - file <- basename(tempfile(tmpdir = root)) - junk <- write_vc(test_data, file = file, root = root, sorting = "test_Date") - correct_yaml <- yaml::read_yaml(file.path(root, junk[2])) - junk_yaml <- correct_yaml - junk_yaml[["..generic"]][["git2rdata"]] <- "0.0.3" - yaml::write_yaml(junk_yaml, file.path(root, junk[2])) expect_error( - read_vc(file = file, root = root), - "Data stored using an older version of `git2rdata`." + read_vc("optimized_0_0_4", path), "Data stored using an older version" ) - - junk_yaml[["..generic"]][["git2rdata"]] <- NULL - yaml::write_yaml(junk_yaml, file.path(root, junk[2])) expect_error( - read_vc(file = file, root = root), - "Data stored using an older version of `git2rdata`." + read_vc("verbose_0_0_4", path), "Data stored using an older version" ) -}) - -test_that("relabel() checks version", { - file <- basename(tempfile(tmpdir = root)) - junk <- write_vc(test_data, file = file, root = root, sorting = "test_Date") - new_labels <- list(test_factor = list(a = "xyz")) - correct_yaml <- yaml::read_yaml(file.path(root, junk[2])) - junk_yaml <- correct_yaml - junk_yaml[["..generic"]][["git2rdata"]] <- "0.0.3" - yaml::write_yaml(junk_yaml, file.path(root, junk[2])) + expect_is(read_vc("optimized_0_3_1", path), "data.frame") expect_error( - relabel(file = file, root = root, change = new_labels), - "Data stored using an older version of `git2rdata`." - ) - - junk_yaml[["..generic"]][["git2rdata"]] <- NULL - yaml::write_yaml(junk_yaml, file.path(root, junk[2])) - expect_error( - relabel(file = file, root = root, change = new_labels), - "Data stored using an older version of `git2rdata`." + read_vc("verbose_0_3_1", path), "Data stored using an older version" ) }) -test_that("upgrade_data() validates metadata", { - file <- basename(tempfile(tmpdir = root)) - junk <- write_vc(test_data, file = file, root = root, sorting = "test_Date") - expect_error( - upgrade_data(file = file, root = pi), - "a 'root' of class numeric is not supported" - ) - correct_yaml <- yaml::read_yaml(file.path(root, junk[2])) - junk_yaml <- correct_yaml - junk_yaml[["..generic"]][["git2rdata"]] <- "0.0.4" - yaml::write_yaml(junk_yaml, file.path(root, junk[2])) - expect_identical( - unname(upgrade_data(file = file, root = root)), - file +test_that("upgrade_data() works on single files", { + expect_message( + z <- upgrade_data(file = "optimized_0_4_0", root = path), + "already up to date" ) - junk_yaml[["..generic"]][["hash"]] <- NULL - yaml::write_yaml(junk_yaml, file.path(root, junk[2])) - expect_error( - upgrade_data(file = file, root = root), - "corrupt metadata, no hash found." + expect_is(z, "character") + expect_silent( + upgrade_data(file = "optimized_0_4_0", root = path, verbose = FALSE) ) - junk_yaml[["..generic"]] <- NULL - yaml::write_yaml(junk_yaml, file.path(root, junk[2])) + expect_is(read_vc("optimized_0_4_0", path), "data.frame") + expect_message( - junk <- upgrade_data(file = file, root = root), - "is not a git2rdata object" + z <- upgrade_data(file = "verbose_0_4_0", root = path), + "already up to date" ) - expect_equivalent(file, junk) - - file <- basename(tempfile(tmpdir = root)) - junk <- write_vc(test_data, file = file, root = root, sorting = "test_Date", - optimize = FALSE) - correct_yaml <- yaml::read_yaml(file.path(root, junk[2])) - junk_yaml <- correct_yaml - junk_yaml[["..generic"]][["git2rdata"]] <- "0.0.5" - yaml::write_yaml(junk_yaml, file.path(root, junk[2])) - expect_identical( - unname(upgrade_data(file = file, root = root)), - file + expect_is(z, "character") + expect_silent( + upgrade_data(file = "verbose_0_4_0", root = path, verbose = FALSE) ) -}) + expect_is(read_vc("verbose_0_4_0", path), "data.frame") -file.remove(list.files(root, recursive = TRUE, full.names = TRUE)) + expect_message( + z <- upgrade_data(file = "optimized_0_3_1", root = path), + "already up to date" + ) + expect_is(z, "character") + expect_silent( + upgrade_data(file = "optimized_0_3_1", root = path, verbose = FALSE) + ) + expect_is(read_vc("optimized_0_3_1", path), "data.frame") -test_that("upgrade_data() works from 0.0.3 to 0.0.4", { - file <- basename(tempfile(tmpdir = root)) - junk <- write_vc(test_data, file = file, root = root, sorting = "test_Date") - correct_yaml <- yaml::read_yaml(file.path(root, junk[2])) - old_yaml <- correct_yaml - old_yaml[["..generic"]][["git2rdata"]] <- NULL - old_yaml[["..generic"]][["data_hash"]] <- NULL - yaml::write_yaml(old_yaml, file.path(root, junk[2])) expect_message( - files <- upgrade_data(file = file, root = root, verbose = TRUE), - paste0(file, ".yml updated") + z <- upgrade_data(file = "verbose_0_3_1", root = path), "updated" ) + expect_true(file_test("-f", file.path(path, "verbose_0_3_1.csv"))) + expect_false(file_test("-f", file.path(path, "verbose_0_3_1.tsv"))) + expect_is(read_vc("verbose_0_3_1", path), "data.frame") + expect_message( - files <- upgrade_data(file = file, root = root, verbose = TRUE), - paste(file, "already up to date") + z <- upgrade_data(file = "optimized_0_0_4", root = path), "updated" ) - expect_equivalent(read_vc(file = file, root = root), sorted_test_data) + expect_true(file_test("-f", file.path(path, "optimized_0_0_4.tsv"))) + expect_is(read_vc("optimized_0_0_4", path), "data.frame") - root <- git2r::init(root) - git2r::config(root, user.name = "Alice", user.email = "alice@example.org") - yaml::write_yaml(old_yaml, file.path(git2r::workdir(root), junk[2])) - git2r::add(root, paste0(file, c(".tsv", ".yml"))) - initial_commit <- commit(root, "initial commit", all = TRUE) expect_message( - files <- upgrade_data(file = file, root = root, verbose = TRUE), - paste0(file, ".yml updated") + z <- upgrade_data(file = "verbose_0_0_4", root = path), "updated" ) - expect_equal( - status(root), - list(staged = list(), unstaged = list(paste0(files, ".yml")), - untracked = list()), - check.attributes = FALSE + expect_true(file_test("-f", file.path(path, "verbose_0_0_4.csv"))) + expect_false(file_test("-f", file.path(path, "verbose_0_0_4.tsv"))) + expect_is(read_vc("verbose_0_0_4", path), "data.frame") +}) + +file.remove( + list.files(root, recursive = TRUE, full.names = TRUE) +) +root <- tempfile("git2rdata-upgrade") +dir.create(root) +origin <- system.file("testthat", package = "git2rdata") +file.copy(origin, root, recursive = TRUE) +path <- file.path(root, "testthat") + +test_that("upgrade_data() works on paths", { + expect_message(z <- upgrade_data(root = root, path = ".")) + expect_is(z, "character") + expect_silent(upgrade_data(root = root, path = ".", verbose = FALSE)) + expect_is(read_vc("optimized_0_4_0", path), "data.frame") + expect_is(read_vc("verbose_0_4_0", path), "data.frame") + expect_is(read_vc("optimized_0_3_1", path), "data.frame") + expect_is(read_vc("verbose_0_3_1", path), "data.frame") + expect_is(read_vc("optimized_0_0_4", path), "data.frame") + expect_is(read_vc("verbose_0_0_4", path), "data.frame") +}) + +file.remove( + list.files(root, recursive = TRUE, full.names = TRUE) +) +root <- tempfile("git2rdata-upgrade") +dir.create(root) +origin <- system.file("testthat", package = "git2rdata") +file.copy(origin, root, recursive = TRUE) +path <- file.path(root, "testthat") +repo <- git2r::init(root) +git2r::add(repo, list.files(root, recursive = TRUE)) +git2r::commit(repo, message = "initial commit") + +test_that("upgrade_data() works on a git repository", { + expect_message(z <- upgrade_data(root = repo, path = ".")) + expect_is(z, "character") + expect_silent(upgrade_data(root = repo, path = ".", verbose = FALSE)) + expect_is(read_vc("optimized_0_4_0", path), "data.frame") + expect_is(read_vc("verbose_0_4_0", path), "data.frame") + expect_is(read_vc("optimized_0_3_1", path), "data.frame") + expect_is(read_vc("verbose_0_3_1", path), "data.frame") + expect_is(read_vc("optimized_0_0_4", path), "data.frame") + expect_is(read_vc("verbose_0_0_4", path), "data.frame") + expect_identical( + vapply(status(repo), length, integer(1)), + c(staged = 0L, unstaged = 5L, untracked = 2L) ) - expect_message( - files <- upgrade_data(file = file, root = root, verbose = TRUE, - stage = TRUE), - paste(file, "already up to date") - ) - expect_equal( - status(root), - list( - staged = list(paste0(files, ".yml")), unstaged = list(), - untracked = list() - ), - check.attributes = FALSE + expect_silent( + upgrade_data(root = repo, path = ".", verbose = FALSE, stage = TRUE) ) + expect_identical( + vapply(status(repo), length, integer(1)), + c(staged = 7L, unstaged = 0L, untracked = 0L) + ) +}) - file <- basename(tempfile(tmpdir = git2r::workdir(root))) - junk <- write_vc(test_data, file = file, root = root, sorting = "test_Date") +test_that("validation", { expect_error( - upgrade_data(file = file, path = ".", root = root, verbose = TRUE), - "specify either 'file' or 'path'" + upgrade_data(root = 1), "a 'root' of class numeric is not supported" ) - expect_is( - upgrade_data(path = ".", root = root, verbose = TRUE), - "character" + yml <- read_yaml(file.path(path, "verbose_0_0_4.yml")) + write_yaml( + yml[names(yml) != "..generic"], file.path(path, "verbose_0_0_4.yml") + ) + expect_message( + upgrade_data(file = "verbose_0_0_4", root = path), + "is not a git2rdata object" ) }) -file.remove(list.files(git2r::workdir(root), recursive = TRUE, - full.names = TRUE)) + +file.remove( + list.files( + git2r::workdir(repo), recursive = TRUE, full.names = TRUE, all.files = TRUE + ) +) From 86b32502a4aad7d449e8d21dbe694db6effe1993 Mon Sep 17 00:00:00 2001 From: Thierry Onkelinx Date: Fri, 26 Nov 2021 17:50:06 +0100 Subject: [PATCH 08/18] update NEWS --- NEWS.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/NEWS.md b/NEWS.md index 1d703d04..d411d33e 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,3 +1,10 @@ +# git2rdata 0.4.0 + +## Internal changes + +* Upgrade to Roxygen2 7.1.2 +* Add `inst/CITATION`, `CITATION.cff`, `.zenodo.json` + # git2rdata 0.3.1 * Use `icuSetCollate()` to define a standardised sorting. From 383822fcf1b799fc7a6fc27b835c7a1ec4119dc4 Mon Sep 17 00:00:00 2001 From: Thierry Onkelinx Date: Fri, 21 Jan 2022 16:03:06 +0100 Subject: [PATCH 09/18] update citation --- .zenodo.json | 50 ++++++++++++++++++++++++++++++++------------------ inst/CITATION | 4 ++-- 2 files changed, 34 insertions(+), 20 deletions(-) diff --git a/.zenodo.json b/.zenodo.json index 5334b715..5b8a8bb2 100644 --- a/.zenodo.json +++ b/.zenodo.json @@ -1,32 +1,46 @@ { - "title": ["git2rdata: Store and Retrieve Data.frames in a Git Repository"], - "version": ["0.4.0"], - "description": ["The git2rdata package is an R package for writing and reading dataframes as plain text files. A metadata file stores important information. 1) Storing metadata allows to maintain the classes of variables. By default, git2rdata optimizes the data for file storage. The optimization is most effective on data containing factors. The optimization makes the data less human readable. The user can turn this off when they prefer a human readable format over smaller files. Details on the implementation are available in vignette(\"plain_text\", package = \"git2rdata\"). 2) Storing metadata also allows smaller row based diffs between two consecutive commits. This is a useful feature when storing data as plain text files under version control. Details on this part of the implementation are available in vignette(\"version_control\", package = \"git2rdata\"). Although we envisioned git2rdata with a git workflow in mind, you can use it in combination with other version control systems like subversion or mercurial. 3) git2rdata is a useful tool in a reproducible and traceable workflow. vignette(\"workflow\", package = \"git2rdata\") gives a toy example. 4) vignette(\"efficiency\", package = \"git2rdata\") provides some insight into the efficiency of file storage, git repository size and speed for writing and reading. Please cite using ."], + "title": "git2rdata: Store and Retrieve Data.frames in a Git Repository", + "version": "0.4.0", + "description": "The git2rdata package is an R package for writing and reading dataframes as plain text files. A metadata file stores important information. 1) Storing metadata allows to maintain the classes of variables. By default, git2rdata optimizes the data for file storage. The optimization is most effective on data containing factors. The optimization makes the data less human readable. The user can turn this off when they prefer a human readable format over smaller files. Details on the implementation are available in vignette(\"plain_text\", package = \"git2rdata\"). 2) Storing metadata also allows smaller row based diffs between two consecutive commits. This is a useful feature when storing data as plain text files under version control. Details on this part of the implementation are available in vignette(\"version_control\", package = \"git2rdata\"). Although we envisioned git2rdata with a git workflow in mind, you can use it in combination with other version control systems like subversion or mercurial. 3) git2rdata is a useful tool in a reproducible and traceable workflow. vignette(\"workflow\", package = \"git2rdata\") gives a toy example. 4) vignette(\"efficiency\", package = \"git2rdata\") provides some insight into the efficiency of file storage, git repository size and speed for writing and reading. Please cite using .", "creators": [ { - "name": ["Onkelinx, Thierry"], - "orcid": ["https://orcid.org/0000-0001-8804-4216"] + "name": "Onkelinx, Thierry", + "orcid": "https://orcid.org/0000-0001-8804-4216" + } + ], + "upload_type": "software", + "access_right": "open", + "license": "GPL-3.0", + "communities": [ + { + "identifier": "inbo" } ], "contributors": [ { - "name": ["Vanderhaeghe, Floris"], - "orcid": ["https://orcid.org/0000-0002-6378-6229"] + "name": "Vanderhaeghe, Floris", + "type": "ProjectMember", + "orcid": "https://orcid.org/0000-0002-6378-6229" + }, + { + "name": "Desmet, Peter", + "type": "ProjectMember", + "orcid": "https://orcid.org/0000-0002-8442-8025" + }, + { + "name": "Lommelen, Els", + "type": "ProjectMember", + "orcid": "https://orcid.org/0000-0002-3481-5684" }, { - "name": ["Desmet, Peter"], - "orcid": ["https://orcid.org/0000-0002-8442-8025"] + "name": "Research Institute for Nature and Forest", + "type": "RightsHolder" }, { - "name": ["Lommelen, Els"], - "orcid": ["https://orcid.org/0000-0002-3481-5684"] + "name": "Onkelinx, Thierry", + "type": "ContactPerson", + "orcid": "https://orcid.org/0000-0001-8804-4216" } ], - "upload_type": ["software"], - "access_rights": ["open"], - "license": ["GPL-3.0"], - "communities": { - "identifier": ["inbo"] - }, - "language": ["en"] + "language": "en" } diff --git a/inst/CITATION b/inst/CITATION index 7a219d15..8bd62cfd 100644 --- a/inst/CITATION +++ b/inst/CITATION @@ -4,9 +4,9 @@ citEntry( entry = "Manual", title = "git2rdata: Store and Retrieve Data.frames in a Git Repository. Version 0.4.0", author = c(person(given = "Thierry", family = "Onkelinx")), - year = 2021, + year = 2022, url = "https://ropensci.github.io/git2rdata/", abstract = "The git2rdata package is an R package for writing and reading dataframes as plain text files. A metadata file stores important information. 1) Storing metadata allows to maintain the classes of variables. By default, git2rdata optimizes the data for file storage. The optimization is most effective on data containing factors. The optimization makes the data less human readable. The user can turn this off when they prefer a human readable format over smaller files. Details on the implementation are available in vignette(\"plain_text\", package = \"git2rdata\"). 2) Storing metadata also allows smaller row based diffs between two consecutive commits. This is a useful feature when storing data as plain text files under version control. Details on this part of the implementation are available in vignette(\"version_control\", package = \"git2rdata\"). Although we envisioned git2rdata with a git workflow in mind, you can use it in combination with other version control systems like subversion or mercurial. 3) git2rdata is a useful tool in a reproducible and traceable workflow. vignette(\"workflow\", package = \"git2rdata\") gives a toy example. 4) vignette(\"efficiency\", package = \"git2rdata\") provides some insight into the efficiency of file storage, git repository size and speed for writing and reading. Please cite using .", - textVersion = "Onkelinx, Thierry (2021) git2rdata: Store and Retrieve Data.frames in a Git Repository. Version 0.4.0. https://ropensci.github.io/git2rdata/", + textVersion = "Onkelinx, Thierry (2022) git2rdata: Store and Retrieve Data.frames in a Git Repository. Version 0.4.0. https://ropensci.github.io/git2rdata/", ) # end checklist entry From 5b24ce381ac42fc8c513e8fa1c793ddd66f84eec Mon Sep 17 00:00:00 2001 From: Thierry Onkelinx Date: Tue, 25 Jan 2022 09:33:35 +0100 Subject: [PATCH 10/18] add verify_vc() --- DESCRIPTION | 1 + NAMESPACE | 1 + R/verify_vc.R | 23 +++++++++++++++++++++++ man/list_data.Rd | 1 + man/prune_meta.Rd | 1 + man/read_vc.Rd | 1 + man/relabel.Rd | 1 + man/rename_variable.Rd | 1 + man/rm_data.Rd | 1 + man/verify_vc.Rd | 35 +++++++++++++++++++++++++++++++++++ man/write_vc.Rd | 3 ++- 11 files changed, 68 insertions(+), 1 deletion(-) create mode 100644 R/verify_vc.R create mode 100644 man/verify_vc.Rd diff --git a/DESCRIPTION b/DESCRIPTION index cdf3b7cf..7ea078c7 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -74,3 +74,4 @@ Collate: 'rename_variable.R' 'upgrade_data.R' 'utils.R' + 'verify_vc.R' diff --git a/NAMESPACE b/NAMESPACE index df4c980a..e8028d3c 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -61,6 +61,7 @@ export(repository) export(rm_data) export(status) export(upgrade_data) +export(verify_vc) export(write_vc) importFrom(assertthat,"on_failure<-") importFrom(assertthat,assert_that) diff --git a/R/verify_vc.R b/R/verify_vc.R new file mode 100644 index 00000000..2c2c2d83 --- /dev/null +++ b/R/verify_vc.R @@ -0,0 +1,23 @@ +#' Read a file an verify the presence of variables +#' +#' Reads the file with [read_vc()]. +#' Then verifies that every variable listed in `variables` is present in the +#' data.frame. +#' @export +#' @inheritParams read_vc +#' @param variables a character vector with variable names. +#' @importFrom assertthat assert_that +#' @family storage +verify_vc <- function(file, root, variables) { + assert_that(is.character(variables), length(variables) > 0, noNA(variables)) + x <- read_vc(file = file, root = root) + ok <- variables %in% colnames(x) + assert_that( + all(ok), + msg = sprintf( + "variables missing from `%s`: %s", file, + paste(variables[!ok], collapse = ", ") + ) + ) + return(x) +} diff --git a/man/list_data.Rd b/man/list_data.Rd index 435ddd38..5f401a09 100644 --- a/man/list_data.Rd +++ b/man/list_data.Rd @@ -105,6 +105,7 @@ Other storage: \code{\link{relabel}()}, \code{\link{rename_variable}()}, \code{\link{rm_data}()}, +\code{\link{verify_vc}()}, \code{\link{write_vc}()} } \concept{storage} diff --git a/man/prune_meta.Rd b/man/prune_meta.Rd index 7d4a6dec..e66e9689 100644 --- a/man/prune_meta.Rd +++ b/man/prune_meta.Rd @@ -119,6 +119,7 @@ Other storage: \code{\link{relabel}()}, \code{\link{rename_variable}()}, \code{\link{rm_data}()}, +\code{\link{verify_vc}()}, \code{\link{write_vc}()} } \concept{storage} diff --git a/man/read_vc.Rd b/man/read_vc.Rd index 69764519..3a57387e 100644 --- a/man/read_vc.Rd +++ b/man/read_vc.Rd @@ -102,6 +102,7 @@ Other storage: \code{\link{relabel}()}, \code{\link{rename_variable}()}, \code{\link{rm_data}()}, +\code{\link{verify_vc}()}, \code{\link{write_vc}()} } \concept{storage} diff --git a/man/relabel.Rd b/man/relabel.Rd index 59146312..a7a1100d 100644 --- a/man/relabel.Rd +++ b/man/relabel.Rd @@ -93,6 +93,7 @@ Other storage: \code{\link{read_vc}()}, \code{\link{rename_variable}()}, \code{\link{rm_data}()}, +\code{\link{verify_vc}()}, \code{\link{write_vc}()} } \concept{storage} diff --git a/man/rename_variable.Rd b/man/rename_variable.Rd index 4d720cd3..860c9465 100644 --- a/man/rename_variable.Rd +++ b/man/rename_variable.Rd @@ -91,6 +91,7 @@ Other storage: \code{\link{read_vc}()}, \code{\link{relabel}()}, \code{\link{rm_data}()}, +\code{\link{verify_vc}()}, \code{\link{write_vc}()} } \concept{storage} diff --git a/man/rm_data.Rd b/man/rm_data.Rd index 31d4052e..4362c1c1 100644 --- a/man/rm_data.Rd +++ b/man/rm_data.Rd @@ -135,6 +135,7 @@ Other storage: \code{\link{read_vc}()}, \code{\link{relabel}()}, \code{\link{rename_variable}()}, +\code{\link{verify_vc}()}, \code{\link{write_vc}()} } \concept{storage} diff --git a/man/verify_vc.Rd b/man/verify_vc.Rd new file mode 100644 index 00000000..022af439 --- /dev/null +++ b/man/verify_vc.Rd @@ -0,0 +1,35 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/verify_vc.R +\name{verify_vc} +\alias{verify_vc} +\title{Read a file an verify the presence of variables} +\usage{ +verify_vc(file, root, variables) +} +\arguments{ +\item{file}{the name of the git2rdata object. Git2rdata objects cannot +have dots in their name. The name may include a relative path. \code{file} is a +path relative to the \code{root}. +Note that \code{file} must point to a location within \code{root}.} + +\item{root}{The root of a project. Can be a file path or a \code{git-repository}. +Defaults to the current working directory (\code{"."}).} + +\item{variables}{a character vector with variable names.} +} +\description{ +Reads the file with \code{\link[=read_vc]{read_vc()}}. +Then verifies that every variable listed in \code{variables} is present in the +data.frame. +} +\seealso{ +Other storage: +\code{\link{list_data}()}, +\code{\link{prune_meta}()}, +\code{\link{read_vc}()}, +\code{\link{relabel}()}, +\code{\link{rename_variable}()}, +\code{\link{rm_data}()}, +\code{\link{write_vc}()} +} +\concept{storage} diff --git a/man/write_vc.Rd b/man/write_vc.Rd index ed92e315..3ea1829a 100644 --- a/man/write_vc.Rd +++ b/man/write_vc.Rd @@ -173,6 +173,7 @@ Other storage: \code{\link{read_vc}()}, \code{\link{relabel}()}, \code{\link{rename_variable}()}, -\code{\link{rm_data}()} +\code{\link{rm_data}()}, +\code{\link{verify_vc}()} } \concept{storage} From 2b977d2edcec4058260e862ee9ff23f9a56e9c1b Mon Sep 17 00:00:00 2001 From: Thierry Onkelinx Date: Tue, 25 Jan 2022 10:58:57 +0100 Subject: [PATCH 11/18] fix linters --- tests/testthat/test_a_basics.R | 40 ++-- tests/testthat/test_b_prune.R | 29 ++- tests/testthat/test_c_git.R | 59 +++--- tests/testthat/test_d_recent_commit.R | 6 +- vignettes/efficiency.Rmd | 255 ++++++++++++++------------ vignettes/split_by.Rmd | 138 ++++++++------ vignettes/version_control.Rmd | 4 +- vignettes/workflow.Rmd | 26 +-- 8 files changed, 321 insertions(+), 236 deletions(-) diff --git a/tests/testthat/test_a_basics.R b/tests/testthat/test_a_basics.R index 495c52ec..4390cb45 100644 --- a/tests/testthat/test_a_basics.R +++ b/tests/testthat/test_a_basics.R @@ -13,11 +13,11 @@ test_that("write_vc() and read_vc() on a file system", { dir.create(root) expect_false(any(file.exists(git2rdata:::clean_data_path(root, "test")))) expect_error( - git2rdata:::clean_data_path(root, "../wrong_location"), + git2rdata:::clean_data_path(root, file.path("..", "wrong_location")), "file should not contain '..'" ) expect_error( - git2rdata:::clean_data_path(root, "./../wrong_location"), + git2rdata:::clean_data_path(root, file.path(".", "..", "wrong_location")), "file should not contain '..'" ) expect_is( @@ -49,7 +49,9 @@ test_that("write_vc() and read_vc() on a file system", { output ) expect_error( - write_vc(data.frame(junk = 5), file = "test", root = root, sorting = "junk"), + write_vc( + data.frame(junk = 5), file = "test", root = root, sorting = "junk" + ), "The data was not overwritten because of the issues below." ) expect_error( @@ -71,20 +73,23 @@ test_that("write_vc() and read_vc() on a file system", { "All sorting variables must be available" ) - expect_false(any(file.exists(git2rdata:::clean_data_path(root, "a/verbose")))) + expect_false( + any( + file.exists(git2rdata:::clean_data_path(root, file.path("a", "verbose"))) + ) + ) expect_is( - output <- - write_vc( - x = test_data, file = "a/verbose", root = root, sorting = "test_Date", - optimize = FALSE - ), + output <- write_vc( + x = test_data, file = file.path("a", "verbose"), root = root, + sorting = "test_Date", optimize = FALSE + ), "character" ) expect_true( all(file.exists(file.path(root, "a", c("verbose.csv", "verbose.yml")))) ) expect_equal( - stored <- read_vc(file = "a/verbose", root = root), + stored <- read_vc(file = file.path("a", "verbose"), root = root), sorted_test_data, check.attributes = FALSE ) @@ -97,7 +102,7 @@ test_that("write_vc() and read_vc() on a file system", { ) } expect_error( - write_vc(x = test_data, file = "a/verbose", root = root), + write_vc(x = test_data, file = file.path("a", "verbose"), root = root), "New data is optimized, whereas old data was verbose" ) @@ -136,8 +141,9 @@ test_that("write_vc() and read_vc() on a file system", { "No sorting applied" ) expect_warning( - output <- - write_vc(test_data, file = "sorting", root = root, sorting = "test_factor"), + output <- write_vc( + test_data, file = "sorting", root = root, sorting = "test_factor" + ), "Sorting on 'test_factor' results in ties" ) expect_is(output, "character") @@ -149,7 +155,9 @@ test_that("write_vc() and read_vc() on a file system", { ) expect_error( suppressWarnings( - write_vc(test_data, file = "sorting", root = root, sorting = "test_factor") + write_vc( + test_data, file = "sorting", root = root, sorting = "test_factor" + ) ), "The sorting variables changed" ) @@ -173,7 +181,9 @@ test_that("write_vc() and read_vc() on a file system", { ) expect_error( suppressWarnings( - write_vc(test_data, file = "sorting", root = root, sorting = "test_logical") + write_vc( + test_data, file = "sorting", root = root, sorting = "test_logical" + ) ), "The sorting variables changed" ) diff --git a/tests/testthat/test_b_prune.R b/tests/testthat/test_b_prune.R index f76fc047..3ee45f07 100644 --- a/tests/testthat/test_b_prune.R +++ b/tests/testthat/test_b_prune.R @@ -1,7 +1,11 @@ test_that("rm_data & prune_meta", { expect_error(rm_data(root = 1), "a 'root' of class numeric is not supported") - expect_error(prune_meta(root = 1), "a 'root' of class numeric is not supported") - expect_error(list_data(root = 1), "a 'root' of class numeric is not supported") + expect_error( + prune_meta(root = 1), "a 'root' of class numeric is not supported" + ) + expect_error( + list_data(root = 1), "a 'root' of class numeric is not supported" + ) root <- tempfile(pattern = "git2rdata-prune") root <- normalizePath(root, winslash = "/", mustWork = FALSE) @@ -11,19 +15,23 @@ test_that("rm_data & prune_meta", { expect_null(prune_meta(root, path = "junk")) write_vc(test_data, file = "test", root = root, sorting = "test_Date") write_vc( - test_data, file = "a/verbose", root = root, sorting = "test_Date", - optimize = FALSE + test_data, file = file.path("a", "verbose"), root = root, + sorting = "test_Date", optimize = FALSE ) current <- list.files(root, recursive = TRUE) - expect_identical(rm_data(root = root, path = "a"), "a/verbose.csv") + expect_identical( + rm_data(root = root, path = "a"), file.path("a", "verbose.csv") + ) expect_identical( list.files(root, recursive = TRUE), current[-grep("^.*/.*\\.csv", current)] ) current <- list.files(root, recursive = TRUE) - expect_identical(prune_meta(root = root, path = "."), "a/verbose.yml") + expect_identical( + prune_meta(root = root, path = "."), file.path("a", "verbose.yml") + ) expect_identical( list.files(root, recursive = TRUE), current[-grep("^.*/.*", current)] @@ -35,8 +43,13 @@ test_that("rm_data & prune_meta", { expect_identical(list.files(root, recursive = TRUE), current) write_vc(test_data, file = "test1", root = root, sorting = "test_Date") - junk <- write_vc(test_data, file = "test2", root = root, sorting = "test_Date") - write_vc(test_data, file = "a/test2", root = root, sorting = "test_Date") + junk <- write_vc( + test_data, file = "test2", root = root, sorting = "test_Date" + ) + write_vc( + test_data, file = file.path("a", "test2"), root = root, + sorting = "test_Date" + ) meta_data <- yaml::read_yaml(file.path(root, junk[2])) meta_data[["..generic"]] <- NULL yaml::write_yaml(meta_data, file = file.path(root, junk[2])) diff --git a/tests/testthat/test_c_git.R b/tests/testthat/test_c_git.R index e06158f5..c4dd7518 100644 --- a/tests/testthat/test_c_git.R +++ b/tests/testthat/test_c_git.R @@ -83,8 +83,8 @@ for (i in colnames(stored)) { } forced <- write_vc( - test_data, file = "forced/force", root = root, sorting = "test_Date", - stage = TRUE, force = TRUE + test_data, file = file.path("forced", "force"), root = root, + sorting = "test_Date", stage = TRUE, force = TRUE ) expect_equal( status(root, ignored = TRUE), @@ -97,7 +97,7 @@ expect_equal( check.attributes = FALSE ) expect_equal( - stored <- read_vc(file = "forced/force", root = root), + stored <- read_vc(file = file.path("forced", "force"), root = root), sorted_test_data, check.attributes = FALSE ) @@ -173,11 +173,11 @@ staged <- write_vc( current <- list.files(git2r::workdir(root), recursive = TRUE) expect_identical( rm_data(root = root, path = "."), - "forced/force.tsv" + file.path("forced", "force.tsv") ) expect_identical( current[!current %in% list.files(git2r::workdir(root), recursive = TRUE)], - "forced/force.tsv" + file.path("forced", "force.tsv") ) expect_error( prune_meta(root = root, path = ".", stage = TRUE), @@ -185,16 +185,16 @@ expect_error( ) expect_identical( current[!current %in% list.files(git2r::workdir(root), recursive = TRUE)], - "forced/force.tsv" + file.path("forced", "force.tsv") ) expect_null(rm_data(root, path = ".")) expect_identical( prune_meta(root = root, path = ".", stage = FALSE), - "forced/force.yml" + file.path("forced", "force.yml") ) expect_identical( current[!current %in% list.files(git2r::workdir(root), recursive = TRUE)], - c("forced/force.tsv", "forced/force.yml") + file.path("forced", c("force.tsv", "force.yml")) ) expect_null(prune_meta(root, path = ".")) git2r::reset(git2r::last_commit(root), reset_type = "hard", path = ".") @@ -205,20 +205,22 @@ staged <- write_vc( ) expect_identical( rm_data(root = root, path = ".", type = "m"), - c("forced/force.tsv", "staged.tsv") + c(file.path("forced", "force.tsv"), "staged.tsv") ) expect_identical( current[!current %in% list.files(git2r::workdir(root), recursive = TRUE)], - c("forced/force.tsv", "staged.tsv") + c(file.path("forced", "force.tsv"), "staged.tsv") ) expect_warning( removed <- prune_meta(root = root, path = ".", stage = FALSE), "data removed and staged, metadata removed but unstaged" ) -expect_identical(removed, c("forced/force.yml", "staged.yml")) +expect_identical(removed, c(file.path("forced", "force.yml"), "staged.yml")) expect_identical( current[!current %in% list.files(git2r::workdir(root), recursive = TRUE)], - c("forced/force.tsv", "forced/force.yml", "staged.tsv", "staged.yml") + c( + file.path("forced", c("force.tsv", "force.yml")), "staged.tsv", "staged.yml" + ) ) git2r::reset(git2r::last_commit(root), reset_type = "hard", path = ".") @@ -228,20 +230,22 @@ staged <- write_vc( ) expect_identical( rm_data(root = root, path = ".", type = "i", stage = TRUE), - c("forced/force.tsv", "ignore.tsv", "staged.tsv") + c(file.path("forced", "force.tsv"), "ignore.tsv", "staged.tsv") ) expect_identical( current[!current %in% list.files(git2r::workdir(root), recursive = TRUE)], - c("forced/force.tsv", "ignore.tsv", "staged.tsv") + c(file.path("forced", "force.tsv"), "ignore.tsv", "staged.tsv") ) expect_identical( prune_meta(root = root, path = ".", stage = TRUE), - c("forced/force.yml", "ignore.yml", "staged.yml") + c(file.path("forced", "force.yml"), "ignore.yml", "staged.yml") ) expect_identical( current[!current %in% list.files(git2r::workdir(root), recursive = TRUE)], - c("forced/force.tsv", "forced/force.yml", "ignore.tsv", "ignore.yml", - "staged.tsv", "staged.yml") + c( + file.path("forced", c("force.tsv", "force.yml")), "ignore.tsv", + "ignore.yml", "staged.tsv", "staged.yml" + ) ) git2r::reset(git2r::last_commit(root), reset_type = "hard", path = ".") @@ -254,20 +258,31 @@ staged <- write_vc( ) expect_identical( rm_data(root = root, path = ".", type = "all", stage = TRUE), - c("forced/force.tsv", "ignore.tsv", "staged.tsv", "untracked.tsv") + c( + file.path("forced", "force.tsv"), "ignore.tsv", "staged.tsv", + "untracked.tsv" + ) ) expect_identical( current[!current %in% list.files(git2r::workdir(root), recursive = TRUE)], - c("forced/force.tsv", "ignore.tsv", "staged.tsv", "untracked.tsv") + c( + file.path("forced", "force.tsv"), "ignore.tsv", "staged.tsv", + "untracked.tsv" + ) ) expect_identical( prune_meta(root = root, path = ".", stage = TRUE), - c("forced/force.yml", "ignore.yml", "staged.yml", "untracked.yml") + c( + file.path("forced", "force.yml"), "ignore.yml", "staged.yml", + "untracked.yml" + ) ) expect_identical( current[!current %in% list.files(git2r::workdir(root), recursive = TRUE)], - c("forced/force.tsv", "forced/force.yml", "ignore.tsv", "ignore.yml", - "staged.tsv", "staged.yml", "untracked.tsv", "untracked.yml") + c( + file.path("forced", c("force.tsv", "force.yml")), "ignore.tsv", + "ignore.yml", "staged.tsv", "staged.yml", "untracked.tsv", "untracked.yml" + ) ) git2r::reset(git2r::last_commit(root), reset_type = "hard", path = ".") }) diff --git a/tests/testthat/test_d_recent_commit.R b/tests/testthat/test_d_recent_commit.R index e5fe9090..38e92370 100644 --- a/tests/testthat/test_d_recent_commit.R +++ b/tests/testthat/test_d_recent_commit.R @@ -20,8 +20,8 @@ write_vc( commit_1 <- commit(root, "initial commit") write_vc( - test_data[3:4, ], file = "junk/test1", root = root, stage = TRUE, - sorting = "test_Date" + test_data[3:4, ], file = file.path("junk", "test1"), root = root, + stage = TRUE, sorting = "test_Date" ) commit_2 <- commit(root, "second file") @@ -55,7 +55,7 @@ expect_identical( ) ) expect_identical( - recent_commit(file = "junk/test1", root, data = TRUE), + recent_commit(file = file.path("junk", "test1"), root, data = TRUE), data.frame( commit = commit_2$sha, author = commit_2$author$name, diff --git a/vignettes/efficiency.Rmd b/vignettes/efficiency.Rmd index 56380af9..50a4cef3 100644 --- a/vignettes/efficiency.Rmd +++ b/vignettes/efficiency.Rmd @@ -1,7 +1,7 @@ --- title: "Efficiency Relative to Storage and Time" author: "Thierry Onkelinx" -output: +output: rmarkdown::html_vignette: fig_caption: yes vignette: > @@ -21,94 +21,111 @@ opts_chunk$set( comment = "#>" ) library(ggplot2) -inbo_colours <- c("#959B38", "#729BB7", "#E87837", "#BDDDD7", "#E4E517", +inbo_colours <- c("#959B38", "#729BB7", "#E87837", "#BDDDD7", "#E4E517", "#843860", "#C04384", "#C2C444", "#685457") theme_inbo <- function(base_size = 12, base_family = "") { - rect.bg <- "white" - legend.bg <- "white" - panel.bg <- "#F3F3F3" - panel.grid <- "white" - plot.bg <- "white" + rect_bg <- "white" + legend_bg <- "white" + panel_bg <- "#F3F3F3" + panel_grid <- "white" + plot_bg <- "white" half_line <- base_size / 2 - theme( - line = element_line(colour = "black", size = 0.5, linetype = 1, + ggplot2::theme( + line = ggplot2::element_line(colour = "black", size = 0.5, linetype = 1, lineend = "butt"), - rect = element_rect(fill = rect.bg, colour = "black", size = 0.5, + rect = ggplot2::element_rect(fill = rect_bg, colour = "black", size = 0.5, linetype = 1), - text = element_text(family = base_family, face = "plain", - colour = "#843860", size = base_size, hjust = 0.5, - vjust = 0.5, angle = 0, lineheight = 0.9, - margin = margin(), debug = FALSE), - axis.line = element_blank(), - axis.line.x = element_blank(), - axis.line.y = element_blank(), - axis.text = element_text(size = rel(0.8)), - axis.text.x = element_text(margin = margin(t = 0.8 * half_line / 2), - vjust = 1), + text = ggplot2::element_text(family = base_family, face = "plain", + colour = "#843860", size = base_size, hjust = 0.5, + vjust = 0.5, angle = 0, lineheight = 0.9, + margin = ggplot2::margin(), debug = FALSE), + axis.line = ggplot2::element_blank(), + axis.line.x = ggplot2::element_blank(), + axis.line.y = ggplot2::element_blank(), + axis.text = ggplot2::element_text(size = ggplot2::rel(0.8)), + axis.text.x = ggplot2::element_text( + margin = ggplot2::margin(t = 0.8 * half_line / 2), vjust = 1 + ), axis.text.x.top = NULL, - axis.text.y = element_text(margin = margin(r = 0.8 * half_line / 2), - hjust = 1), + axis.text.y = ggplot2::element_text( + margin = ggplot2::margin(r = 0.8 * half_line / 2), hjust = 1 + ), axis.text.y.right = NULL, - axis.ticks = element_line(), - axis.ticks.length = unit(0.15, "cm"), - axis.title = element_text(colour = "black"), - axis.title.x = element_text( - margin = margin(t = 0.8 * half_line, b = 0.8 * half_line / 2) + axis.ticks = ggplot2::element_line(), + axis.ticks.length = ggplot2::unit(0.15, "cm"), + axis.title = ggplot2::element_text(colour = "black"), + axis.title.x = ggplot2::element_text( + margin = ggplot2::margin(t = 0.8 * half_line, b = 0.8 * half_line / 2) ), axis.title.x.top = NULL, - axis.title.y = element_text( - margin = margin(r = 0.8 * half_line, l = 0.8 * half_line / 2), + axis.title.y = ggplot2::element_text( + margin = ggplot2::margin(r = 0.8 * half_line, l = 0.8 * half_line / 2), angle = 90 ), axis.title.y.right = NULL, - legend.background = element_rect(colour = NA, fill = legend.bg), - legend.key = element_rect(fill = panel.bg, colour = NA), - legend.key.size = unit(1.2, "lines"), + legend.background = ggplot2::element_rect(colour = NA, fill = legend_bg), + legend.key = ggplot2::element_rect(fill = panel_bg, colour = NA), + legend.key.size = ggplot2::unit(1.2, "lines"), legend.key.height = NULL, legend.key.width = NULL, legend.margin = NULL, - legend.spacing = unit(0.2, "cm"), + legend.spacing = ggplot2::unit(0.2, "cm"), legend.spacing.x = NULL, legend.spacing.y = NULL, - legend.text = element_text(size = rel(0.8)), + legend.text = ggplot2::element_text(size = ggplot2::rel(0.8)), legend.text.align = NULL, - legend.title = element_text(size = rel(0.8), face = "bold", hjust = 0, - colour = "black"), + legend.title = ggplot2::element_text( + size = ggplot2::rel(0.8), face = "bold", hjust = 0, colour = "black" + ), legend.title.align = NULL, legend.position = "right", legend.direction = NULL, legend.justification = "center", legend.box = NULL, - legend.box.margin = margin(t = half_line, r = half_line, b = half_line, - l = half_line), - legend.box.background = element_rect(colour = NA, fill = legend.bg), - legend.box.spacing = unit(0.2, "cm"), - panel.background = element_rect(fill = panel.bg, colour = NA), - panel.border = element_blank(), - panel.grid = element_line(colour = panel.grid), - panel.grid.minor = element_line(colour = panel.grid, size = 0.25), - panel.spacing = unit(half_line, "pt"), + legend.box.margin = ggplot2::margin( + t = half_line, r = half_line, b = half_line, l = half_line + ), + legend.box.background = ggplot2::element_rect( + colour = NA, fill = legend_bg + ), + legend.box.spacing = ggplot2::unit(0.2, "cm"), + panel.background = ggplot2::element_rect(fill = panel_bg, colour = NA), + panel.border = ggplot2::element_blank(), + panel.grid = ggplot2::element_line(colour = panel_grid), + panel.grid.minor = ggplot2::element_line(colour = panel_grid, size = 0.25), + panel.spacing = ggplot2::unit(half_line, "pt"), panel.spacing.x = NULL, panel.spacing.y = NULL, panel.ontop = FALSE, - strip.background = element_rect(fill = "#8E9DA7", colour = NA), - strip.text = element_text(size = rel(0.8), colour = "#F3F3F3"), - strip.text.x = element_text(margin = margin(t = half_line, b = half_line)), - strip.text.y = element_text(margin = margin(r = half_line, l = half_line), - angle = -90), - strip.switch.pad.grid = unit(0.1, "cm"), - strip.switch.pad.wrap = unit(0.1, "cm"), + strip.background = ggplot2::element_rect(fill = "#8E9DA7", colour = NA), + strip.text = ggplot2::element_text( + size = ggplot2::rel(0.8), colour = "#F3F3F3" + ), + strip.text.x = ggplot2::element_text( + margin = ggplot2::margin(t = half_line, b = half_line) + ), + strip.text.y = ggplot2::element_text( + margin = ggplot2::margin(r = half_line, l = half_line), angle = -90 + ), + strip.switch.pad.grid = ggplot2::unit(0.1, "cm"), + strip.switch.pad.wrap = ggplot2::unit(0.1, "cm"), strip.placement = "outside", - plot.background = element_rect(colour = NA, fill = plot.bg), - plot.title = element_text(size = rel(1.2), - margin = margin(0, 0, half_line, 0)), - plot.subtitle = element_text(size = rel(1), - margin = margin(0, 0, half_line, 0)), - plot.caption = element_text(size = rel(0.6), - margin = margin(0, 0, half_line, 0)), - plot.margin = margin(t = half_line, r = half_line, b = half_line, - l = half_line), - plot.tag = element_text(size = rel(1.2), hjust = 0.5, vjust = 0.5), + plot.background = ggplot2::element_rect(colour = NA, fill = plot_bg), + plot.title = ggplot2::element_text( + size = ggplot2::rel(1.2), margin = ggplot2::margin(0, 0, half_line, 0) + ), + plot.subtitle = ggplot2::element_text( + size = ggplot2::rel(1), margin = ggplot2::margin(0, 0, half_line, 0) + ), + plot.caption = ggplot2::element_text( + size = ggplot2::rel(0.6), margin = ggplot2::margin(0, 0, half_line, 0) + ), + plot.margin = ggplot2::margin( + t = half_line, r = half_line, b = half_line, l = half_line + ), + plot.tag = ggplot2::element_text( + size = ggplot2::rel(1.2), hjust = 0.5, vjust = 0.5 + ), plot.tag.position = "topleft", complete = TRUE ) @@ -121,9 +138,11 @@ update_geom_defaults("boxplot", list(colour = "#356196")) ## Introduction -This vignette compares storage and retrieval of data by `git2rdata` with other standard R functionality. We consider `write.table()` and `read.table()` for data stored in a plain text format. `saveRDS()` and `readRDS()` use a compressed binary format. +This vignette compares storage and retrieval of data by `git2rdata` with other standard R functionality. +We consider `write.table()` and `read.table()` for data stored in a plain text format. +`saveRDS()` and `readRDS()` use a compressed binary format. -To get some meaningful results, we will use the `nassCDS` dataset from the [DAAG](https://www.rdocumentation.org/packages/DAAG/versions/1.22/topics/nassCDS) package. +To get some meaningful results, we will use the `nassCDS` dataset from the [DAAG](https://www.rdocumentation.org/packages/DAAG/versions/1.22/topics/nassCDS) package. We'll avoid the dependency on the package by directly downloading the data. ```{r download_data, eval = system.file("efficiency", "airbag.rds", package = "git2rdata") == ""} @@ -150,11 +169,12 @@ if (system.file("efficiency", "airbag.rds", package = "git2rdata") == "") { str(airbag) ``` -## Data Storage +## Data Storage ### On a File System -We start by writing the dataset as is with `write.table()`, `saveRDS()`, `write_vc()` and `write_vc()` without storage optimization. Note that `write_vc()` uses optimization by default. Since `write_vc()` creates two files for each data set, we take their combined file size into account. +We start by writing the dataset as is with `write.table()`, `saveRDS()`, `write_vc()` and `write_vc()` without storage optimization. +Note that `write_vc()` uses optimization by default. Since `write_vc()` creates two files for each data set, we take their combined file size into account. ```{r set_tmp_dir} library(git2rdata) @@ -176,29 +196,30 @@ fn <- write_vc(airbag, "airbag_verbose", root, sorting = "X", optimize = FALSE) verbose_size <- sum(file.size(file.path(root, fn))) ``` -Since the data is highly compressible, `saveRDS()` yields the smallest file at the cost of having a binary file format. Both `write_vc()` formats yield smaller files than `write.table()`. -Partly because `write_vc()` doesn't store row names and doesn't use quotes unless needed. -The difference between the optimized and verbose version of `write_vc()` is, in this case, solely due to the way `write_vc()` stores factors in the data (`tsv`) file. -The optimized version stores the indices of the factor whereas the verbose version stores the levels. -For example: `airbag$dvcat` has 5 levels with short labels (on average 5 character), storing the index requires 1 character. +Since the data is highly compressible, `saveRDS()` yields the smallest file at the cost of having a binary file format. Both `write_vc()` formats yield smaller files than `write.table()`. +Partly because `write_vc()` doesn't store row names and doesn't use quotes unless needed. +The difference between the optimized and verbose version of `write_vc()` is, in this case, solely due to the way `write_vc()` stores factors in the data (`tsv`) file. +The optimized version stores the indices of the factor whereas the verbose version stores the levels. +For example: `airbag$dvcat` has 5 levels with short labels (on average 5 character), storing the index requires 1 character. This results in more compact files. ```{r table_file_size, echo = FALSE} kable( data.frame( - method = c("saveRDS()", "write_vc(), optimized", "write_vc(), verbose", + method = c("saveRDS()", "write_vc(), optimized", "write_vc(), verbose", "write.table()"), file_size = c(rds_size, optim_size, verbose_size, base_size) / 2 ^ 10, relative = c(rds_size, optim_size, verbose_size, base_size) / base_size ), - caption = "Resulting file sizes (in kB) and file sizes relative to the size of write.table().", + caption = "Resulting file sizes (in kB) and file sizes relative to the size of + write.table().", digits = 2 ) ``` -The reduction in file size when storing in factors depends on the length of the labels, the number of levels and the number of observations. -The figure below illustrates the strong gain as soon as the level labels contain more than two characters. -The gain is less pronounced when the factor has a large number of levels. +The reduction in file size when storing in factors depends on the length of the labels, the number of levels and the number of observations. +The figure below illustrates the strong gain as soon as the level labels contain more than two characters. +The gain is less pronounced when the factor has a large number of levels. The optimization fails in extreme cases with short factor labels and a high number of levels. ```{r factor_label_length, echo = FALSE, fig.cap = "Effect of the label length on the efficiency of storing factor optimized, assuming 1000 observations", warning = FALSE} @@ -240,7 +261,7 @@ ggplot(f_ratio, aes(x = label_length, y = ratio, colour = levels)) + geom_hline(yintercept = 1, linetype = 2) + geom_line() + scale_x_continuous("label length (characters)") + - scale_y_continuous("optimized bytes / verbose bytes", + scale_y_continuous(paste("optimized bytes", "verbose bytes", sep = " / "), breaks = seq(0, 1.25, by = 0.25)) + scale_colour_manual("number of \nlevels", values = inbo_colours) ``` @@ -276,34 +297,34 @@ ggplot(f_ratio, aes(x = observations, y = ratio, colour = levels)) + geom_hline(yintercept = 1, linetype = 2) + geom_line() + scale_x_log10() + - scale_y_continuous("optimized bytes / verbose bytes", + scale_y_continuous(paste("optimized bytes", "verbose bytes", sep = " / "), breaks = seq(0, 1.25, by = 0.25)) + scale_colour_manual("number of \nlevels", values = inbo_colours) ``` ### In Git Repositories -Here we will simulate how much space the data requires to store the history in a git repository. -We will create a git repository for each method and store different subsets of the same data. -Each commit contains a new version of the data. Each version is a random sample containing 90% of the observations of the `airbag` data. +Here we will simulate how much space the data requires to store the history in a git repository. +We will create a git repository for each method and store different subsets of the same data. +Each commit contains a new version of the data. Each version is a random sample containing 90% of the observations of the `airbag` data. Two consecutive versions of the subset will have about 90% of the observations in common. -After writing each version, we commit the file, perform garbage collection (`git gc`) on the git repository and then calculate the size of the git history (`git count-objects -v`). +After writing each version, we commit the file, perform garbage collection (`git gc`) on the git repository and then calculate the size of the git history (`git count-objects -v`). ```{r git_size, eval = system.file("efficiency", "git_size.rds", package = "git2rdata") == ""} library(git2r) tmp_repo <- function() { root <- tempfile("git2rdata-efficient-git") dir.create(root) - repo <- init(root) - config(repo, user.name = "me", user.email = "me@me.com") + repo <- git2r::init(root) + git2r::config(repo, user.name = "me", user.email = "me@me.com") return(repo) } commit_and_size <- function(repo, filename) { add(repo, filename) commit(repo, "test", session = TRUE) git_size <- system( - sprintf("cd %s\ngit gc\ngit count-objects -v", dirname(repo$path)), + sprintf("cd %s\ngit gc\ngit count-objects -v", dirname(repo$path)), intern = TRUE ) git_size <- git_size[grep("size-pack", git_size)] @@ -320,7 +341,7 @@ repo_size <- replicate( 100, { observed_subset <- rbinom(nrow(airbag), size = 1, prob = 0.9) == 1 this <- airbag[ - sample(which(observed_subset)), + sample(which(observed_subset)), sample(ncol(airbag)) ] this_sorted <- airbag[observed_subset, ] @@ -337,8 +358,8 @@ repo_size <- replicate( c( write.table = commit_and_size(repo_wt, fn_wt), write.table.sorted = commit_and_size(repo_wts, fn_wts), - saveRDS = commit_and_size(repo_rds, fn_rds), - write_vc.optimized = commit_and_size(repo_wvco, fn_wvco), + saveRDS = commit_and_size(repo_rds, fn_rds), + write_vc.optimized = commit_and_size(repo_wvco, fn_wvco), write_vc.verbose = commit_and_size(repo_wvcv, fn_wvcv) ) }) @@ -354,12 +375,12 @@ if (system.file("efficiency", "git_size.rds", package = "git2rdata") == "") { } ``` -Each version of the data has on purpose a random order of observations and variables. This is what would happen in a worst case scenario as it would generate the largest possible diff. We also test `write.table()` with a stable ordering of the observations and variables. +Each version of the data has on purpose a random order of observations and variables. This is what would happen in a worst case scenario as it would generate the largest possible diff. We also test `write.table()` with a stable ordering of the observations and variables. -The randomised `write.table()` yields the largest git repository, converging to about `r sprintf("%.1f", repo_size["write.table", 100] / repo_size["write.table.sorted", 100])` times the size of a git repository based on the sorted `write.table()`. `saveRDS()` yields a `r sprintf("%.0f%%", 100 - 100 * repo_size["saveRDS", 100] / repo_size["write.table", 100])` reduction in repository size compared to the randomised `write.table()`, but still is `r sprintf("%.1f", repo_size["saveRDS", 100] / repo_size["write.table.sorted", 100])` times larger than the sorted `write.table()`. -Note that the gain of storing binary files in a git repository is much smaller than the gain in individual file size because git compresses its history. -The optimized `write_vc()` starts at `r sprintf("%.0f%%", 100 * repo_size["write_vc.optimized", 1] / repo_size["write.table.sorted", 1])` and converges toward `r sprintf("%.0f%%", 100 * repo_size["write_vc.optimized", 100] / repo_size["write.table.sorted", 100])`, the verbose version starts at `r sprintf("%.0f%%", 100 * repo_size["write_vc.verbose", 1] / repo_size["write.table.sorted", 1])` and converges towards `r sprintf("%.0f%%", 100 * repo_size["write_vc.verbose", 100] / repo_size["write.table.sorted", 100])`. -Storage size is a lot smaller when using `write_vc()` with optimization. +The randomised `write.table()` yields the largest git repository, converging to about `r sprintf("%.1f", repo_size["write.table", 100] / repo_size["write.table.sorted", 100])` times the size of a git repository based on the sorted `write.table()`. `saveRDS()` yields a `r sprintf("%.0f%%", 100 - 100 * repo_size["saveRDS", 100] / repo_size["write.table", 100])` reduction in repository size compared to the randomised `write.table()`, but still is `r sprintf("%.1f", repo_size["saveRDS", 100] / repo_size["write.table.sorted", 100])` times larger than the sorted `write.table()`. +Note that the gain of storing binary files in a git repository is much smaller than the gain in individual file size because git compresses its history. +The optimized `write_vc()` starts at `r sprintf("%.0f%%", 100 * repo_size["write_vc.optimized", 1] / repo_size["write.table.sorted", 1])` and converges toward `r sprintf("%.0f%%", 100 * repo_size["write_vc.optimized", 100] / repo_size["write.table.sorted", 100])`, the verbose version starts at `r sprintf("%.0f%%", 100 * repo_size["write_vc.verbose", 1] / repo_size["write.table.sorted", 1])` and converges towards `r sprintf("%.0f%%", 100 * repo_size["write_vc.verbose", 100] / repo_size["write.table.sorted", 100])`. +Storage size is a lot smaller when using `write_vc()` with optimization. The verbose option of `write_vc()` has little the gain in storage size. Another advantage is that `write_vc()` stores metadata. @@ -369,28 +390,30 @@ rs <- lapply( function(x) { if (x == "saveRDS") { fun <- "saveRDS" - optimized = "yes" + optimized <- "yes" } else if (x == "write_vc.optimized") { fun <- "write_vc" - optimized = "yes" + optimized <- "yes" } else if (x == "write_vc.verbose") { fun <- "write_vc" - optimized = "no" + optimized <- "no" } else if (x == "write.table") { fun <- "write.table" - optimized = "no" + optimized <- "no" } else if (x == "write.table.sorted") { fun <- "write.table" - optimized = "yes" + optimized <- "yes" } - data.frame(commit = seq_along(repo_size[x, ]), size = repo_size[x, ], + data.frame(commit = seq_along(repo_size[x, ]), size = repo_size[x, ], rel_size = repo_size[x, ] / repo_size["write.table.sorted", ], fun = fun, optimized = optimized, stringsAsFactors = FALSE) } ) rs <- do.call(rbind, rs) rs$optimized <- factor(rs$optimized, levels = c("yes", "no")) -ggplot(rs, aes(x = commit, y = size / 2^10, colour = fun, linetype = optimized)) + +ggplot( + rs, aes(x = commit, y = size / 2^10, colour = fun, linetype = optimized) +) + geom_line() + scale_y_continuous("repo size (in MiB)") + scale_colour_manual("function", values = inbo_colours) @@ -399,13 +422,14 @@ ggplot(rs, aes(x = commit, y = size / 2^10, colour = fun, linetype = optimized)) ```{r plot_rel_git_size, echo = FALSE, fig.cap = "Relative size of the git repository when compared to write.table()."} ggplot(rs, aes(x = commit, y = rel_size, colour = fun, linetype = optimized)) + geom_line() + - scale_y_continuous("size relative to sorted write.table()", breaks = 0:10) + + scale_y_continuous("size relative to sorted write.table()", breaks = 0:10) + scale_colour_manual("function", values = inbo_colours) ``` ## Timings -The code below runs a microbenchmark on the four methods. A microbenchmark runs the code a hundred times and yields a distribution of timings for each expression. +The code below runs a microbenchmark on the four methods. +A microbenchmark runs the code a hundred times and yields a distribution of timings for each expression. ### Writing Data @@ -415,14 +439,16 @@ mb <- microbenchmark( write.table = write.table(airbag, file.path(root, "base_R.tsv"), sep = "\t"), saveRDS = saveRDS(airbag, file.path(root, "base_R.rds")), write_vc.optim = write_vc(airbag, "airbag_optimize", root, sorting = "X"), - write_vc.verbose = write_vc(airbag, "airbag_verbose", root, sorting = "X", + write_vc.verbose = write_vc(airbag, "airbag_verbose", root, sorting = "X", optimize = FALSE) ) mb$time <- mb$time / 1e6 ``` ```{r store_file_timings, echo = FALSE} -if (system.file("efficiency", "file_timings.rds", package = "git2rdata") == "") { +if ( + system.file("efficiency", "file_timings.rds", package = "git2rdata") == "" +) { saveRDS(mb, file.path("..", "inst", "efficiency", "file_timings.rds")) } else { mb <- readRDS( @@ -433,14 +459,13 @@ if (system.file("efficiency", "file_timings.rds", package = "git2rdata") == "") ```{r median_write, echo = FALSE} median_time <- aggregate(time ~ expr, data = mb, FUN = median) -write_ratio <- 100 * median_time$time / +write_ratio <- 100 * median_time$time / median_time$time[median_time$expr == "write.table"] names(write_ratio) <- median_time$expr ``` - -`write_vc()` takes `r paste(sprintf("%.0f%%", -100 + write_ratio[grep("write_vc", names(write_ratio))]), collapse = " to ")` more time than `write.table()` because it needs to prepare the metadata and sort the observations and variables. -When overwriting existing data, `write_vc()` checks the new data against the existing metadata. +`write_vc()` takes `r paste(sprintf("%.0f%%", -100 + write_ratio[grep("write_vc", names(write_ratio))]), collapse = " to ")` more time than `write.table()` because it needs to prepare the metadata and sort the observations and variables. +When overwriting existing data, `write_vc()` checks the new data against the existing metadata. `saveRDS()` requires `r sprintf("%.0f%%", write_ratio["saveRDS"])` of the time that `write.table()` needs. ```{r plot_file_timings, echo = FALSE, fig.cap = "Boxplot of the write timings for the different methods."} @@ -449,14 +474,14 @@ levels(mb$expr) <- gsub("write_vc\\.", "write_vc\n", levels(mb$expr)) ggplot(mb, aes(x = expr, y = time)) + geom_boxplot() + scale_y_continuous("Time (in milliseconds)", limits = c(0, NA)) + - theme(axis.title.x = element_blank()) + theme(axis.title.x = ggplot2::element_blank()) ``` ### Reading Data ```{r get_read_timings, eval = system.file("efficiency", "read_timings.rds", package = "git2rdata") == ""} mb <- microbenchmark( - read.table = read.table(file.path(root, "base_R.tsv"), header = TRUE, + read.table = read.table(file.path(root, "base_R.tsv"), header = TRUE, sep = "\t"), readRDS = readRDS(file.path(root, "base_R.rds")), read_vc.optim = read_vc("airbag_optimize", root), @@ -466,7 +491,9 @@ mb$time <- mb$time / 1e6 ``` ```{r store_read_timings, echo = FALSE} -if (system.file("efficiency", "read_timings.rds", package = "git2rdata") == "") { +if ( + system.file("efficiency", "read_timings.rds", package = "git2rdata") == "" +) { saveRDS(mb, file.path("..", "inst", "efficiency", "read_timings.rds")) } else { mb <- readRDS( @@ -477,7 +504,7 @@ if (system.file("efficiency", "read_timings.rds", package = "git2rdata") == "") ```{r median_read, echo = FALSE} median_time <- aggregate(time ~ expr, data = mb, FUN = median) -read_ratio <- 100 * median_time$time / +read_ratio <- 100 * median_time$time / median_time$time[median_time$expr == "read.table"] names(read_ratio) <- median_time$expr ``` @@ -486,12 +513,12 @@ The timings on reading the data is another story. Reading the binary format take ```{r plot_read_timings, echo = FALSE, fig.cap = "Boxplots for the read timings for the different methods."} mb$expr <- factor( - mb$expr, + mb$expr, levels = c("readRDS", "read.table", "read_vc.optim", "read_vc.verbose") ) levels(mb$expr) <- gsub("read_vc\\.", "read_vc\n", levels(mb$expr)) ggplot(mb, aes(x = expr, y = time)) + geom_boxplot() + scale_y_continuous("Time (in milliseconds)", limits = c(0, NA)) + - theme(axis.title.x = element_blank()) + theme(axis.title.x = ggplot2::element_blank()) ``` diff --git a/vignettes/split_by.Rmd b/vignettes/split_by.Rmd index 90cea152..243ccfed 100644 --- a/vignettes/split_by.Rmd +++ b/vignettes/split_by.Rmd @@ -30,85 +30,105 @@ theme_inbo <- function(base_size = 12, base_family = "") { panel_grid <- "white" plot_bg <- "white" half_line <- base_size / 2 - theme( - line = element_line(colour = "black", size = 0.5, linetype = 1, - lineend = "butt"), - rect = element_rect(fill = rect_bg, colour = "black", size = 0.5, - linetype = 1), - text = element_text(family = base_family, face = "plain", - colour = "#843860", size = base_size, hjust = 0.5, - vjust = 0.5, angle = 0, lineheight = 0.9, - margin = margin(), debug = FALSE), - axis.line = element_blank(), - axis.line.x = element_blank(), - axis.line.y = element_blank(), - axis.text = element_text(size = rel(0.8)), - axis.text.x = element_text(margin = margin(t = 0.8 * half_line / 2), - vjust = 1), + ggplot2::theme( + line = ggplot2::element_line( + colour = "black", size = 0.5, linetype = 1, lineend = "butt" + ), + rect = ggplot2::element_rect( + fill = rect_bg, colour = "black", size = 0.5, linetype = 1 + ), + text = ggplot2::element_text( + family = base_family, face = "plain", colour = "#843860", + size = base_size, hjust = 0.5, vjust = 0.5, angle = 0, lineheight = 0.9, + margin = ggplot2::margin(), debug = FALSE + ), + axis.line = ggplot2::element_blank(), + axis.line.x = ggplot2::element_blank(), + axis.line.y = ggplot2::element_blank(), + axis.text = ggplot2::element_text(size = ggplot2::rel(0.8)), + axis.text.x = ggplot2::element_text( + margin = ggplot2::margin(t = 0.8 * half_line / 2), vjust = 1 + ), axis.text.x.top = NULL, - axis.text.y = element_text(margin = margin(r = 0.8 * half_line / 2), - hjust = 1), + axis.text.y = ggplot2::element_text( + margin = ggplot2::margin(r = 0.8 * half_line / 2), hjust = 1 + ), axis.text.y.right = NULL, - axis.ticks = element_line(), - axis.ticks.length = unit(0.15, "cm"), - axis.title = element_text(colour = "black"), - axis.title.x = element_text( - margin = margin(t = 0.8 * half_line, b = 0.8 * half_line / 2) + axis.ticks = ggplot2::element_line(), + axis.ticks.length = ggplot2::unit(0.15, "cm"), + axis.title = ggplot2::element_text(colour = "black"), + axis.title.x = ggplot2::element_text( + margin = ggplot2::margin(t = 0.8 * half_line, b = 0.8 * half_line / 2) ), axis.title.x.top = NULL, - axis.title.y = element_text( - margin = margin(r = 0.8 * half_line, l = 0.8 * half_line / 2), + axis.title.y = ggplot2::element_text( + margin = ggplot2::margin(r = 0.8 * half_line, l = 0.8 * half_line / 2), angle = 90 ), axis.title.y.right = NULL, - legend.background = element_rect(colour = NA, fill = legend_bg), - legend.key = element_rect(fill = panel_bg, colour = NA), - legend.key.size = unit(1.2, "lines"), + legend.background = ggplot2::element_rect(colour = NA, fill = legend_bg), + legend.key = ggplot2::element_rect(fill = panel_bg, colour = NA), + legend.key.size = ggplot2::unit(1.2, "lines"), legend.key.height = NULL, legend.key.width = NULL, legend.margin = NULL, - legend.spacing = unit(0.2, "cm"), + legend.spacing = ggplot2::unit(0.2, "cm"), legend.spacing.x = NULL, legend.spacing.y = NULL, - legend.text = element_text(size = rel(0.8)), + legend.text = ggplot2::element_text(size = ggplot2::rel(0.8)), legend.text.align = NULL, - legend.title = element_text(size = rel(0.8), face = "bold", hjust = 0, - colour = "black"), + legend.title = ggplot2::element_text( + size = ggplot2::rel(0.8), face = "bold", hjust = 0, colour = "black" + ), legend.title.align = NULL, legend.position = "right", legend.direction = NULL, legend.justification = "center", legend.box = NULL, - legend.box.margin = margin(t = half_line, r = half_line, b = half_line, - l = half_line), - legend.box.background = element_rect(colour = NA, fill = legend_bg), - legend.box.spacing = unit(0.2, "cm"), - panel.background = element_rect(fill = panel_bg, colour = NA), - panel.border = element_blank(), - panel.grid = element_line(colour = panel_grid), - panel.grid.minor = element_line(colour = panel_grid, size = 0.25), - panel.spacing = unit(half_line, "pt"), + legend.box.margin = ggplot2::margin( + t = half_line, r = half_line, b = half_line, l = half_line + ), + legend.box.background = ggplot2::element_rect( + colour = NA, fill = legend_bg + ), + legend.box.spacing = ggplot2::unit(0.2, "cm"), + panel.background = ggplot2::element_rect(fill = panel_bg, colour = NA), + panel.border = ggplot2::element_blank(), + panel.grid = ggplot2::element_line(colour = panel_grid), + panel.grid.minor = ggplot2::element_line(colour = panel_grid, size = 0.25), + panel.spacing = ggplot2::unit(half_line, "pt"), panel.spacing.x = NULL, panel.spacing.y = NULL, panel.ontop = FALSE, - strip.background = element_rect(fill = "#8E9DA7", colour = NA), - strip.text = element_text(size = rel(0.8), colour = "#F3F3F3"), - strip.text.x = element_text(margin = margin(t = half_line, b = half_line)), - strip.text.y = element_text(margin = margin(r = half_line, l = half_line), - angle = -90), - strip.switch.pad.grid = unit(0.1, "cm"), - strip.switch.pad.wrap = unit(0.1, "cm"), + strip.background = ggplot2::element_rect(fill = "#8E9DA7", colour = NA), + strip.text = ggplot2::element_text( + size = ggplot2::rel(0.8), colour = "#F3F3F3" + ), + strip.text.x = ggplot2::element_text( + margin = ggplot2::margin(t = half_line, b = half_line) + ), + strip.text.y = ggplot2::element_text( + margin = ggplot2::margin(r = half_line, l = half_line), angle = -90 + ), + strip.switch.pad.grid = ggplot2::unit(0.1, "cm"), + strip.switch.pad.wrap = ggplot2::unit(0.1, "cm"), strip.placement = "outside", - plot.background = element_rect(colour = NA, fill = plot_bg), - plot.title = element_text(size = rel(1.2), - margin = margin(0, 0, half_line, 0)), - plot.subtitle = element_text(size = rel(1), - margin = margin(0, 0, half_line, 0)), - plot.caption = element_text(size = rel(0.6), - margin = margin(0, 0, half_line, 0)), - plot.margin = margin(t = half_line, r = half_line, b = half_line, - l = half_line), - plot.tag = element_text(size = rel(1.2), hjust = 0.5, vjust = 0.5), + plot.background = ggplot2::element_rect(colour = NA, fill = plot_bg), + plot.title = ggplot2::element_text( + size = ggplot2::rel(1.2), margin = ggplot2::margin(0, 0, half_line, 0) + ), + plot.subtitle = ggplot2::element_text( + size = ggplot2::rel(1), margin = ggplot2::margin(0, 0, half_line, 0) + ), + plot.caption = ggplot2::element_text( + size = ggplot2::rel(0.6), margin = ggplot2::margin(0, 0, half_line, 0) + ), + plot.margin = ggplot2::margin( + t = half_line, r = half_line, b = half_line, l = half_line + ), + plot.tag = ggplot2::element_text( + size = ggplot2::rel(1.2), hjust = 0.5, vjust = 0.5 + ), plot.tag.position = "topleft", complete = TRUE ) @@ -202,7 +222,7 @@ ggplot(combinations, aes(x = b, y = ratio, colour = factor(a))) + geom_line() + facet_wrap(~ paste("r =", r)) + scale_x_continuous( - expression(b~{"="}~N[s]~{"/"}~N), + expression(b~{"="}~N[s]~{"/"}~N), # nolint labels = function(x) { paste0(100 * x, "%") } @@ -214,7 +234,7 @@ ggplot(combinations, aes(x = b, y = ratio, colour = factor(a))) + } ) + scale_colour_manual( - "a = s / r", + paste("a = s", "r", sep = " / "), values = inbo_colours, labels = c("1/4", "1/2", "1", "2", "4") ) diff --git a/vignettes/version_control.Rmd b/vignettes/version_control.Rmd index 92ed48c4..15155465 100644 --- a/vignettes/version_control.Rmd +++ b/vignettes/version_control.Rmd @@ -226,7 +226,7 @@ Let's add an observation with a new factor level. If we store the updated datafr ```{r factor2} updated <- data.frame( - color = c("red", "green", "blue"), + color = c("red", "green", "blue"), stringsAsFactors = TRUE ) write_vc(updated, "factor2", root, sorting = "color") @@ -291,7 +291,7 @@ write_vc(old, "relabel", root, sorting = "color") relabel("relabel", root, change = list(color = c(red = "rood", blue = "blauw"))) print_file("relabel.yml", root) relabel( - "relabel", root, + "relabel", root, change = data.frame( factor = "color", old = "blauw", new = "blue", stringsAsFactors = TRUE ) diff --git a/vignettes/workflow.Rmd b/vignettes/workflow.Rmd index b6f0b904..ada961c4 100644 --- a/vignettes/workflow.Rmd +++ b/vignettes/workflow.Rmd @@ -61,7 +61,7 @@ writeLines("*extra*", file.path(path, ".gitignore")) git2r::add(init_repo, ".gitignore", force = TRUE) git2r::commit(init_repo, message = "Initial commit") # push initial commit to remote -git2r::push(init_repo, "origin", "refs/heads/master") +git2r::push(init_repo, "origin", "refs/heads/master") # nolint rm(init_repo) ``` @@ -131,7 +131,7 @@ Sys.sleep(1.2) ```{r} status(repo, ignored = TRUE) -fn <- write_vc(beaver2, "extra_beaver", repo, sorting = "time", stage = TRUE, +fn <- write_vc(beaver2, "extra_beaver", repo, sorting = "time", stage = TRUE, force = TRUE) status(repo) cm2 <- commit(repo, message = "Second commit") @@ -154,7 +154,7 @@ Sys.sleep(1.2) beaver1$beaver <- 1 beaver2$beaver <- 2 beaver <- rbind(beaver1, beaver2) -fn <- write_vc(beaver, "beaver", repo, sorting = c("beaver", "time"), +fn <- write_vc(beaver, "beaver", repo, sorting = c("beaver", "time"), strict = FALSE, stage = TRUE) file.remove(list.files(path, "extra", full.names = TRUE)) status(repo) @@ -185,7 +185,7 @@ Below is an example script recreating the "beaver" git2rdata object from the [th library(git2rdata) # step 1: setup the repository and data path repo <- repository(".") -data_path <- "data/beaver" +data_path <- file.path("data", "beaver") # step 1b: sync the repository with the remote pull(repo = repo) # step 2: remove all existing data files @@ -195,7 +195,7 @@ rm_data(root = repo, path = data_path, stage = TRUE) beaver1$beaver <- 1 beaver2$beaver <- 2 body_temp <- rbind(beaver1, beaver2) -fn <- write_vc(x = body_temp, file = file.path(data_path, "body_temperature"), +fn <- write_vc(x = body_temp, file = file.path(data_path, "body_temperature"), root = repo, sorting = c("beaver", "time"), stage = TRUE) # step 4: remove any dangling metadata files @@ -233,26 +233,26 @@ Consider running the import from the command line. e.g. `Rscript -e 'mypackage:: import_body_temp <- function(path) { # step 1: setup the repository and data path repo <- repository(path) - data_path <- "data/beaver" + data_path <- file.path("data", "beaver") # step 1b: sync the repository with the remote pull(repo = repo) # step 2: remove all existing data files rm_data(root = repo, path = data_path, stage = TRUE) - + # step 3: write all relevant git2rdata objects to the data path beaver1$beaver <- 1 beaver2$beaver <- 2 body_temp <- rbind(beaver1, beaver2) - fn <- write_vc(x = body_temp, file = file.path(data_path, "body_temperature"), + write_vc(x = body_temp, file = file.path(data_path, "body_temperature"), root = repo, sorting = c("beaver", "time"), stage = TRUE) - + # step 4: remove any dangling metadata files prune_meta(root = repo, path = data_path, stage = TRUE) - + # step 5: commit the changes - cm <- commit(repo = repo, message = "import", session = TRUE) + commit(repo = repo, message = "import", session = TRUE) # step 5b: sync the repository with the remote - push(repo = repo) + push(object = repo) } ``` @@ -274,7 +274,7 @@ analysis <- function(ds_name, repo) { report <- function(x) { knitr::kable( coef(summary(x$model)), - caption = sprintf("**dataset:** %s \n**commit:** %s \n**repository:** %s", + caption = sprintf("**dataset:** %s \n**commit:** %s \n**repository:** %s", x$dataset, x$commit$commit, x$repository) ) } From 087fa91ff9968715c6a425384e074f29aa9ddb87 Mon Sep 17 00:00:00 2001 From: Thierry Onkelinx Date: Tue, 25 Jan 2022 12:01:56 +0100 Subject: [PATCH 12/18] reduce cyclomatic complexity --- R/is_git2rmeta.R | 124 ++++++++++++++++++++++++++--------------------- 1 file changed, 68 insertions(+), 56 deletions(-) diff --git a/R/is_git2rmeta.R b/R/is_git2rmeta.R index 868b2ad4..1261966e 100644 --- a/R/is_git2rmeta.R +++ b/R/is_git2rmeta.R @@ -30,77 +30,73 @@ is_git2rmeta.default <- function(file, root, #' @importFrom assertthat assert_that is.string #' @importFrom yaml read_yaml #' @importFrom utils packageVersion -is_git2rmeta.character <- function(file, root = ".", - message = c("none", "warning", "error")) { +is_git2rmeta.character <- function( + file, root = ".", message = c("none", "warning", "error") +) { assert_that(is.string(file), is.string(root)) message <- match.arg(message) root <- normalizePath(root, winslash = "/", mustWork = TRUE) file <- clean_data_path(root = root, file = file) - if (!file.exists(file["meta_file"])) { - msg <- ifelse( + check <- error_warning( + file.exists(file["meta_file"]), + msg = ifelse( file.exists(file["raw_file"]), "Metadata file missing.", "`git2rdata` object not found." - ) - switch(message, error = stop(msg, call. = FALSE), - warning = warning(msg, call. = FALSE)) - return(FALSE) + ), + message = message + ) + if (!check) { + return(check) } # read the metadata meta_data <- read_yaml(file["meta_file"]) - if (!has_name(meta_data, "..generic")) { - msg <- "No '..generic' element." - switch(message, error = stop(msg, call. = FALSE), - warning = warning(msg, call. = FALSE)) - return(FALSE) - } - if (!has_name(meta_data[["..generic"]], "hash")) { - msg <- "Corrupt metadata, no hash found." - switch(message, error = stop(msg, call. = FALSE), - warning = warning(msg, call. = FALSE)) - return(FALSE) - } - if (!has_name(meta_data[["..generic"]], "git2rdata")) { - msg <- "Data stored using an older version of `git2rdata`. -See `?upgrade_data()`." - switch(message, error = stop(msg, call. = FALSE), - warning = warning(msg, call. = FALSE)) - return(FALSE) - } - if (!has_name(meta_data[["..generic"]], "optimize")) { - msg <- "Corrupt metadata, optimize flag not found." - switch(message, error = stop(msg, call. = FALSE), - warning = warning(msg, call. = FALSE)) - return(FALSE) - } + check <- error_warning( + has_name(meta_data, "..generic"), + msg = "No '..generic' element.", + message = message, previous = check + ) + + check <- error_warning( + has_name(meta_data[["..generic"]], "hash"), + msg = "Corrupt metadata, no hash found.", + message = message, previous = check + ) + + check <- error_warning( + has_name(meta_data[["..generic"]], "git2rdata"), + msg = "Data stored using an older version of `git2rdata`. +See `?upgrade_data()`.", + message = message, previous = check + ) + used_version <- package_version(meta_data[["..generic"]][["git2rdata"]]) - if (used_version < package_version("0.1.0.9001") || ( - used_version < package_version("0.4.0") & - !meta_data[["..generic"]][["optimize"]] - )) { - msg <- "Data stored using an older version of `git2rdata`. -See `?upgrade_data()`." - switch(message, error = stop(msg, call. = FALSE), - warning = warning(msg, call. = FALSE)) - return(FALSE) - } - if (!has_name(meta_data[["..generic"]], "data_hash")) { - msg <- "Corrupt metadata, no data hash found." - switch(message, error = stop(msg, call. = FALSE), - warning = warning(msg, call. = FALSE)) - return(FALSE) - } + check <- error_warning( + used_version >= package_version("0.4.0") || ( + used_version >= package_version("0.2.0") && + meta_data[["..generic"]][["optimize"]] + ), + msg = "Data stored using an older version of `git2rdata`. +See `?upgrade_data()`.", + message = message, previous = check + ) + + check <- error_warning( + has_name(meta_data[["..generic"]], "data_hash"), + msg = "Corrupt metadata, no data hash found.", + message = message, previous = check + ) + current_hash <- meta_data[["..generic"]][["hash"]] - if (current_hash != metadata_hash(meta_data)) { - msg <- "Corrupt metadata, mismatching hash." - switch(message, error = stop(msg, call. = FALSE), - warning = warning(msg, call. = FALSE)) - return(FALSE) - } + check <- error_warning( + current_hash == metadata_hash(meta_data), + msg = "Corrupt metadata, mismatching hash.", + message = message, previous = check + ) - return(TRUE) + return(check) } #' @export @@ -119,3 +115,19 @@ metadata_hash <- function(meta_data) { meta_data[["..generic"]][["data_hash"]] <- NULL hash(as.yaml(meta_data)) } + +error_warning <- function( + test, msg, message = c("none", "warning", "error"), previous = TRUE +) { + message <- match.arg(message) + if (!previous) { + return(FALSE) + } + if (!test) { + switch( + message, error = stop(msg, call. = FALSE), + warning = warning(msg, call. = FALSE) + ) + } + return(test) +} From 52e8a52cc7bb3e47241744018fe836ba2d5fb734 Mon Sep 17 00:00:00 2001 From: Thierry Onkelinx Date: Tue, 25 Jan 2022 13:41:19 +0100 Subject: [PATCH 13/18] reduce cyclomatic complexity of upgrade_data() --- NAMESPACE | 1 + R/upgrade_data.R | 96 ++++++++++++++++----------------- R/utils.R | 14 +++++ tests/testthat/test_e_upgrade.R | 48 ++++++++--------- 4 files changed, 83 insertions(+), 76 deletions(-) diff --git a/NAMESPACE b/NAMESPACE index e8028d3c..9e7634f7 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -82,6 +82,7 @@ importFrom(git2r,workdir) importFrom(methods,setOldClass) importFrom(stats,setNames) importFrom(utils,file_test) +importFrom(utils,flush.console) importFrom(utils,packageVersion) importFrom(utils,read.table) importFrom(utils,write.table) diff --git a/R/upgrade_data.R b/R/upgrade_data.R index 3e942be4..cbf99c00 100644 --- a/R/upgrade_data.R +++ b/R/upgrade_data.R @@ -37,13 +37,13 @@ upgrade_data.default <- function(file, root, verbose, path, ...) { stop("a 'root' of class ", class(root), " is not supported", call. = FALSE) } -#' @importFrom assertthat assert_that is.string is.flag noNA +#' @importFrom assertthat assert_that is.string #' @importFrom yaml read_yaml write_yaml #' @importFrom utils packageVersion #' @export upgrade_data.character <- function( file, root = ".", verbose = TRUE, ..., path) { - assert_that(is.string(root), is.flag(verbose), noNA(verbose)) + assert_that(is.string(root)) root <- normalizePath(root, winslash = "/", mustWork = TRUE) if (missing(file)) { assert_that(is.string(path)) @@ -72,56 +72,54 @@ upgrade_data.character <- function( if (has_name(meta_data[["..generic"]], "git2rdata")) { current <- package_version(meta_data[["..generic"]][["git2rdata"]]) if (current >= package_version("0.4.0")) { - if (verbose) { - message(target, " already up to date") - } + display(verbose, c(target, " already up to date")) return(target) } - if ( - current >= package_version("0.1.0.9001") && - meta_data[["..generic"]][["optimize"]] - ) { - assert_that( - has_name(meta_data[["..generic"]], "optimize"), - msg = paste(target, "has corrupt metadata, optimize flag not found.") - ) - if (verbose) { - message(target, " already up to date") - } + assert_that( + has_name(meta_data[["..generic"]], "optimize"), + msg = paste(target, "has corrupt metadata, optimize flag not found.") + ) + assert_that( + current >= package_version("0.2.0"), + msg = "Data stored with ancient version of git2rdata. +Please install version 0.3.1 and upgrade to that version first. +Then reinstall the current version and upgrade to this version. +Install version 0.3.1 with remotes::install_github('ropensci/git2rdata@v0.3.1')" + ) + if (meta_data[["..generic"]][["optimize"]]) { + display(verbose, c(target, " already up to date")) return(target) } - if (!meta_data[["..generic"]][["optimize"]]) { - na_string <- meta_data[["..generic"]][["NA string"]] - details <- meta_data[names(meta_data) != "..generic"] - col_names <- names(details) - col_classes <- vapply(details, "[[", character(1), "class") - col_type <- c( - character = "character", factor = "character", integer = "integer", - numeric = "numeric", logical = "logical", Date = "Date", - POSIXct = "character", complex = "complex" - ) - old <- read.table( - file = file["raw_file"], header = TRUE, sep = "\t", quote = "\"", - dec = ".", numerals = "warn.loss", na.strings = na_string, - colClasses = setNames(col_type[col_classes], col_names), - comment.char = "", - stringsAsFactors = FALSE, fileEncoding = "UTF-8" - ) - file.remove(file["raw_file"]) - file["raw_file"] <- gsub("\\.tsv$", ".csv", file["raw_file"]) - for (i in which(col_type[col_classes] == "character")) { - x <- gsub("\\\"", "\\\"\\\"", old[[i]]) - to_escape <- grepl("(\"|,|\n)", x) - x[to_escape] <- paste0("\"", x[to_escape], "\"") - x[is.na(x)] <- na_string - old[[i]] <- x - } - write.table( - x = old, file = file["raw_file"], - append = FALSE, quote = FALSE, sep = ",", eol = "\n", na = na_string, - dec = ".", row.names = FALSE, col.names = TRUE, fileEncoding = "UTF-8" - ) + na_string <- meta_data[["..generic"]][["NA string"]] + details <- meta_data[names(meta_data) != "..generic"] + col_names <- names(details) + col_classes <- vapply(details, "[[", character(1), "class") + col_type <- c( + character = "character", factor = "character", integer = "integer", + numeric = "numeric", logical = "logical", Date = "Date", + POSIXct = "character", complex = "complex" + ) + old <- read.table( + file = file["raw_file"], header = TRUE, sep = "\t", quote = "\"", + dec = ".", numerals = "warn.loss", na.strings = na_string, + colClasses = setNames(col_type[col_classes], col_names), + comment.char = "", + stringsAsFactors = FALSE, fileEncoding = "UTF-8" + ) + file.remove(file["raw_file"]) + file["raw_file"] <- gsub("\\.tsv$", ".csv", file["raw_file"]) + for (i in which(col_type[col_classes] == "character")) { + x <- gsub("\\\"", "\\\"\\\"", old[[i]]) + to_escape <- grepl("(\"|,|\n)", x) + x[to_escape] <- paste0("\"", x[to_escape], "\"") + x[is.na(x)] <- na_string + old[[i]] <- x } + write.table( + x = old, file = file["raw_file"], + append = FALSE, quote = FALSE, sep = ",", eol = "\n", na = na_string, + dec = ".", row.names = FALSE, col.names = TRUE, fileEncoding = "UTF-8" + ) meta_data[["..generic"]][["git2rdata"]] <- NULL meta_data[["..generic"]][["data_hash"]] <- NULL } @@ -134,9 +132,7 @@ upgrade_data.character <- function( meta_data[["..generic"]][["data_hash"]] <- datahash(file["raw_file"]) } write_yaml(meta_data, file["meta_file"], fileEncoding = "UTF-8") - if (verbose) { - message(file["meta_file"], " updated") - } + display(verbose, c(file["meta_file"], " updated")) return(target) } diff --git a/R/utils.R b/R/utils.R index b93e3a57..3c139c21 100644 --- a/R/utils.R +++ b/R/utils.R @@ -5,3 +5,17 @@ release_questions <- function() { # nocov start "Did you ran `gramr::check_project(exclude_chunks = TRUE)`" ) } # nocov end + +#' @noRd +#' @importFrom utils flush.console +#' @importFrom assertthat assert_that is.flag noNA +display <- function(verbose, message, linefeed = TRUE) { + assert_that(is.flag(verbose), noNA(verbose)) + assert_that(is.flag(linefeed), noNA(linefeed)) + + if (verbose) { + message(message, appendLF = linefeed) + flush.console() + } + return(invisible(NULL)) +} diff --git a/tests/testthat/test_e_upgrade.R b/tests/testthat/test_e_upgrade.R index 89a21c40..aef7dc69 100644 --- a/tests/testthat/test_e_upgrade.R +++ b/tests/testthat/test_e_upgrade.R @@ -56,18 +56,9 @@ test_that("upgrade_data() works on single files", { expect_false(file_test("-f", file.path(path, "verbose_0_3_1.tsv"))) expect_is(read_vc("verbose_0_3_1", path), "data.frame") - expect_message( - z <- upgrade_data(file = "optimized_0_0_4", root = path), "updated" - ) - expect_true(file_test("-f", file.path(path, "optimized_0_0_4.tsv"))) - expect_is(read_vc("optimized_0_0_4", path), "data.frame") - - expect_message( - z <- upgrade_data(file = "verbose_0_0_4", root = path), "updated" + expect_error( + upgrade_data(file = "optimized_0_0_4", root = path), "ancient" ) - expect_true(file_test("-f", file.path(path, "verbose_0_0_4.csv"))) - expect_false(file_test("-f", file.path(path, "verbose_0_0_4.tsv"))) - expect_is(read_vc("verbose_0_0_4", path), "data.frame") }) file.remove( @@ -78,7 +69,9 @@ dir.create(root) origin <- system.file("testthat", package = "git2rdata") file.copy(origin, root, recursive = TRUE) path <- file.path(root, "testthat") - +file.remove( + list.files(path, pattern = "0_0_4", full.names = TRUE) +) test_that("upgrade_data() works on paths", { expect_message(z <- upgrade_data(root = root, path = ".")) expect_is(z, "character") @@ -87,18 +80,19 @@ test_that("upgrade_data() works on paths", { expect_is(read_vc("verbose_0_4_0", path), "data.frame") expect_is(read_vc("optimized_0_3_1", path), "data.frame") expect_is(read_vc("verbose_0_3_1", path), "data.frame") - expect_is(read_vc("optimized_0_0_4", path), "data.frame") - expect_is(read_vc("verbose_0_0_4", path), "data.frame") }) - file.remove( list.files(root, recursive = TRUE, full.names = TRUE) ) + root <- tempfile("git2rdata-upgrade") dir.create(root) origin <- system.file("testthat", package = "git2rdata") file.copy(origin, root, recursive = TRUE) path <- file.path(root, "testthat") +file.remove( + list.files(path, pattern = "0_0_4", full.names = TRUE) +) repo <- git2r::init(root) git2r::add(repo, list.files(root, recursive = TRUE)) git2r::commit(repo, message = "initial commit") @@ -111,22 +105,30 @@ test_that("upgrade_data() works on a git repository", { expect_is(read_vc("verbose_0_4_0", path), "data.frame") expect_is(read_vc("optimized_0_3_1", path), "data.frame") expect_is(read_vc("verbose_0_3_1", path), "data.frame") - expect_is(read_vc("optimized_0_0_4", path), "data.frame") - expect_is(read_vc("verbose_0_0_4", path), "data.frame") expect_identical( vapply(status(repo), length, integer(1)), - c(staged = 0L, unstaged = 5L, untracked = 2L) + c(staged = 0L, unstaged = 2L, untracked = 1L) ) expect_silent( upgrade_data(root = repo, path = ".", verbose = FALSE, stage = TRUE) ) expect_identical( vapply(status(repo), length, integer(1)), - c(staged = 7L, unstaged = 0L, untracked = 0L) + c(staged = 3L, unstaged = 0L, untracked = 0L) ) }) +file.remove( + list.files( + git2r::workdir(repo), recursive = TRUE, full.names = TRUE, all.files = TRUE + ) +) test_that("validation", { + root <- tempfile("git2rdata-upgrade") + dir.create(root) + origin <- system.file("testthat", package = "git2rdata") + file.copy(origin, root, recursive = TRUE) + path <- file.path(root, "testthat") expect_error( upgrade_data(root = 1), "a 'root' of class numeric is not supported" ) @@ -138,11 +140,5 @@ test_that("validation", { upgrade_data(file = "verbose_0_0_4", root = path), "is not a git2rdata object" ) + file.remove(list.files(path, full.names = TRUE)) }) - - -file.remove( - list.files( - git2r::workdir(repo), recursive = TRUE, full.names = TRUE, all.files = TRUE - ) -) From 01cd4f6ccb4f38bf5b23c5cb3c9a02d432f62993 Mon Sep 17 00:00:00 2001 From: Thierry Onkelinx Date: Tue, 25 Jan 2022 14:34:45 +0100 Subject: [PATCH 14/18] recent_commit() handles data files that no longer exist in the workspace. --- R/recent_commit.R | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/R/recent_commit.R b/R/recent_commit.R index b95587a0..7e479201 100644 --- a/R/recent_commit.R +++ b/R/recent_commit.R @@ -89,21 +89,13 @@ recent_commit.default <- function(file, root, data = FALSE) { recent_commit.git_repository <- function(file, root, data = FALSE) { assert_that(is.string(file), is.flag(data), noNA(data)) - path <- unique(dirname(file)) - if (path == ".") { - path <- "" - } + path <- ifelse(dirname(file) == ".", "", dirname(file)) if (data) { - is_git2rdata(file = file, root = root, message = "error") - file <- clean_data_path(root = workdir(root), file, normalize = FALSE) - meta_data <- read_yaml(file["meta_file"]) - file["raw_file"] <- ifelse( - meta_data[["..generic"]][["optimize"]], - file["raw_file"], - gsub("\\.tsv$", ".csv", file["raw_file"]) - ) + bn <- gsub("\\..*$", "", basename(file)) + name <- paste(bn, c("tsv", "csv"), sep = ".") + } else { + name <- basename(file) } - name <- basename(file) blobs <- odb_blobs(root) blobs <- blobs[blobs$path == path & blobs$name %in% name, ] blobs <- blobs[blobs$when <= as.data.frame(last_commit(root))$when, ] From 8bee1283fd2f5ae387a5693757240f222678f840 Mon Sep 17 00:00:00 2001 From: Thierry Onkelinx Date: Tue, 25 Jan 2022 14:37:52 +0100 Subject: [PATCH 15/18] update to most recent checklist --- .github/workflows/check_on_branch.yml | 6 ++- .github/workflows/check_on_different_r_os.yml | 6 +-- .github/workflows/check_on_main.yml | 7 ++-- _pkgdown.yml | 8 ++-- pkgdown/extra.css | 37 +++++++++++++++++-- 5 files changed, 48 insertions(+), 16 deletions(-) diff --git a/.github/workflows/check_on_branch.yml b/.github/workflows/check_on_branch.yml index 7e61c527..ae975696 100644 --- a/.github/workflows/check_on_branch.yml +++ b/.github/workflows/check_on_branch.yml @@ -11,8 +11,10 @@ jobs: check-package: runs-on: ubuntu-latest name: "check package" + env: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} + ORCID_TOKEN: ${{ secrets.ORCID_TOKEN }} steps: - uses: inbo/actions/check_pkg@master with: - CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} - ORCID_TOKEN: ${{ secrets.ORCID_TOKEN }} + token: ${{ secrets.PAT }} diff --git a/.github/workflows/check_on_different_r_os.yml b/.github/workflows/check_on_different_r_os.yml index ada27336..736abd9e 100644 --- a/.github/workflows/check_on_different_r_os.yml +++ b/.github/workflows/check_on_different_r_os.yml @@ -23,7 +23,7 @@ jobs: - {os: macOS-latest, r: 'release'} - {os: windows-latest, r: 'release'} - {os: ubuntu-20.04, r: 'devel', rspm: "https://packagemanager.rstudio.com/cran/__linux__/focal/latest"} - - {os: ubuntu-18.04, r: 'oldrel', rspm: "https://packagemanager.rstudio.com/cran/__linux__/xenial/latest"} + - {os: ubuntu-20.04, r: 'oldrel', rspm: "https://packagemanager.rstudio.com/cran/__linux__/focal/latest"} env: R_REMOTES_NO_ERRORS_FROM_WARNINGS: true @@ -81,7 +81,7 @@ jobs: - name: Check env: _R_CHECK_CRAN_INCOMING_: false - run: rcmdcheck::rcmdcheck(args = c("--no-manual", "--as-cran"), error_on = "warning", check_dir = "check") + run: rcmdcheck::rcmdcheck(args = c("--no-manual", "--as-cran"), error_on = "error", check_dir = "check") shell: Rscript {0} - name: Show testthat output @@ -95,4 +95,4 @@ jobs: with: name: ${{ runner.os }}-r${{ matrix.config.r }}-results path: check - retention-days: 14 + retention-days: 5 diff --git a/.github/workflows/check_on_main.yml b/.github/workflows/check_on_main.yml index f9b22e9a..7a183419 100644 --- a/.github/workflows/check_on_main.yml +++ b/.github/workflows/check_on_main.yml @@ -12,9 +12,10 @@ jobs: check-package: runs-on: ubuntu-latest name: "check package" + env: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} + ORCID_TOKEN: ${{ secrets.ORCID_TOKEN }} steps: - uses: inbo/actions/check_pkg@master with: - CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} - ORCID_TOKEN: ${{ secrets.ORCID_TOKEN }} - token: ${{ secrets.pat }} + token: ${{ secrets.PAT }} diff --git a/_pkgdown.yml b/_pkgdown.yml index bd478a96..5f965002 100644 --- a/_pkgdown.yml +++ b/_pkgdown.yml @@ -22,11 +22,11 @@ navbar: - text: Contributing href: CONTRIBUTING.html right: - - icon: fa-github fa-lg + - icon: "fa fa-github" href: https://github.com/ropensci/git2rdata - - icon: fa-twitter fa-lg + - icon: "fa fa-twitter" href: https://twitter.com/INBOVlaanderen - - icon: fa-facebook fg-lg + - icon: "fa fa-facebook" href: https://www.facebook.com/pg/INBOVlaanderen reference: @@ -44,5 +44,5 @@ authors: Thierry Onkelinx: href: "https://www.muscardinus.be" Research Institute for Nature and Forest: - href: "https://www.inbo.be/en" + href: "https://www.vlaanderen.be/inbo/en-gb" html: "" diff --git a/pkgdown/extra.css b/pkgdown/extra.css index 42934f7e..00938dd1 100644 --- a/pkgdown/extra.css +++ b/pkgdown/extra.css @@ -1,5 +1,5 @@ body { - background-color: #efefef; + background-color: #f5f5f5; color: #5e5e5e; background-image: url('reference/figures/background-pattern.png'); font-family: FlandersArtSans-Light, Verdana, Arial, sans-serif; @@ -13,14 +13,13 @@ a { color: #c04384; } a:hover { - color: #c2c444; + color: #337ab7; } - .navbar, .label-default, .navbar-default .navbar-nav>.active>a, .navbar-default .navbar-nav>.active>a:hover, .navbar-default .navbar-nav>.active>a:focus { - background-color: #c04384; + background-color: #356196; } .navbar-default .navbar-link, @@ -61,3 +60,33 @@ code.sourceCode.diff span.va { background-color: #E4E517; font-weight: bold; } + +/*selection color*/ +::selection { + background: #c04384; + color: #fff; +} +::-moz-selection { + background: #c04384; + color: #fff; +} + +.navbar-default .navbar-nav>.open>a, .navbar-default .navbar-nav>.open>a:focus, .navbar-default .navbar-nav>.open>a:hover{ + color: #337ab7; + background: #fff; +} + +.navbar-default .navbar-nav>.active>a, .navbar-default .navbar-nav>.active>a:focus, .navbar-default .navbar-nav>.active>a:hover { + color: #337ab7; + background: #fff; +} + +.navbar-default .navbar-nav>li>a:hover{ + color: #337ab7; + background: #fff; +} + +.dropdown-menu>li>a:hover{ + color: #fff; + background: #337ab7; +} From 50ab6a7c7da649652305c4f4cb1d16038786d1c6 Mon Sep 17 00:00:00 2001 From: Thierry Onkelinx Date: Tue, 25 Jan 2022 14:39:38 +0100 Subject: [PATCH 16/18] move DOI from Description: to URL: --- .zenodo.json | 2 +- CITATION.cff | 3 ++- DESCRIPTION | 6 +++--- inst/CITATION | 5 +++-- man/git2rdata-package.Rd | 3 ++- 5 files changed, 11 insertions(+), 8 deletions(-) diff --git a/.zenodo.json b/.zenodo.json index 5b8a8bb2..802b700e 100644 --- a/.zenodo.json +++ b/.zenodo.json @@ -1,7 +1,7 @@ { "title": "git2rdata: Store and Retrieve Data.frames in a Git Repository", "version": "0.4.0", - "description": "The git2rdata package is an R package for writing and reading dataframes as plain text files. A metadata file stores important information. 1) Storing metadata allows to maintain the classes of variables. By default, git2rdata optimizes the data for file storage. The optimization is most effective on data containing factors. The optimization makes the data less human readable. The user can turn this off when they prefer a human readable format over smaller files. Details on the implementation are available in vignette(\"plain_text\", package = \"git2rdata\"). 2) Storing metadata also allows smaller row based diffs between two consecutive commits. This is a useful feature when storing data as plain text files under version control. Details on this part of the implementation are available in vignette(\"version_control\", package = \"git2rdata\"). Although we envisioned git2rdata with a git workflow in mind, you can use it in combination with other version control systems like subversion or mercurial. 3) git2rdata is a useful tool in a reproducible and traceable workflow. vignette(\"workflow\", package = \"git2rdata\") gives a toy example. 4) vignette(\"efficiency\", package = \"git2rdata\") provides some insight into the efficiency of file storage, git repository size and speed for writing and reading. Please cite using .", + "description": "The git2rdata package is an R package for writing and reading dataframes as plain text files. A metadata file stores important information. 1) Storing metadata allows to maintain the classes of variables. By default, git2rdata optimizes the data for file storage. The optimization is most effective on data containing factors. The optimization makes the data less human readable. The user can turn this off when they prefer a human readable format over smaller files. Details on the implementation are available in vignette(\"plain_text\", package = \"git2rdata\"). 2) Storing metadata also allows smaller row based diffs between two consecutive commits. This is a useful feature when storing data as plain text files under version control. Details on this part of the implementation are available in vignette(\"version_control\", package = \"git2rdata\"). Although we envisioned git2rdata with a git workflow in mind, you can use it in combination with other version control systems like subversion or mercurial. 3) git2rdata is a useful tool in a reproducible and traceable workflow. vignette(\"workflow\", package = \"git2rdata\") gives a toy example. 4) vignette(\"efficiency\", package = \"git2rdata\") provides some insight into the efficiency of file storage, git repository size and speed for writing and reading.", "creators": [ { "name": "Onkelinx, Thierry", diff --git a/CITATION.cff b/CITATION.cff index fcbd5efa..f3cc8a0c 100644 --- a/CITATION.cff +++ b/CITATION.cff @@ -27,9 +27,10 @@ abstract: The git2rdata package is an R package for writing and reading datafram mercurial. 3) git2rdata is a useful tool in a reproducible and traceable workflow. vignette("workflow", package = "git2rdata") gives a toy example. 4) vignette("efficiency", package = "git2rdata") provides some insight into the efficiency of file storage, - git repository size and speed for writing and reading. Please cite using . + git repository size and speed for writing and reading. license: GPL-3.0 type: software +doi: 10.5281/zenodo.1485309 identifiers: - type: url value: https://ropensci.github.io/git2rdata/ diff --git a/DESCRIPTION b/DESCRIPTION index 7ea078c7..7d4adf92 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -31,10 +31,10 @@ Description: The git2rdata package is an R package for writing and reading traceable workflow. vignette("workflow", package = "git2rdata") gives a toy example. 4) vignette("efficiency", package = "git2rdata") provides some insight into the efficiency of file storage, git - repository size and speed for writing and reading. Please cite using - . + repository size and speed for writing and reading. License: GPL-3 -URL: https://ropensci.github.io/git2rdata/ +URL: https://ropensci.github.io/git2rdata/, + https://doi.org/10.5281/zenodo.1485309 BugReports: https://github.com/ropensci/git2rdata/issues Depends: R (>= 3.5.0) diff --git a/inst/CITATION b/inst/CITATION index 8bd62cfd..25c90a8c 100644 --- a/inst/CITATION +++ b/inst/CITATION @@ -6,7 +6,8 @@ citEntry( author = c(person(given = "Thierry", family = "Onkelinx")), year = 2022, url = "https://ropensci.github.io/git2rdata/", - abstract = "The git2rdata package is an R package for writing and reading dataframes as plain text files. A metadata file stores important information. 1) Storing metadata allows to maintain the classes of variables. By default, git2rdata optimizes the data for file storage. The optimization is most effective on data containing factors. The optimization makes the data less human readable. The user can turn this off when they prefer a human readable format over smaller files. Details on the implementation are available in vignette(\"plain_text\", package = \"git2rdata\"). 2) Storing metadata also allows smaller row based diffs between two consecutive commits. This is a useful feature when storing data as plain text files under version control. Details on this part of the implementation are available in vignette(\"version_control\", package = \"git2rdata\"). Although we envisioned git2rdata with a git workflow in mind, you can use it in combination with other version control systems like subversion or mercurial. 3) git2rdata is a useful tool in a reproducible and traceable workflow. vignette(\"workflow\", package = \"git2rdata\") gives a toy example. 4) vignette(\"efficiency\", package = \"git2rdata\") provides some insight into the efficiency of file storage, git repository size and speed for writing and reading. Please cite using .", - textVersion = "Onkelinx, Thierry (2022) git2rdata: Store and Retrieve Data.frames in a Git Repository. Version 0.4.0. https://ropensci.github.io/git2rdata/", + abstract = "The git2rdata package is an R package for writing and reading dataframes as plain text files. A metadata file stores important information. 1) Storing metadata allows to maintain the classes of variables. By default, git2rdata optimizes the data for file storage. The optimization is most effective on data containing factors. The optimization makes the data less human readable. The user can turn this off when they prefer a human readable format over smaller files. Details on the implementation are available in vignette(\"plain_text\", package = \"git2rdata\"). 2) Storing metadata also allows smaller row based diffs between two consecutive commits. This is a useful feature when storing data as plain text files under version control. Details on this part of the implementation are available in vignette(\"version_control\", package = \"git2rdata\"). Although we envisioned git2rdata with a git workflow in mind, you can use it in combination with other version control systems like subversion or mercurial. 3) git2rdata is a useful tool in a reproducible and traceable workflow. vignette(\"workflow\", package = \"git2rdata\") gives a toy example. 4) vignette(\"efficiency\", package = \"git2rdata\") provides some insight into the efficiency of file storage, git repository size and speed for writing and reading.", + textVersion = "Onkelinx, Thierry (2022) git2rdata: Store and Retrieve Data.frames in a Git Repository. Version 0.4.0. https://ropensci.github.io/git2rdata/, https://doi.org/10.5281/zenodo.1485309", + doi = "10.5281/zenodo.1485309", ) # end checklist entry diff --git a/man/git2rdata-package.Rd b/man/git2rdata-package.Rd index 2bd448fb..2e03d8e6 100644 --- a/man/git2rdata-package.Rd +++ b/man/git2rdata-package.Rd @@ -6,12 +6,13 @@ \alias{git2rdata-package} \title{git2rdata: Store and Retrieve Data.frames in a Git Repository} \description{ -The git2rdata package is an R package for writing and reading dataframes as plain text files. A metadata file stores important information. 1) Storing metadata allows to maintain the classes of variables. By default, git2rdata optimizes the data for file storage. The optimization is most effective on data containing factors. The optimization makes the data less human readable. The user can turn this off when they prefer a human readable format over smaller files. Details on the implementation are available in vignette("plain_text", package = "git2rdata"). 2) Storing metadata also allows smaller row based diffs between two consecutive commits. This is a useful feature when storing data as plain text files under version control. Details on this part of the implementation are available in vignette("version_control", package = "git2rdata"). Although we envisioned git2rdata with a git workflow in mind, you can use it in combination with other version control systems like subversion or mercurial. 3) git2rdata is a useful tool in a reproducible and traceable workflow. vignette("workflow", package = "git2rdata") gives a toy example. 4) vignette("efficiency", package = "git2rdata") provides some insight into the efficiency of file storage, git repository size and speed for writing and reading. Please cite using . +The git2rdata package is an R package for writing and reading dataframes as plain text files. A metadata file stores important information. 1) Storing metadata allows to maintain the classes of variables. By default, git2rdata optimizes the data for file storage. The optimization is most effective on data containing factors. The optimization makes the data less human readable. The user can turn this off when they prefer a human readable format over smaller files. Details on the implementation are available in vignette("plain_text", package = "git2rdata"). 2) Storing metadata also allows smaller row based diffs between two consecutive commits. This is a useful feature when storing data as plain text files under version control. Details on this part of the implementation are available in vignette("version_control", package = "git2rdata"). Although we envisioned git2rdata with a git workflow in mind, you can use it in combination with other version control systems like subversion or mercurial. 3) git2rdata is a useful tool in a reproducible and traceable workflow. vignette("workflow", package = "git2rdata") gives a toy example. 4) vignette("efficiency", package = "git2rdata") provides some insight into the efficiency of file storage, git repository size and speed for writing and reading. } \seealso{ Useful links: \itemize{ \item \url{https://ropensci.github.io/git2rdata/} + \item \url{https://doi.org/10.5281/zenodo.1485309} \item Report bugs at \url{https://github.com/ropensci/git2rdata/issues} } From acf8ae2dc28fa5fa5ec0aa008c9cfc695c8b75af Mon Sep 17 00:00:00 2001 From: Thierry Onkelinx Date: Thu, 24 Feb 2022 11:45:18 +0100 Subject: [PATCH 17/18] remove LazyData from DESCRIPTION --- DESCRIPTION | 1 - 1 file changed, 1 deletion(-) diff --git a/DESCRIPTION b/DESCRIPTION index 7d4adf92..c259940d 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -54,7 +54,6 @@ VignetteBuilder: knitr Encoding: UTF-8 Language: en-GB -LazyData: true Roxygen: list(markdown = TRUE) RoxygenNote: 7.1.2 Collate: From 5948e7bdd70bfa4efb8cb45f74881859402d287a Mon Sep 17 00:00:00 2001 From: Thierry Onkelinx Date: Fri, 25 Feb 2022 12:09:53 +0100 Subject: [PATCH 18/18] don't mention DOI in DESCRIPION due to problems with pkgdown and roxygen2 --- CITATION.cff | 2 +- DESCRIPTION | 2 +- inst/CITATION | 3 +-- man/git2rdata-package.Rd | 2 +- 4 files changed, 4 insertions(+), 5 deletions(-) diff --git a/CITATION.cff b/CITATION.cff index f3cc8a0c..db063d44 100644 --- a/CITATION.cff +++ b/CITATION.cff @@ -30,7 +30,7 @@ abstract: The git2rdata package is an R package for writing and reading datafram git repository size and speed for writing and reading. license: GPL-3.0 type: software -doi: 10.5281/zenodo.1485309 +repository-code: https://github.com/ropensci/git2rdata/ identifiers: - type: url value: https://ropensci.github.io/git2rdata/ diff --git a/DESCRIPTION b/DESCRIPTION index c259940d..7dbaddd0 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -34,7 +34,7 @@ Description: The git2rdata package is an R package for writing and reading repository size and speed for writing and reading. License: GPL-3 URL: https://ropensci.github.io/git2rdata/, - https://doi.org/10.5281/zenodo.1485309 + https://github.com/ropensci/git2rdata/ BugReports: https://github.com/ropensci/git2rdata/issues Depends: R (>= 3.5.0) diff --git a/inst/CITATION b/inst/CITATION index 25c90a8c..5b659422 100644 --- a/inst/CITATION +++ b/inst/CITATION @@ -7,7 +7,6 @@ citEntry( year = 2022, url = "https://ropensci.github.io/git2rdata/", abstract = "The git2rdata package is an R package for writing and reading dataframes as plain text files. A metadata file stores important information. 1) Storing metadata allows to maintain the classes of variables. By default, git2rdata optimizes the data for file storage. The optimization is most effective on data containing factors. The optimization makes the data less human readable. The user can turn this off when they prefer a human readable format over smaller files. Details on the implementation are available in vignette(\"plain_text\", package = \"git2rdata\"). 2) Storing metadata also allows smaller row based diffs between two consecutive commits. This is a useful feature when storing data as plain text files under version control. Details on this part of the implementation are available in vignette(\"version_control\", package = \"git2rdata\"). Although we envisioned git2rdata with a git workflow in mind, you can use it in combination with other version control systems like subversion or mercurial. 3) git2rdata is a useful tool in a reproducible and traceable workflow. vignette(\"workflow\", package = \"git2rdata\") gives a toy example. 4) vignette(\"efficiency\", package = \"git2rdata\") provides some insight into the efficiency of file storage, git repository size and speed for writing and reading.", - textVersion = "Onkelinx, Thierry (2022) git2rdata: Store and Retrieve Data.frames in a Git Repository. Version 0.4.0. https://ropensci.github.io/git2rdata/, https://doi.org/10.5281/zenodo.1485309", - doi = "10.5281/zenodo.1485309", + textVersion = "Onkelinx, Thierry (2022) git2rdata: Store and Retrieve Data.frames in a Git Repository. Version 0.4.0. https://ropensci.github.io/git2rdata/, https://github.com/ropensci/git2rdata/", ) # end checklist entry diff --git a/man/git2rdata-package.Rd b/man/git2rdata-package.Rd index 2e03d8e6..93e7ab2d 100644 --- a/man/git2rdata-package.Rd +++ b/man/git2rdata-package.Rd @@ -12,7 +12,7 @@ The git2rdata package is an R package for writing and reading dataframes as plai Useful links: \itemize{ \item \url{https://ropensci.github.io/git2rdata/} - \item \url{https://doi.org/10.5281/zenodo.1485309} + \item \url{https://github.com/ropensci/git2rdata/} \item Report bugs at \url{https://github.com/ropensci/git2rdata/issues} }