├── .Rbuildignore ├── .covrignore ├── .github ├── .gitignore └── workflows │ ├── R-CMD-check.yaml │ ├── pkgdown.yaml │ └── test-coverage.yaml ├── .gitignore ├── DESCRIPTION ├── LICENSE.md ├── NAMESPACE ├── NEWS.md ├── R ├── aaa.R ├── als.R ├── as-lines.R ├── checkers.R ├── context-binder.R ├── context-selectors.R ├── credit-roles.R ├── data-encyclopedists.R ├── enumerate.R ├── icon.R ├── name-handler.R ├── plm-template.R ├── plume-handler.R ├── plume-package.R ├── plume-quarto.R ├── plume.R ├── sequential.R ├── set-default-names.R ├── status-setter.R ├── utils-list.R ├── utils-plm.R ├── utils-tbl.R ├── utils.R ├── yaml.R └── zzz.R ├── README.Rmd ├── README.md ├── _pkgdown.yml ├── codecov.yml ├── cran-comments.md ├── data-raw └── encyclopedists.R ├── data ├── encyclopedists.rda └── encyclopedists_fr.rda ├── inst └── icons │ ├── orcid-bw.pdf │ ├── orcid-bw.svg │ ├── orcid.pdf │ └── orcid.svg ├── man ├── Plume.Rd ├── PlumeHandler.Rd ├── PlumeQuarto.Rd ├── StatusSetter.Rd ├── StatusSetterPlume.Rd ├── StatusSetterPlumeQuarto.Rd ├── as_lines.Rd ├── credit_roles.Rd ├── encyclopedists.Rd ├── enumerate.Rd ├── everyone.Rd ├── figures │ ├── lifecycle-archived.svg │ ├── lifecycle-defunct.svg │ ├── lifecycle-deprecated.svg │ ├── lifecycle-experimental.svg │ ├── lifecycle-maturing.svg │ ├── lifecycle-questioning.svg │ ├── lifecycle-stable.svg │ └── lifecycle-superseded.svg ├── icn_orcid.Rd ├── orcid.Rd ├── plm_template.Rd ├── plume-package.Rd ├── sequential.Rd └── set_default_names.Rd ├── plume.Rproj ├── tests ├── testthat.R └── testthat │ ├── _snaps │ ├── credit-roles.md │ ├── defunct.md │ ├── deprecated.md │ ├── get-author-list.md │ ├── get-contact-details.md │ ├── get-contributions.md │ ├── get-footnotes.md │ ├── get-orcids.md │ ├── icon.md │ ├── initialize.md │ ├── plm-template.md │ ├── sequential.md │ ├── set-default-names.md │ ├── set-main-contributors.md │ ├── set-status.md │ └── to-yaml.md │ ├── helper-plume.R │ ├── test-as-lines.R │ ├── test-credit-roles.R │ ├── test-defunct.R │ ├── test-deprecated.R │ ├── test-get-author-list.R │ ├── test-get-contact-details.R │ ├── test-get-contributions.R │ ├── test-get-footnotes.R │ ├── test-get-orcids.R │ ├── test-icon.R │ ├── test-initialize.R │ ├── test-plm-template.R │ ├── test-print.R │ ├── test-sequential.R │ ├── test-set-default-names.R │ ├── test-set-main-contributors.R │ ├── test-set-status.R │ └── test-to-yaml.R └── vignettes ├── .gitignore ├── comprehensive-affiliation-schema.Rmd ├── plume-workflow.Rmd ├── plume.Rmd └── working-in-other-languages.Rmd /.Rbuildignore: -------------------------------------------------------------------------------- 1 | ^.*\.Rproj$ 2 | ^\.Rproj\.user$ 3 | ^LICENSE\.md$ 4 | ^README\.Rmd$ 5 | ^\.github$ 6 | ^_pkgdown\.yml$ 7 | ^docs$ 8 | ^pkgdown$ 9 | ^codecov\.yml$ 10 | ^cran-comments\.md$ 11 | ^CRAN-SUBMISSION$ 12 | ^data-raw$ 13 | ^\.covrignore$ 14 | -------------------------------------------------------------------------------- /.covrignore: -------------------------------------------------------------------------------- 1 | R/aaa.R 2 | R/zzz.R 3 | -------------------------------------------------------------------------------- /.github/.gitignore: -------------------------------------------------------------------------------- 1 | *.html 2 | -------------------------------------------------------------------------------- /.github/workflows/R-CMD-check.yaml: -------------------------------------------------------------------------------- 1 | # Workflow derived from https://github.com/r-lib/actions/tree/v2/examples 2 | # Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help 3 | on: 4 | push: 5 | branches: [main, master] 6 | pull_request: 7 | 8 | name: R-CMD-check.yaml 9 | 10 | permissions: read-all 11 | 12 | jobs: 13 | R-CMD-check: 14 | runs-on: ${{ matrix.config.os }} 15 | 16 | name: ${{ matrix.config.os }} (${{ matrix.config.r }}) 17 | 18 | strategy: 19 | fail-fast: false 20 | matrix: 21 | config: 22 | - {os: macos-latest, r: 'release'} 23 | - {os: windows-latest, r: 'release'} 24 | - {os: ubuntu-latest, r: 'devel', http-user-agent: 'release'} 25 | - {os: ubuntu-latest, r: 'release'} 26 | - {os: ubuntu-latest, r: 'oldrel-1'} 27 | 28 | env: 29 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} 30 | R_KEEP_PKG_SOURCE: yes 31 | 32 | steps: 33 | - uses: actions/checkout@v4 34 | 35 | - uses: r-lib/actions/setup-pandoc@v2 36 | 37 | - uses: r-lib/actions/setup-r@v2 38 | with: 39 | r-version: ${{ matrix.config.r }} 40 | http-user-agent: ${{ matrix.config.http-user-agent }} 41 | use-public-rspm: true 42 | 43 | - uses: r-lib/actions/setup-r-dependencies@v2 44 | with: 45 | extra-packages: any::rcmdcheck 46 | needs: check 47 | 48 | - uses: r-lib/actions/check-r-package@v2 49 | with: 50 | upload-snapshots: true 51 | build_args: 'c("--no-manual","--compact-vignettes=gs+qpdf")' 52 | -------------------------------------------------------------------------------- /.github/workflows/pkgdown.yaml: -------------------------------------------------------------------------------- 1 | # Workflow derived from https://github.com/r-lib/actions/tree/v2/examples 2 | # Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help 3 | on: 4 | push: 5 | branches: [main, master] 6 | pull_request: 7 | release: 8 | types: [published] 9 | workflow_dispatch: 10 | 11 | name: pkgdown.yaml 12 | 13 | permissions: read-all 14 | 15 | jobs: 16 | pkgdown: 17 | runs-on: ubuntu-latest 18 | # Only restrict concurrency for non-PR jobs 19 | concurrency: 20 | group: pkgdown-${{ github.event_name != 'pull_request' || github.run_id }} 21 | env: 22 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} 23 | permissions: 24 | contents: write 25 | steps: 26 | - uses: actions/checkout@v4 27 | 28 | - uses: r-lib/actions/setup-pandoc@v2 29 | 30 | - uses: r-lib/actions/setup-r@v2 31 | with: 32 | use-public-rspm: true 33 | 34 | - uses: r-lib/actions/setup-r-dependencies@v2 35 | with: 36 | extra-packages: any::pkgdown, local::. 37 | needs: website 38 | 39 | - name: Build site 40 | run: pkgdown::build_site_github_pages(new_process = FALSE, install = FALSE) 41 | shell: Rscript {0} 42 | 43 | - name: Deploy to GitHub pages 🚀 44 | if: github.event_name != 'pull_request' 45 | uses: JamesIves/github-pages-deploy-action@v4.5.0 46 | with: 47 | clean: false 48 | branch: gh-pages 49 | folder: docs 50 | -------------------------------------------------------------------------------- /.github/workflows/test-coverage.yaml: -------------------------------------------------------------------------------- 1 | # Workflow derived from https://github.com/r-lib/actions/tree/v2/examples 2 | # Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help 3 | on: 4 | push: 5 | branches: [main, master] 6 | pull_request: 7 | 8 | name: test-coverage.yaml 9 | 10 | permissions: read-all 11 | 12 | jobs: 13 | test-coverage: 14 | runs-on: ubuntu-latest 15 | env: 16 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} 17 | 18 | steps: 19 | - uses: actions/checkout@v4 20 | 21 | - uses: r-lib/actions/setup-r@v2 22 | with: 23 | use-public-rspm: true 24 | 25 | - uses: r-lib/actions/setup-r-dependencies@v2 26 | with: 27 | extra-packages: any::covr, any::xml2 28 | needs: coverage 29 | 30 | - name: Test coverage 31 | run: | 32 | cov <- covr::package_coverage( 33 | quiet = FALSE, 34 | clean = FALSE, 35 | install_path = file.path(normalizePath(Sys.getenv("RUNNER_TEMP"), winslash = "/"), "package") 36 | ) 37 | covr::to_cobertura(cov) 38 | shell: Rscript {0} 39 | 40 | - uses: codecov/codecov-action@v4 41 | with: 42 | # Fail if error if not on PR, or if on PR and token is given 43 | fail_ci_if_error: ${{ github.event_name != 'pull_request' || secrets.CODECOV_TOKEN }} 44 | file: ./cobertura.xml 45 | plugin: noop 46 | disable_search: true 47 | token: ${{ secrets.CODECOV_TOKEN }} 48 | 49 | - name: Show testthat output 50 | if: always() 51 | run: | 52 | ## -------------------------------------------------------------------- 53 | find '${{ runner.temp }}/package' -name 'testthat.Rout*' -exec cat '{}' \; || true 54 | shell: bash 55 | 56 | - name: Upload test results 57 | if: failure() 58 | uses: actions/upload-artifact@v4 59 | with: 60 | name: coverage-test-failures 61 | path: ${{ runner.temp }}/package 62 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .Rproj.user 2 | .Rhistory 3 | .RData 4 | .Ruserdata 5 | .DS_Store 6 | 7 | inst/doc 8 | docs 9 | -------------------------------------------------------------------------------- /DESCRIPTION: -------------------------------------------------------------------------------- 1 | Type: Package 2 | Package: plume 3 | Title: A Simple Author Handler for Scientific Writing 4 | Version: 0.2.5.9000 5 | Authors@R: 6 | person("Arnaud", "Gallou", , "arangacas@gmail.com", role = c("aut", "cre", "cph"), 7 | comment = c(ORCID = "0000-0002-1002-4247")) 8 | Description: Handles and formats author information in scientific writing 9 | in 'R Markdown' and 'Quarto'. 'plume' provides easy-to-use and 10 | flexible tools for injecting author metadata in 'YAML' headers as well 11 | as generating author and contribution lists (among others) as strings 12 | from tabular data. 13 | License: GPL (>= 3) 14 | URL: https://arnaudgallou.github.io/plume/, 15 | https://github.com/arnaudgallou/plume 16 | BugReports: https://github.com/arnaudgallou/plume/issues 17 | Depends: 18 | R (>= 4.1.0) 19 | Imports: 20 | dplyr (>= 1.0.0), 21 | glue (>= 1.3.2), 22 | jsonlite (>= 1.6.0), 23 | knitr (>= 1.40), 24 | lifecycle (>= 1.0.3), 25 | purrr (>= 1.0.0), 26 | R6, 27 | readr (>= 1.0.0), 28 | rlang (>= 1.0.0), 29 | stringr, 30 | tibble (>= 3.0.0), 31 | tidyr (>= 1.1.0), 32 | tidyselect (>= 1.0.0), 33 | vctrs (>= 0.3.0), 34 | yaml (>= 2.3.8) 35 | Suggests: 36 | covr, 37 | fontawesome, 38 | gt, 39 | rmarkdown, 40 | testthat, 41 | waldo (>= 0.3.0), 42 | withr 43 | VignetteBuilder: 44 | knitr 45 | Config/Needs/website: arnaudgallou/cygne 46 | Config/testthat/edition: 3 47 | Encoding: UTF-8 48 | Language: en-GB 49 | LazyData: true 50 | Roxygen: list(markdown = TRUE) 51 | RoxygenNote: 7.3.2 52 | -------------------------------------------------------------------------------- /NAMESPACE: -------------------------------------------------------------------------------- 1 | # Generated by roxygen2: do not edit by hand 2 | 3 | S3method(print,plm) 4 | S3method(print,plm_icon) 5 | export(Plume) 6 | export(PlumeQuarto) 7 | export(as_lines) 8 | export(credit_roles) 9 | export(enumerate) 10 | export(everyone) 11 | export(everyone_but) 12 | export(icn_orcid) 13 | export(orcid) 14 | export(plm_template) 15 | export(sequential) 16 | export(set_default_names) 17 | importFrom(R6,R6Class) 18 | importFrom(dplyr,across) 19 | importFrom(dplyr,arrange) 20 | importFrom(dplyr,dense_rank) 21 | importFrom(dplyr,distinct) 22 | importFrom(dplyr,filter) 23 | importFrom(dplyr,if_else) 24 | importFrom(dplyr,mutate) 25 | importFrom(dplyr,rename) 26 | importFrom(dplyr,select) 27 | importFrom(dplyr,summarise) 28 | importFrom(glue,glue) 29 | importFrom(glue,glue_collapse) 30 | importFrom(jsonlite,parse_json) 31 | importFrom(jsonlite,toJSON) 32 | importFrom(lifecycle,deprecated) 33 | importFrom(purrr,iwalk) 34 | importFrom(purrr,list_assign) 35 | importFrom(purrr,list_modify) 36 | importFrom(purrr,list_rbind) 37 | importFrom(purrr,list_transpose) 38 | importFrom(purrr,map) 39 | importFrom(purrr,map2_vec) 40 | importFrom(purrr,map_vec) 41 | importFrom(purrr,partial) 42 | importFrom(purrr,reduce) 43 | importFrom(readr,read_file) 44 | importFrom(readr,write_lines) 45 | importFrom(rlang,":=") 46 | importFrom(rlang,caller_arg) 47 | importFrom(rlang,caller_env) 48 | importFrom(rlang,current_env) 49 | importFrom(rlang,enexprs) 50 | importFrom(rlang,have_name) 51 | importFrom(rlang,is_bool) 52 | importFrom(rlang,is_call) 53 | importFrom(rlang,is_named) 54 | importFrom(rlang,is_string) 55 | importFrom(rlang,is_true) 56 | importFrom(rlang,set_names) 57 | importFrom(rlang,sym) 58 | importFrom(rlang,syms) 59 | importFrom(stringr,fixed) 60 | importFrom(stringr,regex) 61 | importFrom(stringr,str_extract) 62 | importFrom(stringr,str_extract_all) 63 | importFrom(stringr,str_remove_all) 64 | importFrom(stringr,str_replace) 65 | importFrom(stringr,str_replace_all) 66 | importFrom(stringr,str_split_1) 67 | importFrom(tibble,as_tibble) 68 | importFrom(tibble,as_tibble_row) 69 | importFrom(tibble,rowid_to_column) 70 | importFrom(tibble,tibble) 71 | importFrom(tidyr,drop_na) 72 | importFrom(tidyr,nest) 73 | importFrom(tidyr,pivot_longer) 74 | importFrom(tidyr,unnest) 75 | importFrom(tidyselect,all_of) 76 | importFrom(tidyselect,any_of) 77 | importFrom(tidyselect,starts_with) 78 | importFrom(vctrs,vec_duplicate_any) 79 | importFrom(vctrs,vec_group_id) 80 | importFrom(vctrs,vec_rank) 81 | importFrom(vctrs,vec_restore) 82 | -------------------------------------------------------------------------------- /NEWS.md: -------------------------------------------------------------------------------- 1 | # plume (development version) 2 | 3 | * The `orcid` variable is now customisable (#96). 4 | 5 | * New helper `as_lines()` to output elements returned by `Plume`'s methods on multiple lines when rendering an R Markdown or Quarto document. 6 | 7 | ## Lifecycle changes 8 | 9 | * All functionalities deprecated in 0.2.1 and earlier now throw an error. This includes the `credit_roles` parameter in `plm_template()` and `$new()`, the `format` parameter in `$get_author_list()`, the `by` parameter in `$set_*()` methods, `everyone_but()`, `$set_equal_contributor()` and the use of explicit roles in the input data. 10 | 11 | ### Newly deprecated 12 | 13 | * `orcid()` is deprecated in favour of `icn_orcid()`. 14 | 15 | * The `format` parameter of `$get_contact_details()` is now deprecated in favour of the more explicit `template` parameter. 16 | 17 | # plume 0.2.5 18 | 19 | * `$get_author_list(suffix =)` is now slightly more flexible and will try less hard to sanitise author list suffixes (#90). 20 | 21 | * `PlumeQuarto` no longer converts roles to lower case (#88). 22 | 23 | * `PlumeQuarto` now supports authors' `degrees` and the `group` affiliation property (#53). 24 | 25 | * `PlumeQuarto` now properly handles authors with no roles (#81). 26 | 27 | * `PlumeQuarto` now supports `.yml` and `.yaml` files (#82). 28 | 29 | # plume 0.2.4 30 | 31 | * Tweaked some examples in the vignettes and expand the `Contributions` section in `vignette("plume")`. 32 | 33 | * Fixed selection helpers not working when imported explicitly with `::` (#76). 34 | 35 | * `citation("plume")` now generates the complete and up-to-date citation of plume (#75). 36 | 37 | # plume 0.2.3 38 | 39 | * `$new(initials_given_name = TRUE)` no longer makes initials if names are written in a script that doesn't use letter cases (#73). 40 | 41 | * Fixed `PlumeQuarto`'s example no longer working with `withr` 3.0.0 due to a wrong usage of `local_tempfile()` (#70). 42 | 43 | * `$get_author_list()` now throws a more informative error if corresponding authors have not been set (#69). 44 | 45 | * The `.roles` parameter in `$set_main_contributors()` now propagates roles that are not already set to any unnamed expression. This allows you to set the same main contributors across all but a few specific roles in a single call (#65). 46 | 47 | * `$to_yaml()` now only adds the `affiliations` schema if there is at least one affiliation (#67). 48 | 49 | * `$to_yaml()` now only adds authors' id if there are at least two authors (#66). 50 | 51 | # plume 0.2.2 52 | 53 | ## Documentation changes 54 | 55 | * `vignette("working-in-other-languages")` has been extended to describe how to overwrite default arguments to match your preferred language better. 56 | 57 | ## Minor improvements and bug fixes 58 | 59 | * Clarified error messages by suppressing purrr's wrapper error (#63). 60 | 61 | * `$to_yaml()` now writes author metadata in a separate YAML header if the original YAML header contains strippable meta-information such as comments, custom tags or folded blocks (#56, #61). 62 | 63 | * The way `$set_*()` methods handle `...` has been overhauled for more consistent results and to ensure the methods work correctly in edge cases (#59, #60). 64 | 65 | * `$get_contributions()` now throws the correct error if you pass a wrong argument to the `sep` parameter (#55). 66 | 67 | * The order of `$get_affiliations()`/`$get_notes()` parameters have been switched for consistency purposes (#54). 68 | 69 | # plume 0.2.1 70 | 71 | * Fixed `$get_contributions()` wrongly reordering authors when using CRediT roles and `by_author = TRUE` (#50). 72 | 73 | * `$get_contributions()` gains a new parameter `sep` that allows for finer control of how to separate contributors or roles (#49). 74 | 75 | * `$to_yaml()` now throws an error when invalid ORCID identifiers are found. 76 | 77 | * `Plume`'s parameter `by` is now working properly (#48). 78 | 79 | * The `format` parameter of `$get_author_list()` is now deprecated in favour of the less ambiguous parameter `suffix` (#47). 80 | 81 | # plume 0.2.0 82 | 83 | ## New features 84 | 85 | * `Plume` gains a new method `$set_main_contributors()` that allows you to force one or more contributors to appear first in the contribution list for any given role. Because of this new method, `Plume`'s contructor gained the parameter `by` to set the default `by`/`.by` value used in all `$set_*()` methods (#40). 86 | 87 | * New helper `credit_roles()` that returns the 14 contributor roles of the [Contributor Roles Taxonomy](https://credit.niso.org). These are now the default roles used by plume. 88 | 89 | 90 | 91 | * The plume role handling system has been overhauled for better flexibility and ease of use (#29). 92 | 93 | * plume classes gain a new parameter `roles` allowing you to specify roles using a named character vector. 94 | 95 | Rather than: 96 | 97 | ``` 98 | # A tibble: 2 × 4 99 | given_name family_name role_1 role_2 100 | 101 | 1 Zip Zap Supervision Writing 102 | 2 Ric Rac NA Writing 103 | ``` 104 | 105 | You can now use the following data structure: 106 | 107 | ``` 108 | # A tibble: 2 × 4 109 | given_name family_name role_1 role_2 110 | 111 | 1 Zip Zap 1 1 112 | 2 Ric Rac NA 1 113 | ``` 114 | 115 | And speficy roles when creating a `plume` object: 116 | 117 | ``` 118 | Plume$new(data, roles = c(role_1 = "Supervision", role_2 = "Writing")) 119 | ``` 120 | 121 | * `plm_template()` gains a new parameter `role_cols` to create role columns from a character vector. 122 | 123 | ## Lifecycle changes 124 | 125 | * `$set_equal_contributor()` is now deprecated in favour of `$set_cofirst_authors()` due to the ambiguous name of the method (#45). 126 | 127 | * `everyone_but()` is now deprecated as this function is not necessary since not more than a couple of authors should normally be given a particular status (#44). 128 | 129 | * The `by` parameter in `$set_*()` methods is now deprecated in favour of `.by` for consistency purposes (#41). 130 | 131 | * Defining roles explicitly in the input data or using `credit_roles = TRUE` are now deprecated in favour of defining role columns and roles via the parameters `role_cols` and `roles`, respectively (see details about the [new role handling system](#new_role_system) above). 132 | 133 | ## Documentation changes 134 | 135 | * The `using-credit-roles` vignette was removed as it is no longer needed. 136 | 137 | ## Minor improvements and bug fixes 138 | 139 | * Removed stringb dependency in favour of stringr (#42). 140 | 141 | * Updated the `encyclopedists` and `encyclopedists_fr` data to comply with the new role column system (#39). Column names have also been homogenised (#46). 142 | 143 | * `$to_yaml()` now preserves line breaks preceding leading or isolated `---` (#37). 144 | 145 | * `plm_template()` now returns role columns as numeric type (#26). 146 | 147 | * Initials now drop dots present in author names (#31). 148 | 149 | * plume classes now error when a role column contains multiple roles (#28). 150 | 151 | # plume 0.1.0 152 | 153 | First CRAN release. 154 | -------------------------------------------------------------------------------- /R/aaa.R: -------------------------------------------------------------------------------- 1 | .names <- list( 2 | internals = list( 3 | id = "id", 4 | initials = "initials", 5 | literal_name = "literal_name", 6 | corresponding = "corresponding", 7 | role = "role" 8 | ), 9 | primaries = list( 10 | given_name = "given_name", 11 | family_name = "family_name" 12 | ), 13 | secondaries = list( 14 | orcid = "orcid", 15 | email = "email", 16 | phone = "phone", 17 | fax = "fax", 18 | url = "url" 19 | ), 20 | nestables = list( 21 | affiliation = "affiliation", 22 | note = "note" 23 | ) 24 | ) 25 | 26 | .col_bullets <- list( 27 | corresponding = c( 28 | i = "Did you forget to assign corresponding authors?", 29 | i = "Use `set_corresponding_authors()` to set corresponding authors." 30 | ) 31 | ) 32 | 33 | .links <- list( 34 | crt = c("Contributor Roles Taxonomy", "https://credit.niso.org"), 35 | quarto_schemas = c( 36 | "author and affiliations schemas", 37 | "https://quarto.org/docs/journals/authors.html" 38 | ) 39 | ) 40 | 41 | link <- function(id) { 42 | els <- .links[[id]] 43 | md_link(els[[2]], els[[1]]) 44 | } 45 | -------------------------------------------------------------------------------- /R/als.R: -------------------------------------------------------------------------------- 1 | als_key_set <- function(format) { 2 | set <- c( 3 | a = "affiliation", 4 | c = "corresponding", 5 | n = "note", 6 | o = "orcid" 7 | ) 8 | keys <- als_extract_keys(format) 9 | set[keys] 10 | } 11 | 12 | als_extract_keys <- function(x) { 13 | str_extract_all(x, "[a-z]")[[1]] 14 | } 15 | 16 | als_extract_mark <- function(format, key) { 17 | mark_regex <- paste0("[,^]{1,2}(?=", key, ")") 18 | mark <- str_extract(format, mark_regex) 19 | if (is.na(mark)) { 20 | return("") 21 | } 22 | mark 23 | } 24 | 25 | als_sanitise <- function(x) { 26 | str_remove_all(x, "(?<=([,^]))\\1+") 27 | } 28 | 29 | als_parse <- function(format) { 30 | format <- als_sanitise(format) 31 | keys <- als_extract_keys(format) 32 | marks <- map_vec(c(keys, "$"), \(key) als_extract_mark(format, key)) 33 | last <- length(marks) 34 | list( 35 | heads = marks[-last], 36 | tail = marks[last] 37 | ) 38 | } 39 | 40 | als_join <- function(x, marks) { 41 | prev_item <- vector("character", 1L) 42 | out <- map2_vec(x, marks, \(item, mark) { 43 | if (is_empty(item) && str_contain(mark, "^")) { 44 | return("^") 45 | } 46 | if (is_empty(item)) { 47 | return(item) 48 | } 49 | if (is_empty(prev_item) && str_contain(mark, ",")) { 50 | mark <- stringr::str_remove(mark, fixed(",")) 51 | } 52 | prev_item <<- item 53 | paste0(mark, item) 54 | }) 55 | collapse(out) 56 | } 57 | 58 | als_make <- function(data, cols, format) { 59 | rows <- itemise_rows(data, cols) 60 | marks <- als_parse(format) 61 | if (is_empty(marks$heads)) { 62 | return(map_vec(rows, collapse)) 63 | } 64 | out <- map_vec(rows, \(row) als_join(row, marks$heads)) 65 | out <- paste0(out, marks$tail) 66 | als_sanitise(out) 67 | } 68 | -------------------------------------------------------------------------------- /R/as-lines.R: -------------------------------------------------------------------------------- 1 | #' @title Print vector elements on multiple lines 2 | #' @description Thin wrapper around [`cat()`] to display vector elements on 3 | #' multiple lines when rendering an R Markdown or Quarto document. This is 4 | #' primarily intended to be used with [`Plume`]'s methods to output each 5 | #' returned element on its own line. 6 | #' @param ... Objects to print. 7 | #' @returns `NULL` invisibly. 8 | #' @examples 9 | #' aut <- Plume$new(encyclopedists) 10 | #' as_lines(aut$get_affiliations()) 11 | #' @export 12 | as_lines <- function(...) { 13 | cat(..., sep = strrep(eol(), 2L)) 14 | } 15 | -------------------------------------------------------------------------------- /R/checkers.R: -------------------------------------------------------------------------------- 1 | has_uppercase <- function(x) { 2 | str_detect(x, "\\p{Lu}") 3 | } 4 | 5 | detect_name <- function(x, name) { 6 | str_detect(names(x), name) 7 | } 8 | 9 | has_name <- function(x, name) { 10 | UseMethod("has_name", object = name) 11 | } 12 | 13 | has_name.default <- function(x, name) { 14 | name %in% names(x) 15 | } 16 | 17 | has_name.stringr_regex <- function(x, name) { 18 | any(detect_name(x, name)) 19 | } 20 | 21 | has_metachr <- function(x) { 22 | str_detect(x, r"{[\\\[\](){}|?$^*+]}") 23 | } 24 | 25 | has_homonyms <- function(x) { 26 | vec_duplicate_any(names(x)) 27 | } 28 | 29 | has_overflowing_ws <- function(x) { 30 | str_detect(x, "^\\s|\\s$") 31 | } 32 | 33 | is_empty <- function(x) { 34 | UseMethod("is_empty") 35 | } 36 | 37 | is_empty.default <- function(x) { 38 | length(x) == 0L 39 | } 40 | 41 | is_empty.tbl_df <- function(x) { 42 | nrow(x) == 0L 43 | } 44 | 45 | is_empty.character <- function(x) { 46 | length(x) == 0L || all(x == "") 47 | } 48 | 49 | is_void <- function(x) { 50 | is.na(x) | is_blank(x) 51 | } 52 | 53 | is_nested <- function(x, item) { 54 | is.list(x[[item]]) 55 | } 56 | 57 | is_blank <- function(x) { 58 | str_detect(x, "^\\s*$") 59 | } 60 | 61 | is_selector <- function(expr) { 62 | nms <- c("everyone", "everyone_but") 63 | is_call(expr, nms, ns = c("plume", "")) 64 | } 65 | 66 | are_credit_roles <- function(x) { 67 | all(x %in% credit_roles()) || all(x %in% credit_roles(FALSE)) 68 | } 69 | 70 | seek <- function(x, callback) { 71 | x <- vec_drop_na(x) 72 | have_passed <- if (missing(callback)) !x else !callback(x) 73 | if (all(have_passed)) { 74 | return() 75 | } 76 | failed <- which(!have_passed)[[1]] 77 | out <- x[failed] 78 | if (!is_named(x)) { 79 | out <- set_names(out, failed) 80 | } 81 | out 82 | } 83 | 84 | without_indexed_error <- function(expr, ...) { 85 | withCallingHandlers(expr, purrr_error_indexed = \(e) { 86 | rlang::cnd_signal(e$parent) 87 | }, ...) 88 | } 89 | 90 | user_call <- function() { 91 | ns <- asNamespace("plume") 92 | for (frame in as.list(sys.frames())) { 93 | if (identical(topenv(frame), ns)) { 94 | return(frame) 95 | } 96 | } 97 | } 98 | 99 | abort <- function(msg, env = caller_env(), call = user_call(), ...) { 100 | if (length(msg) == 1L) { 101 | msg <- glue(msg, .null = "", .envir = env) 102 | } 103 | rlang::abort(msg, call = call, ...) 104 | } 105 | 106 | check_dots_not_empty <- function() { 107 | dots <- substitute(...(), caller_env()) 108 | if (!is.null(dots)) { 109 | return(invisible()) 110 | } 111 | abort("`...` must not be empty.") 112 | } 113 | 114 | check_named <- function(x, arg = caller_arg(x)) { 115 | if (is_named(x) && !has_homonyms(x)) { 116 | return(invisible()) 117 | } 118 | check_unique_names(x, arg) 119 | abort("All `{arg}` inputs must be named.") 120 | } 121 | 122 | check_unique_names <- function(x, arg) { 123 | if (has_homonyms(x)) { 124 | abort("`{arg}` must have unique input names.") 125 | } 126 | } 127 | 128 | check_unique_values <- function(x, what = "input values", arg) { 129 | if (vec_duplicate_any(x)) { 130 | abort("`{arg}` must have unique {what}.") 131 | } 132 | } 133 | 134 | check_type <- function(x, asserter, expected, arg) { 135 | if (!asserter(x)) { 136 | abort("`{arg}` must be {expected}.") 137 | } 138 | } 139 | 140 | is_type <- function(x, type) { 141 | do.call(paste0("is.", type), list(x)) 142 | } 143 | 144 | check_vec <- function(x, type, expected, let, arg) { 145 | if (let$null && is.null(x)) { 146 | return(invisible()) 147 | } 148 | check_type(x, \(x) is_type(x, type), expected, arg) 149 | if (!let$unnamed) { 150 | check_named(x, arg = arg) 151 | } 152 | if (!let$duplicates) { 153 | check_unique_values(x, arg = arg) 154 | } 155 | } 156 | 157 | .allowed_default <- list( 158 | null = FALSE, 159 | empty = FALSE, 160 | duplicates = FALSE, 161 | unnamed = FALSE 162 | ) 163 | 164 | allow <- function(...) { 165 | nms <- c(...) 166 | if (is_empty(nms)) { 167 | return(.allowed_default) 168 | } 169 | allowed <- recycle_to_names(TRUE, nms) 170 | list_replace(.allowed_default, allowed) 171 | } 172 | 173 | check_list <- function(x, let = allow(), arg = caller_arg(x)) { 174 | check_vec(x, "list", "a list", let, arg) 175 | } 176 | 177 | check_character <- function(x, let = allow(), arg = caller_arg(x)) { 178 | check_vec(x, "character", "a character vector", let, arg) 179 | } 180 | 181 | check_num <- function(x, let = allow("unnamed"), arg = caller_arg(x)) { 182 | check_vec(x, "numeric", "a numeric vector", let, arg) 183 | } 184 | 185 | is_df <- function(x) { 186 | inherits(x, c("data.frame", "tbl_df")) 187 | } 188 | 189 | check_df <- function(x, arg = caller_arg(x)) { 190 | check_type(x, is_df, "a data frame or tibble", arg) 191 | } 192 | 193 | check_bool <- function(x, arg = caller_arg(x)) { 194 | check_type(x, is_bool, "`TRUE` or `FALSE`", arg) 195 | } 196 | 197 | is_stringish <- function(x, allow_empty) { 198 | if (is_string(x) && (allow_empty || !is_string(x, ""))) { 199 | return(TRUE) 200 | } 201 | FALSE 202 | } 203 | 204 | check_string <- function(x, let = allow(), arg = caller_arg(x)) { 205 | asserter <- \(x) is_stringish(x, let$empty) || let$null && is.null(x) 206 | type <- if (is_string(x)) "non-empty" else "character" 207 | check_type(x, asserter, glue("a {type} string"), arg) 208 | } 209 | 210 | check_args <- function(type, quosures, ...) { 211 | fn <- paste0("check_", type) 212 | without_indexed_error( 213 | iwalk(quosures, \(value, key) { 214 | do.call(fn, list(rlang::eval_tidy(value), arg = key, ...)) 215 | }) 216 | ) 217 | } 218 | 219 | check_als_template <- function(x, arg = caller_arg(x)) { 220 | check_string(x, allow("null", "empty"), arg) 221 | if (is.null(x)) { 222 | return(invisible()) 223 | } 224 | check_unique_values(als_extract_keys(x), what = "keys", arg) 225 | check_set(split_chars(x), allowed = split_chars("acno^,"), arg) 226 | } 227 | 228 | format_valid <- function(x, last = " or ") { 229 | enumerate(wrap(x, "`"), last = last) 230 | } 231 | 232 | check_set <- function(x, allowed, arg) { 233 | if (any(!x %in% allowed)) { 234 | abort("`{arg}` must only contain any of {format_valid(allowed)}.") 235 | } 236 | } 237 | 238 | path_is_relative <- function(x) { 239 | !str_detect(x, "^(/|[A-Za-z]:|\\\\|~)") 240 | } 241 | 242 | check_path <- function(x) { 243 | if (file.exists(x)) { 244 | return(invisible()) 245 | } 246 | where <- if (path_is_relative(x)) " in the current directory" 247 | abort("`{x}` doesn't exist{where}.") 248 | } 249 | 250 | file_ext <- function(x) { 251 | str_extract(x, "(?<=\\.)[^.]+$") 252 | } 253 | 254 | check_file <- function(x, exts, arg = caller_arg(x)) { 255 | check_string(x, arg = arg) 256 | asserter <- function(x) vec_in(x, exts) 257 | valid_exts <- format_valid(predot(exts)) 258 | check_type(file_ext(x), asserter, glue("a {valid_exts} file"), arg) 259 | check_path(x) 260 | } 261 | 262 | is_glueish <- function(x) { 263 | is_string(x) && str_detect(x, "\\{[^}]+\\}") 264 | } 265 | 266 | check_glue <- function(x, vars, arg = caller_arg(x)) { 267 | check_type(x, is_glueish, "a glue specification", arg) 268 | check_glue_vars(x, vars, arg) 269 | } 270 | 271 | check_glue_vars <- function(x, allowed, arg = caller_arg(x)) { 272 | vars <- extract_glue_vars(x) 273 | if (all(vec_in(vars, allowed, ignore_case = FALSE))) { 274 | return(invisible()) 275 | } 276 | invalid_var <- seek(vars, \(var) !var %in% allowed) 277 | allowed_vars <- format_valid(allowed, last = " and/or ") 278 | abort(c( 279 | glue("Invalid variable `{invalid_var}`."), 280 | i = glue("`{arg}` must use variables {allowed_vars}.") 281 | )) 282 | } 283 | 284 | is_orcid <- function(x) { 285 | str_detect(x, "^(?:\\d{4}-){3}\\d{3}(?:\\d|X)$") 286 | } 287 | 288 | check_orcid <- function(x) { 289 | invalid_orcid <- seek(x, Negate(is_orcid)) 290 | if (is.null(invalid_orcid)) { 291 | return(invisible()) 292 | } 293 | abort(c( 294 | glue("Invalid ORCID identifier found: `{invalid_orcid}`."), 295 | i = paste("ORCID identifiers must have 16 digits,", 296 | "separated by a hyphen every 4 digits."), 297 | i = "The last character of the identifiers must be a digit or `X`." 298 | )) 299 | } 300 | 301 | is_icon <- function(x) { 302 | inherits(x, "plm_icon") 303 | } 304 | 305 | check_orcid_icon <- function(x, arg = caller_arg(x)) { 306 | if (is_icon(x)) { 307 | return(invisible()) 308 | } 309 | abort(c( 310 | glue("Invalid `{arg}` input."), 311 | i = "Use `icn_orcid()` to set the ORCID icon." 312 | )) 313 | } 314 | -------------------------------------------------------------------------------- /R/context-binder.R: -------------------------------------------------------------------------------- 1 | ContextBinder <- R6Class( 2 | public = list( 3 | bind = function(x) { 4 | private$data <- x 5 | private$clear(caller_env()) 6 | }, 7 | 8 | pull = function() { 9 | private$check_context() 10 | private$data 11 | } 12 | ), 13 | 14 | private = list( 15 | data = NULL, 16 | 17 | clear = function(env) { 18 | expr <- as.call(list(function() private$data <- NULL)) 19 | do.call(on.exit, list(expr), envir = env) 20 | }, 21 | 22 | check_context = function() { 23 | if (!is.null(private$data)) { 24 | return(invisible()) 25 | } 26 | caller <- deparse(rlang::caller_call(2)) 27 | abort("`{caller}` must be used within a *status setter* method.") 28 | } 29 | ) 30 | ) 31 | -------------------------------------------------------------------------------- /R/context-selectors.R: -------------------------------------------------------------------------------- 1 | #' @title Select all authors or exclude some from a selection 2 | #' @description Selection helpers to use in conjonction with status setter 3 | #' methods (i.e. methods that assign a status to authors with either `TRUE` 4 | #' or `FALSE`): 5 | #' * [`everyone()`] select all authors. 6 | #' * [`everyone_but()`] `r lifecycle::badge("deprecated")` this function was 7 | #' deprecated as I believe it is not necessary since not more than a couple of 8 | #' authors should normally be given a particular status. 9 | #' @examples 10 | #' aut <- Plume$new(encyclopedists) 11 | #' 12 | #' aut$set_corresponding_authors(everyone()) 13 | #' aut$get_plume() |> dplyr::select(1:3, corresponding) 14 | #' @export 15 | everyone <- function() { 16 | binder$pull() 17 | } 18 | 19 | #' @rdname everyone 20 | #' @keywords internal 21 | #' @param ... One or more unquoted expressions separated by commas. Expressions 22 | #' matching values in the column defined by the `by`/`.by` parameters of 23 | #' `set_*()` methods are used to set a given status to authors. Matching of 24 | #' values is case-insensitive. 25 | #' @export 26 | everyone_but <- function(...) { 27 | lifecycle::deprecate_stop("0.2.0", "everyone_but()") 28 | } 29 | -------------------------------------------------------------------------------- /R/credit-roles.R: -------------------------------------------------------------------------------- 1 | #' @title CRediT roles 2 | #' @description Helper function returning the 14 contributor roles of the 3 | #' `r link("crt")` (CRediT). This function is the default argument of the 4 | #' `roles` and `role_cols` parameters in plume classes and [`plm_template()`], 5 | #' respectively. 6 | #' @param oxford_spelling Should the suffix -ize/-ization be used? 7 | #' @returns A named vector. 8 | #' @examples 9 | #' credit_roles() 10 | #' @export 11 | credit_roles <- function(oxford_spelling = TRUE) { 12 | check_bool(oxford_spelling) 13 | if (oxford_spelling) { 14 | return(.credit_roles) 15 | } 16 | ise(.credit_roles) 17 | } 18 | 19 | ise <- function(x) { 20 | pattern <- "(?<=i)z(?=ation)" 21 | names(x) <- str_replace(names(x), pattern, "s") 22 | x[] <- str_replace(x, pattern, "s") 23 | x 24 | } 25 | 26 | .credit_roles <- c( 27 | conceptualization = "Conceptualization", 28 | data_curation = "Data curation", 29 | analysis = "Formal analysis", 30 | funding = "Funding acquisition", 31 | investigation = "Investigation", 32 | methodology = "Methodology", 33 | administration = "Project administration", 34 | resources = "Resources", 35 | software = "Software", 36 | supervision = "Supervision", 37 | validation = "Validation", 38 | visualization = "Visualization", 39 | writing = "Writing - original draft", 40 | editing = "Writing - review & editing" 41 | ) 42 | -------------------------------------------------------------------------------- /R/data-encyclopedists.R: -------------------------------------------------------------------------------- 1 | #' Famous encyclopedists 2 | #' 3 | #' Data on four famous authors of the Encyclopédie (originally "Encyclopédie, ou 4 | #' dictionnaire raisonné des sciences, des arts et des métiers") published in 5 | #' France in the second half of the 18th century. The data set is available in 6 | #' English (`encyclopedists`) and French (`encyclopedists_fr`). 7 | #' 8 | #' @format A tibble with 4 rows and 10 variables: 9 | #' \describe{ 10 | #' \item{given_name,prénom}{authors' given names} 11 | #' \item{family_name,nom}{authors' family names} 12 | #' \item{email,courriel}{authors' email addresses} 13 | #' \item{phone,téléphone}{authors' phone numbers} 14 | #' \item{orcid}{authors' ORCID} 15 | #' \item{affiliation_1,affiliation_2}{authors' affiliations} 16 | #' \item{supervision}{authors that supervised the project} 17 | #' \item{writing,rédaction}{authors involved in the writing} 18 | #' \item{note}{special notes about authors} 19 | #' } 20 | #' @examples 21 | #' encyclopedists 22 | #' 23 | #' encyclopedists_fr 24 | "encyclopedists" 25 | 26 | #' @rdname encyclopedists 27 | #' @format NULL 28 | "encyclopedists_fr" 29 | -------------------------------------------------------------------------------- /R/enumerate.R: -------------------------------------------------------------------------------- 1 | #' @title Enumerate vector elements 2 | #' @description Wrapper around [`glue_collapse()`][glue::glue_collapse()] using 3 | #' `sep = ", "` and `last = " and "` as default arguments. 4 | #' @param x A character vector. 5 | #' @param sep Separator used to separate the terms. 6 | #' @param last Separator used to separate the last two items if `x` has at least 7 | #' 2 items. 8 | #' @returns A character string with the same class as `x`. 9 | #' @examples 10 | #' aut <- Plume$new(encyclopedists) 11 | #' aut$get_author_list() |> enumerate() 12 | #' @export 13 | enumerate <- function(x, sep = ", ", last = " and ") { 14 | out <- glue_collapse(x, sep = sep, last = last) 15 | vec_restore(out, x) 16 | } 17 | -------------------------------------------------------------------------------- /R/icon.R: -------------------------------------------------------------------------------- 1 | #' @title Create an ORCID icon 2 | #' @description Create an ORCID icon for use with the [`Plume`] class. 3 | #' @param size Size of the icon (in pixels). 4 | #' @param bw Should the black and white version of the icon be used? 5 | #' @returns A plume icon, i.e. an object with S3 class `plm_icon`. 6 | #' @examples 7 | #' aut <- Plume$new(encyclopedists, orcid_icon = icn_orcid(bw = TRUE)) 8 | #' @export 9 | icn_orcid <- function(size = 16, bw = FALSE) { 10 | check_num(size) 11 | check_bool(bw) 12 | new_icon("orcid", size = size, bw = bw) 13 | } 14 | 15 | #' @title ORCID icon 16 | #' @description 17 | #' `r lifecycle::badge("deprecated")` 18 | #' 19 | #' Please use [icn_orcid()] instead. 20 | #' @inheritParams icn_orcid 21 | #' @returns A plume icon. 22 | #' @keywords internal 23 | #' @export 24 | orcid <- function(size = 16, bw = FALSE) { 25 | lifecycle::deprecate_warn("0.2.6", "orcid()", "icn_orcid()") 26 | icn_orcid(size, bw) 27 | } 28 | 29 | #' @export 30 | print.plm_icon <- function(x, ...) { 31 | cat(sprintf("<%s icon>%s", x, eol())) 32 | } 33 | 34 | new_icon <- function(x, ..., size, bw) { 35 | attrs <- icn_get_attrs(x, size, bw, ...) 36 | do.call(structure, c(list(x, class = "plm_icon"), attrs)) 37 | } 38 | 39 | md_link <- function(uri, content = NULL) { 40 | if (is.null(content)) { 41 | out <- sprintf("<%s>", uri) 42 | } else { 43 | out <- sprintf("[%s](%s)", content, uri) 44 | } 45 | propagate_na(out, from = uri) 46 | } 47 | 48 | md_image <- function(image, size, style, spacing) { 49 | wrap(sprintf("![](%s){height=%ipx%s}", image, size, style), spacing) 50 | } 51 | 52 | as_svg <- function(x) { 53 | add_class(x, "svg", inherit = FALSE) 54 | } 55 | 56 | as_pdf <- function(x) { 57 | add_class(x, "pdf", inherit = FALSE) 58 | } 59 | 60 | icn_format <- function(x) { 61 | if (knitr::is_html_output()) { 62 | return(as_svg(x)) 63 | } 64 | as_pdf(x) 65 | } 66 | 67 | icn_filename <- function(x, bw) { 68 | bw <- if (bw) "-bw" else "" 69 | sprintf("%s%s.%s", x, bw, class(x)) 70 | } 71 | 72 | icn_buffer <- function(x, margin) { 73 | UseMethod("icn_buffer") 74 | } 75 | 76 | icn_buffer.default <- function(x, margin) { 77 | list( 78 | style = "", 79 | spacing = sprintf("\\hspace{%ipt}", round(margin * .75)) 80 | ) 81 | } 82 | 83 | icn_buffer.svg <- function(x, margin) { 84 | list( 85 | style = sprintf( 86 | " style='margin: 0 %ipx; vertical-align: baseline'", 87 | margin 88 | ), 89 | spacing = "" 90 | ) 91 | } 92 | 93 | icn_get_attrs <- function(x, size, bw, ...) { 94 | x <- icn_format(x) 95 | attrs <- list(size = round(size), filename = icn_filename(x, bw), ...) 96 | buffer <- icn_buffer(x, margin = round(size / 4L)) 97 | c(attrs, buffer) 98 | } 99 | 100 | icn_path <- function(file) { 101 | system.file(file.path("icons", file), package = "plume") 102 | } 103 | 104 | icn_create <- function(attrs) { 105 | file <- icn_path(attrs$filename) 106 | md_image(file, attrs$size, attrs$style, attrs$spacing) 107 | } 108 | 109 | make_orcid_uri <- function(x) { 110 | check_orcid(x) 111 | out <- set_names(paste0("https://orcid.org/", x), x) 112 | propagate_na(out, from = x) 113 | } 114 | 115 | make_orcid_icon <- function(orcid, attrs) { 116 | uris <- make_orcid_uri(orcid) 117 | icon <- icn_create(attrs) 118 | md_link(uris, icon) 119 | } 120 | 121 | make_orcid_link <- function(orcid, compact) { 122 | uris <- make_orcid_uri(orcid) 123 | if (compact) { 124 | return(md_link(uris, names(uris))) 125 | } 126 | md_link(uris) 127 | } 128 | -------------------------------------------------------------------------------- /R/name-handler.R: -------------------------------------------------------------------------------- 1 | #' @title NameHandler class 2 | #' @description Internal class that handles the names of a `plume` object. 3 | #' @keywords internal 4 | #' @noRd 5 | NameHandler <- R6Class( 6 | classname = "NameHandler", 7 | public = list( 8 | initialize = function(names) { 9 | check_list(names) 10 | private$names <- names 11 | } 12 | ), 13 | 14 | private = list( 15 | names = NULL, 16 | 17 | pick = function(..., squash = TRUE) { 18 | list_fetch_all(private$names, ..., squash = squash) 19 | }, 20 | 21 | set_names = function(x) { 22 | private$names <- list_replace(private$names, x) 23 | } 24 | ) 25 | ) 26 | -------------------------------------------------------------------------------- /R/plm-template.R: -------------------------------------------------------------------------------- 1 | #' @title Create a table template for plume classes 2 | #' @description This helper function allows you to generate an empty 3 | #' [`tibble`][tibble::tibble()] that you can use as a template to supply 4 | #' author data. 5 | #' @param minimal If `TRUE`, returns an empty tibble with the following columns: 6 | #' `given_name`, `family_name`, `email`, `orcid`, `affiliation` and `note`. 7 | #' Otherwise the function returns a template with all columns that can be 8 | #' supplied to plume classes that are not `PlumeQuarto`-specific. 9 | #' @param role_cols A vector of names defining role columns to create. If the 10 | #' vector contains key-value pairs, columns will be named after the keys. 11 | #' @param credit_roles `r lifecycle::badge("deprecated")` 12 | #' 13 | #' It is now recommended to use `role_cols = credit_roles()` to use the 14 | #' `r link("crt")`. 15 | #' @returns An empty tibble. 16 | #' @examples 17 | #' plm_template() 18 | #' 19 | #' plm_template(role_cols = paste0("role_", 1:5)) 20 | #' @export 21 | plm_template <- function(minimal = TRUE, role_cols = credit_roles(), credit_roles = FALSE) { 22 | check_args("bool", quos(minimal, credit_roles)) 23 | check_character(role_cols, allow("null", "unnamed")) 24 | if (credit_roles) { 25 | lifecycle::deprecate_stop( 26 | "0.2.0", 27 | "plm_template(credit_roles)", 28 | I("`role_cols = credit_roles()`") 29 | ) 30 | } 31 | vars <- get_template_vars(minimal, role_cols) 32 | tibble(!!!vars, .rows = 0L) 33 | } 34 | 35 | get_template_vars <- function(minimal, role_cols) { 36 | vars <- list_fetch(.names, "primaries") 37 | vars <- c(vars, get_secondaries(minimal), get_nestables()) 38 | vars <- recycle_to_names(NA_character_, vars) 39 | if (!is.null(role_cols)) { 40 | role_cols <- recycle_to_names(NA_real_, role_cols) 41 | } 42 | c(vars, role_cols) 43 | } 44 | 45 | get_secondaries <- function(minimal) { 46 | if (minimal) { 47 | return(list(orcid = "orcid", email = "email")) 48 | } 49 | list_fetch(.names, "secondaries") 50 | } 51 | 52 | get_nestables <- function() { 53 | vars <- c(seq_names("affiliation", n = 2L), "note") 54 | as.list(set_names(vars)) 55 | } 56 | 57 | seq_names <- function(..., n) { 58 | paste(rep(c(...), each = n), seq_len(n), sep = "_") 59 | } 60 | -------------------------------------------------------------------------------- /R/plume-handler.R: -------------------------------------------------------------------------------- 1 | #' @title PlumeHandler class 2 | #' @description Internal class processing and shaping tabular data into a 3 | #' `plume` object. 4 | #' @keywords internal 5 | PlumeHandler <- R6Class( 6 | classname = "PlumeHandler", 7 | inherit = NameHandler, 8 | public = list( 9 | initialize = function( 10 | data, 11 | names, 12 | roles, 13 | credit_roles, 14 | initials_given_name, 15 | family_name_first = FALSE, 16 | interword_spacing = TRUE 17 | ) { 18 | check_df(data) 19 | check_args("character", quos(names, roles), allow("null")) 20 | check_args("bool", quos( 21 | credit_roles, 22 | initials_given_name, 23 | family_name_first, 24 | interword_spacing 25 | )) 26 | super$initialize(private$plume_names) 27 | private$plume <- as_tibble(data) 28 | private$initials_given_name <- initials_given_name 29 | private$family_name_first <- family_name_first 30 | if (!interword_spacing) { 31 | private$interword_spacing <- "" 32 | } 33 | if (credit_roles) { 34 | lifecycle::deprecate_stop( 35 | "0.2.0", 36 | "new(credit_roles)", 37 | I("`roles = credit_roles()`") 38 | ) 39 | } 40 | private$roles <- roles 41 | private$check_role_system() 42 | if (!is.null(names)) { 43 | private$set_names(names) 44 | } 45 | private$check_col(private$pick("primaries")) 46 | private$check_authors() 47 | private$mount() 48 | }, 49 | 50 | print = function() { 51 | print(private$plume) 52 | }, 53 | 54 | get_plume = function() { 55 | private$plume 56 | }, 57 | 58 | get_roles = function() { 59 | private$roles 60 | } 61 | ), 62 | 63 | private = list( 64 | plume = NULL, 65 | plume_names = .names, 66 | initials_given_name = NULL, 67 | family_name_first = NULL, 68 | roles = NULL, 69 | interword_spacing = " ", 70 | 71 | mount = function() { 72 | private$build() 73 | for (var in private$pick("nestables", "role")) { 74 | if (private$is_nestable(var)) { 75 | private$nest(var) 76 | } 77 | } 78 | }, 79 | 80 | build = function() { 81 | private$mold() 82 | private$sanitise() 83 | private$add_author_names() 84 | if (!is.null(private$roles)) { 85 | private$process_roles() 86 | } 87 | private$add_ids() 88 | }, 89 | 90 | mold = function(...) { 91 | private$plume <- select( 92 | private$plume, 93 | all_of(private$pick("primaries")), 94 | any_of(c(private$pick("secondaries"), names(private$roles))), 95 | starts_with(private$pick("nestables")), 96 | ... 97 | ) 98 | }, 99 | 100 | nest = function(col) { 101 | out <- pivot_longer( 102 | private$plume, 103 | cols = starts_with(col), 104 | values_to = col, 105 | names_to = NULL 106 | ) 107 | private$plume <- nest(out, !!col := all_of(col)) 108 | }, 109 | 110 | process_roles = function() { 111 | roles <- private$roles 112 | roles <- roles[names(roles) %in% names(private$plume)] 113 | out <- assign_roles(private$plume, roles) 114 | private$plume <- rename_roles(out, roles, key = private$pick("role")) 115 | }, 116 | 117 | add_author_names = function() { 118 | if (private$initials_given_name) { 119 | private$make_initials("given_name", dot = TRUE) 120 | } 121 | private$add_literal_names() 122 | private$add_initials() 123 | }, 124 | 125 | add_literal_names = function() { 126 | nominal <- private$pick("primaries") 127 | if (private$family_name_first) { 128 | nominal <- rev(nominal) 129 | } 130 | vars <- private$pick("literal_name", "family_name", squash = FALSE) 131 | private$plume <- mutate(private$plume, !!vars$literal_name := paste( 132 | !!!syms(nominal), 133 | sep = private$interword_spacing 134 | ), .after = all_of(vars$family_name)) 135 | }, 136 | 137 | add_initials = function() { 138 | private$make_initials("literal_name", name = private$pick("initials")) 139 | }, 140 | 141 | make_initials = function(col, name, dot = FALSE) { 142 | col <- private$pick(col) 143 | if (!private$has_uppercase(col)) { 144 | return() 145 | } 146 | if (missing(name)) { 147 | name <- col 148 | } 149 | private$plume <- mutate( 150 | private$plume, 151 | !!name := make_initials(.data[[col]], dot = dot), 152 | .after = all_of(col) 153 | ) 154 | }, 155 | 156 | add_ids = function() { 157 | private$plume <- rowid_to_column(private$plume, var = private$pick("id")) 158 | }, 159 | 160 | sanitise = function() { 161 | private$plume <- mutate( 162 | private$plume, 163 | across(\(x) any(is_blank(x)), blank_to_na), 164 | across(\(x) any(has_overflowing_ws(x)), trimws) 165 | ) 166 | }, 167 | 168 | pull = function(col) { 169 | private$plume[[private$pick(col)]] 170 | }, 171 | 172 | is_nestable = function(var) { 173 | var <- begins_with(var) 174 | private$has_col(var) && col_count(private$plume, var) > 1L 175 | }, 176 | 177 | has_uppercase = function(var) { 178 | any(has_uppercase(private$plume[[var]])) 179 | }, 180 | 181 | has_col = function(col) { 182 | if (any(has_metachr(col))) { 183 | col <- regex(col) 184 | } 185 | has_name(private$plume, col) 186 | }, 187 | 188 | check_col = function(x) { 189 | missing_col <- seek(x, Negate(private$has_col)) 190 | if (is.null(missing_col)) { 191 | return() 192 | } 193 | bullets <- .col_bullets[[names(missing_col)]] 194 | abort(glue("Column `{missing_col}` doesn't exist."), footer = bullets) 195 | }, 196 | 197 | check_authors = function() { 198 | nominal <- private$pick("primaries") 199 | authors <- select(private$plume, all_of(nominal)) 200 | missing_name <- reduce(authors, \(x, y) is_void(x) | is_void(y)) 201 | missing_name <- seek(missing_name) 202 | if (is.null(missing_name)) { 203 | return() 204 | } 205 | abort(c( 206 | glue("Missing author name found in position {names(missing_name)}."), 207 | i = "All authors must have a given and family name." 208 | )) 209 | } 210 | ) 211 | ) 212 | 213 | PlumeHandler$set("private", "check_role_system", function() { 214 | var <- private$pick("role") 215 | if (!private$has_col(begins_with(var))) { 216 | return() 217 | } 218 | roles <- select(private$plume, starts_with(var)) 219 | have_explicit_roles <- map_vec(roles, \(role) any(str_detect(role, "\\D"))) 220 | if (!all(have_explicit_roles)) { 221 | return() 222 | } 223 | lifecycle::deprecate_stop( 224 | "0.2.0", 225 | what = I("Defining explicit roles in the input data"), 226 | with = "new(roles)", 227 | details = paste0( 228 | "See <", 229 | "https://arnaudgallou.github.io/plume/articles/plume.html", 230 | "#defining-roles-and-contributors", 231 | ">." 232 | ) 233 | ) 234 | }) 235 | -------------------------------------------------------------------------------- /R/plume-package.R: -------------------------------------------------------------------------------- 1 | #' @keywords internal 2 | "_PACKAGE" 3 | 4 | ## usethis namespace: start 5 | #' @importFrom dplyr across 6 | #' @importFrom dplyr arrange 7 | #' @importFrom dplyr dense_rank 8 | #' @importFrom dplyr distinct 9 | #' @importFrom dplyr filter 10 | #' @importFrom dplyr if_else 11 | #' @importFrom dplyr mutate 12 | #' @importFrom dplyr rename 13 | #' @importFrom dplyr select 14 | #' @importFrom dplyr summarise 15 | #' @importFrom glue glue 16 | #' @importFrom glue glue_collapse 17 | #' @importFrom jsonlite parse_json 18 | #' @importFrom jsonlite toJSON 19 | #' @importFrom lifecycle deprecated 20 | #' @importFrom purrr iwalk 21 | #' @importFrom purrr list_assign 22 | #' @importFrom purrr list_modify 23 | #' @importFrom purrr list_rbind 24 | #' @importFrom purrr list_transpose 25 | #' @importFrom purrr map 26 | #' @importFrom purrr map_vec 27 | #' @importFrom purrr map2_vec 28 | #' @importFrom purrr partial 29 | #' @importFrom purrr reduce 30 | #' @importFrom R6 R6Class 31 | #' @importFrom readr read_file 32 | #' @importFrom readr write_lines 33 | #' @importFrom rlang := 34 | #' @importFrom rlang caller_arg 35 | #' @importFrom rlang caller_env 36 | #' @importFrom rlang current_env 37 | #' @importFrom rlang enexprs 38 | #' @importFrom rlang have_name 39 | #' @importFrom rlang is_bool 40 | #' @importFrom rlang is_call 41 | #' @importFrom rlang is_named 42 | #' @importFrom rlang is_string 43 | #' @importFrom rlang is_true 44 | #' @importFrom rlang set_names 45 | #' @importFrom rlang sym 46 | #' @importFrom rlang syms 47 | #' @importFrom stringr fixed 48 | #' @importFrom stringr regex 49 | #' @importFrom stringr str_extract 50 | #' @importFrom stringr str_extract_all 51 | #' @importFrom stringr str_remove_all 52 | #' @importFrom stringr str_replace 53 | #' @importFrom stringr str_replace_all 54 | #' @importFrom stringr str_split_1 55 | #' @importFrom tibble as_tibble 56 | #' @importFrom tibble as_tibble_row 57 | #' @importFrom tibble rowid_to_column 58 | #' @importFrom tibble tibble 59 | #' @importFrom tidyr drop_na 60 | #' @importFrom tidyr nest 61 | #' @importFrom tidyr pivot_longer 62 | #' @importFrom tidyr unnest 63 | #' @importFrom tidyselect all_of 64 | #' @importFrom tidyselect any_of 65 | #' @importFrom tidyselect starts_with 66 | #' @importFrom vctrs vec_duplicate_any 67 | #' @importFrom vctrs vec_group_id 68 | #' @importFrom vctrs vec_rank 69 | #' @importFrom vctrs vec_restore 70 | ## usethis namespace: end 71 | NULL 72 | -------------------------------------------------------------------------------- /R/plume-quarto.R: -------------------------------------------------------------------------------- 1 | .names_quarto <- list_modify( 2 | .names, 3 | internals = list( 4 | deceased = "deceased", 5 | equal_contributor = "equal_contributor" 6 | ), 7 | secondaries = list( 8 | number = "number", 9 | dropping_particle = "dropping_particle", 10 | acknowledgements = "acknowledgements" 11 | ), 12 | nestables = list( 13 | degree = "degree" 14 | ) 15 | ) 16 | 17 | #' @title PlumeQuarto class 18 | #' @description Class that pushes author metadata in YAML files or the YAML 19 | #' header of Quarto files. 20 | #' @examples 21 | #' # Create a simple temporary file with a YAML header 22 | #' # containing a title 23 | #' tmp_file <- tempfile(fileext = ".qmd") 24 | #' readr::write_lines("---\ntitle: Encyclopédie\n---", tmp_file) 25 | #' 26 | #' # View the temporary file 27 | #' cat(readr::read_file(tmp_file)) 28 | #' 29 | #' # Create a PlumeQuarto instance using the temporary file 30 | #' # you've just created 31 | #' aut <- PlumeQuarto$new( 32 | #' encyclopedists, 33 | #' file = tmp_file 34 | #' ) 35 | #' 36 | #' # And push author data to the YAML header 37 | #' aut$to_yaml() 38 | #' 39 | #' cat(readr::read_file(tmp_file)) 40 | #' 41 | #' # Pushing again with new data updates the YAML 42 | #' # header accordingly 43 | #' aut <- PlumeQuarto$new( 44 | #' dplyr::slice(encyclopedists, 2), 45 | #' file = tmp_file 46 | #' ) 47 | #' aut$to_yaml() 48 | #' 49 | #' cat(readr::read_file(tmp_file)) 50 | #' 51 | #' # Clean up the temporary file 52 | #' unlink(tmp_file) 53 | #' @export 54 | PlumeQuarto <- R6Class( 55 | classname = "PlumeQuarto", 56 | inherit = StatusSetterPlumeQuarto, 57 | public = list( 58 | #' @description Create a `PlumeQuarto` object. 59 | #' @param data A data frame containing author-related data. 60 | #' @param file A `.qmd`, `.yml` or `.yaml` file to insert author data into. 61 | #' @param names A vector of key-value pairs specifying custom names to use, 62 | #' where keys are default names and values their respective replacements. 63 | #' @param roles A vector of key-value pairs defining roles where keys 64 | #' identify columns and values describe the actual roles to use. 65 | #' @param credit_roles `r lifecycle::badge("deprecated")` 66 | #' 67 | #' It is now recommended to use `roles = credit_roles()` to use the 68 | #' `r link("crt")`. 69 | #' @param initials_given_name Should the initials of given names be used? 70 | #' @param by A character string defining the default variable used to assign 71 | #' specific metadata to authors in all `set_*()` methods. By default, uses 72 | #' authors' id. 73 | #' @return A `PlumeQuarto` object. 74 | initialize = function( 75 | data, 76 | file, 77 | names = NULL, 78 | roles = credit_roles(), 79 | credit_roles = FALSE, 80 | initials_given_name = FALSE, 81 | by = NULL 82 | ) { 83 | check_file(file, exts = c("qmd", "yml", "yaml")) 84 | super$initialize(data, names, roles, credit_roles, initials_given_name, by = by) 85 | private$file <- file 86 | private$id <- private$pick("id") 87 | }, 88 | 89 | #' @description Push or update author information in a YAML file or YAML 90 | #' header. The generated YAML complies with Quarto's 91 | #' `r link("quarto_schemas")`. 92 | #' @details 93 | #' If missing, `to_yaml()` inserts author information into the desired file. 94 | #' Otherwise, the function replaces old `author` and `affiliations` values 95 | #' with the ones provided in the input data. 96 | #' @return The input `file` invisibly. 97 | to_yaml = function() { 98 | yaml_push(private$get_template(), file = private$file) 99 | } 100 | ), 101 | 102 | private = list( 103 | file = NULL, 104 | plume_names = .names_quarto, 105 | meta_key = "meta-", 106 | id = NULL, 107 | 108 | mold = function(...) { 109 | super$mold(starts_with(private$meta_key), ...) 110 | }, 111 | 112 | get_template = function() { 113 | out <- list( 114 | author = private$author_tbl(), 115 | affiliations = private$affiliation_tbl() 116 | ) 117 | add_class(out, cls = file_ext(private$file)) 118 | }, 119 | 120 | author_tbl = function() { 121 | tibble( 122 | id = private$author_ids(), 123 | number = private$pull("number"), 124 | name = tibble( 125 | given = private$pull("given_name"), 126 | family = private$pull("family_name"), 127 | `dropping-particle` = private$pull("dropping_particle") 128 | ), 129 | url = private$pull("url"), 130 | email = private$pull("email"), 131 | phone = private$pull("phone"), 132 | fax = private$pull("fax"), 133 | orcid = private$author_orcids(), 134 | note = private$author_notes(), 135 | degrees = private$itemise("degree"), 136 | acknowledgements = private$pull("acknowledgements"), 137 | attributes = private$author_attributes(), 138 | roles = private$itemise("role"), 139 | metadata = private$author_metadata(), 140 | affiliations = private$author_affiliations() 141 | ) 142 | }, 143 | 144 | author_ids = function() { 145 | ids <- private$pull("id") 146 | if (length(ids) == 1L) { 147 | return() 148 | } 149 | paste0("aut", ids) 150 | }, 151 | 152 | author_orcids = function() { 153 | out <- private$pull("orcid") 154 | if (!is.null(out)) { 155 | check_orcid(out) 156 | } 157 | out 158 | }, 159 | 160 | itemise = function(var) { 161 | private$pull_nestable(var, \(x) list(vec_drop_na(x))) 162 | }, 163 | 164 | author_notes = function() { 165 | private$pull_nestable("note", \(x) bind(x, sep = ". ", arrange = FALSE)) 166 | }, 167 | 168 | pull_nestable = function(var, callback) { 169 | col <- private$pick(var) 170 | if (!private$has_col(col)) { 171 | return() 172 | } 173 | if (!is_nested(private$plume, col)) { 174 | return(private$pull(var)) 175 | } 176 | out <- unnest(private$plume, cols = all_of(col)) 177 | out <- summarise(out, `_` = if_not_na( 178 | .data[[col]], 179 | callback(.data[[col]]), 180 | all = TRUE 181 | ), .by = private$id) 182 | out[["_"]] 183 | }, 184 | 185 | author_attributes = function() { 186 | out <- tibble( 187 | corresponding = private$pull("corresponding"), 188 | deceased = private$pull("deceased"), 189 | `equal-contributor` = private$pull("equal_contributor") 190 | ) 191 | if (is_empty(out)) { 192 | return() 193 | } 194 | out 195 | }, 196 | 197 | author_affiliations = function() { 198 | col <- private$pick("affiliation") 199 | if (!private$has_col(col)) { 200 | return() 201 | } 202 | .col <- predot(col) 203 | out <- unnest(private$plume, cols = all_of(col)) 204 | out <- add_group_ids(out, col) 205 | out <- mutate(out, !!.col := if_not_na( 206 | .data[[.col]], 207 | paste0("aff", .data[[.col]]) 208 | )) 209 | out <- summarise(out, `_` = list( 210 | tibble(ref = sort(!!sym(.col))) 211 | ), .by = private$id) 212 | out[["_"]] 213 | }, 214 | 215 | author_metadata = function() { 216 | if (!private$has_col(begins_with(private$meta_key))) { 217 | return() 218 | } 219 | select(private$plume, starts_with(private$meta_key)) 220 | }, 221 | 222 | affiliation_tbl = function() { 223 | affiliations <- private$pull("affiliation") 224 | if (is.null(affiliations)) { 225 | return() 226 | } 227 | affiliations <- condense(affiliations) 228 | if (!any(has_affiliation_sep(affiliations))) { 229 | ids <- make_affiliation_id(affiliations) 230 | return(tibble(id = ids, name = affiliations)) 231 | } 232 | out <- map(affiliations, \(affiliation) { 233 | as_tibble_row(parse_affiliation(affiliation)) 234 | }) 235 | out <- list_rbind(out, names_to = "id") 236 | out <- mutate(out, id = make_affiliation_id(id)) 237 | if (is_empty(out)) { 238 | return() 239 | } 240 | out 241 | } 242 | ) 243 | ) 244 | 245 | .affiliation_keys <- c( 246 | "number", "name", "department", "address", "city", "region", "state", 247 | "country", "postal-code", "url", "isni", "ringgold", "ror", "group" 248 | ) 249 | 250 | parse_affiliation <- function(x) { 251 | if (!has_affiliation_sep(x)) { 252 | return(set_names(x, "name")) 253 | } 254 | keys <- collapse(.affiliation_keys, sep = "|") 255 | keys_regex <- paste0("\\b(?i:", keys, ")") 256 | nms <- str_extract_all(x, sprintf("%s(?==)", keys_regex), simplify = TRUE) 257 | els <- str_split_1(x, sprintf("\\s*%s=\\s*", keys_regex))[-1] 258 | set_names(els, tolower(nms)) 259 | } 260 | 261 | make_affiliation_id <- function(x) { 262 | paste0("aff", seq_along(x)) 263 | } 264 | 265 | has_affiliation_sep <- function(x) { 266 | str_contain(x, "=") 267 | } 268 | -------------------------------------------------------------------------------- /R/sequential.R: -------------------------------------------------------------------------------- 1 | #' @title Control the sequencing behaviour of character vectors 2 | #' @description Modifier function used to generate logical sequences of 3 | #' characters. 4 | #' @param x A character vector. 5 | #' @returns A character vector with parent S3 class `sequential`. 6 | #' @examples 7 | #' aut <- Plume$new( 8 | #' tibble::tibble( 9 | #' given_name = "X", 10 | #' family_name = "Y", 11 | #' affiliation = 1:60 12 | #' ), 13 | #' symbols = list(affiliation = sequential(letters)) 14 | #' ) 15 | #' 16 | #' aut$get_affiliations(sep = ": ", superscript = FALSE) 17 | #' @export 18 | sequential <- function(x) { 19 | check_character(x, allow("unnamed")) 20 | add_class(x, "sequential") 21 | } 22 | 23 | is_sequential <- function(x) { 24 | inherits(x, "sequential") 25 | } 26 | 27 | seq_symbols <- function(x, i) { 28 | i <- length(i) 29 | n_elements <- length(x) 30 | if (is_sequential(x)) { 31 | n_rep <- log(i, base = n_elements) 32 | } else { 33 | n_rep <- i / n_elements 34 | } 35 | n_rep <- ceiling(n_rep) 36 | seq_vector(x, n_rep) 37 | } 38 | 39 | seq_vector <- function(x, n) { 40 | if (n <= 1L) { 41 | return(x) 42 | } 43 | UseMethod("seq_vector") 44 | } 45 | 46 | seq_vector.default <- function(x, n) { 47 | out <- map(seq_len(n), \(i) strrep(x, i)) 48 | unlist(out) 49 | } 50 | 51 | seq_vector.sequential <- function(x, n) { 52 | out <- map(seq_len(n - 1L), \(i) c("", x)) 53 | out <- c(out, list(x)) 54 | out <- set_names(out, seq_along(out)) 55 | out <- vctrs::vec_expand_grid(!!!out) 56 | out <- reduce(out, paste0) 57 | unique(out) 58 | } 59 | -------------------------------------------------------------------------------- /R/set-default-names.R: -------------------------------------------------------------------------------- 1 | #' @title Set new default names to a plume subclass 2 | #' @description This helper function allows you to set new default names to a 3 | #' plume subclass, e.g. to set default names to a language other than English. 4 | #' @param ... Key-value pairs where keys are default names and values their 5 | #' respective replacements. 6 | #' @param .plume_quarto Are you setting new names for [`PlumeQuarto`]? 7 | #' @details 8 | #' Available names are: 9 | #' 10 | #' `r wrap(squash(.names_plume), "\x60")`. 11 | #' 12 | #' Using `.plume_quarto = TRUE` adds `deceased`, `equal_contributor`, `number`, 13 | #' `dropping_particle` and `acknowledgements`. 14 | #' @returns A named list. 15 | #' @examples 16 | #' # Extending `Plume` with default names in French 17 | #' PlumeFr <- R6::R6Class( 18 | #' classname = "PlumeFr", 19 | #' inherit = Plume, 20 | #' private = list( 21 | #' plume_names = set_default_names( 22 | #' initials = "initiales", 23 | #' literal_name = "nom_complet", 24 | #' corresponding = "correspondant", 25 | #' given_name = "prénom", 26 | #' family_name = "nom", 27 | #' email = "courriel", 28 | #' phone = "téléphone" 29 | #' ) 30 | #' ) 31 | #' ) 32 | #' 33 | #' PlumeFr$new(encyclopedists_fr) 34 | #' @export 35 | set_default_names <- function(..., .plume_quarto = FALSE) { 36 | check_dots_not_empty() 37 | dots <- c(...) 38 | check_character(dots, arg = "...") 39 | check_bool(.plume_quarto) 40 | nms <- if (.plume_quarto) .names_quarto else .names_plume 41 | list_replace(nms, dots) 42 | } 43 | -------------------------------------------------------------------------------- /R/status-setter.R: -------------------------------------------------------------------------------- 1 | binder <- ContextBinder$new() 2 | 3 | #' @title StatusSetter class 4 | #' @description Internal class that manages authors' status. 5 | #' @keywords internal 6 | StatusSetter <- R6Class( 7 | classname = "StatusSetter", 8 | inherit = PlumeHandler, 9 | public = list( 10 | initialize = function(..., by) { 11 | super$initialize(...) 12 | check_string(by, allow("null")) 13 | if (is.null(by)) { 14 | private$by <- private$pick("id") 15 | } else { 16 | private$check_col(by) 17 | private$by <- by 18 | } 19 | }, 20 | 21 | #' @description Set corresponding authors. 22 | #' @param ... One or more unquoted expressions separated by commas. 23 | #' Expressions matching values in the column defined by `by`/`.by` 24 | #' determine corresponding authors. Matching of values is case- 25 | #' insensitive. 26 | #' @param .by Variable used to set corresponding authors. By default, uses 27 | #' authors' id. 28 | #' @param by `r lifecycle::badge("deprecated")` 29 | #' 30 | #' Please use the `.by` parameter instead. 31 | #' @return The class instance. 32 | set_corresponding_authors = function(..., .by = NULL, by = deprecated()) { 33 | private$set_status("corresponding", ..., .by = .by, by = by) 34 | } 35 | ), 36 | 37 | private = list( 38 | by = NULL, 39 | 40 | set_status = function(col, ..., .by, by) { 41 | check_dots_not_empty() 42 | if (lifecycle::is_present(by)) { 43 | call <- if (col == "corresponding") "corresponding_author" else col 44 | call <- glue("set_{call}") 45 | lifecycle::deprecate_stop( 46 | "0.2.0", 47 | glue("{call}(by)"), 48 | glue("{call}(.by)") 49 | ) 50 | } 51 | by <- private$process_by(.by) 52 | binder$bind(private$plume[[by]]) 53 | dots <- collect_dots(...) 54 | private$plume <- mutate( 55 | private$plume, 56 | !!private$pick(col) := vec_in(.data[[by]], dots) 57 | ) 58 | invisible(self) 59 | }, 60 | 61 | process_by = function(by) { 62 | if (is.null(by)) { 63 | return(private$by) 64 | } 65 | check_string(by, allow("null"), arg = ".by") 66 | private$check_col(by) 67 | by 68 | } 69 | ) 70 | ) 71 | 72 | #' @title StatusSetterPlume class 73 | #' @description Internal class extending `StatusSetter` for `Plume`. 74 | #' @keywords internal 75 | StatusSetterPlume <- R6Class( 76 | classname = "StatusSetterPlume", 77 | inherit = StatusSetter, 78 | public = list( 79 | #' @description Force one or more contributors' names to appear first in the 80 | #' contribution list. 81 | #' @param ... One or more unquoted expressions separated by commas. 82 | #' Expressions matching values in the column defined by `by`/`.by` 83 | #' determine main contributors. Expressions can be named after any role to 84 | #' set different main contributors to different roles at once, in which 85 | #' case the `.roles` parameter only applies roles that are not already set 86 | #' to unnamed expressions. Matching of values is case-insensitive. 87 | #' @param .roles Roles to assign main contributors to. If `.roles` is a 88 | #' named vector, only the names will be used. 89 | #' @param .by Variable used to specify which authors are main contributors. 90 | #' By default, uses authors' id. 91 | #' @return The class instance. 92 | set_main_contributors = function(..., .roles = NULL, .by = NULL) { 93 | private$set_ranks(..., .roles = .roles, .by = .by) 94 | } 95 | ), 96 | 97 | private = list( 98 | set_ranks = function(..., .roles, .by) { 99 | check_dots_not_empty() 100 | check_character(.roles, allow("null", "unnamed")) 101 | by <- private$process_by(.by) 102 | vars <- private$pick("role", "contributor_rank", squash = FALSE) 103 | dots <- collect_dots(...) 104 | if (!(is.null(.roles) && is_named(dots))) { 105 | dots <- propagate_names(dots, nms = .roles) 106 | } 107 | out <- unnest(private$plume, col = all_of(vars$role)) 108 | out <- add_contribution_ranks(out, dots, private$roles, by, vars) 109 | private$plume <- nest(out, !!vars$role := squash(vars)) 110 | invisible(self) 111 | } 112 | ) 113 | ) 114 | 115 | #' @title StatusSetterPlumeQuarto class 116 | #' @description Internal class extending `StatusSetter` for `PlumeQuarto`. 117 | #' @keywords internal 118 | StatusSetterPlumeQuarto <- R6Class( 119 | classname = "StatusSetterPlumeQuarto", 120 | inherit = StatusSetter, 121 | public = list( 122 | #' @description Set co-first authors. 123 | #' @param ... One or more unquoted expressions separated by commas. 124 | #' Expressions matching values in the column defined by `by`/`.by` 125 | #' determine co-first authors. Matching of values is case-insensitive. 126 | #' @param .by Variable used to specify which authors contributed equally to 127 | #' the work. By default, uses authors' id. 128 | #' @return The class instance. 129 | set_cofirst_authors = function(..., .by = NULL) { 130 | private$set_status("equal_contributor", ..., .by = .by) 131 | }, 132 | 133 | #' @description `r lifecycle::badge("deprecated")` 134 | #' 135 | #' This method has been deprecated in favour of `set_cofirst_authors()`. 136 | #' @param ... One or more unquoted expressions separated by commas. 137 | #' Expressions matching values in the column defined by `by`/`.by` 138 | #' determine equal contributors. Matching of values is case-insensitive. 139 | #' @param .by Variable used to specify which authors are equal contributors. 140 | #' By default, uses authors' id. 141 | #' @param by `r lifecycle::badge("deprecated")` 142 | #' 143 | #' Please use the `.by` parameter instead. 144 | #' @return The class instance. 145 | set_equal_contributor = function(..., .by = NULL, by = deprecated()) { 146 | lifecycle::deprecate_stop( 147 | "0.2.0", 148 | "set_equal_contributor()", 149 | "set_cofirst_authors()" 150 | ) 151 | }, 152 | 153 | #' @description Set deceased authors. 154 | #' @param ... One or more unquoted expressions separated by commas. 155 | #' Expressions matching values in the column defined by `by`/`.by` 156 | #' determine deceased authors. Matching of values is case-insensitive. 157 | #' @param .by Variable used to specify whether an author is deceased or not. 158 | #' By default, uses authors' id. 159 | #' @param by `r lifecycle::badge("deprecated")` 160 | #' 161 | #' Please use the `.by` parameter instead. 162 | #' @return The class instance. 163 | set_deceased = function(..., .by = NULL, by = deprecated()) { 164 | private$set_status("deceased", ..., .by = .by, by = by) 165 | } 166 | ) 167 | ) 168 | -------------------------------------------------------------------------------- /R/utils-list.R: -------------------------------------------------------------------------------- 1 | list_fetch_all <- function(x, ..., squash) { 2 | out <- list() 3 | for (name in unique(c(...))) { 4 | out <- c(out, list_fetch(x, name)) 5 | } 6 | if (squash) { 7 | return(squash(out)) 8 | } 9 | out 10 | } 11 | 12 | list_fetch <- function(x, name) { 13 | out <- NULL 14 | for (i in names(x)) { 15 | x_i <- x[[i]] 16 | if (length(out)) { 17 | break 18 | } else if (i == name) { 19 | out <- if (length(x_i) > 1L) x_i else x[i] 20 | } else if (is.list(x_i)) { 21 | out <- list_fetch(x_i, name) 22 | } 23 | } 24 | out 25 | } 26 | 27 | list_replace <- function(x, y) { 28 | for (i in names(x)) { 29 | x_i <- x[[i]] 30 | if (is.list(x_i)) { 31 | list_slice(x, i) <- list_replace(x_i, y) 32 | } else if (any(names(y) == i)) { 33 | list_slice(x, i) <- y[[i]] 34 | } 35 | } 36 | x 37 | } 38 | 39 | `list_slice<-` <- function(x, i, value) { 40 | if (is.null(value)) { 41 | x[i] <- list(NULL) 42 | } else { 43 | x[[i]] <- value 44 | } 45 | x 46 | } 47 | 48 | list_drop_empty <- function(x) { 49 | are_empty <- map_vec(x, is_empty.default) 50 | x[are_empty] <- NULL 51 | x 52 | } 53 | -------------------------------------------------------------------------------- /R/utils-plm.R: -------------------------------------------------------------------------------- 1 | #' @export 2 | print.plm <- function(x, ..., sep = "\n") { 3 | if (length(x) > 0L) { 4 | cat(x, ..., sep = sep) 5 | } 6 | invisible(x) 7 | } 8 | 9 | as_plm <- function(x) { 10 | add_class(x, "plm") 11 | } 12 | 13 | compare_proxy.plm <- function(x, path = "x") { 14 | x <- unstructure(x) 15 | NextMethod() 16 | } 17 | -------------------------------------------------------------------------------- /R/utils-tbl.R: -------------------------------------------------------------------------------- 1 | col_count <- function(data, name) { 2 | length(grep(name, names(data))) 3 | } 4 | 5 | itemise_rows <- function(data, cols) { 6 | out <- map(data[cols], as.character) 7 | list_transpose(out) 8 | } 9 | 10 | collapse_cols <- function(data, cols, sep) { 11 | if (length(cols) == 1L) { 12 | return(data[[cols]]) 13 | } 14 | rows <- itemise_rows(data, cols) 15 | map_vec(rows, \(row) collapse(vec_drop_na(row), sep)) 16 | } 17 | 18 | unnest_drop_na <- function(data, cols) { 19 | data <- unnest(data, cols = all_of(cols)) 20 | drop_na(data, all_of(cols)) 21 | } 22 | 23 | add_group_ids <- function(data, cols) { 24 | for (col in cols) { 25 | data[predot(col)] <- group_id(data[[col]]) 26 | } 27 | data 28 | } 29 | 30 | add_suffixes <- function(data, cols, symbols) { 31 | data 32 | .cols <- predot(cols) 33 | without_indexed_error( 34 | iwalk(symbols[names(cols)], \(value, key) { 35 | if (is.null(value)) { 36 | return() 37 | } 38 | data <<- switch(key, 39 | orcid = add_orcid_icons(data, key, value), 40 | add_symbols(data, .cols[[key]], value) 41 | ) 42 | }) 43 | ) 44 | data 45 | } 46 | 47 | add_symbols <- function(data, col, symbols) { 48 | if (is.null(symbols)) { 49 | return(data) 50 | } 51 | values <- data[[col]] 52 | symbols <- seq_symbols(symbols, values) 53 | data[col] <- symbols[values] 54 | data 55 | } 56 | 57 | add_orcid_icons <- function(data, col, orcid) { 58 | data[predot(col)] <- make_orcid_icon(data[[col]], attributes(orcid)) 59 | data 60 | } 61 | 62 | add_orcid_links <- function(data, orcid, compact) { 63 | .col <- predot(orcid) 64 | links <- make_orcid_link(data[[orcid]], compact) 65 | data[.col] <- paste0(data[[.col]], links) 66 | data 67 | } 68 | 69 | add_contribution_ranks <- function(data, values, roles, by, cols) { 70 | data <- col_init(data, cols$contributor_rank) 71 | iwalk(values, \(value, key) { 72 | data[cols$contributor_rank] <<- if_else( 73 | !is.na(roles[key]) & data[[cols$role]] == roles[key], 74 | rank(data[[by]], value), 75 | data[[cols$contributor_rank]] 76 | ) 77 | }) 78 | data 79 | } 80 | 81 | col_init <- function(data, name) { 82 | if (!has_name(data, name)) { 83 | data[name] <- NA 84 | } 85 | data 86 | } 87 | 88 | assign_roles <- function(data, roles) { 89 | data 90 | iwalk(roles, \(value, key) { 91 | if (!has_name(data, key)) { 92 | return() 93 | } 94 | data[key] <<- if_else(data[[key]] == 1L, value, NA) 95 | }) 96 | data 97 | } 98 | 99 | rename_roles <- function(data, roles, key) { 100 | nms <- names(roles) 101 | if (length(nms) > 1L) { 102 | key <- paste(key, seq_along(nms), sep = "_") 103 | } 104 | rename(data, any_of(set_names(nms, key))) 105 | } 106 | -------------------------------------------------------------------------------- /R/utils.R: -------------------------------------------------------------------------------- 1 | unique.list <- function(x, ...) { 2 | unique(squash(x), ...) 3 | } 4 | 5 | squash <- function(x) { 6 | unlist(x, use.names = FALSE) 7 | } 8 | 9 | begins_with <- function(x) { 10 | paste0("^", x) 11 | } 12 | 13 | if_not_na <- function(x, value, ..., all = FALSE) { 14 | cnd <- is.na(x) 15 | if (all) { 16 | cnd <- all(cnd) 17 | } 18 | if_else(cnd, NA, value, ...) 19 | } 20 | 21 | all_na <- function(cols) { 22 | dplyr::if_all(all_of(cols), is.na) 23 | } 24 | 25 | dot <- function(x) { 26 | str_replace_all(x, "(?<=[\\p{L}\\p{N}](?!\\p{Po}))", ".") 27 | } 28 | 29 | make_initials <- function(x, dot = FALSE) { 30 | out <- str_remove_all(x, "\\B\\w+|[\\s.]+") 31 | if (dot) { 32 | out <- dot(out) 33 | } 34 | out 35 | } 36 | 37 | vec_drop_na <- function(x) { 38 | x[!is.na(x)] 39 | } 40 | 41 | vec_arrange <- function(x) { 42 | x[order(nchar(x), x)] 43 | } 44 | 45 | vec_in <- function(x, y, ignore_case = TRUE) { 46 | if (ignore_case) { 47 | x <- tolower(x) 48 | y <- tolower(y) 49 | } 50 | x %in% y 51 | } 52 | 53 | vec_match <- function(x, y, ignore_case = TRUE) { 54 | if (ignore_case) { 55 | x <- tolower(x) 56 | y <- tolower(y) 57 | } 58 | match(x, y) 59 | } 60 | 61 | rank <- function(x, base) { 62 | matches <- vec_match(x, base) 63 | vec_rank(matches, ties = "dense") 64 | } 65 | 66 | recycle_to_names <- function(x, nms) { 67 | if (is_named(nms)) { 68 | nms <- names(nms) 69 | } 70 | x <- rep(list(x), length(nms)) 71 | set_names(x, nms) 72 | } 73 | 74 | propagate_names <- function(x, nms) { 75 | if (is_named(nms)) { 76 | nms <- names(nms) 77 | } 78 | nms <- nms[!nms %in% names(x)] 79 | named <- have_name(x) 80 | items <- list(x[named], squash(x[!named])) 81 | items[[2]] <- recycle_to_names(items[[2]], nms) 82 | unlist(items, recursive = FALSE) 83 | } 84 | 85 | condense <- function(x) { 86 | vec_drop_na(unique(x)) 87 | } 88 | 89 | collapse <- function(x, sep = "") { 90 | paste(x, collapse = sep) 91 | } 92 | 93 | bind <- function(x, sep = ",", arrange = TRUE) { 94 | out <- condense(x) 95 | if (arrange) { 96 | out <- vec_arrange(out) 97 | } 98 | collapse(out, sep) 99 | } 100 | 101 | expr_type <- function(expr) { 102 | if (is.vector(expr) || is.symbol(expr)) { 103 | "symbol" 104 | } else if (is_call(expr, "c")) { 105 | "atomic" 106 | } else if (is_selector(expr)) { 107 | "selector" 108 | } else { 109 | typeof(expr) 110 | } 111 | } 112 | 113 | expr_cases <- function(expr) { 114 | switch( 115 | expr_type(expr), 116 | symbol = as.character(expr), 117 | atomic = as.character(expr[-1]), 118 | selector = eval(expr), 119 | abort( 120 | "Can't match elements with `{deparse(expr)}`.", 121 | call = caller_env(5) 122 | ) 123 | ) 124 | } 125 | 126 | collect_dots <- function(...) { 127 | out <- without_indexed_error(map(enexprs(...), expr_cases)) 128 | if (any(have_name(out))) { 129 | return(out) 130 | } 131 | squash(out) 132 | } 133 | 134 | caller_args <- function(n = 2) { 135 | as.list(caller_env(n)) 136 | } 137 | 138 | get_detail_vars <- function() { 139 | args <- caller_args() 140 | args_true <- args[map_vec(args, is_true)] 141 | names(args_true) 142 | } 143 | 144 | extract_glue_vars <- function(x) { 145 | str_extract_all(x, "(?<=\\{\\b)[^}]+", simplify = TRUE) 146 | } 147 | 148 | group_id <- function(x) { 149 | out <- vec_group_id(x) 150 | out <- replace(out, is.na(x) | x == 0L, NA) 151 | dense_rank(out) 152 | } 153 | 154 | predot <- function(x) { 155 | x[] <- paste0(".", x) 156 | x 157 | } 158 | 159 | propagate_na <- function(x, from) { 160 | replace(x, is.na(from), NA) 161 | } 162 | 163 | str_contain <- function(string, pattern) { 164 | str_detect(string, fixed(pattern)) 165 | } 166 | 167 | str_detect <- function(string, pattern) { 168 | out <- stringr::str_detect(string, pattern) 169 | replace(out, is.na(string), FALSE) 170 | } 171 | 172 | wrap <- function(x, value) { 173 | paste0(value, x, value) 174 | } 175 | 176 | blank_to_na <- function(x) { 177 | replace(x, is_blank(x), NA) 178 | } 179 | 180 | unstructure <- function(x) { 181 | attributes(x) <- NULL 182 | x 183 | } 184 | 185 | add_class <- function(x, cls, inherit = TRUE) { 186 | class(x) <- c(cls, if (inherit) class(x)) 187 | x 188 | } 189 | 190 | split_chars <- function(x) { 191 | strsplit(x, "", fixed = TRUE)[[1]] 192 | } 193 | 194 | quos <- function(...) { 195 | rlang::quos(..., .named = TRUE) 196 | } 197 | -------------------------------------------------------------------------------- /R/yaml.R: -------------------------------------------------------------------------------- 1 | eol <- function() { 2 | if (.Platform$OS.type == "unix") "\n" else "\r\n" # nocov 3 | } 4 | 5 | .yaml_args <- list( 6 | line.sep = eol(), 7 | indent.mapping.sequence = TRUE, 8 | handlers = list(logical = yaml::verbatim_logical) 9 | ) 10 | 11 | schemas_are_up_to_date <- function(old, new) { 12 | old <- old[c("author", "affiliations")] 13 | identical(old, new) 14 | } 15 | 16 | json_update <- function(old, new) { 17 | new <- as_json(new) 18 | if (schemas_are_up_to_date(old, new)) { 19 | return() 20 | } 21 | out <- if (is.null(old)) new else list_assign(old, !!!new) 22 | list_drop_empty(out) 23 | } 24 | 25 | as_json <- function(x) { 26 | x <- toJSON(x) 27 | parse_json(x) 28 | } 29 | 30 | separate_yaml_header <- function(x) { 31 | str_split_1(x, "(?m:^|\\R\\K)-{3}(?:\\R|$)") 32 | } 33 | 34 | yaml_inject <- function(x, lines) { 35 | yaml <- do.call(yaml::as.yaml, c(list(x), .yaml_args)) 36 | out <- replace(lines, 2L, yaml) 37 | collapse(out, paste0("---", eol())) 38 | } 39 | 40 | has_yaml <- function(x) { 41 | str_detect(x, "(?s)^\\R*---\\R.*\\B---(?:\\R|$)") 42 | } 43 | 44 | check_has_yaml <- function(x) { 45 | if (has_yaml(x)) { 46 | return(invisible()) 47 | } 48 | abort(c( 49 | "No YAML headers found.", 50 | i = "YAML headers must be at the beginning of the document.", 51 | i = "YAML headers must start and end with three hyphens." 52 | )) 53 | } 54 | 55 | yaml_has_strippable <- function(x) { 56 | pattern <- paste( 57 | "\\B#(?=(?:[^'\"]*['\"][^'\"]*['\"])*[^'\"]*$)", 58 | ":\\s*(?:>|!!?[a-z]+|&)", 59 | sep = "|" 60 | ) 61 | str_detect(x, pattern) 62 | } 63 | 64 | add_yaml_header <- function(x) { 65 | rlang::warn(c( 66 | "Writing author metadata in a separate YAML header.", 67 | i = "This happens because the original YAML contained information such as", 68 | " comments, custom tags or folded blocks that would otherwise be lost." 69 | )) 70 | c("", "", x) 71 | } 72 | 73 | yaml_push <- function(x, file) { 74 | UseMethod("yaml_push") 75 | } 76 | 77 | yaml_push.default <- function(x, file) { 78 | old <- yaml::read_yaml(file) 79 | json <- json_update(old, x) 80 | if (is.null(json)) { 81 | return(invisible()) 82 | } 83 | do.call(yaml::write_yaml, c(list(json), file, .yaml_args)) 84 | } 85 | 86 | yaml_push.qmd <- function(x, file) { 87 | text <- read_file(file) 88 | check_has_yaml(text) 89 | items <- separate_yaml_header(text) 90 | if (yaml_has_strippable(items[[2]])) { 91 | items <- add_yaml_header(items) 92 | } 93 | old <- yaml::yaml.load(items[[2]]) 94 | json <- json_update(old, x) 95 | if (is.null(json)) { 96 | return(invisible()) 97 | } 98 | lines <- yaml_inject(json, items) 99 | write_lines(lines, file = file, sep = "") 100 | } 101 | -------------------------------------------------------------------------------- /R/zzz.R: -------------------------------------------------------------------------------- 1 | .onLoad <- function(...) { 2 | vctrs::s3_register("waldo::compare_proxy", "plm") 3 | invisible() 4 | } 5 | -------------------------------------------------------------------------------- /README.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | output: github_document 3 | --- 4 | 5 | 6 | 7 | ```{r, include = FALSE} 8 | knitr::opts_chunk$set( 9 | warning = FALSE, 10 | collapse = TRUE, 11 | comment = "#>", 12 | fig.path = "man/figures/README-", 13 | out.width = "100%" 14 | ) 15 | ``` 16 | 17 | # plume 18 | 19 | 20 | [![CRAN status](https://www.r-pkg.org/badges/version/plume)](https://CRAN.R-project.org/package=plume) 21 | [![R-CMD-check](https://github.com/arnaudgallou/plume/actions/workflows/R-CMD-check.yaml/badge.svg)](https://github.com/arnaudgallou/plume/actions/workflows/R-CMD-check.yaml) 22 | [![Codecov test coverage](https://codecov.io/gh/arnaudgallou/plume/graph/badge.svg)](https://app.codecov.io/gh/arnaudgallou/plume) 23 | 24 | 25 | ## Overview 26 | 27 | plume provides tools for handling and generating author-related information for scientific writing in R Markdown and Quarto. The package implements two R6 classes: 28 | 29 | - `PlumeQuarto`: class that allows you to push author metadata in YAML files or the YAML header of Quarto files. The generated YAML complies with Quarto's [author and affiliations schemas](https://quarto.org/docs/journals/authors.html). This is the class to use if you work with journal templates. 30 | 31 | - `Plume`: class that generates author lists and other author-related information as character strings. This is an easy and convenient solution when you don't need preformatted documents. 32 | 33 | ## Installation 34 | 35 | Install plume from CRAN with: 36 | 37 | ```{r, eval = FALSE} 38 | install.packages("plume") 39 | ``` 40 | 41 | Alternatively, you can install the development version of plume from GitHub with: 42 | 43 | ```{r, eval = FALSE} 44 | # install.packages("pak") 45 | pak::pak("arnaudgallou/plume") 46 | ``` 47 | 48 | ## Usage 49 | 50 | The minimal required data to work with plume classes is a data set containing given and family names but you would normally want to provide more information such as email addresses, ORCIDs, affiliations, etc. 51 | 52 | ```{r, message = FALSE} 53 | library(plume) 54 | 55 | encyclopedists 56 | 57 | Plume$new(encyclopedists) 58 | ``` 59 | 60 | ```{r, include = FALSE} 61 | tmp_file <- withr::local_tempfile( 62 | lines = "---\ntitle: Encyclopédie\n---\n\nQui scribit bis legit", 63 | fileext = ".qmd" 64 | ) 65 | ``` 66 | 67 | `PlumeQuarto` lets you push author metadata in YAML files or the YAML header of any `.qmd` file using the `to_yaml()` method. 68 | 69 | Consider the following example: 70 | 71 | ```{r, echo = FALSE, comment = ""} 72 | cat(readr::read_file(tmp_file)) 73 | ``` 74 | 75 | ```{r, eval = FALSE} 76 | aut <- PlumeQuarto$new( 77 | encyclopedists, 78 | file = "file.qmd" 79 | ) 80 | aut$set_corresponding_authors(1, 4) 81 | aut$to_yaml() 82 | ``` 83 | 84 | ```{r, echo = FALSE, comment = ""} 85 | aut <- PlumeQuarto$new(encyclopedists, tmp_file) 86 | aut$set_corresponding_authors(1, 4) 87 | aut$to_yaml() 88 | cat(readr::read_file(tmp_file)) 89 | ``` 90 | 91 | Alternatively, you can generate author information as character strings using `Plume`: 92 | 93 | ```{r, message = FALSE} 94 | aut <- Plume$new(encyclopedists) 95 | aut$set_corresponding_authors(diderot, .by = "family_name") 96 | 97 | aut$get_author_list(suffix = "^a,^cn") 98 | 99 | aut$get_contact_details() 100 | 101 | aut$get_affiliations() 102 | 103 | aut$get_notes() 104 | 105 | aut$get_contributions() 106 | 107 | aut2 <- Plume$new( 108 | encyclopedists, 109 | roles = c( 110 | supervision = "supervised the project", 111 | writing = "contributed to the Encyclopédie" 112 | ), 113 | symbols = list(affiliation = letters) 114 | ) 115 | 116 | aut2$get_author_list("^a^") 117 | 118 | aut2$get_contributions(roles_first = FALSE, divider = " ") 119 | ``` 120 | 121 | ## Acknowledgements 122 | 123 | Thanks to: 124 | 125 | * [Richard J. Telford](https://github.com/richardjtelford) for his advice that helped me conceive this package. 126 | 127 | * [Maëlle Salmon](https://github.com/maelle) and [Gábor Csárdi](https://github.com/gaborcsardi) for their help when I was stuck with unit tests, roxygen2 or pkgdown. 128 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | # plume 5 | 6 | 7 | 8 | [![CRAN 9 | status](https://www.r-pkg.org/badges/version/plume)](https://CRAN.R-project.org/package=plume) 10 | [![R-CMD-check](https://github.com/arnaudgallou/plume/actions/workflows/R-CMD-check.yaml/badge.svg)](https://github.com/arnaudgallou/plume/actions/workflows/R-CMD-check.yaml) 11 | [![Codecov test 12 | coverage](https://codecov.io/gh/arnaudgallou/plume/graph/badge.svg)](https://app.codecov.io/gh/arnaudgallou/plume) 13 | 14 | 15 | ## Overview 16 | 17 | plume provides tools for handling and generating author-related 18 | information for scientific writing in R Markdown and Quarto. The package 19 | implements two R6 classes: 20 | 21 | - `PlumeQuarto`: class that allows you to push author metadata in YAML 22 | files or the YAML header of Quarto files. The generated YAML complies 23 | with Quarto’s [author and affiliations 24 | schemas](https://quarto.org/docs/journals/authors.html). This is the 25 | class to use if you work with journal templates. 26 | 27 | - `Plume`: class that generates author lists and other author-related 28 | information as character strings. This is an easy and convenient 29 | solution when you don’t need preformatted documents. 30 | 31 | ## Installation 32 | 33 | Install plume from CRAN with: 34 | 35 | ``` r 36 | install.packages("plume") 37 | ``` 38 | 39 | Alternatively, you can install the development version of plume from 40 | GitHub with: 41 | 42 | ``` r 43 | # install.packages("pak") 44 | pak::pak("arnaudgallou/plume") 45 | ``` 46 | 47 | ## Usage 48 | 49 | The minimal required data to work with plume classes is a data set 50 | containing given and family names but you would normally want to provide 51 | more information such as email addresses, ORCIDs, affiliations, etc. 52 | 53 | ``` r 54 | library(plume) 55 | 56 | encyclopedists 57 | #> # A tibble: 4 × 10 58 | #> given_name family_name email phone orcid supervision writing note 59 | #> 60 | #> 1 Denis Diderot dider… +1234 0000… 1 1 born… 61 | #> 2 Jean-Jacques Rousseau rouss… 0000… NA 1 62 | #> 3 François-Marie Arouet aroue… NA 1 also… 63 | #> 4 Jean Le Rond d'Alembert alemb… 0000… 1 1 born… 64 | #> # ℹ 2 more variables: affiliation_1 , affiliation_2 65 | 66 | Plume$new(encyclopedists) 67 | #> # A tibble: 4 × 11 68 | #> id given_name family_name literal_name initials email phone orcid note 69 | #> 70 | #> 1 1 Denis Diderot Denis Dider… DD dide… +1234 0000… born… 71 | #> 2 2 Jean-Jacques Rousseau Jean-Jacque… J-JR rous… 0000… 72 | #> 3 3 François-Marie Arouet François-Ma… F-MA arou… also… 73 | #> 4 4 Jean Le Rond d'… Jean Le Ron… JLRd'A alem… 0000… born… 74 | #> # ℹ 2 more variables: affiliation , role 75 | ``` 76 | 77 | `PlumeQuarto` lets you push author metadata in YAML files or the YAML 78 | header of any `.qmd` file using the `to_yaml()` method. 79 | 80 | Consider the following example: 81 | 82 | --- 83 | title: Encyclopédie 84 | --- 85 | 86 | Qui scribit bis legit 87 | 88 | ``` r 89 | aut <- PlumeQuarto$new( 90 | encyclopedists, 91 | file = "file.qmd" 92 | ) 93 | aut$set_corresponding_authors(1, 4) 94 | aut$to_yaml() 95 | ``` 96 | 97 | --- 98 | title: Encyclopédie 99 | author: 100 | - id: aut1 101 | name: 102 | given: Denis 103 | family: Diderot 104 | email: diderot@encyclopediste.fr 105 | phone: '+1234' 106 | orcid: 0000-0000-0000-0001 107 | note: born in 1713 in Langres 108 | attributes: 109 | corresponding: true 110 | roles: 111 | - Supervision 112 | - Writing - original draft 113 | affiliations: 114 | - ref: aff1 115 | - id: aut2 116 | name: 117 | given: Jean-Jacques 118 | family: Rousseau 119 | email: rousseau@encyclopediste.fr 120 | orcid: 0000-0000-0000-0002 121 | attributes: 122 | corresponding: false 123 | roles: 124 | - Writing - original draft 125 | affiliations: 126 | - ref: aff2 127 | - id: aut3 128 | name: 129 | given: François-Marie 130 | family: Arouet 131 | email: arouet@encyclopediste.fr 132 | note: also known as Voltaire 133 | attributes: 134 | corresponding: false 135 | roles: 136 | - Writing - original draft 137 | affiliations: 138 | - ref: aff2 139 | - id: aut4 140 | name: 141 | given: Jean 142 | family: Le Rond d'Alembert 143 | email: alembert@encyclopediste.fr 144 | orcid: 0000-0000-0000-0003 145 | note: born in 1717 in Paris 146 | attributes: 147 | corresponding: true 148 | roles: 149 | - Supervision 150 | - Writing - original draft 151 | affiliations: 152 | - ref: aff1 153 | - ref: aff3 154 | affiliations: 155 | - id: aff1 156 | name: Université de Paris 157 | - id: aff2 158 | name: Lycée Louis-le-Grand 159 | - id: aff3 160 | name: Collège des Quatre-Nations 161 | --- 162 | 163 | Qui scribit bis legit 164 | 165 | Alternatively, you can generate author information as character strings 166 | using `Plume`: 167 | 168 | ``` r 169 | aut <- Plume$new(encyclopedists) 170 | aut$set_corresponding_authors(diderot, .by = "family_name") 171 | 172 | aut$get_author_list(suffix = "^a,^cn") 173 | #> Denis Diderot^1,^\*† 174 | #> Jean-Jacques Rousseau^2^ 175 | #> François-Marie Arouet^2^‡ 176 | #> Jean Le Rond d'Alembert^1,3^§ 177 | 178 | aut$get_contact_details() 179 | #> diderot@encyclopediste.fr (Denis Diderot) 180 | 181 | aut$get_affiliations() 182 | #> ^1^Université de Paris 183 | #> ^2^Lycée Louis-le-Grand 184 | #> ^3^Collège des Quatre-Nations 185 | 186 | aut$get_notes() 187 | #> ^†^born in 1713 in Langres 188 | #> ^‡^also known as Voltaire 189 | #> ^§^born in 1717 in Paris 190 | 191 | aut$get_contributions() 192 | #> Supervision: D.D. and J.L.R.d'A. 193 | #> Writing - original draft: D.D., J.-J.R., F.-M.A. and J.L.R.d'A. 194 | 195 | aut2 <- Plume$new( 196 | encyclopedists, 197 | roles = c( 198 | supervision = "supervised the project", 199 | writing = "contributed to the Encyclopédie" 200 | ), 201 | symbols = list(affiliation = letters) 202 | ) 203 | 204 | aut2$get_author_list("^a^") 205 | #> Denis Diderot^a^ 206 | #> Jean-Jacques Rousseau^b^ 207 | #> François-Marie Arouet^b^ 208 | #> Jean Le Rond d'Alembert^a,c^ 209 | 210 | aut2$get_contributions(roles_first = FALSE, divider = " ") 211 | #> D.D. and J.L.R.d'A. supervised the project 212 | #> D.D., J.-J.R., F.-M.A. and J.L.R.d'A. contributed to the Encyclopédie 213 | ``` 214 | 215 | ## Acknowledgements 216 | 217 | Thanks to: 218 | 219 | - [Richard J. Telford](https://github.com/richardjtelford) for his 220 | advice that helped me conceive this package. 221 | 222 | - [Maëlle Salmon](https://github.com/maelle) and [Gábor 223 | Csárdi](https://github.com/gaborcsardi) for their help when I was 224 | stuck with unit tests, roxygen2 or pkgdown. 225 | -------------------------------------------------------------------------------- /_pkgdown.yml: -------------------------------------------------------------------------------- 1 | url: https://arnaudgallou.github.io/plume/ 2 | template: 3 | bootstrap: 5 4 | package: cygne 5 | includes: 6 | after_body: > 7 | 9 | reference: 10 | - title: Classes 11 | contents: 12 | - PlumeQuarto 13 | - Plume 14 | - title: Helpers 15 | contents: 16 | - as_lines 17 | - credit_roles 18 | - enumerate 19 | - everyone 20 | - icn_orcid 21 | - plm_template 22 | - sequential 23 | - set_default_names 24 | - title: Built-in datasets 25 | contents: 26 | - encyclopedists 27 | - encyclopedists_fr 28 | articles: 29 | - title: Get started 30 | navbar: ~ 31 | contents: 32 | - plume 33 | - plume-workflow 34 | - working-in-other-languages 35 | - comprehensive-affiliation-schema 36 | development: 37 | mode: auto 38 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | comment: false 2 | 3 | coverage: 4 | status: 5 | project: 6 | default: 7 | target: auto 8 | threshold: 1% 9 | informational: true 10 | patch: 11 | default: 12 | target: auto 13 | threshold: 1% 14 | informational: true 15 | -------------------------------------------------------------------------------- /cran-comments.md: -------------------------------------------------------------------------------- 1 | ## R CMD check results 2 | 3 | 0 errors | 0 warnings | 0 notes 4 | -------------------------------------------------------------------------------- /data-raw/encyclopedists.R: -------------------------------------------------------------------------------- 1 | make_emails <- function(x) { 2 | out <- gsub(".+\\b(\\w+)$", "\\1", x, perl = TRUE) 3 | out <- tolower(out) 4 | paste0(out, "@encyclopediste.fr") 5 | } 6 | 7 | encyclopedists <- tibble::tibble( 8 | given_name = c("Denis", "Jean-Jacques", "François-Marie", "Jean"), 9 | family_name = c("Diderot", "Rousseau", "Arouet", "Le Rond d'Alembert"), 10 | email = make_emails(family_name), 11 | phone = c("+1234", NA, NA, NA), 12 | orcid = c(paste0("0000-0000-0000-000", 1:2), NA, "0000-0000-0000-0003"), 13 | supervision = c(1, NA, NA, 1), 14 | writing = 1, 15 | note = c( 16 | "born in 1713 in Langres", 17 | NA, 18 | "also known as Voltaire", 19 | "born in 1717 in Paris" 20 | ), 21 | affiliation_1 = c( 22 | "Université de Paris", 23 | rep("Lycée Louis-le-Grand", 2L), 24 | "Université de Paris" 25 | ), 26 | affiliation_2 = c(NA, NA, NA, "Collège des Quatre-Nations"), 27 | ) 28 | 29 | encyclopedists_fr <- encyclopedists |> 30 | dplyr::rename( 31 | prénom = given_name, 32 | nom = family_name, 33 | courriel = email, 34 | téléphone = phone, 35 | rédaction = writing 36 | ) |> 37 | dplyr::mutate(note = c( 38 | "né en 1713 à Langres", 39 | NA, 40 | "dit Voltaire", 41 | "né en 1717 à Paris" 42 | )) 43 | 44 | usethis::use_data(encyclopedists, encyclopedists_fr, overwrite = TRUE) 45 | -------------------------------------------------------------------------------- /data/encyclopedists.rda: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arnaudgallou/plume/d507781b9cbf12c565e9b88b4b8a636543ff6279/data/encyclopedists.rda -------------------------------------------------------------------------------- /data/encyclopedists_fr.rda: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arnaudgallou/plume/d507781b9cbf12c565e9b88b4b8a636543ff6279/data/encyclopedists_fr.rda -------------------------------------------------------------------------------- /inst/icons/orcid-bw.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arnaudgallou/plume/d507781b9cbf12c565e9b88b4b8a636543ff6279/inst/icons/orcid-bw.pdf -------------------------------------------------------------------------------- /inst/icons/orcid-bw.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 9 | 10 | 11 | 12 | 14 | 16 | 17 | -------------------------------------------------------------------------------- /inst/icons/orcid.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arnaudgallou/plume/d507781b9cbf12c565e9b88b4b8a636543ff6279/inst/icons/orcid.pdf -------------------------------------------------------------------------------- /inst/icons/orcid.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 9 | 10 | 11 | 12 | 14 | 16 | 17 | -------------------------------------------------------------------------------- /man/PlumeHandler.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/plume-handler.R 3 | \name{PlumeHandler} 4 | \alias{PlumeHandler} 5 | \title{PlumeHandler class} 6 | \description{ 7 | Internal class processing and shaping tabular data into a 8 | \code{plume} object. 9 | } 10 | \keyword{internal} 11 | \section{Super class}{ 12 | \code{plume::NameHandler} -> \code{PlumeHandler} 13 | } 14 | \section{Methods}{ 15 | \subsection{Public methods}{ 16 | \itemize{ 17 | \item \href{#method-PlumeHandler-new}{\code{PlumeHandler$new()}} 18 | \item \href{#method-PlumeHandler-print}{\code{PlumeHandler$print()}} 19 | \item \href{#method-PlumeHandler-get_plume}{\code{PlumeHandler$get_plume()}} 20 | \item \href{#method-PlumeHandler-get_roles}{\code{PlumeHandler$get_roles()}} 21 | \item \href{#method-PlumeHandler-clone}{\code{PlumeHandler$clone()}} 22 | } 23 | } 24 | \if{html}{\out{
}} 25 | \if{html}{\out{
}} 26 | \if{latex}{\out{\hypertarget{method-PlumeHandler-new}{}}} 27 | \subsection{Method \code{new()}}{ 28 | \subsection{Usage}{ 29 | \if{html}{\out{
}}\preformatted{PlumeHandler$new( 30 | data, 31 | names, 32 | roles, 33 | credit_roles, 34 | initials_given_name, 35 | family_name_first = FALSE, 36 | interword_spacing = TRUE 37 | )}\if{html}{\out{
}} 38 | } 39 | 40 | } 41 | \if{html}{\out{
}} 42 | \if{html}{\out{}} 43 | \if{latex}{\out{\hypertarget{method-PlumeHandler-print}{}}} 44 | \subsection{Method \code{print()}}{ 45 | \subsection{Usage}{ 46 | \if{html}{\out{
}}\preformatted{PlumeHandler$print()}\if{html}{\out{
}} 47 | } 48 | 49 | } 50 | \if{html}{\out{
}} 51 | \if{html}{\out{}} 52 | \if{latex}{\out{\hypertarget{method-PlumeHandler-get_plume}{}}} 53 | \subsection{Method \code{get_plume()}}{ 54 | \subsection{Usage}{ 55 | \if{html}{\out{
}}\preformatted{PlumeHandler$get_plume()}\if{html}{\out{
}} 56 | } 57 | 58 | } 59 | \if{html}{\out{
}} 60 | \if{html}{\out{}} 61 | \if{latex}{\out{\hypertarget{method-PlumeHandler-get_roles}{}}} 62 | \subsection{Method \code{get_roles()}}{ 63 | \subsection{Usage}{ 64 | \if{html}{\out{
}}\preformatted{PlumeHandler$get_roles()}\if{html}{\out{
}} 65 | } 66 | 67 | } 68 | \if{html}{\out{
}} 69 | \if{html}{\out{}} 70 | \if{latex}{\out{\hypertarget{method-PlumeHandler-clone}{}}} 71 | \subsection{Method \code{clone()}}{ 72 | The objects of this class are cloneable with this method. 73 | \subsection{Usage}{ 74 | \if{html}{\out{
}}\preformatted{PlumeHandler$clone(deep = FALSE)}\if{html}{\out{
}} 75 | } 76 | 77 | \subsection{Arguments}{ 78 | \if{html}{\out{
}} 79 | \describe{ 80 | \item{\code{deep}}{Whether to make a deep clone.} 81 | } 82 | \if{html}{\out{
}} 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /man/PlumeQuarto.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/plume-quarto.R 3 | \name{PlumeQuarto} 4 | \alias{PlumeQuarto} 5 | \title{PlumeQuarto class} 6 | \description{ 7 | Class that pushes author metadata in YAML files or the YAML 8 | header of Quarto files. 9 | } 10 | \examples{ 11 | # Create a simple temporary file with a YAML header 12 | # containing a title 13 | tmp_file <- tempfile(fileext = ".qmd") 14 | readr::write_lines("---\ntitle: Encyclopédie\n---", tmp_file) 15 | 16 | # View the temporary file 17 | cat(readr::read_file(tmp_file)) 18 | 19 | # Create a PlumeQuarto instance using the temporary file 20 | # you've just created 21 | aut <- PlumeQuarto$new( 22 | encyclopedists, 23 | file = tmp_file 24 | ) 25 | 26 | # And push author data to the YAML header 27 | aut$to_yaml() 28 | 29 | cat(readr::read_file(tmp_file)) 30 | 31 | # Pushing again with new data updates the YAML 32 | # header accordingly 33 | aut <- PlumeQuarto$new( 34 | dplyr::slice(encyclopedists, 2), 35 | file = tmp_file 36 | ) 37 | aut$to_yaml() 38 | 39 | cat(readr::read_file(tmp_file)) 40 | 41 | # Clean up the temporary file 42 | unlink(tmp_file) 43 | } 44 | \section{Super classes}{ 45 | \code{plume::NameHandler} -> \code{\link[plume:PlumeHandler]{plume::PlumeHandler}} -> \code{\link[plume:StatusSetter]{plume::StatusSetter}} -> \code{\link[plume:StatusSetterPlumeQuarto]{plume::StatusSetterPlumeQuarto}} -> \code{PlumeQuarto} 46 | } 47 | \section{Methods}{ 48 | \subsection{Public methods}{ 49 | \itemize{ 50 | \item \href{#method-PlumeQuarto-new}{\code{PlumeQuarto$new()}} 51 | \item \href{#method-PlumeQuarto-to_yaml}{\code{PlumeQuarto$to_yaml()}} 52 | \item \href{#method-PlumeQuarto-clone}{\code{PlumeQuarto$clone()}} 53 | } 54 | } 55 | \if{html}{\out{ 56 |
Inherited methods 57 | 66 |
67 | }} 68 | \if{html}{\out{
}} 69 | \if{html}{\out{}} 70 | \if{latex}{\out{\hypertarget{method-PlumeQuarto-new}{}}} 71 | \subsection{Method \code{new()}}{ 72 | Create a \code{PlumeQuarto} object. 73 | \subsection{Usage}{ 74 | \if{html}{\out{
}}\preformatted{PlumeQuarto$new( 75 | data, 76 | file, 77 | names = NULL, 78 | roles = credit_roles(), 79 | credit_roles = FALSE, 80 | initials_given_name = FALSE, 81 | by = NULL 82 | )}\if{html}{\out{
}} 83 | } 84 | 85 | \subsection{Arguments}{ 86 | \if{html}{\out{
}} 87 | \describe{ 88 | \item{\code{data}}{A data frame containing author-related data.} 89 | 90 | \item{\code{file}}{A \code{.qmd}, \code{.yml} or \code{.yaml} file to insert author data into.} 91 | 92 | \item{\code{names}}{A vector of key-value pairs specifying custom names to use, 93 | where keys are default names and values their respective replacements.} 94 | 95 | \item{\code{roles}}{A vector of key-value pairs defining roles where keys 96 | identify columns and values describe the actual roles to use.} 97 | 98 | \item{\code{credit_roles}}{\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#deprecated}{\figure{lifecycle-deprecated.svg}{options: alt='[Deprecated]'}}}{\strong{[Deprecated]}} 99 | 100 | It is now recommended to use \code{roles = credit_roles()} to use the 101 | \href{https://credit.niso.org}{Contributor Roles Taxonomy}.} 102 | 103 | \item{\code{initials_given_name}}{Should the initials of given names be used?} 104 | 105 | \item{\code{by}}{A character string defining the default variable used to assign 106 | specific metadata to authors in all \verb{set_*()} methods. By default, uses 107 | authors' id.} 108 | } 109 | \if{html}{\out{
}} 110 | } 111 | \subsection{Returns}{ 112 | A \code{PlumeQuarto} object. 113 | } 114 | } 115 | \if{html}{\out{
}} 116 | \if{html}{\out{}} 117 | \if{latex}{\out{\hypertarget{method-PlumeQuarto-to_yaml}{}}} 118 | \subsection{Method \code{to_yaml()}}{ 119 | Push or update author information in a YAML file or YAML 120 | header. The generated YAML complies with Quarto's 121 | \href{https://quarto.org/docs/journals/authors.html}{author and affiliations schemas}. 122 | \subsection{Usage}{ 123 | \if{html}{\out{
}}\preformatted{PlumeQuarto$to_yaml()}\if{html}{\out{
}} 124 | } 125 | 126 | \subsection{Details}{ 127 | If missing, \code{to_yaml()} inserts author information into the desired file. 128 | Otherwise, the function replaces old \code{author} and \code{affiliations} values 129 | with the ones provided in the input data. 130 | } 131 | 132 | \subsection{Returns}{ 133 | The input \code{file} invisibly. 134 | } 135 | } 136 | \if{html}{\out{
}} 137 | \if{html}{\out{}} 138 | \if{latex}{\out{\hypertarget{method-PlumeQuarto-clone}{}}} 139 | \subsection{Method \code{clone()}}{ 140 | The objects of this class are cloneable with this method. 141 | \subsection{Usage}{ 142 | \if{html}{\out{
}}\preformatted{PlumeQuarto$clone(deep = FALSE)}\if{html}{\out{
}} 143 | } 144 | 145 | \subsection{Arguments}{ 146 | \if{html}{\out{
}} 147 | \describe{ 148 | \item{\code{deep}}{Whether to make a deep clone.} 149 | } 150 | \if{html}{\out{
}} 151 | } 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /man/StatusSetter.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/status-setter.R 3 | \name{StatusSetter} 4 | \alias{StatusSetter} 5 | \title{StatusSetter class} 6 | \description{ 7 | Internal class that manages authors' status. 8 | } 9 | \keyword{internal} 10 | \section{Super classes}{ 11 | \code{plume::NameHandler} -> \code{\link[plume:PlumeHandler]{plume::PlumeHandler}} -> \code{StatusSetter} 12 | } 13 | \section{Methods}{ 14 | \subsection{Public methods}{ 15 | \itemize{ 16 | \item \href{#method-StatusSetter-new}{\code{StatusSetter$new()}} 17 | \item \href{#method-StatusSetter-set_corresponding_authors}{\code{StatusSetter$set_corresponding_authors()}} 18 | \item \href{#method-StatusSetter-clone}{\code{StatusSetter$clone()}} 19 | } 20 | } 21 | \if{html}{\out{ 22 |
Inherited methods 23 | 28 |
29 | }} 30 | \if{html}{\out{
}} 31 | \if{html}{\out{}} 32 | \if{latex}{\out{\hypertarget{method-StatusSetter-new}{}}} 33 | \subsection{Method \code{new()}}{ 34 | \subsection{Usage}{ 35 | \if{html}{\out{
}}\preformatted{StatusSetter$new(..., by)}\if{html}{\out{
}} 36 | } 37 | 38 | } 39 | \if{html}{\out{
}} 40 | \if{html}{\out{}} 41 | \if{latex}{\out{\hypertarget{method-StatusSetter-set_corresponding_authors}{}}} 42 | \subsection{Method \code{set_corresponding_authors()}}{ 43 | Set corresponding authors. 44 | \subsection{Usage}{ 45 | \if{html}{\out{
}}\preformatted{StatusSetter$set_corresponding_authors(..., .by = NULL, by = deprecated())}\if{html}{\out{
}} 46 | } 47 | 48 | \subsection{Arguments}{ 49 | \if{html}{\out{
}} 50 | \describe{ 51 | \item{\code{...}}{One or more unquoted expressions separated by commas. 52 | Expressions matching values in the column defined by \code{by}/\code{.by} 53 | determine corresponding authors. Matching of values is case- 54 | insensitive.} 55 | 56 | \item{\code{.by}}{Variable used to set corresponding authors. By default, uses 57 | authors' id.} 58 | 59 | \item{\code{by}}{\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#deprecated}{\figure{lifecycle-deprecated.svg}{options: alt='[Deprecated]'}}}{\strong{[Deprecated]}} 60 | 61 | Please use the \code{.by} parameter instead.} 62 | } 63 | \if{html}{\out{
}} 64 | } 65 | \subsection{Returns}{ 66 | The class instance. 67 | } 68 | } 69 | \if{html}{\out{
}} 70 | \if{html}{\out{}} 71 | \if{latex}{\out{\hypertarget{method-StatusSetter-clone}{}}} 72 | \subsection{Method \code{clone()}}{ 73 | The objects of this class are cloneable with this method. 74 | \subsection{Usage}{ 75 | \if{html}{\out{
}}\preformatted{StatusSetter$clone(deep = FALSE)}\if{html}{\out{
}} 76 | } 77 | 78 | \subsection{Arguments}{ 79 | \if{html}{\out{
}} 80 | \describe{ 81 | \item{\code{deep}}{Whether to make a deep clone.} 82 | } 83 | \if{html}{\out{
}} 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /man/StatusSetterPlume.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/status-setter.R 3 | \name{StatusSetterPlume} 4 | \alias{StatusSetterPlume} 5 | \title{StatusSetterPlume class} 6 | \description{ 7 | Internal class extending \code{StatusSetter} for \code{Plume}. 8 | } 9 | \keyword{internal} 10 | \section{Super classes}{ 11 | \code{plume::NameHandler} -> \code{\link[plume:PlumeHandler]{plume::PlumeHandler}} -> \code{\link[plume:StatusSetter]{plume::StatusSetter}} -> \code{StatusSetterPlume} 12 | } 13 | \section{Methods}{ 14 | \subsection{Public methods}{ 15 | \itemize{ 16 | \item \href{#method-StatusSetterPlume-set_main_contributors}{\code{StatusSetterPlume$set_main_contributors()}} 17 | \item \href{#method-StatusSetterPlume-clone}{\code{StatusSetterPlume$clone()}} 18 | } 19 | } 20 | \if{html}{\out{ 21 |
Inherited methods 22 | 29 |
30 | }} 31 | \if{html}{\out{
}} 32 | \if{html}{\out{}} 33 | \if{latex}{\out{\hypertarget{method-StatusSetterPlume-set_main_contributors}{}}} 34 | \subsection{Method \code{set_main_contributors()}}{ 35 | Force one or more contributors' names to appear first in the 36 | contribution list. 37 | \subsection{Usage}{ 38 | \if{html}{\out{
}}\preformatted{StatusSetterPlume$set_main_contributors(..., .roles = NULL, .by = NULL)}\if{html}{\out{
}} 39 | } 40 | 41 | \subsection{Arguments}{ 42 | \if{html}{\out{
}} 43 | \describe{ 44 | \item{\code{...}}{One or more unquoted expressions separated by commas. 45 | Expressions matching values in the column defined by \code{by}/\code{.by} 46 | determine main contributors. Expressions can be named after any role to 47 | set different main contributors to different roles at once, in which 48 | case the \code{.roles} parameter only applies roles that are not already set 49 | to unnamed expressions. Matching of values is case-insensitive.} 50 | 51 | \item{\code{.roles}}{Roles to assign main contributors to. If \code{.roles} is a 52 | named vector, only the names will be used.} 53 | 54 | \item{\code{.by}}{Variable used to specify which authors are main contributors. 55 | By default, uses authors' id.} 56 | } 57 | \if{html}{\out{
}} 58 | } 59 | \subsection{Returns}{ 60 | The class instance. 61 | } 62 | } 63 | \if{html}{\out{
}} 64 | \if{html}{\out{}} 65 | \if{latex}{\out{\hypertarget{method-StatusSetterPlume-clone}{}}} 66 | \subsection{Method \code{clone()}}{ 67 | The objects of this class are cloneable with this method. 68 | \subsection{Usage}{ 69 | \if{html}{\out{
}}\preformatted{StatusSetterPlume$clone(deep = FALSE)}\if{html}{\out{
}} 70 | } 71 | 72 | \subsection{Arguments}{ 73 | \if{html}{\out{
}} 74 | \describe{ 75 | \item{\code{deep}}{Whether to make a deep clone.} 76 | } 77 | \if{html}{\out{
}} 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /man/StatusSetterPlumeQuarto.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/status-setter.R 3 | \name{StatusSetterPlumeQuarto} 4 | \alias{StatusSetterPlumeQuarto} 5 | \title{StatusSetterPlumeQuarto class} 6 | \description{ 7 | Internal class extending \code{StatusSetter} for \code{PlumeQuarto}. 8 | } 9 | \keyword{internal} 10 | \section{Super classes}{ 11 | \code{plume::NameHandler} -> \code{\link[plume:PlumeHandler]{plume::PlumeHandler}} -> \code{\link[plume:StatusSetter]{plume::StatusSetter}} -> \code{StatusSetterPlumeQuarto} 12 | } 13 | \section{Methods}{ 14 | \subsection{Public methods}{ 15 | \itemize{ 16 | \item \href{#method-StatusSetterPlumeQuarto-set_cofirst_authors}{\code{StatusSetterPlumeQuarto$set_cofirst_authors()}} 17 | \item \href{#method-StatusSetterPlumeQuarto-set_equal_contributor}{\code{StatusSetterPlumeQuarto$set_equal_contributor()}} 18 | \item \href{#method-StatusSetterPlumeQuarto-set_deceased}{\code{StatusSetterPlumeQuarto$set_deceased()}} 19 | \item \href{#method-StatusSetterPlumeQuarto-clone}{\code{StatusSetterPlumeQuarto$clone()}} 20 | } 21 | } 22 | \if{html}{\out{ 23 |
Inherited methods 24 | 31 |
32 | }} 33 | \if{html}{\out{
}} 34 | \if{html}{\out{}} 35 | \if{latex}{\out{\hypertarget{method-StatusSetterPlumeQuarto-set_cofirst_authors}{}}} 36 | \subsection{Method \code{set_cofirst_authors()}}{ 37 | Set co-first authors. 38 | \subsection{Usage}{ 39 | \if{html}{\out{
}}\preformatted{StatusSetterPlumeQuarto$set_cofirst_authors(..., .by = NULL)}\if{html}{\out{
}} 40 | } 41 | 42 | \subsection{Arguments}{ 43 | \if{html}{\out{
}} 44 | \describe{ 45 | \item{\code{...}}{One or more unquoted expressions separated by commas. 46 | Expressions matching values in the column defined by \code{by}/\code{.by} 47 | determine co-first authors. Matching of values is case-insensitive.} 48 | 49 | \item{\code{.by}}{Variable used to specify which authors contributed equally to 50 | the work. By default, uses authors' id.} 51 | } 52 | \if{html}{\out{
}} 53 | } 54 | \subsection{Returns}{ 55 | The class instance. 56 | } 57 | } 58 | \if{html}{\out{
}} 59 | \if{html}{\out{}} 60 | \if{latex}{\out{\hypertarget{method-StatusSetterPlumeQuarto-set_equal_contributor}{}}} 61 | \subsection{Method \code{set_equal_contributor()}}{ 62 | \ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#deprecated}{\figure{lifecycle-deprecated.svg}{options: alt='[Deprecated]'}}}{\strong{[Deprecated]}} 63 | 64 | This method has been deprecated in favour of \code{set_cofirst_authors()}. 65 | \subsection{Usage}{ 66 | \if{html}{\out{
}}\preformatted{StatusSetterPlumeQuarto$set_equal_contributor( 67 | ..., 68 | .by = NULL, 69 | by = deprecated() 70 | )}\if{html}{\out{
}} 71 | } 72 | 73 | \subsection{Arguments}{ 74 | \if{html}{\out{
}} 75 | \describe{ 76 | \item{\code{...}}{One or more unquoted expressions separated by commas. 77 | Expressions matching values in the column defined by \code{by}/\code{.by} 78 | determine equal contributors. Matching of values is case-insensitive.} 79 | 80 | \item{\code{.by}}{Variable used to specify which authors are equal contributors. 81 | By default, uses authors' id.} 82 | 83 | \item{\code{by}}{\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#deprecated}{\figure{lifecycle-deprecated.svg}{options: alt='[Deprecated]'}}}{\strong{[Deprecated]}} 84 | 85 | Please use the \code{.by} parameter instead.} 86 | } 87 | \if{html}{\out{
}} 88 | } 89 | \subsection{Returns}{ 90 | The class instance. 91 | } 92 | } 93 | \if{html}{\out{
}} 94 | \if{html}{\out{}} 95 | \if{latex}{\out{\hypertarget{method-StatusSetterPlumeQuarto-set_deceased}{}}} 96 | \subsection{Method \code{set_deceased()}}{ 97 | Set deceased authors. 98 | \subsection{Usage}{ 99 | \if{html}{\out{
}}\preformatted{StatusSetterPlumeQuarto$set_deceased(..., .by = NULL, by = deprecated())}\if{html}{\out{
}} 100 | } 101 | 102 | \subsection{Arguments}{ 103 | \if{html}{\out{
}} 104 | \describe{ 105 | \item{\code{...}}{One or more unquoted expressions separated by commas. 106 | Expressions matching values in the column defined by \code{by}/\code{.by} 107 | determine deceased authors. Matching of values is case-insensitive.} 108 | 109 | \item{\code{.by}}{Variable used to specify whether an author is deceased or not. 110 | By default, uses authors' id.} 111 | 112 | \item{\code{by}}{\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#deprecated}{\figure{lifecycle-deprecated.svg}{options: alt='[Deprecated]'}}}{\strong{[Deprecated]}} 113 | 114 | Please use the \code{.by} parameter instead.} 115 | } 116 | \if{html}{\out{
}} 117 | } 118 | \subsection{Returns}{ 119 | The class instance. 120 | } 121 | } 122 | \if{html}{\out{
}} 123 | \if{html}{\out{}} 124 | \if{latex}{\out{\hypertarget{method-StatusSetterPlumeQuarto-clone}{}}} 125 | \subsection{Method \code{clone()}}{ 126 | The objects of this class are cloneable with this method. 127 | \subsection{Usage}{ 128 | \if{html}{\out{
}}\preformatted{StatusSetterPlumeQuarto$clone(deep = FALSE)}\if{html}{\out{
}} 129 | } 130 | 131 | \subsection{Arguments}{ 132 | \if{html}{\out{
}} 133 | \describe{ 134 | \item{\code{deep}}{Whether to make a deep clone.} 135 | } 136 | \if{html}{\out{
}} 137 | } 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /man/as_lines.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/as-lines.R 3 | \name{as_lines} 4 | \alias{as_lines} 5 | \title{Print vector elements on multiple lines} 6 | \usage{ 7 | as_lines(...) 8 | } 9 | \arguments{ 10 | \item{...}{Objects to print.} 11 | } 12 | \value{ 13 | \code{NULL} invisibly. 14 | } 15 | \description{ 16 | Thin wrapper around \code{\link[=cat]{cat()}} to display vector elements on 17 | multiple lines when rendering an R Markdown or Quarto document. This is 18 | primarily intended to be used with \code{\link{Plume}}'s methods to output each 19 | returned element on its own line. 20 | } 21 | \examples{ 22 | aut <- Plume$new(encyclopedists) 23 | as_lines(aut$get_affiliations()) 24 | } 25 | -------------------------------------------------------------------------------- /man/credit_roles.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/credit-roles.R 3 | \name{credit_roles} 4 | \alias{credit_roles} 5 | \title{CRediT roles} 6 | \usage{ 7 | credit_roles(oxford_spelling = TRUE) 8 | } 9 | \arguments{ 10 | \item{oxford_spelling}{Should the suffix -ize/-ization be used?} 11 | } 12 | \value{ 13 | A named vector. 14 | } 15 | \description{ 16 | Helper function returning the 14 contributor roles of the 17 | \href{https://credit.niso.org}{Contributor Roles Taxonomy} (CRediT). This function is the default argument of the 18 | \code{roles} and \code{role_cols} parameters in plume classes and \code{\link[=plm_template]{plm_template()}}, 19 | respectively. 20 | } 21 | \examples{ 22 | credit_roles() 23 | } 24 | -------------------------------------------------------------------------------- /man/encyclopedists.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/data-encyclopedists.R 3 | \docType{data} 4 | \name{encyclopedists} 5 | \alias{encyclopedists} 6 | \alias{encyclopedists_fr} 7 | \title{Famous encyclopedists} 8 | \format{ 9 | A tibble with 4 rows and 10 variables: 10 | \describe{ 11 | \item{given_name,prénom}{authors' given names} 12 | \item{family_name,nom}{authors' family names} 13 | \item{email,courriel}{authors' email addresses} 14 | \item{phone,téléphone}{authors' phone numbers} 15 | \item{orcid}{authors' ORCID} 16 | \item{affiliation_1,affiliation_2}{authors' affiliations} 17 | \item{supervision}{authors that supervised the project} 18 | \item{writing,rédaction}{authors involved in the writing} 19 | \item{note}{special notes about authors} 20 | } 21 | } 22 | \usage{ 23 | encyclopedists 24 | 25 | encyclopedists_fr 26 | } 27 | \description{ 28 | Data on four famous authors of the Encyclopédie (originally "Encyclopédie, ou 29 | dictionnaire raisonné des sciences, des arts et des métiers") published in 30 | France in the second half of the 18th century. The data set is available in 31 | English (\code{encyclopedists}) and French (\code{encyclopedists_fr}). 32 | } 33 | \examples{ 34 | encyclopedists 35 | 36 | encyclopedists_fr 37 | } 38 | \keyword{datasets} 39 | -------------------------------------------------------------------------------- /man/enumerate.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/enumerate.R 3 | \name{enumerate} 4 | \alias{enumerate} 5 | \title{Enumerate vector elements} 6 | \usage{ 7 | enumerate(x, sep = ", ", last = " and ") 8 | } 9 | \arguments{ 10 | \item{x}{A character vector.} 11 | 12 | \item{sep}{Separator used to separate the terms.} 13 | 14 | \item{last}{Separator used to separate the last two items if \code{x} has at least 15 | 2 items.} 16 | } 17 | \value{ 18 | A character string with the same class as \code{x}. 19 | } 20 | \description{ 21 | Wrapper around \code{\link[glue:glue_collapse]{glue_collapse()}} using 22 | \code{sep = ", "} and \code{last = " and "} as default arguments. 23 | } 24 | \examples{ 25 | aut <- Plume$new(encyclopedists) 26 | aut$get_author_list() |> enumerate() 27 | } 28 | -------------------------------------------------------------------------------- /man/everyone.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/context-selectors.R 3 | \name{everyone} 4 | \alias{everyone} 5 | \alias{everyone_but} 6 | \title{Select all authors or exclude some from a selection} 7 | \usage{ 8 | everyone() 9 | 10 | everyone_but(...) 11 | } 12 | \arguments{ 13 | \item{...}{One or more unquoted expressions separated by commas. Expressions 14 | matching values in the column defined by the \code{by}/\code{.by} parameters of 15 | \verb{set_*()} methods are used to set a given status to authors. Matching of 16 | values is case-insensitive.} 17 | } 18 | \description{ 19 | Selection helpers to use in conjonction with status setter 20 | methods (i.e. methods that assign a status to authors with either \code{TRUE} 21 | or \code{FALSE}): 22 | \itemize{ 23 | \item \code{\link[=everyone]{everyone()}} select all authors. 24 | \item \code{\link[=everyone_but]{everyone_but()}} \ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#deprecated}{\figure{lifecycle-deprecated.svg}{options: alt='[Deprecated]'}}}{\strong{[Deprecated]}} this function was 25 | deprecated as I believe it is not necessary since not more than a couple of 26 | authors should normally be given a particular status. 27 | } 28 | } 29 | \examples{ 30 | aut <- Plume$new(encyclopedists) 31 | 32 | aut$set_corresponding_authors(everyone()) 33 | aut$get_plume() |> dplyr::select(1:3, corresponding) 34 | } 35 | \keyword{internal} 36 | -------------------------------------------------------------------------------- /man/figures/lifecycle-archived.svg: -------------------------------------------------------------------------------- 1 | lifecyclelifecyclearchivedarchived -------------------------------------------------------------------------------- /man/figures/lifecycle-defunct.svg: -------------------------------------------------------------------------------- 1 | lifecyclelifecycledefunctdefunct -------------------------------------------------------------------------------- /man/figures/lifecycle-deprecated.svg: -------------------------------------------------------------------------------- 1 | lifecyclelifecycledeprecateddeprecated -------------------------------------------------------------------------------- /man/figures/lifecycle-experimental.svg: -------------------------------------------------------------------------------- 1 | lifecyclelifecycleexperimentalexperimental -------------------------------------------------------------------------------- /man/figures/lifecycle-maturing.svg: -------------------------------------------------------------------------------- 1 | lifecyclelifecyclematuringmaturing -------------------------------------------------------------------------------- /man/figures/lifecycle-questioning.svg: -------------------------------------------------------------------------------- 1 | lifecyclelifecyclequestioningquestioning -------------------------------------------------------------------------------- /man/figures/lifecycle-stable.svg: -------------------------------------------------------------------------------- 1 | lifecyclelifecyclestablestable -------------------------------------------------------------------------------- /man/figures/lifecycle-superseded.svg: -------------------------------------------------------------------------------- 1 | lifecyclelifecyclesupersededsuperseded -------------------------------------------------------------------------------- /man/icn_orcid.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/icon.R 3 | \name{icn_orcid} 4 | \alias{icn_orcid} 5 | \title{Create an ORCID icon} 6 | \usage{ 7 | icn_orcid(size = 16, bw = FALSE) 8 | } 9 | \arguments{ 10 | \item{size}{Size of the icon (in pixels).} 11 | 12 | \item{bw}{Should the black and white version of the icon be used?} 13 | } 14 | \value{ 15 | A plume icon, i.e. an object with S3 class \code{plm_icon}. 16 | } 17 | \description{ 18 | Create an ORCID icon for use with the \code{\link{Plume}} class. 19 | } 20 | \examples{ 21 | aut <- Plume$new(encyclopedists, orcid_icon = icn_orcid(bw = TRUE)) 22 | } 23 | -------------------------------------------------------------------------------- /man/orcid.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/icon.R 3 | \name{orcid} 4 | \alias{orcid} 5 | \title{ORCID icon} 6 | \usage{ 7 | orcid(size = 16, bw = FALSE) 8 | } 9 | \arguments{ 10 | \item{size}{Size of the icon (in pixels).} 11 | 12 | \item{bw}{Should the black and white version of the icon be used?} 13 | } 14 | \value{ 15 | A plume icon. 16 | } 17 | \description{ 18 | \ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#deprecated}{\figure{lifecycle-deprecated.svg}{options: alt='[Deprecated]'}}}{\strong{[Deprecated]}} 19 | 20 | Please use \code{\link[=icn_orcid]{icn_orcid()}} instead. 21 | } 22 | \keyword{internal} 23 | -------------------------------------------------------------------------------- /man/plm_template.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/plm-template.R 3 | \name{plm_template} 4 | \alias{plm_template} 5 | \title{Create a table template for plume classes} 6 | \usage{ 7 | plm_template(minimal = TRUE, role_cols = credit_roles(), credit_roles = FALSE) 8 | } 9 | \arguments{ 10 | \item{minimal}{If \code{TRUE}, returns an empty tibble with the following columns: 11 | \code{given_name}, \code{family_name}, \code{email}, \code{orcid}, \code{affiliation} and \code{note}. 12 | Otherwise the function returns a template with all columns that can be 13 | supplied to plume classes that are not \code{PlumeQuarto}-specific.} 14 | 15 | \item{role_cols}{A vector of names defining role columns to create. If the 16 | vector contains key-value pairs, columns will be named after the keys.} 17 | 18 | \item{credit_roles}{\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#deprecated}{\figure{lifecycle-deprecated.svg}{options: alt='[Deprecated]'}}}{\strong{[Deprecated]}} 19 | 20 | It is now recommended to use \code{role_cols = credit_roles()} to use the 21 | \href{https://credit.niso.org}{Contributor Roles Taxonomy}.} 22 | } 23 | \value{ 24 | An empty tibble. 25 | } 26 | \description{ 27 | This helper function allows you to generate an empty 28 | \code{\link[tibble:tibble]{tibble}} that you can use as a template to supply 29 | author data. 30 | } 31 | \examples{ 32 | plm_template() 33 | 34 | plm_template(role_cols = paste0("role_", 1:5)) 35 | } 36 | -------------------------------------------------------------------------------- /man/plume-package.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/plume-package.R 3 | \docType{package} 4 | \name{plume-package} 5 | \alias{plume} 6 | \alias{plume-package} 7 | \title{plume: A Simple Author Handler for Scientific Writing} 8 | \description{ 9 | Handles and formats author information in scientific writing in 'R Markdown' and 'Quarto'. 'plume' provides easy-to-use and flexible tools for injecting author metadata in 'YAML' headers as well as generating author and contribution lists (among others) as strings from tabular data. 10 | } 11 | \seealso{ 12 | Useful links: 13 | \itemize{ 14 | \item \url{https://arnaudgallou.github.io/plume/} 15 | \item \url{https://github.com/arnaudgallou/plume} 16 | \item Report bugs at \url{https://github.com/arnaudgallou/plume/issues} 17 | } 18 | 19 | } 20 | \author{ 21 | \strong{Maintainer}: Arnaud Gallou \email{arangacas@gmail.com} (\href{https://orcid.org/0000-0002-1002-4247}{ORCID}) [copyright holder] 22 | 23 | } 24 | \keyword{internal} 25 | -------------------------------------------------------------------------------- /man/sequential.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/sequential.R 3 | \name{sequential} 4 | \alias{sequential} 5 | \title{Control the sequencing behaviour of character vectors} 6 | \usage{ 7 | sequential(x) 8 | } 9 | \arguments{ 10 | \item{x}{A character vector.} 11 | } 12 | \value{ 13 | A character vector with parent S3 class \code{sequential}. 14 | } 15 | \description{ 16 | Modifier function used to generate logical sequences of 17 | characters. 18 | } 19 | \examples{ 20 | aut <- Plume$new( 21 | tibble::tibble( 22 | given_name = "X", 23 | family_name = "Y", 24 | affiliation = 1:60 25 | ), 26 | symbols = list(affiliation = sequential(letters)) 27 | ) 28 | 29 | aut$get_affiliations(sep = ": ", superscript = FALSE) 30 | } 31 | -------------------------------------------------------------------------------- /man/set_default_names.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/set-default-names.R 3 | \name{set_default_names} 4 | \alias{set_default_names} 5 | \title{Set new default names to a plume subclass} 6 | \usage{ 7 | set_default_names(..., .plume_quarto = FALSE) 8 | } 9 | \arguments{ 10 | \item{...}{Key-value pairs where keys are default names and values their 11 | respective replacements.} 12 | 13 | \item{.plume_quarto}{Are you setting new names for \code{\link{PlumeQuarto}}?} 14 | } 15 | \value{ 16 | A named list. 17 | } 18 | \description{ 19 | This helper function allows you to set new default names to a 20 | plume subclass, e.g. to set default names to a language other than English. 21 | } 22 | \details{ 23 | Available names are: 24 | 25 | \code{id}, \code{initials}, \code{literal_name}, \code{corresponding}, \code{role}, \code{contributor_rank}, \code{given_name}, \code{family_name}, \code{orcid}, \code{email}, \code{phone}, \code{fax}, \code{url}, \code{affiliation}, \code{note}. 26 | 27 | Using \code{.plume_quarto = TRUE} adds \code{deceased}, \code{equal_contributor}, \code{number}, 28 | \code{dropping_particle} and \code{acknowledgements}. 29 | } 30 | \examples{ 31 | # Extending `Plume` with default names in French 32 | PlumeFr <- R6::R6Class( 33 | classname = "PlumeFr", 34 | inherit = Plume, 35 | private = list( 36 | plume_names = set_default_names( 37 | initials = "initiales", 38 | literal_name = "nom_complet", 39 | corresponding = "correspondant", 40 | given_name = "prénom", 41 | family_name = "nom", 42 | email = "courriel", 43 | phone = "téléphone" 44 | ) 45 | ) 46 | ) 47 | 48 | PlumeFr$new(encyclopedists_fr) 49 | } 50 | -------------------------------------------------------------------------------- /plume.Rproj: -------------------------------------------------------------------------------- 1 | Version: 1.0 2 | 3 | RestoreWorkspace: Default 4 | SaveWorkspace: Default 5 | AlwaysSaveHistory: Default 6 | 7 | EnableCodeIndexing: Yes 8 | UseSpacesForTab: Yes 9 | NumSpacesForTab: 2 10 | Encoding: UTF-8 11 | 12 | RnwWeave: Sweave 13 | LaTeX: pdfLaTeX 14 | 15 | AutoAppendNewline: Yes 16 | StripTrailingWhitespace: Yes 17 | 18 | BuildType: Package 19 | PackageUseDevtools: Yes 20 | PackageInstallArgs: --no-multiarch --with-keep.source 21 | -------------------------------------------------------------------------------- /tests/testthat.R: -------------------------------------------------------------------------------- 1 | library(testthat) 2 | library(plume) 3 | 4 | test_check("plume") 5 | -------------------------------------------------------------------------------- /tests/testthat/_snaps/credit-roles.md: -------------------------------------------------------------------------------- 1 | # credit_roles() returns CRediT roles 2 | 3 | Code 4 | credit_roles() 5 | Output 6 | conceptualization data_curation 7 | "Conceptualization" "Data curation" 8 | analysis funding 9 | "Formal analysis" "Funding acquisition" 10 | investigation methodology 11 | "Investigation" "Methodology" 12 | administration resources 13 | "Project administration" "Resources" 14 | software supervision 15 | "Software" "Supervision" 16 | validation visualization 17 | "Validation" "Visualization" 18 | writing editing 19 | "Writing - original draft" "Writing - review & editing" 20 | 21 | --- 22 | 23 | Code 24 | credit_roles(oxford_spelling = FALSE) 25 | Output 26 | conceptualisation data_curation 27 | "Conceptualisation" "Data curation" 28 | analysis funding 29 | "Formal analysis" "Funding acquisition" 30 | investigation methodology 31 | "Investigation" "Methodology" 32 | administration resources 33 | "Project administration" "Resources" 34 | software supervision 35 | "Software" "Supervision" 36 | validation visualisation 37 | "Validation" "Visualisation" 38 | writing editing 39 | "Writing - original draft" "Writing - review & editing" 40 | 41 | # credit_roles() gives meaningful error messages 42 | 43 | Code 44 | credit_roles(oxford_spelling = 1) 45 | Condition 46 | Error in `credit_roles()`: 47 | ! `oxford_spelling` must be `TRUE` or `FALSE`. 48 | 49 | -------------------------------------------------------------------------------- /tests/testthat/_snaps/defunct.md: -------------------------------------------------------------------------------- 1 | # defunct functionalities generate informative errors 2 | 3 | Code 4 | aut <- PlumeQuarto$new(basic_df, temp_file()) 5 | aut$set_equal_contributor(1) 6 | Condition 7 | Error: 8 | ! `set_equal_contributor()` was deprecated in plume 0.2.0 and is now defunct. 9 | i Please use `set_cofirst_authors()` instead. 10 | Code 11 | aut <- Plume$new(basic_df) 12 | aut$set_corresponding_authors(zip, by = "given_name") 13 | Condition 14 | Error: 15 | ! The `by` argument of `set_corresponding_author()` was deprecated in plume 0.2.0 and is now defunct. 16 | i Please use the `.by` argument instead. 17 | Code 18 | aut$get_author_list(format = "a") 19 | Condition 20 | Error: 21 | ! The `format` argument of `get_author_list()` was deprecated in plume 0.2.1 and is now defunct. 22 | i Please use the `suffix` argument instead. 23 | Code 24 | Plume$new(data.frame(given_name = "X", family_name = "Y", role = "a")) 25 | Condition 26 | Error: 27 | ! Defining explicit roles in the input data was deprecated in plume 0.2.0 and is now defunct. 28 | i Please use the `roles` argument of `new()` instead. 29 | i See . 30 | Code 31 | Plume$new(basic_df, credit_roles = TRUE) 32 | Condition 33 | Error: 34 | ! The `credit_roles` argument of `new()` was deprecated in plume 0.2.0 and is now defunct. 35 | i Please use `roles = credit_roles()` instead. 36 | Code 37 | plm_template(credit_roles = TRUE) 38 | Condition 39 | Error: 40 | ! The `credit_roles` argument of `plm_template()` was deprecated in plume 0.2.0 and is now defunct. 41 | i Please use `role_cols = credit_roles()` instead. 42 | Code 43 | everyone_but() 44 | Condition 45 | Error: 46 | ! `everyone_but()` was deprecated in plume 0.2.0 and is now defunct. 47 | 48 | -------------------------------------------------------------------------------- /tests/testthat/_snaps/deprecated.md: -------------------------------------------------------------------------------- 1 | # deprecated functionalities generate informative errors 2 | 3 | Code 4 | aut <- Plume$new(basic_df) 5 | aut$set_corresponding_authors(1) 6 | aut$get_contact_details(format = "{name} {details}") 7 | Condition 8 | Warning: 9 | The `format` argument of `get_contact_details()` is deprecated as of plume 0.2.6. 10 | i Please use the `template` argument instead. 11 | Output 12 | Zip Zap zipzap@test.com 13 | Code 14 | orcid() 15 | Condition 16 | Warning: 17 | `orcid()` was deprecated in plume 0.2.6. 18 | i Please use `icn_orcid()` instead. 19 | Output 20 | 21 | 22 | -------------------------------------------------------------------------------- /tests/testthat/_snaps/get-author-list.md: -------------------------------------------------------------------------------- 1 | # get_author_list() makes ORCID icons 2 | 3 | Code 4 | aut$get_author_list("o") 5 | Output 6 | Zip Zap[\hspace{3pt}![](orcid.pdf){height=16px}\hspace{3pt}](https://orcid.org/0000-0000-0000-0001) 7 | Ric Rac[\hspace{3pt}![](orcid.pdf){height=16px}\hspace{3pt}](https://orcid.org/0000-0000-0000-0002) 8 | Pim-Pam Pom 9 | 10 | # get_author_list() gives meaningful error messages 11 | 12 | Code 13 | (expect_error(aut$get_author_list(1))) 14 | Output 15 | 16 | Error in `aut$get_author_list()`: 17 | ! `suffix` must be a character string. 18 | Code 19 | (expect_error(aut$get_author_list("aa"))) 20 | Output 21 | 22 | Error in `aut$get_author_list()`: 23 | ! `suffix` must have unique keys. 24 | Code 25 | (expect_error(aut$get_author_list("az"))) 26 | Output 27 | 28 | Error in `aut$get_author_list()`: 29 | ! `suffix` must only contain any of `a`, `c`, `n`, `o`, `^` or `,`. 30 | Code 31 | (expect_error(aut$get_author_list("ac"))) 32 | Output 33 | 34 | Error in `aut$get_author_list()`: 35 | ! Column `corresponding` doesn't exist. 36 | i Did you forget to assign corresponding authors? 37 | i Use `set_corresponding_authors()` to set corresponding authors. 38 | Code 39 | (expect_error(aut$get_author_list("o"))) 40 | Output 41 | 42 | Error in `aut$get_author_list()`: 43 | ! Invalid ORCID identifier found: `0000`. 44 | i ORCID identifiers must have 16 digits, separated by a hyphen every 4 digits. 45 | i The last character of the identifiers must be a digit or `X`. 46 | 47 | -------------------------------------------------------------------------------- /tests/testthat/_snaps/get-contact-details.md: -------------------------------------------------------------------------------- 1 | # get_conctact_details() gives meaningful error messages 2 | 3 | Code 4 | (expect_error(aut$get_contact_details("foo"))) 5 | Output 6 | 7 | Error in `aut$get_contact_details()`: 8 | ! `template` must be a glue specification. 9 | Code 10 | (expect_error(aut$get_contact_details("{foo}"))) 11 | Output 12 | 13 | Error in `aut$get_contact_details()`: 14 | ! Invalid variable `foo`. 15 | i `template` must use variables `name` and/or `details`. 16 | Code 17 | (expect_error(aut$get_contact_details())) 18 | Output 19 | 20 | Error in `aut$get_contact_details()`: 21 | ! Column `corresponding` doesn't exist. 22 | i Did you forget to assign corresponding authors? 23 | i Use `set_corresponding_authors()` to set corresponding authors. 24 | Code 25 | (expect_error(aut$get_contact_details(email = 1))) 26 | Output 27 | 28 | Error in `aut$get_contact_details()`: 29 | ! `email` must be `TRUE` or `FALSE`. 30 | Code 31 | (expect_error(aut$get_contact_details(sep = NULL))) 32 | Output 33 | 34 | Error in `aut$get_contact_details()`: 35 | ! `sep` must be a character string. 36 | Code 37 | (expect_error(aut$get_contact_details(sep = ""))) 38 | Output 39 | 40 | Error in `aut$get_contact_details()`: 41 | ! `sep` must be a non-empty string. 42 | Code 43 | aut$set_corresponding_authors(1) 44 | (expect_error(aut$get_contact_details(fax = TRUE))) 45 | Output 46 | 47 | Error in `aut$get_contact_details()`: 48 | ! Column `fax` doesn't exist. 49 | 50 | -------------------------------------------------------------------------------- /tests/testthat/_snaps/get-contributions.md: -------------------------------------------------------------------------------- 1 | # get_contributions() gives meaningful error messages 2 | 3 | Code 4 | (expect_error(aut$get_contributions(roles_first = ""))) 5 | Output 6 | 7 | Error in `aut$get_contributions()`: 8 | ! `roles_first` must be `TRUE` or `FALSE`. 9 | Code 10 | (expect_error(aut$get_contributions(by_author = ""))) 11 | Output 12 | 13 | Error in `aut$get_contributions()`: 14 | ! `by_author` must be `TRUE` or `FALSE`. 15 | Code 16 | (expect_error(aut$get_contributions(alphabetical_order = ""))) 17 | Output 18 | 19 | Error in `aut$get_contributions()`: 20 | ! `alphabetical_order` must be `TRUE` or `FALSE`. 21 | Code 22 | (expect_error(aut$get_contributions(dotted_initials = ""))) 23 | Output 24 | 25 | Error in `aut$get_contributions()`: 26 | ! `dotted_initials` must be `TRUE` or `FALSE`. 27 | Code 28 | (expect_error(aut$get_contributions(literal_names = ""))) 29 | Output 30 | 31 | Error in `aut$get_contributions()`: 32 | ! `literal_names` must be `TRUE` or `FALSE`. 33 | Code 34 | (expect_error(aut$get_contributions(sep = 1))) 35 | Output 36 | 37 | Error in `aut$get_contributions()`: 38 | ! `sep` must be a character string. 39 | Code 40 | (expect_error(aut$get_contributions(sep_last = 1))) 41 | Output 42 | 43 | Error in `aut$get_contributions()`: 44 | ! `sep_last` must be a character string. 45 | Code 46 | (expect_error(aut$get_contributions(divider = 1))) 47 | Output 48 | 49 | Error in `aut$get_contributions()`: 50 | ! `divider` must be a character string. 51 | 52 | --- 53 | 54 | Code 55 | aut$get_contributions() 56 | Condition 57 | Error in `aut$get_contributions()`: 58 | ! Column `role` doesn't exist. 59 | 60 | -------------------------------------------------------------------------------- /tests/testthat/_snaps/get-footnotes.md: -------------------------------------------------------------------------------- 1 | # get_affiliations()/get_notes() give meaningful error messages 2 | 3 | Code 4 | (expect_error(aut$get_affiliations(sep = 1))) 5 | Output 6 | 7 | Error in `aut$get_affiliations()`: 8 | ! `sep` must be a character string. 9 | Code 10 | (expect_error(aut$get_affiliations(superscript = ""))) 11 | Output 12 | 13 | Error in `aut$get_affiliations()`: 14 | ! `superscript` must be `TRUE` or `FALSE`. 15 | Code 16 | (expect_error(aut$get_notes(sep = 1))) 17 | Output 18 | 19 | Error in `aut$get_notes()`: 20 | ! `sep` must be a character string. 21 | Code 22 | (expect_error(aut$get_notes(superscript = ""))) 23 | Output 24 | 25 | Error in `aut$get_notes()`: 26 | ! `superscript` must be `TRUE` or `FALSE`. 27 | 28 | --- 29 | 30 | Code 31 | (expect_error(aut$get_affiliations())) 32 | Output 33 | 34 | Error in `aut$get_affiliations()`: 35 | ! Column `affiliation` doesn't exist. 36 | Code 37 | (expect_error(aut$get_notes())) 38 | Output 39 | 40 | Error in `aut$get_notes()`: 41 | ! Column `note` doesn't exist. 42 | 43 | -------------------------------------------------------------------------------- /tests/testthat/_snaps/get-orcids.md: -------------------------------------------------------------------------------- 1 | # get_orcids() returns authors' ORCID 2 | 3 | Code 4 | aut$get_orcids() 5 | Output 6 | Zip Zap[\hspace{3pt}![](orcid.pdf){height=16px}\hspace{3pt}](https://orcid.org/0000-0000-0000-0001) 7 | Ric Rac[\hspace{3pt}![](orcid.pdf){height=16px}\hspace{3pt}](https://orcid.org/0000-0000-0000-0002) 8 | 9 | --- 10 | 11 | Code 12 | aut$get_orcids(sep = " - ") 13 | Output 14 | Zip Zap - [\hspace{3pt}![](orcid.pdf){height=16px}\hspace{3pt}](https://orcid.org/0000-0000-0000-0001) 15 | Ric Rac - [\hspace{3pt}![](orcid.pdf){height=16px}\hspace{3pt}](https://orcid.org/0000-0000-0000-0002) 16 | 17 | --- 18 | 19 | Code 20 | aut$get_orcids(icon = FALSE) 21 | Output 22 | Zip Zap 23 | Ric Rac 24 | 25 | --- 26 | 27 | Code 28 | aut$get_orcids(icon = FALSE, compact = TRUE) 29 | Output 30 | Zip Zap[0000-0000-0000-0001](https://orcid.org/0000-0000-0000-0001) 31 | Ric Rac[0000-0000-0000-0002](https://orcid.org/0000-0000-0000-0002) 32 | 33 | # get_orcids() gives meaningful error messages 34 | 35 | Code 36 | (expect_error(aut$get_orcids(compact = 1))) 37 | Output 38 | 39 | Error in `aut$get_orcids()`: 40 | ! `compact` must be `TRUE` or `FALSE`. 41 | Code 42 | (expect_error(aut$get_orcids(icon = 1))) 43 | Output 44 | 45 | Error in `aut$get_orcids()`: 46 | ! `icon` must be `TRUE` or `FALSE`. 47 | Code 48 | (expect_error(aut$get_orcids(sep = 1))) 49 | Output 50 | 51 | Error in `aut$get_orcids()`: 52 | ! `sep` must be a character string. 53 | 54 | --- 55 | 56 | Code 57 | aut$get_orcids(icon = FALSE) 58 | Condition 59 | Error in `aut$get_orcids()`: 60 | ! Invalid ORCID identifier found: `0000`. 61 | i ORCID identifiers must have 16 digits, separated by a hyphen every 4 digits. 62 | i The last character of the identifiers must be a digit or `X`. 63 | 64 | --- 65 | 66 | Code 67 | aut$get_orcids() 68 | Condition 69 | Error in `aut$get_orcids()`: 70 | ! Column `orcid` doesn't exist. 71 | 72 | -------------------------------------------------------------------------------- /tests/testthat/_snaps/icon.md: -------------------------------------------------------------------------------- 1 | # icn_orcid() makes orcid icon metadata 2 | 3 | Code 4 | read_rendered_md() 5 | Output 6 | --- 7 | title: test 8 | --- 9 | 10 | ``` r 11 | str(attributes(icn_orcid())) 12 | ``` 13 | 14 | List of 5 15 | $ class : chr "plm_icon" 16 | $ size : num 16 17 | $ filename: chr "orcid.pdf" 18 | $ style : chr "" 19 | $ spacing : chr "\\hspace{3pt}" 20 | 21 | ``` r 22 | str(attributes(icn_orcid(size = 24))) 23 | ``` 24 | 25 | List of 5 26 | $ class : chr "plm_icon" 27 | $ size : num 24 28 | $ filename: chr "orcid.pdf" 29 | $ style : chr "" 30 | $ spacing : chr "\\hspace{4pt}" 31 | 32 | ``` r 33 | str(attributes(icn_orcid(bw = TRUE))) 34 | ``` 35 | 36 | List of 5 37 | $ class : chr "plm_icon" 38 | $ size : num 16 39 | $ filename: chr "orcid-bw.pdf" 40 | $ style : chr "" 41 | $ spacing : chr "\\hspace{3pt}" 42 | 43 | --- 44 | 45 | Code 46 | read_rendered_md() 47 | Output 48 | --- 49 | title: test 50 | --- 51 | 52 | ``` r 53 | str(attributes(icn_orcid())) 54 | ``` 55 | 56 | List of 5 57 | $ class : chr "plm_icon" 58 | $ size : num 16 59 | $ filename: chr "orcid.svg" 60 | $ style : chr " style='margin: 0 4px; vertical-align: baseline'" 61 | $ spacing : chr "" 62 | 63 | ``` r 64 | str(attributes(icn_orcid(size = 24))) 65 | ``` 66 | 67 | List of 5 68 | $ class : chr "plm_icon" 69 | $ size : num 24 70 | $ filename: chr "orcid.svg" 71 | $ style : chr " style='margin: 0 6px; vertical-align: baseline'" 72 | $ spacing : chr "" 73 | 74 | ``` r 75 | str(attributes(icn_orcid(bw = TRUE))) 76 | ``` 77 | 78 | List of 5 79 | $ class : chr "plm_icon" 80 | $ size : num 16 81 | $ filename: chr "orcid-bw.svg" 82 | $ style : chr " style='margin: 0 4px; vertical-align: baseline'" 83 | $ spacing : chr "" 84 | 85 | # icn_orcid() gives meaningful error messages 86 | 87 | Code 88 | (expect_error(icn_orcid(size = NULL))) 89 | Output 90 | 91 | Error in `icn_orcid()`: 92 | ! `size` must be a numeric vector. 93 | Code 94 | (expect_error(icn_orcid(bw = 1))) 95 | Output 96 | 97 | Error in `icn_orcid()`: 98 | ! `bw` must be `TRUE` or `FALSE`. 99 | 100 | -------------------------------------------------------------------------------- /tests/testthat/_snaps/initialize.md: -------------------------------------------------------------------------------- 1 | # initialize() gives meaningful error messages 2 | 3 | Code 4 | (expect_error(Plume$new(list(x = 1)))) 5 | Output 6 | 7 | Error in `initialize()`: 8 | ! `data` must be a data frame or tibble. 9 | Code 10 | (expect_error(Plume$new(data.frame(family_name = "x")))) 11 | Output 12 | 13 | Error in `initialize()`: 14 | ! Column `given_name` doesn't exist. 15 | Code 16 | (expect_error(Plume$new(data.frame(given_name = "x", family_name = "")))) 17 | Output 18 | 19 | Error in `initialize()`: 20 | ! Missing author name found in position 1. 21 | i All authors must have a given and family name. 22 | Code 23 | (expect_error(Plume$new(data.frame(given_name = "x")))) 24 | Output 25 | 26 | Error in `initialize()`: 27 | ! Column `family_name` doesn't exist. 28 | Code 29 | (expect_error(Plume$new(basic_df, names = list(given_name = "prénom")))) 30 | Output 31 | 32 | Error in `initialize()`: 33 | ! `names` must be a character vector. 34 | Code 35 | (expect_error(Plume$new(basic_df, names = "prénom"))) 36 | Output 37 | 38 | Error in `initialize()`: 39 | ! All `names` inputs must be named. 40 | Code 41 | (expect_error(Plume$new(basic_df, names = c(given_name = "prénom", family_name = "prénom"))) 42 | ) 43 | Output 44 | 45 | Error in `initialize()`: 46 | ! `names` must have unique input values. 47 | Code 48 | (expect_error(Plume$new(basic_df, names = c(given_name = "prénom", given_name = "nom"))) 49 | ) 50 | Output 51 | 52 | Error in `initialize()`: 53 | ! `names` must have unique input names. 54 | Code 55 | (expect_error(Plume$new(basic_df, symbols = c(note = letters)))) 56 | Output 57 | 58 | Error in `initialize()`: 59 | ! `symbols` must be a list. 60 | Code 61 | (expect_error(Plume$new(basic_df, symbols = list(note = NULL, note = NULL)))) 62 | Output 63 | 64 | Error in `initialize()`: 65 | ! `symbols` must have unique input names. 66 | Code 67 | (expect_error(Plume$new(basic_df, orcid_icon = NULL))) 68 | Output 69 | 70 | Error in `initialize()`: 71 | ! Invalid `orcid_icon` input. 72 | i Use `icn_orcid()` to set the ORCID icon. 73 | Code 74 | (expect_error(Plume$new(basic_df, initials_given_name = 1))) 75 | Output 76 | 77 | Error in `initialize()`: 78 | ! `initials_given_name` must be `TRUE` or `FALSE`. 79 | Code 80 | (expect_error(Plume$new(basic_df, family_name_first = 1))) 81 | Output 82 | 83 | Error in `initialize()`: 84 | ! `family_name_first` must be `TRUE` or `FALSE`. 85 | Code 86 | (expect_error(Plume$new(basic_df, credit_roles = 1))) 87 | Output 88 | 89 | Error in `initialize()`: 90 | ! `credit_roles` must be `TRUE` or `FALSE`. 91 | Code 92 | (expect_error(Plume$new(basic_df, interword_spacing = 1))) 93 | Output 94 | 95 | Error in `initialize()`: 96 | ! `interword_spacing` must be `TRUE` or `FALSE`. 97 | Code 98 | (expect_error(Plume$new(basic_df, roles = 1))) 99 | Output 100 | 101 | Error in `initialize()`: 102 | ! `roles` must be a character vector. 103 | Code 104 | (expect_error(Plume$new(basic_df, roles = "foo"))) 105 | Output 106 | 107 | Error in `initialize()`: 108 | ! All `roles` inputs must be named. 109 | Code 110 | (expect_error(Plume$new(basic_df, roles = c(role = "foo", role = "bar")))) 111 | Output 112 | 113 | Error in `initialize()`: 114 | ! `roles` must have unique input names. 115 | Code 116 | (expect_error(Plume$new(basic_df, roles = c(role = "foo", role_2 = "foo")))) 117 | Output 118 | 119 | Error in `initialize()`: 120 | ! `roles` must have unique input values. 121 | Code 122 | (expect_error(PlumeQuarto$new(basic_df, file = 1))) 123 | Output 124 | 125 | Error in `initialize()`: 126 | ! `file` must be a character string. 127 | Code 128 | (expect_error(PlumeQuarto$new(basic_df, file = ""))) 129 | Output 130 | 131 | Error in `initialize()`: 132 | ! `file` must be a non-empty string. 133 | Code 134 | (expect_error(PlumeQuarto$new(basic_df, file = "test.rmd"))) 135 | Output 136 | 137 | Error in `initialize()`: 138 | ! `file` must be a `.qmd`, `.yml` or `.yaml` file. 139 | Code 140 | (expect_error(PlumeQuarto$new(basic_df, file = "~/test.qmd"))) 141 | Output 142 | 143 | Error in `initialize()`: 144 | ! `~/test.qmd` doesn't exist. 145 | Code 146 | (expect_error(PlumeQuarto$new(basic_df, temp_file(), by = 1))) 147 | Output 148 | 149 | Error in `initialize()`: 150 | ! `by` must be a character string. 151 | Code 152 | (expect_error(PlumeQuarto$new(basic_df, temp_file(), by = ""))) 153 | Output 154 | 155 | Error in `initialize()`: 156 | ! `by` must be a non-empty string. 157 | Code 158 | (expect_error(PlumeQuarto$new(basic_df, temp_file(), by = "foo"))) 159 | Output 160 | 161 | Error in `initialize()`: 162 | ! Column `foo` doesn't exist. 163 | 164 | -------------------------------------------------------------------------------- /tests/testthat/_snaps/plm-template.md: -------------------------------------------------------------------------------- 1 | # plm_template() gives meaningful error messages 2 | 3 | Code 4 | (expect_error(plm_template(minimal = 1))) 5 | Output 6 | 7 | Error in `plm_template()`: 8 | ! `minimal` must be `TRUE` or `FALSE`. 9 | Code 10 | (expect_error(plm_template(role_cols = 1))) 11 | Output 12 | 13 | Error in `plm_template()`: 14 | ! `role_cols` must be a character vector. 15 | Code 16 | (expect_error(plm_template(role_cols = c("foo", "foo")))) 17 | Output 18 | 19 | Error in `plm_template()`: 20 | ! `role_cols` must have unique input values. 21 | 22 | -------------------------------------------------------------------------------- /tests/testthat/_snaps/sequential.md: -------------------------------------------------------------------------------- 1 | # sequential() gives meaningful error messages 2 | 3 | Code 4 | sequential(1) 5 | Condition 6 | Error in `sequential()`: 7 | ! `x` must be a character vector. 8 | 9 | -------------------------------------------------------------------------------- /tests/testthat/_snaps/set-default-names.md: -------------------------------------------------------------------------------- 1 | # set_default_names() sets new plume names 2 | 3 | Code 4 | str(nms) 5 | Output 6 | List of 4 7 | $ internals :List of 6 8 | ..$ id : chr "id" 9 | ..$ initials : chr "initiales" 10 | ..$ literal_name : chr "nom_complet" 11 | ..$ corresponding : chr "correspondant" 12 | ..$ role : chr "role" 13 | ..$ contributor_rank: chr "rang_contributeur" 14 | $ primaries :List of 2 15 | ..$ given_name : chr "prénom" 16 | ..$ family_name: chr "nom" 17 | $ secondaries:List of 5 18 | ..$ orcid: chr "orcid" 19 | ..$ email: chr "courriel" 20 | ..$ phone: chr "téléphone" 21 | ..$ fax : chr "fax" 22 | ..$ url : chr "url" 23 | $ nestables :List of 2 24 | ..$ affiliation: chr "affiliation" 25 | ..$ note : chr "note" 26 | 27 | # set_default_names() gives meaningful error messages 28 | 29 | Code 30 | (expect_error(set_default_names())) 31 | Output 32 | 33 | Error in `set_default_names()`: 34 | ! `...` must not be empty. 35 | Code 36 | (expect_error(set_default_names(1))) 37 | Output 38 | 39 | Error in `set_default_names()`: 40 | ! `...` must be a character vector. 41 | Code 42 | (expect_error(set_default_names("a"))) 43 | Output 44 | 45 | Error in `set_default_names()`: 46 | ! All `...` inputs must be named. 47 | Code 48 | (expect_error(set_default_names(x = "a", y = "a"))) 49 | Output 50 | 51 | Error in `set_default_names()`: 52 | ! `...` must have unique input values. 53 | Code 54 | (expect_error(set_default_names(x = "a", x = "b"))) 55 | Output 56 | 57 | Error in `set_default_names()`: 58 | ! `...` must have unique input names. 59 | Code 60 | (expect_error(set_default_names(given_name = "nom", .plume_quarto = 1))) 61 | Output 62 | 63 | Error in `set_default_names()`: 64 | ! `.plume_quarto` must be `TRUE` or `FALSE`. 65 | 66 | -------------------------------------------------------------------------------- /tests/testthat/_snaps/set-main-contributors.md: -------------------------------------------------------------------------------- 1 | # set_ranks() gives meaningful error messages 2 | 3 | Code 4 | (expect_error(aut$set_main_contributors())) 5 | Output 6 | 7 | Error in `aut$set_main_contributors()`: 8 | ! `...` must not be empty. 9 | Code 10 | (expect_error(aut$set_main_contributors(1, .roles = 1))) 11 | Output 12 | 13 | Error in `aut$set_main_contributors()`: 14 | ! `.roles` must be a character vector. 15 | Code 16 | (expect_error(aut$set_main_contributors(1, .roles = c("x", "x")))) 17 | Output 18 | 19 | Error in `aut$set_main_contributors()`: 20 | ! `.roles` must have unique input values. 21 | Code 22 | (expect_error(aut$set_main_contributors(1, .by = 1))) 23 | Output 24 | 25 | Error in `aut$set_main_contributors()`: 26 | ! `.by` must be a character string. 27 | 28 | -------------------------------------------------------------------------------- /tests/testthat/_snaps/set-status.md: -------------------------------------------------------------------------------- 1 | # set_*() methods give meaningful error messages 2 | 3 | Code 4 | (expect_error(aut$set_corresponding_authors())) 5 | Output 6 | 7 | Error in `aut$set_corresponding_authors()`: 8 | ! `...` must not be empty. 9 | Code 10 | (expect_error(aut$set_corresponding_authors(a, .by = "foo"))) 11 | Output 12 | 13 | Error in `aut$set_corresponding_authors()`: 14 | ! Column `foo` doesn't exist. 15 | Code 16 | (expect_error(aut$set_corresponding_authors(a, .by = ""))) 17 | Output 18 | 19 | Error in `aut$set_corresponding_authors()`: 20 | ! `.by` must be a non-empty string. 21 | Code 22 | (expect_error(aut$set_corresponding_authors(a, .by = 1))) 23 | Output 24 | 25 | Error in `aut$set_corresponding_authors()`: 26 | ! `.by` must be a character string. 27 | Code 28 | (expect_error(aut$set_corresponding_authors(x <- y))) 29 | Output 30 | 31 | Error in `aut$set_corresponding_authors()`: 32 | ! Can't match elements with `x <- y`. 33 | Code 34 | (expect_error(aut$set_cofirst_authors(a, .by = "foo"))) 35 | Output 36 | 37 | Error in `aut$set_cofirst_authors()`: 38 | ! Column `foo` doesn't exist. 39 | Code 40 | (expect_error(aut$set_cofirst_authors(a, .by = ""))) 41 | Output 42 | 43 | Error in `aut$set_cofirst_authors()`: 44 | ! `.by` must be a non-empty string. 45 | Code 46 | (expect_error(aut$set_cofirst_authors(a, .by = 1))) 47 | Output 48 | 49 | Error in `aut$set_cofirst_authors()`: 50 | ! `.by` must be a character string. 51 | Code 52 | (expect_error(aut$set_deceased(a, .by = "foo"))) 53 | Output 54 | 55 | Error in `aut$set_deceased()`: 56 | ! Column `foo` doesn't exist. 57 | Code 58 | (expect_error(aut$set_deceased(a, .by = ""))) 59 | Output 60 | 61 | Error in `aut$set_deceased()`: 62 | ! `.by` must be a non-empty string. 63 | Code 64 | (expect_error(aut$set_deceased(a, .by = 1))) 65 | Output 66 | 67 | Error in `aut$set_deceased()`: 68 | ! `.by` must be a character string. 69 | 70 | # everyone*() selectors error if used in a wrong context 71 | 72 | Code 73 | everyone() 74 | Condition 75 | Error in `everyone()`: 76 | ! `everyone()` must be used within a *status setter* method. 77 | 78 | -------------------------------------------------------------------------------- /tests/testthat/_snaps/to-yaml.md: -------------------------------------------------------------------------------- 1 | # to_yaml() injects authors and affiliations into a `.qmd` 2 | 3 | Code 4 | read_test_file(tmp_file) 5 | Output 6 | --- 7 | title: test 8 | author: 9 | - id: aut1 10 | name: 11 | given: Zip 12 | family: Zap 13 | email: zipzap@test.com 14 | phone: '+1234' 15 | orcid: 0000-0000-0000-0001 16 | note: a. c 17 | attributes: 18 | corresponding: true 19 | roles: 20 | - Formal analysis 21 | - Writing - original draft 22 | affiliations: 23 | - ref: aff1 24 | - ref: aff2 25 | - id: aut2 26 | name: 27 | given: Ric 28 | family: Rac 29 | email: ricrac@test.com 30 | orcid: 0000-0000-0000-0002 31 | attributes: 32 | corresponding: false 33 | roles: 34 | - Formal analysis 35 | affiliations: 36 | - ref: aff3 37 | - id: aut3 38 | name: 39 | given: Pim-Pam 40 | family: Pom 41 | email: pimpampom@test.com 42 | note: b 43 | attributes: 44 | corresponding: false 45 | roles: 46 | - Formal analysis 47 | affiliations: 48 | - ref: aff1 49 | - ref: aff4 50 | affiliations: 51 | - id: aff1 52 | name: a 53 | - id: aff2 54 | name: b 55 | - id: aff3 56 | name: c 57 | - id: aff4 58 | name: d 59 | --- 60 | 61 | ```{r} 62 | #| echo: false 63 | x <- 1 64 | ``` 65 | 66 | --- 67 | 68 | Code 69 | read_test_file(tmp_file) 70 | Output 71 | --- 72 | title: test 73 | author: 74 | - id: aut1 75 | name: 76 | given: Pim-Pam 77 | family: Pom 78 | email: pimpampom@test.com 79 | note: b 80 | attributes: 81 | corresponding: true 82 | roles: 83 | - Formal analysis 84 | affiliations: 85 | - ref: aff1 86 | - ref: aff2 87 | - id: aut2 88 | name: 89 | given: Ric 90 | family: Rac 91 | email: ricrac@test.com 92 | orcid: 0000-0000-0000-0002 93 | attributes: 94 | corresponding: false 95 | roles: 96 | - Formal analysis 97 | affiliations: 98 | - ref: aff3 99 | - id: aut3 100 | name: 101 | given: Zip 102 | family: Zap 103 | email: zipzap@test.com 104 | phone: '+1234' 105 | orcid: 0000-0000-0000-0001 106 | note: a. c 107 | attributes: 108 | corresponding: false 109 | roles: 110 | - Formal analysis 111 | - Writing - original draft 112 | affiliations: 113 | - ref: aff2 114 | - ref: aff4 115 | affiliations: 116 | - id: aff1 117 | name: d 118 | - id: aff2 119 | name: a 120 | - id: aff3 121 | name: c 122 | - id: aff4 123 | name: b 124 | --- 125 | 126 | ```{r} 127 | #| echo: false 128 | x <- 1 129 | ``` 130 | 131 | --- 132 | 133 | Code 134 | read_test_file(tmp_file) 135 | Output 136 | --- 137 | title: test 138 | author: 139 | - name: 140 | given: Zip 141 | family: Zap 142 | affiliations: 143 | - ref: aff1 144 | - ref: aff2 145 | - ref: aff3 146 | affiliations: 147 | - id: aff1 148 | name: a 149 | department: b 150 | city: c 151 | postal-code: d 152 | - id: aff2 153 | name: f 154 | department: g 155 | city: e 156 | - id: aff3 157 | name: h 158 | --- 159 | 160 | ```{r} 161 | #| echo: false 162 | x <- 1 163 | ``` 164 | 165 | # to_yaml() doesn't add the `affiliations` schema if there're no affiliations 166 | 167 | Code 168 | read_test_file(tmp_file) 169 | Output 170 | --- 171 | author: 172 | - name: 173 | given: Zip 174 | family: Zap 175 | --- 176 | 177 | # to_yaml() pushes data to empty YAML headers 178 | 179 | Code 180 | read_test_file(tmp_file) 181 | Output 182 | --- 183 | author: 184 | - name: 185 | given: Zip 186 | family: Zap 187 | metadata: 188 | meta-foo: bar 189 | --- 190 | 191 | # to_yaml() preserves line breaks preceding `---` (#37) 192 | 193 | Code 194 | read_test_file(tmp_file) 195 | Output 196 | --- 197 | author: 198 | - name: 199 | given: Zip 200 | family: Zap 201 | --- 202 | Lorem ipsum 203 | --- 204 | 205 | # to_yaml() writes in a separate header to preserve strippable data (#56) 206 | 207 | Code 208 | read_test_file(tmp_file) 209 | Output 210 | --- 211 | author: 212 | - name: 213 | given: Zip 214 | family: Zap 215 | --- 216 | --- 217 | title: test # this is a title 218 | foo: > 219 | Lorem ipsum 220 | Vivamus quis 221 | --- 222 | 223 | # to_yaml() can push data into YAML files 224 | 225 | Code 226 | read_test_file(tmp_file) 227 | Output 228 | title: foo 229 | author: 230 | - name: 231 | given: Zip 232 | family: Zap 233 | 234 | # to_yaml() properly handles authors with no roles (#81) 235 | 236 | Code 237 | read_test_file(tmp_file) 238 | Output 239 | title: foo 240 | author: 241 | - id: aut1 242 | name: 243 | given: A 244 | family: A 245 | roles: 246 | - Formal analysis 247 | - Writing - original draft 248 | - id: aut2 249 | name: 250 | given: B 251 | family: B 252 | roles: {} 253 | 254 | # to_yaml() errors if no YAML headers is found 255 | 256 | Code 257 | aut$to_yaml() 258 | Condition 259 | Error in `aut$to_yaml()`: 260 | ! No YAML headers found. 261 | i YAML headers must be at the beginning of the document. 262 | i YAML headers must start and end with three hyphens. 263 | 264 | # to_yaml() errors if an invalid ORCID identifier is found 265 | 266 | Code 267 | aut$to_yaml() 268 | Condition 269 | Error in `aut$to_yaml()`: 270 | ! Invalid ORCID identifier found: `0000`. 271 | i ORCID identifiers must have 16 digits, separated by a hyphen every 4 digits. 272 | i The last character of the identifiers must be a digit or `X`. 273 | 274 | -------------------------------------------------------------------------------- /tests/testthat/helper-plume.R: -------------------------------------------------------------------------------- 1 | basic_df <- tibble( 2 | given_name = c("Zip", "Ric", "Pim-Pam"), 3 | family_name = c("Zap", "Rac", "Pom"), 4 | literal_name = paste(given_name, family_name), 5 | initials = c("ZZ", "RR", "P-PP"), 6 | affiliation = c("a", "c", "d"), 7 | affiliation2 = c("b", NA, "a"), 8 | analysis = rep(1, 3), 9 | writing = c(1, NA, NA), 10 | note = c("a", NA, "b"), 11 | note2 = c("c", NA, NA), 12 | email = paste0(c("zipzap", "ricrac", "pimpampom"), "@test.com"), 13 | phone = c("+1234", NA, NA), 14 | orcid = c( 15 | "0000-0000-0000-0001", 16 | "0000-0000-0000-0002", 17 | NA 18 | ), 19 | ) 20 | 21 | temp_file <- function() { 22 | withr::local_tempfile( 23 | lines = "---\n---", 24 | fileext = ".qmd", 25 | .local_envir = rlang::caller_env() 26 | ) 27 | } 28 | 29 | dedent <- function(string) { 30 | indent <- sub("(?s)\\S*\n(\\s*).+", "\\1", string, perl = TRUE) 31 | out <- gsub(paste0("(?m)^", indent), "", string, perl = TRUE) 32 | trimws(out) 33 | } 34 | 35 | read_test_file <- function(file) { 36 | cat(readr::read_file(file)) 37 | } 38 | 39 | scrub_icon_path <- function(x) { 40 | path_regex <- "(?<=\\()(?:[A-Z]:)?\\/.+\\/(?=[\\w-]+\\.(?:pdf|svg)\\))" 41 | sub(path_regex, "", x, perl = TRUE) 42 | } 43 | 44 | pull_nested_var <- function(cls, nested_var, pull) { 45 | out <- unnest(cls$get_plume(), cols = all_of(nested_var)) 46 | out[[pull]] 47 | } 48 | -------------------------------------------------------------------------------- /tests/testthat/test-as-lines.R: -------------------------------------------------------------------------------- 1 | test_that("as_lines() prints vector elements on distinct lines", { 2 | expect_output(as_lines("a", "b"), "a\\R{2}b", perl = TRUE) 3 | }) 4 | -------------------------------------------------------------------------------- /tests/testthat/test-credit-roles.R: -------------------------------------------------------------------------------- 1 | test_that("credit_roles() returns CRediT roles", { 2 | expect_snapshot(credit_roles()) 3 | expect_snapshot(credit_roles(oxford_spelling = FALSE)) 4 | }) 5 | 6 | # Errors ---- 7 | 8 | test_that("credit_roles() gives meaningful error messages", { 9 | expect_snapshot({ 10 | credit_roles(oxford_spelling = 1) 11 | }, error = TRUE) 12 | }) 13 | -------------------------------------------------------------------------------- /tests/testthat/test-defunct.R: -------------------------------------------------------------------------------- 1 | test_that("defunct functionalities generate informative errors", { 2 | expect_snapshot(error = TRUE, { 3 | aut <- PlumeQuarto$new(basic_df, temp_file()) 4 | aut$set_equal_contributor(1) 5 | 6 | aut <- Plume$new(basic_df) 7 | aut$set_corresponding_authors(zip, by = "given_name") 8 | aut$get_author_list(format = "a") 9 | 10 | Plume$new(data.frame( 11 | given_name = "X", 12 | family_name = "Y", 13 | role = "a" 14 | )) 15 | 16 | Plume$new(basic_df, credit_roles = TRUE) 17 | 18 | plm_template(credit_roles = TRUE) 19 | 20 | everyone_but() 21 | }) 22 | }) 23 | -------------------------------------------------------------------------------- /tests/testthat/test-deprecated.R: -------------------------------------------------------------------------------- 1 | test_that("deprecated functionalities generate informative errors", { 2 | expect_snapshot({ 3 | aut <- Plume$new(basic_df) 4 | 5 | aut$set_corresponding_authors(1) 6 | aut$get_contact_details(format = "{name} {details}") 7 | 8 | orcid() 9 | }) 10 | }) 11 | -------------------------------------------------------------------------------- /tests/testthat/test-get-author-list.R: -------------------------------------------------------------------------------- 1 | test_that("get_author_list() returns author list", { 2 | aut <- Plume$new(basic_df) 3 | aut$set_corresponding_authors(1, 3) 4 | 5 | expect_s3_class(aut$get_author_list(), "plm") 6 | expect_equal( 7 | aut$get_author_list(NULL), 8 | c("Zip Zap", "Ric Rac", "Pim-Pam Pom") 9 | ) 10 | expect_equal( 11 | aut$get_author_list(""), 12 | c("Zip Zap", "Ric Rac", "Pim-Pam Pom") 13 | ) 14 | 15 | affix_to_authors <- function(...) { 16 | paste0(basic_df$literal_name, ...) 17 | } 18 | 19 | .a <- c("1,2", "3", "1,4") 20 | .c <- c("\\*", "", "\\*") 21 | .n <- c("†,‡", "", "§") 22 | 23 | expect_equal( 24 | aut$get_author_list("a"), 25 | affix_to_authors(.a) 26 | ) 27 | expect_equal( 28 | aut$get_author_list("c"), 29 | affix_to_authors(.c) 30 | ) 31 | expect_equal( 32 | aut$get_author_list("n"), 33 | affix_to_authors(.n) 34 | ) 35 | expect_equal( 36 | aut$get_author_list("anc"), 37 | affix_to_authors(.a, .n, .c) 38 | ) 39 | expect_equal( 40 | aut$get_author_list("cna"), 41 | affix_to_authors(.c, .n, .a) 42 | ) 43 | expect_equal( 44 | aut$get_author_list("^ac^n"), 45 | affix_to_authors("^", .a, .c, "^", .n) 46 | ) 47 | 48 | .seps <- c(",", "", ",") 49 | 50 | expect_equal( 51 | aut$get_author_list("a,c"), 52 | affix_to_authors(.a, .seps, .c) 53 | ) 54 | expect_equal( 55 | aut$get_author_list("^a,^c"), 56 | affix_to_authors("^", .a, .seps, "^", .c) 57 | ) 58 | expect_equal( 59 | aut$get_author_list("c^,a^"), 60 | affix_to_authors(.c, "^", .seps, .a, "^") 61 | ) 62 | 63 | expect_equal( 64 | aut$get_author_list("a,,c"), 65 | affix_to_authors(.a, .seps, .c) 66 | ) 67 | expect_equal( 68 | aut$get_author_list("^^ac^^"), 69 | affix_to_authors("^", .a, .c, "^") 70 | ) 71 | 72 | .hats <- c("^", "", "^") 73 | 74 | expect_equal( 75 | aut$get_author_list("^a^c^n^"), 76 | affix_to_authors("^", .a, .hats, .c, .hats, .n, "^") 77 | ) 78 | 79 | # overrides default symbols 80 | 81 | aut <- Plume$new(basic_df, symbols = list( 82 | affiliation = letters, 83 | corresponding = "#", 84 | note = NULL 85 | )) 86 | aut$set_corresponding_authors(1, 3) 87 | 88 | .a <- c("a,b", "c", "a,d") 89 | .c <- c("#", "", "#") 90 | .n <- c("1,2", "", "3") 91 | 92 | expect_equal( 93 | aut$get_author_list("anc"), 94 | affix_to_authors(.a, .n, .c) 95 | ) 96 | 97 | # using custom names 98 | 99 | aut <- Plume$new( 100 | data.frame(given_name = "X", family_name = "Y", aff = "a", aff2 = "b"), 101 | names = c(affiliation = "aff") 102 | ) 103 | 104 | expect_equal( 105 | aut$get_author_list("a"), 106 | "X Y1,2" 107 | ) 108 | }) 109 | 110 | test_that("get_author_list() makes ORCID icons", { 111 | aut <- Plume$new(basic_df) 112 | expect_snapshot(aut$get_author_list("o"), transform = scrub_icon_path) 113 | }) 114 | 115 | # Errors ---- 116 | 117 | test_that("get_author_list() gives meaningful error messages", { 118 | basic_df["orcid"] <- c(NA, "0000", NA) 119 | aut <- Plume$new(basic_df) 120 | 121 | expect_snapshot({ 122 | (expect_error( 123 | aut$get_author_list(1) 124 | )) 125 | (expect_error( 126 | aut$get_author_list("aa") 127 | )) 128 | (expect_error( 129 | aut$get_author_list("az") 130 | )) 131 | (expect_error( 132 | aut$get_author_list("ac") 133 | )) 134 | (expect_error( 135 | aut$get_author_list("o") 136 | )) 137 | }) 138 | }) 139 | -------------------------------------------------------------------------------- /tests/testthat/test-get-contact-details.R: -------------------------------------------------------------------------------- 1 | test_that("get_contact_details() returns contact details of corresponding authors", { 2 | aut <- Plume$new(basic_df) 3 | aut$set_corresponding_authors(1, 2) 4 | 5 | expect_s3_class(aut$get_contact_details(), "plm") 6 | 7 | literal_names <- c("Zip Zap", "Ric Rac") 8 | emails <- c("zipzap@test.com", "ricrac@test.com") 9 | 10 | expect_equal( 11 | aut$get_contact_details(), 12 | paste0(emails, " (", literal_names, ")") 13 | ) 14 | expect_equal( 15 | aut$get_contact_details("{name}: {details}"), 16 | paste(literal_names, emails, sep = ": ") 17 | ) 18 | expect_equal( 19 | aut$get_contact_details("{details}"), 20 | emails 21 | ) 22 | expect_equal( 23 | aut$get_contact_details(phone = TRUE), 24 | paste0(emails, c(", +1234", ""), " (", literal_names, ")") 25 | ) 26 | expect_equal( 27 | aut$get_contact_details(phone = TRUE, sep = "; "), 28 | paste0(emails, c("; +1234", ""), " (", literal_names, ")") 29 | ) 30 | expect_equal( 31 | aut$get_contact_details(email = FALSE, phone = TRUE), 32 | "+1234 (Zip Zap)" 33 | ) 34 | }) 35 | 36 | test_that("get_contact_details() returns `NULL` if all booleans are `FALSE`", { 37 | aut <- Plume$new(basic_df) 38 | aut$set_corresponding_authors(1) 39 | expect_null(aut$get_contact_details(email = FALSE)) 40 | }) 41 | 42 | # Errors ---- 43 | 44 | test_that("get_conctact_details() gives meaningful error messages", { 45 | aut <- Plume$new(basic_df) 46 | 47 | expect_snapshot({ 48 | (expect_error( 49 | aut$get_contact_details("foo") 50 | )) 51 | (expect_error( 52 | aut$get_contact_details("{foo}") 53 | )) 54 | (expect_error( 55 | aut$get_contact_details() 56 | )) 57 | (expect_error( 58 | aut$get_contact_details(email = 1) 59 | )) 60 | (expect_error( 61 | aut$get_contact_details(sep = NULL) 62 | )) 63 | (expect_error( 64 | aut$get_contact_details(sep = "") 65 | )) 66 | 67 | aut$set_corresponding_authors(1) 68 | 69 | (expect_error( 70 | aut$get_contact_details(fax = TRUE) 71 | )) 72 | }) 73 | }) 74 | -------------------------------------------------------------------------------- /tests/testthat/test-get-contributions.R: -------------------------------------------------------------------------------- 1 | test_that("get_contributions() return authors' contributions", { 2 | aut <- Plume$new(basic_df, roles = c(analysis = "a", writing = "b")) 3 | 4 | expect_s3_class(aut$get_contributions(), "plm") 5 | 6 | # contributors-roles combinations 7 | 8 | expect_equal( 9 | aut$get_contributions(roles_first = FALSE, by_author = TRUE), 10 | c("Z.Z.: a and b", "R.R.: a", "P.-P.P.: a") 11 | ) 12 | expect_equal( 13 | aut$get_contributions(roles_first = TRUE, by_author = TRUE), 14 | c("a and b: Z.Z.", "a: R.R.", "a: P.-P.P.") 15 | ) 16 | expect_equal( 17 | aut$get_contributions(roles_first = FALSE, by_author = FALSE), 18 | c("Z.Z., R.R. and P.-P.P.: a", "Z.Z.: b") 19 | ) 20 | expect_equal( 21 | aut$get_contributions(roles_first = TRUE, by_author = FALSE), 22 | c("a: Z.Z., R.R. and P.-P.P.", "b: Z.Z.") 23 | ) 24 | 25 | # other arguments 26 | 27 | expect_equal( 28 | aut$get_contributions(by_author = FALSE, divider = " "), 29 | c("a Z.Z., R.R. and P.-P.P.", "b Z.Z.") 30 | ) 31 | expect_equal( 32 | aut$get_contributions(by_author = FALSE, alphabetical_order = TRUE), 33 | c("a: P.-P.P., R.R. and Z.Z.", "b: Z.Z.") 34 | ) 35 | expect_equal( 36 | aut$get_contributions(by_author = FALSE, sep = "; "), 37 | c("a: Z.Z.; R.R. and P.-P.P.", "b: Z.Z.") 38 | ) 39 | expect_equal( 40 | aut$get_contributions(by_author = FALSE, sep_last = " & "), 41 | c("a: Z.Z., R.R. & P.-P.P.", "b: Z.Z.") 42 | ) 43 | expect_equal( 44 | aut$get_contributions(by_author = FALSE, dotted_initials = FALSE), 45 | c("a: ZZ, RR and P-PP", "b: ZZ") 46 | ) 47 | expect_equal( 48 | aut$get_contributions(by_author = FALSE, literal_names = TRUE), 49 | c("a: Zip Zap, Ric Rac and Pim-Pam Pom", "b: Zip Zap") 50 | ) 51 | }) 52 | 53 | test_that("get_contributions() returns `NULL` if no contributions", { 54 | aut <- Plume$new(data.frame( 55 | given_name = "Zip", 56 | family_name = "Zap", 57 | analysis = "" 58 | )) 59 | expect_null(aut$get_contributions()) 60 | }) 61 | 62 | test_that("get_contributions() rearranges authors only (#18)", { 63 | aut <- Plume$new( 64 | data.frame( 65 | given_name = c("Zip", "Pim"), 66 | family_name = c("Zap", "Pam"), 67 | role_1 = c(1, NA), 68 | role_2 = c(1, 1) 69 | ), 70 | roles = c(role_1 = "z", role_2 = "a") 71 | ) 72 | 73 | expect_equal( 74 | aut$get_contributions(alphabetical_order = TRUE), 75 | c("z: Z.Z.", "a: P.P. and Z.Z.") 76 | ) 77 | expect_equal( 78 | aut$get_contributions(by_author = TRUE, alphabetical_order = TRUE), 79 | c("z and a: Z.Z.", "a: P.P.") 80 | ) 81 | }) 82 | 83 | test_that("get_contributions() handles namesakes (#15)", { 84 | aut <- Plume$new( 85 | data.frame( 86 | given_name = c("Zip", "Zip"), 87 | family_name = c("Zap", "Zap"), 88 | role_1 = c(1, NA), 89 | role_2 = c(1, 1) 90 | ), 91 | roles = c(role_1 = "a", role_2 = "b") 92 | ) 93 | 94 | expect_equal( 95 | aut$get_contributions(roles_first = FALSE), 96 | c("Z.Z.: a", "Z.Z. and Z.Z.: b") 97 | ) 98 | expect_equal( 99 | aut$get_contributions(roles_first = FALSE, by_author = TRUE), 100 | c("Z.Z.: a and b", "Z.Z.: b") 101 | ) 102 | }) 103 | 104 | test_that("get_contributions() reorders CRediT roles alphabetically", { 105 | aut <- Plume$new(data.frame( 106 | given_name = c("Zip", "Ric"), 107 | family_name = c("Zap", "Rac"), 108 | writing = c(1, NA), 109 | analysis = c(NA, 1) 110 | ), roles = credit_roles()) 111 | 112 | expect_equal( 113 | aut$get_contributions(), 114 | c("Formal analysis: R.R.", "Writing - original draft: Z.Z.") 115 | ) 116 | }) 117 | 118 | test_that("author order is preserved when using CRediT and `by_author` (#50)", { 119 | aut <- Plume$new(data.frame( 120 | given_name = c("Z", "A"), 121 | family_name = c("Z", "A"), 122 | writing = c(1, NA), 123 | analysis = c(NA, 1) 124 | )) 125 | expect_equal( 126 | aut$get_contributions(roles_first = FALSE, by_author = TRUE), 127 | c("Z.Z.: Writing - original draft", "A.A.: Formal analysis") 128 | ) 129 | }) 130 | 131 | test_that("set_main_contributors() ranks contributors", { 132 | aut <- Plume$new( 133 | data.frame( 134 | given_name = c("C", "B", "A"), 135 | family_name = c("C", "B", "A"), 136 | writing = rep(1, 3), 137 | analysis = rep(1, 3) 138 | ), 139 | roles = c(writing = "Writing", analysis = "Analysis") 140 | ) 141 | 142 | aut$set_main_contributors(3, 2, .roles = "writing") 143 | expect_equal( 144 | aut$get_contributions(), 145 | c("Writing: A.A., B.B. and C.C.", "Analysis: C.C., B.B. and A.A.") 146 | ) 147 | 148 | aut$set_main_contributors(2, .roles = "writing") 149 | expect_equal( 150 | aut$get_contributions(alphabetical_order = TRUE), 151 | c("Writing: B.B., A.A. and C.C.", "Analysis: A.A., B.B. and C.C.") 152 | ) 153 | 154 | aut$set_main_contributors(writing = c(3, 2)) 155 | expect_equal( 156 | aut$get_contributions(), 157 | c("Writing: A.A., B.B. and C.C.", "Analysis: C.C., B.B. and A.A.") 158 | ) 159 | 160 | aut$set_main_contributors(3, .roles = c("writing", "analysis", "test")) 161 | expect_equal( 162 | aut$get_contributions(), 163 | c("Writing: A.A., C.C. and B.B.", "Analysis: A.A., C.C. and B.B.") 164 | ) 165 | 166 | aut$set_main_contributors(writing = aa, analysis = bb, .by = "initials") 167 | expect_equal( 168 | aut$get_contributions(), 169 | c("Writing: A.A., C.C. and B.B.", "Analysis: B.B., C.C. and A.A.") 170 | ) 171 | 172 | aut$set_main_contributors( 173 | writing = c(aa, bb), analysis = bb, 174 | .by = "initials" 175 | ) 176 | expect_equal( 177 | aut$get_contributions(), 178 | c("Writing: A.A., B.B. and C.C.", "Analysis: B.B., C.C. and A.A.") 179 | ) 180 | }) 181 | 182 | # Errors ---- 183 | 184 | test_that("get_contributions() gives meaningful error messages", { 185 | aut <- Plume$new(basic_df) 186 | 187 | expect_snapshot({ 188 | (expect_error( 189 | aut$get_contributions(roles_first = "") 190 | )) 191 | (expect_error( 192 | aut$get_contributions(by_author = "") 193 | )) 194 | (expect_error( 195 | aut$get_contributions(alphabetical_order = "") 196 | )) 197 | (expect_error( 198 | aut$get_contributions(dotted_initials = "") 199 | )) 200 | (expect_error( 201 | aut$get_contributions(literal_names = "") 202 | )) 203 | (expect_error( 204 | aut$get_contributions(sep = 1) 205 | )) 206 | (expect_error( 207 | aut$get_contributions(sep_last = 1) 208 | )) 209 | (expect_error( 210 | aut$get_contributions(divider = 1) 211 | )) 212 | }) 213 | 214 | aut <- Plume$new(data.frame(given_name = "A", family_name = "B")) 215 | expect_snapshot(aut$get_contributions(), error = TRUE) 216 | }) 217 | -------------------------------------------------------------------------------- /tests/testthat/test-get-footnotes.R: -------------------------------------------------------------------------------- 1 | test_that("get_affiliations/notes() return affiliations/notes", { 2 | aut <- Plume$new(basic_df) 3 | 4 | expect_s3_class(aut$get_notes(), "plm") 5 | 6 | expect_equal( 7 | aut$get_affiliations(), 8 | c("^1^a", "^2^b", "^3^c", "^4^d") 9 | ) 10 | expect_equal( 11 | aut$get_affiliations(superscript = FALSE), 12 | c("1a", "2b", "3c", "4d") 13 | ) 14 | expect_equal( 15 | aut$get_affiliations(sep = ": ", superscript = FALSE), 16 | c("1: a", "2: b", "3: c", "4: d") 17 | ) 18 | 19 | aut <- Plume$new(basic_df, symbols = list(affiliation = letters)) 20 | 21 | expect_equal(aut$get_affiliations(), c("^a^a", "^b^b", "^c^c", "^d^d")) 22 | expect_equal(aut$get_notes(), c("^†^a", "^‡^c", "^§^b")) 23 | }) 24 | 25 | test_that("get_affiliations/notes() return `NULL` if no affiliations/notes", { 26 | aut <- Plume$new(data.frame( 27 | given_name = "Zip", 28 | family_name = "Zap", 29 | note = "" 30 | )) 31 | expect_null(aut$get_notes()) 32 | }) 33 | 34 | # Errors ---- 35 | 36 | test_that("get_affiliations()/get_notes() give meaningful error messages", { 37 | aut <- Plume$new(basic_df) 38 | 39 | expect_snapshot({ 40 | (expect_error( 41 | aut$get_affiliations(sep = 1) 42 | )) 43 | (expect_error( 44 | aut$get_affiliations(superscript = "") 45 | )) 46 | (expect_error( 47 | aut$get_notes(sep = 1) 48 | )) 49 | (expect_error( 50 | aut$get_notes(superscript = "") 51 | )) 52 | }) 53 | 54 | aut <- Plume$new(data.frame(given_name = "A", family_name = "B")) 55 | 56 | expect_snapshot({ 57 | (expect_error( 58 | aut$get_affiliations() 59 | )) 60 | (expect_error( 61 | aut$get_notes() 62 | )) 63 | }) 64 | }) 65 | -------------------------------------------------------------------------------- /tests/testthat/test-get-orcids.R: -------------------------------------------------------------------------------- 1 | test_that("get_orcids() returns authors' ORCID", { 2 | aut <- Plume$new(basic_df) 3 | 4 | expect_s3_class(aut$get_orcids(), "plm") 5 | 6 | expect_snapshot(aut$get_orcids(), transform = scrub_icon_path) 7 | expect_snapshot(aut$get_orcids(sep = " - "), transform = scrub_icon_path) 8 | expect_snapshot(aut$get_orcids(icon = FALSE)) 9 | expect_snapshot(aut$get_orcids(icon = FALSE, compact = TRUE)) 10 | }) 11 | 12 | # Errors ---- 13 | 14 | test_that("get_orcids() gives meaningful error messages", { 15 | aut <- Plume$new(basic_df) 16 | 17 | expect_snapshot({ 18 | (expect_error( 19 | aut$get_orcids(compact = 1) 20 | )) 21 | (expect_error( 22 | aut$get_orcids(icon = 1) 23 | )) 24 | (expect_error( 25 | aut$get_orcids(sep = 1) 26 | )) 27 | }) 28 | 29 | basic_df$orcid <- c(NA, "0000", NA) 30 | aut <- Plume$new(basic_df) 31 | expect_snapshot(aut$get_orcids(icon = FALSE), error = TRUE) 32 | 33 | aut <- Plume$new(data.frame(given_name = "A", family_name = "B")) 34 | expect_snapshot(aut$get_orcids(), error = TRUE) 35 | }) 36 | -------------------------------------------------------------------------------- /tests/testthat/test-icon.R: -------------------------------------------------------------------------------- 1 | test_that("printing a `plm_icon` object returns a formatted output", { 2 | expect_output(print(icn_orcid()), "") 3 | }) 4 | 5 | test_that("icn_orcid() makes orcid icon metadata", { 6 | skip_if(!rmarkdown::pandoc_available(), "pandoc is not available") 7 | 8 | render <- partial(rmarkdown::render, clean = FALSE, quiet = TRUE) 9 | 10 | read_rendered_md <- function() { 11 | read_test_file(list.files(pattern = "\\.md$")) 12 | } 13 | 14 | withr::with_tempdir({ 15 | tmp_file <- withr::local_tempfile(lines = dedent(" 16 | --- 17 | title: test 18 | --- 19 | ```{r results = 'asis'} 20 | str(attributes(icn_orcid())) 21 | str(attributes(icn_orcid(size = 24))) 22 | str(attributes(icn_orcid(bw = TRUE))) 23 | ``` 24 | "), fileext = ".Rmd", tmpdir = getwd()) 25 | 26 | # pdf 27 | # use rtf_document to speed up test runs 28 | render(tmp_file, output_format = "rtf_document") 29 | expect_snapshot(read_rendered_md()) 30 | 31 | # svg 32 | render(tmp_file, output_format = "html_document") 33 | expect_snapshot(read_rendered_md()) 34 | }) 35 | }) 36 | 37 | # Errors ---- 38 | 39 | test_that("icn_orcid() gives meaningful error messages", { 40 | expect_snapshot({ 41 | (expect_error( 42 | icn_orcid(size = NULL) 43 | )) 44 | (expect_error( 45 | icn_orcid(bw = 1) 46 | )) 47 | }) 48 | }) 49 | -------------------------------------------------------------------------------- /tests/testthat/test-plm-template.R: -------------------------------------------------------------------------------- 1 | test_that("plm_template() returns a table template", { 2 | df <- plm_template(role_cols = NULL) 3 | 4 | expect_equal(nrow(df), 0L) 5 | 6 | nms_minimal <- c( 7 | "given_name", "family_name", "email", "orcid", 8 | "affiliation_1", "affiliation_2", "note" 9 | ) 10 | nms_minimal_crt <- c(nms_minimal, names(credit_roles())) 11 | 12 | expect_named(df, nms_minimal, ignore.order = TRUE) 13 | expect_named( 14 | plm_template(role_cols = credit_roles()), 15 | nms_minimal_crt, 16 | ignore.order = TRUE 17 | ) 18 | 19 | df <- plm_template(minimal = FALSE, role_cols = NULL) 20 | nms_minimal_all <- c(nms_minimal, "phone", "fax", "url") 21 | 22 | expect_named(df, nms_minimal_all, ignore.order = TRUE) 23 | }) 24 | 25 | test_that("role columns are of type numeric", { 26 | get_numeric_cols <- function(tbl) { 27 | names(tbl)[sapply(tbl, is.numeric)] 28 | } 29 | 30 | df <- plm_template(role_cols = "foo") 31 | expect_equal(get_numeric_cols(df), "foo") 32 | 33 | df <- plm_template(role_cols = c(a = "foo")) 34 | expect_equal(get_numeric_cols(df), "a") 35 | }) 36 | 37 | # Errors ---- 38 | 39 | test_that("plm_template() gives meaningful error messages", { 40 | expect_snapshot({ 41 | (expect_error( 42 | plm_template(minimal = 1) 43 | )) 44 | (expect_error( 45 | plm_template(role_cols = 1) 46 | )) 47 | (expect_error( 48 | plm_template(role_cols = c("foo", "foo")) 49 | )) 50 | }) 51 | }) 52 | -------------------------------------------------------------------------------- /tests/testthat/test-print.R: -------------------------------------------------------------------------------- 1 | test_that("printing a plume object is identical to cat()", { 2 | aut <- Plume$new(data.frame(given_name = "X", family_name = "Y")) 3 | expect_output(print(aut$get_author_list()), "X Y") 4 | }) 5 | -------------------------------------------------------------------------------- /tests/testthat/test-sequential.R: -------------------------------------------------------------------------------- 1 | test_that("sequences of characters are properly generated", { 2 | df <- data.frame(given_name = "X", family_name = "Y", affiliation = 1:55) 3 | aut <- Plume$new(df, symbols = list(affiliation = letters)) 4 | 5 | .h <- paste0("X Y", c("a", "b", "c", "d", "e", "f")) 6 | 7 | expect_equal(head(aut$get_author_list("a")), .h) 8 | expect_equal( 9 | tail(aut$get_author_list("a")), 10 | paste0("X Y", c("xx", "yy", "zz", "aaa", "bbb", "ccc")) 11 | ) 12 | 13 | aut <- Plume$new(df, symbols = list(affiliation = sequential(letters))) 14 | 15 | expect_equal(head(aut$get_author_list("a")), .h) 16 | expect_equal( 17 | tail(aut$get_author_list("a")), 18 | paste0("X Y", c("ax", "ay", "az", "ba", "bb", "bc")) 19 | ) 20 | }) 21 | 22 | # Errors ---- 23 | 24 | test_that("sequential() gives meaningful error messages", { 25 | expect_snapshot(sequential(1), error = TRUE) 26 | }) 27 | -------------------------------------------------------------------------------- /tests/testthat/test-set-default-names.R: -------------------------------------------------------------------------------- 1 | test_that("set_default_names() sets new plume names", { 2 | nms <- set_default_names( 3 | initials = "initiales", 4 | literal_name = "nom_complet", 5 | corresponding = "correspondant", 6 | contributor_rank = "rang_contributeur", 7 | given_name = "prénom", 8 | family_name = "nom", 9 | email = "courriel", 10 | phone = "téléphone" 11 | ) 12 | 13 | expect_snapshot(str(nms)) 14 | 15 | nms <- set_default_names( 16 | equal_contributor = "contribution_égale", 17 | .plume_quarto = FALSE 18 | ) 19 | 20 | expect_null(list_fetch(nms, "equal_contributor")[[1]]) 21 | 22 | nms <- set_default_names( 23 | equal_contributor = "contribution_égale", 24 | .plume_quarto = TRUE 25 | ) 26 | 27 | expect_equal( 28 | list_fetch(nms, "equal_contributor")[[1]], 29 | "contribution_égale" 30 | ) 31 | 32 | nms <- set_default_names(foo = "foo", analysis = "bar") 33 | 34 | expect_equal(nms, .names_plume) 35 | }) 36 | 37 | # Errors ---- 38 | 39 | test_that("set_default_names() gives meaningful error messages", { 40 | expect_snapshot({ 41 | (expect_error(set_default_names())) 42 | (expect_error(set_default_names(1))) 43 | (expect_error(set_default_names("a"))) 44 | (expect_error(set_default_names(x = "a", y = "a"))) 45 | (expect_error(set_default_names(x = "a", x = "b"))) 46 | (expect_error(set_default_names(given_name = "nom", .plume_quarto = 1))) 47 | }) 48 | }) 49 | -------------------------------------------------------------------------------- /tests/testthat/test-set-main-contributors.R: -------------------------------------------------------------------------------- 1 | test_that("ranking contributors makes a `contributor_rank` column", { 2 | aut <- Plume$new(basic_df) 3 | aut$set_main_contributors(1, .roles = "analysis") 4 | 5 | expect_named( 6 | aut$get_plume()$role[[1]], 7 | c("role", "contributor_rank") 8 | ) 9 | }) 10 | 11 | test_that("set_main_contributors() ranks contributors", { 12 | aut <- Plume$new(basic_df) 13 | aut$set_main_contributors(2, 3, .roles = "analysis") 14 | 15 | expect_equal( 16 | pull_nested_var(aut, "role", "contributor_rank"), 17 | c(3, NA, 1, NA, 2, NA) 18 | ) 19 | }) 20 | 21 | test_that("`.roles` applies to any unnamed expression (#65)", { 22 | aut <- Plume$new(data.frame( 23 | given_name = LETTERS[1:3], 24 | family_name = LETTERS[1:3], 25 | analysis = 1, 26 | writing = 1 27 | )) 28 | 29 | aut$set_main_contributors(analysis = 3, 2, 3, .roles = "writing") 30 | expect_equal( 31 | pull_nested_var(aut, "role", "contributor_rank"), 32 | c(2, 3, 2, 1, 1, 2) 33 | ) 34 | 35 | aut$set_main_contributors(analysis = 3, 2, 3, .roles = c(writing = "foo")) 36 | expect_equal( 37 | pull_nested_var(aut, "role", "contributor_rank"), 38 | c(2, 3, 2, 1, 1, 2) 39 | ) 40 | }) 41 | 42 | test_that("named expressions have the priority over `.roles`", { 43 | aut <- Plume$new(basic_df) 44 | aut$set_main_contributors(analysis = 3, 2, .roles = "analysis") 45 | 46 | expect_equal( 47 | pull_nested_var(aut, "role", "contributor_rank"), 48 | c(2, NA, 2, NA, 1, NA) 49 | ) 50 | }) 51 | 52 | # Errors ---- 53 | 54 | test_that("set_ranks() gives meaningful error messages", { 55 | aut <- Plume$new(basic_df) 56 | 57 | expect_snapshot({ 58 | (expect_error(aut$set_main_contributors())) 59 | (expect_error(aut$set_main_contributors(1, .roles = 1))) 60 | (expect_error(aut$set_main_contributors(1, .roles = c("x", "x")))) 61 | (expect_error(aut$set_main_contributors(1, .by = 1))) 62 | }) 63 | }) 64 | -------------------------------------------------------------------------------- /tests/testthat/test-set-status.R: -------------------------------------------------------------------------------- 1 | test_that("sets status to selected authors", { 2 | aut <- PlumeQuarto$new(basic_df, temp_file()) 3 | 4 | # set_corresponding_authors 5 | 6 | expect_equal({ 7 | aut$set_corresponding_authors(everyone()) 8 | aut$get_plume()$corresponding 9 | }, rep(TRUE, 3)) 10 | 11 | expect_equal({ 12 | aut$set_corresponding_authors(plume::everyone()) 13 | aut$get_plume()$corresponding 14 | }, rep(TRUE, 3)) 15 | 16 | expect_equal({ 17 | aut$set_corresponding_authors(1) 18 | aut$get_plume()$corresponding 19 | }, c(TRUE, FALSE, FALSE)) 20 | 21 | expect_equal({ 22 | aut$set_corresponding_authors(1, 3) 23 | aut$get_plume()$corresponding 24 | }, c(TRUE, FALSE, TRUE)) 25 | 26 | expect_equal({ 27 | aut$set_corresponding_authors(zip, .by = "given_name") 28 | aut$get_plume()$corresponding 29 | }, c(TRUE, FALSE, FALSE)) 30 | 31 | # set_cofirst_authors 32 | 33 | expect_equal({ 34 | aut$set_cofirst_authors(zip, .by = "given_name") 35 | aut$get_plume()$equal_contributor 36 | }, c(TRUE, FALSE, FALSE)) 37 | 38 | # set_deceased 39 | 40 | expect_equal({ 41 | aut$set_deceased(zip, .by = "given_name") 42 | aut$get_plume()$deceased 43 | }, c(TRUE, FALSE, FALSE)) 44 | }) 45 | 46 | # Errors ---- 47 | 48 | test_that("set_*() methods give meaningful error messages", { 49 | aut <- PlumeQuarto$new(basic_df, temp_file()) 50 | 51 | expect_snapshot({ 52 | (expect_error( 53 | aut$set_corresponding_authors() 54 | )) 55 | (expect_error( 56 | aut$set_corresponding_authors(a, .by = "foo") 57 | )) 58 | (expect_error( 59 | aut$set_corresponding_authors(a, .by = "") 60 | )) 61 | (expect_error( 62 | aut$set_corresponding_authors(a, .by = 1) 63 | )) 64 | (expect_error( 65 | aut$set_corresponding_authors(x <- y) 66 | )) 67 | (expect_error( 68 | aut$set_cofirst_authors(a, .by = "foo") 69 | )) 70 | (expect_error( 71 | aut$set_cofirst_authors(a, .by = "") 72 | )) 73 | (expect_error( 74 | aut$set_cofirst_authors(a, .by = 1) 75 | )) 76 | (expect_error( 77 | aut$set_deceased(a, .by = "foo") 78 | )) 79 | (expect_error( 80 | aut$set_deceased(a, .by = "") 81 | )) 82 | (expect_error( 83 | aut$set_deceased(a, .by = 1) 84 | )) 85 | }) 86 | }) 87 | 88 | test_that("everyone*() selectors error if used in a wrong context", { 89 | expect_snapshot(everyone(), error = TRUE) 90 | }) 91 | -------------------------------------------------------------------------------- /tests/testthat/test-to-yaml.R: -------------------------------------------------------------------------------- 1 | test_that("to_yaml() injects authors and affiliations into a `.qmd`", { 2 | tmp_file <- withr::local_tempfile( 3 | lines = "---\ntitle: test\n---\n\n```{r}\n#| echo: false\nx <- 1\n```", 4 | fileext = ".qmd" 5 | ) 6 | 7 | aut <- PlumeQuarto$new(basic_df, tmp_file) 8 | aut$set_corresponding_authors(1) 9 | aut$to_yaml() 10 | expect_snapshot(read_test_file(tmp_file)) 11 | 12 | aut <- PlumeQuarto$new(basic_df[c(3, 2, 1), ], tmp_file) 13 | aut$set_corresponding_authors(1) 14 | aut$to_yaml() 15 | expect_snapshot(read_test_file(tmp_file)) 16 | 17 | aut <- PlumeQuarto$new(data.frame( 18 | given_name = "Zip", family_name = "Zap", 19 | affiliation1 = "name=a department=b city=c postal-code=d", 20 | affiliation2 = "city=e name=f department=g", 21 | affiliation3 = "h" 22 | ), tmp_file) 23 | aut$to_yaml() 24 | expect_snapshot(read_test_file(tmp_file)) 25 | }) 26 | 27 | test_that("to_yaml() doesn't add the `affiliations` schema if there're no affiliations", { 28 | tmp_file <- withr::local_tempfile( 29 | lines = dedent(" 30 | --- 31 | affiliations: 32 | - name: a 33 | --- 34 | "), 35 | fileext = ".qmd" 36 | ) 37 | 38 | aut <- PlumeQuarto$new( 39 | data.frame(given_name = "Zip", family_name = "Zap"), 40 | tmp_file 41 | ) 42 | aut$to_yaml() 43 | 44 | expect_snapshot(read_test_file(tmp_file)) 45 | }) 46 | 47 | test_that("to_yaml() pushes data to empty YAML headers", { 48 | tmp_file <- withr::local_tempfile(lines = "---\n---", fileext = ".qmd") 49 | 50 | aut <- PlumeQuarto$new(tibble( 51 | given_name = "Zip", 52 | family_name = "Zap", 53 | `meta-foo` = "bar" 54 | ), tmp_file) 55 | aut$to_yaml() 56 | 57 | expect_snapshot(read_test_file(tmp_file)) 58 | }) 59 | 60 | test_that("to_yaml() exits before pushing new header if invalid YAML", { 61 | tmp_file <- withr::local_tempfile( 62 | lines = "---\ntitle: test\n--\n\nLorem ipsum\n\n---", 63 | fileext = ".qmd" 64 | ) 65 | 66 | old <- readr::read_file(tmp_file) 67 | 68 | aut <- PlumeQuarto$new(basic_df, tmp_file) 69 | try(aut$to_yaml(), silent = TRUE) 70 | 71 | new <- readr::read_file(tmp_file) 72 | 73 | expect_equal(old, new) 74 | }) 75 | 76 | test_that("to_yaml() preserves line breaks preceding `---` (#37)", { 77 | tmp_file <- withr::local_tempfile( 78 | lines = "---\n---\nLorem ipsum\n---", 79 | fileext = ".qmd" 80 | ) 81 | 82 | aut <- PlumeQuarto$new( 83 | data.frame(given_name = "Zip", family_name = "Zap"), 84 | tmp_file 85 | ) 86 | aut$to_yaml() 87 | 88 | expect_snapshot(read_test_file(tmp_file)) 89 | }) 90 | 91 | test_that("to_yaml() writes in a separate header to preserve strippable data (#56)", { 92 | tmp_file <- withr::local_tempfile( 93 | lines = dedent(" 94 | --- 95 | title: test # this is a title 96 | foo: > 97 | Lorem ipsum 98 | Vivamus quis 99 | --- 100 | "), 101 | fileext = ".qmd" 102 | ) 103 | 104 | aut <- PlumeQuarto$new( 105 | data.frame(given_name = "Zip", family_name = "Zap"), 106 | tmp_file 107 | ) 108 | 109 | expect_warning( 110 | aut$to_yaml(), 111 | "Writing author metadata in a separate YAML header" 112 | ) 113 | expect_snapshot(read_test_file(tmp_file)) 114 | }) 115 | 116 | test_that("to_yaml() can push data into YAML files", { 117 | tmp_file <- withr::local_tempfile( 118 | lines = "title: foo", 119 | fileext = ".yaml" 120 | ) 121 | 122 | aut <- PlumeQuarto$new( 123 | data.frame(given_name = "Zip", family_name = "Zap"), 124 | tmp_file 125 | ) 126 | aut$to_yaml() 127 | 128 | expect_snapshot(read_test_file(tmp_file)) 129 | }) 130 | 131 | test_that("to_yaml() properly handles authors with no roles (#81)", { 132 | tmp_file <- withr::local_tempfile( 133 | lines = "title: foo", 134 | fileext = ".yaml" 135 | ) 136 | 137 | aut <- PlumeQuarto$new( 138 | data.frame( 139 | given_name = c("A", "B"), 140 | family_name = c("A", "B"), 141 | writing = c(1, NA), 142 | analysis = c(1, NA) 143 | ), 144 | tmp_file 145 | ) 146 | aut$to_yaml() 147 | 148 | expect_snapshot(read_test_file(tmp_file)) 149 | }) 150 | 151 | # Errors ---- 152 | 153 | test_that("to_yaml() errors if no YAML headers is found", { 154 | tmp_file <- withr::local_tempfile( 155 | lines = "---\ntitle: test---", 156 | fileext = ".qmd" 157 | ) 158 | aut <- PlumeQuarto$new(basic_df, tmp_file) 159 | 160 | expect_snapshot(aut$to_yaml(), error = TRUE) 161 | }) 162 | 163 | test_that("to_yaml() errors if an invalid ORCID identifier is found ", { 164 | aut <- PlumeQuarto$new( 165 | data.frame(given_name = "X", family_name = "Y", orcid = "0000"), 166 | temp_file() 167 | ) 168 | expect_snapshot(aut$to_yaml(), error = TRUE) 169 | }) 170 | -------------------------------------------------------------------------------- /vignettes/.gitignore: -------------------------------------------------------------------------------- 1 | *.html 2 | *.R 3 | -------------------------------------------------------------------------------- /vignettes/comprehensive-affiliation-schema.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Comprehensive affiliation schema" 3 | output: rmarkdown::html_vignette 4 | vignette: > 5 | %\VignetteIndexEntry{Comprehensive affiliation schema} 6 | %\VignetteEngine{knitr::rmarkdown} 7 | %\VignetteEncoding{UTF-8} 8 | --- 9 | 10 | ```{r, include = FALSE} 11 | knitr::opts_chunk$set(collapse = TRUE, comment = "#>") 12 | library(plume) 13 | ``` 14 | 15 | Quarto provides a complete [affiliation schema](https://quarto.org/docs/journals/authors.html#affiliations-schema) to define affiliations in more detail. 16 | 17 | To generate comprehensive affiliations in plume, you can specify affiliation items in the input data using the following syntax `key_1=item_1 key_2=item_2 key_n=item_n`. `PlumeQuarto` will automatically parse affiliations and assign each item to its respective key. 18 | 19 | Affiliations with no or unrecognised keys are assigned to the `name` key. 20 | 21 | ```{r} 22 | author <- tibble::tibble( 23 | given_name = "René", 24 | family_name = "Descartes", 25 | affiliation1 = "Collège royal Henri-le-Grand", 26 | affiliation2 = " 27 | city=Poitiers 28 | name=Université de Poitiers 29 | address=15 Rue de l'Hôtel Dieu 30 | country=Kingdom of France 31 | ", 32 | affiliation3 = "name=Academia Franekerensis country=The Netherlands city=Franeker" 33 | ) 34 | ``` 35 | 36 | ```{r, include = FALSE} 37 | tmp_file <- withr::local_tempfile( 38 | lines = "---\ntitle: Cogito ergo sum\n---", 39 | fileext = ".qmd" 40 | ) 41 | aut <- PlumeQuarto$new(author, tmp_file) 42 | ``` 43 | 44 | ```{r, eval = FALSE} 45 | aut <- PlumeQuarto$new(author, file = "file.qmd") 46 | aut$to_yaml() 47 | ``` 48 | 49 | The above will produce the following YAML header: 50 | 51 | ```{r, echo = FALSE, comment = ""} 52 | aut$to_yaml() 53 | cat(readr::read_file(tmp_file)) 54 | ``` 55 | -------------------------------------------------------------------------------- /vignettes/plume-workflow.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: "plume workflow" 3 | output: rmarkdown::html_vignette 4 | vignette: > 5 | %\VignetteIndexEntry{plume workflow} 6 | %\VignetteEngine{knitr::rmarkdown} 7 | %\VignetteEncoding{UTF-8} 8 | --- 9 | 10 | ```{r include = FALSE} 11 | knitr::opts_chunk$set( 12 | collapse = TRUE, 13 | comment = "#>" 14 | ) 15 | ``` 16 | 17 | ## Data 18 | 19 | plume is designed to work with spreadsheets, which makes it easy to store, maintain and share data with co-authors. I recommend using [Google Sheets](https://docs.google.com/spreadsheets/) to take advantage of the R interface provided by the [googlesheets4](https://googlesheets4.tidyverse.org) package. 20 | 21 | First, you'll need to create a spreadsheet. `plm_template()` provides a default template for that purpose. 22 | 23 | ```{r eval = FALSE} 24 | library(googlesheets4) 25 | 26 | gs4_create( 27 | name = "authors", 28 | sheets = plm_template() 29 | ) 30 | ``` 31 | 32 | The above will create a sheet named `authors` with the columns defined by `plm_template()`. 33 | 34 | Enter your information and share the sheet with your collaborators so they can add theirs too. 35 | 36 | Note that if it's the first time you use googlesheets4, you'll need to grant the package permission to work with Google Sheets. You can read more about googlesheets4 authentication [here](https://googlesheets4.tidyverse.org/reference/gs4_auth.html). 37 | 38 | Once the sheet is online, use `read_sheet()` to read it in R. 39 | 40 | ```{r eval = FALSE} 41 | read_sheet(gs4_find("authors")) 42 | ``` 43 | 44 | ## Set up 45 | 46 | ### Plume 47 | 48 | If you use `Plume`, put the code directly in your R Markdown or Quarto document as shown in the example below: 49 | 50 | ````{verbatim} 51 | --- 52 | title: An awesome publication 53 | execute: 54 | echo: false 55 | --- 56 | 57 | ```{r} 58 | #| label: setup 59 | #| include: false 60 | 61 | library(googlesheets4) 62 | library(plume) 63 | 64 | tbl_authors <- read_sheet(gs4_find("sheet_name")) 65 | 66 | aut <- Plume$new(tbl_authors) 67 | aut$set_corresponding_authors(1) 68 | ``` 69 | 70 | `r aut$get_author_list("^a,^co")` 71 | 72 | ```{r} 73 | #| results: asis 74 | aut$get_affiliations() |> cat(sep = "\n\n") 75 | ``` 76 | 77 | \*Correspondence to: `r aut$get_contact_details()` 78 | 79 | ## Main text 80 | 81 | Lorem ipsum... 82 | 83 | ## Contributions 84 | 85 | ```{r} 86 | #| results: asis 87 | aut$get_contributions() |> cat(sep = "; ") 88 | ``` 89 | ```` 90 | 91 | To modify author data, simply edit the spreadsheet. All author information in the document will update automatically the next time you render it. 92 | 93 | ### PlumeQuarto 94 | 95 | If you use `PlumeQuarto` to inject author information in a Quarto document, you'll have to pass the data from a separate R script. 96 | 97 | ```{r, eval = FALSE} 98 | library(googlesheets4) 99 | library(plume) 100 | 101 | tbl_authors <- read_sheet(gs4_find("sheet_name")) 102 | 103 | aut <- PlumeQuarto$new(tbl_authors, file = "file.qmd") 104 | aut$set_corresponding_authors(1) 105 | aut$to_yaml() 106 | ``` 107 | 108 | Remember to run the script everytime the spreadsheet is updated to implement the changes in your document. 109 | -------------------------------------------------------------------------------- /vignettes/working-in-other-languages.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Working in other languages" 3 | output: rmarkdown::html_vignette 4 | vignette: > 5 | %\VignetteIndexEntry{Working in other languages} 6 | %\VignetteEngine{knitr::rmarkdown} 7 | %\VignetteEncoding{UTF-8} 8 | --- 9 | 10 | ```{r, include = FALSE} 11 | knitr::opts_chunk$set( 12 | collapse = TRUE, 13 | comment = "#>" 14 | ) 15 | library(plume) 16 | ``` 17 | 18 | If you work in a language other than English, you can extend plume classes with default names in your desired language. plume provides `set_default_names()` to help you set new default names. 19 | 20 | E.g. to extend `Plume` with default names in French: 21 | 22 | ```{r} 23 | PlumeFr <- R6::R6Class( 24 | classname = "PlumeFr", 25 | inherit = Plume, 26 | private = list( 27 | plume_names = set_default_names( 28 | initials = "initiales", 29 | literal_name = "nom_complet", 30 | corresponding = "correspondant", 31 | given_name = "prénom", 32 | family_name = "nom", 33 | email = "courriel", 34 | phone = "téléphone" 35 | ) 36 | ) 37 | ) 38 | ``` 39 | 40 | ```{r} 41 | PlumeFr$new(encyclopedists_fr) 42 | ``` 43 | 44 | You can also overwrite the default arguments of some methods to match your language. I recommend doing it in the `public` argument of the class definition. For the purpose of this example, I'll use the `set()` method instead. For example, to change `divider` and `sep_last` to ` : ` and ` et ` in `get_contributions()`: 45 | 46 | ```{r} 47 | PlumeFr$set("public", "get_contributions", function( 48 | roles_first = TRUE, 49 | by_author = FALSE, 50 | alphabetical_order = FALSE, 51 | dotted_initials = TRUE, 52 | literal_names = FALSE, 53 | divider = " : ", 54 | sep = ", ", 55 | sep_last = " et " 56 | ) { 57 | super$get_contributions( 58 | roles_first, by_author, alphabetical_order, dotted_initials, 59 | literal_names, divider, sep, sep_last 60 | ) 61 | }) 62 | ``` 63 | 64 | ```{r} 65 | aut <- PlumeFr$new( 66 | encyclopedists_fr, 67 | roles = c(supervision = "Supervision", rédaction = "Rédaction") 68 | ) 69 | aut$get_contributions() 70 | ``` 71 | --------------------------------------------------------------------------------