├── .Rbuildignore ├── .github ├── .gitignore └── workflows │ ├── R-CMD-check-dev.yaml │ ├── R-CMD-check-status.yaml │ ├── R-CMD-check.yaml │ ├── check │ └── action.yml │ ├── commit │ └── action.yml │ ├── covr │ └── action.yml │ ├── dep-matrix │ └── action.yml │ ├── dep-suggests-matrix │ ├── action.R │ └── action.yml │ ├── fledge.yaml │ ├── get-extra │ └── action.yml │ ├── git-identity │ └── action.yml │ ├── install │ └── action.yml │ ├── lock.yaml │ ├── matrix-check │ └── action.yml │ ├── pkgdown-build │ └── action.yml │ ├── pkgdown-deploy │ └── action.yml │ ├── pkgdown.yaml │ ├── pr-commands.yaml │ ├── rate-limit │ └── action.yml │ ├── revdep.yaml │ ├── roxygenize │ └── action.yml │ ├── style │ └── action.yml │ ├── update-snapshots │ └── action.yml │ └── versions-matrix │ ├── action.R │ └── action.yml ├── .gitignore ├── .travis.yml ├── DESCRIPTION ├── LICENSE ├── NAMESPACE ├── NEWS.md ├── R ├── bindr-package.R ├── populate.R └── utils.R ├── README.Rmd ├── README.md ├── _pkgdown.yml ├── appveyor.yml ├── bindr.Rproj ├── codecov.yml ├── cran-comments.md ├── index.md ├── man ├── bindr-package.Rd └── create_env.Rd ├── revdep ├── .gitignore ├── README.md ├── cran.md ├── failures.md └── problems.md ├── tests ├── testthat.R └── testthat │ ├── test-create.R │ ├── test-error.R │ ├── test-payload.R │ └── test-populate.R └── tic.R /.Rbuildignore: -------------------------------------------------------------------------------- 1 | ^.*\.Rproj$ 2 | ^\.Rproj\.user$ 3 | ^appveyor\.yml$ 4 | ^\.travis\.yml$ 5 | ^README\.Rmd$ 6 | ^README-.*\.png$ 7 | ^codecov\.yml$ 8 | ^README_cache$ 9 | ^tic\.R$ 10 | ^_pkgdown\.yml$ 11 | ^cran-comments\.md$ 12 | ^docs$ 13 | ^\.github$ 14 | ^revdep$ 15 | ^index\.md$ 16 | -------------------------------------------------------------------------------- /.github/.gitignore: -------------------------------------------------------------------------------- 1 | /pkg.lock 2 | -------------------------------------------------------------------------------- /.github/workflows/R-CMD-check-dev.yaml: -------------------------------------------------------------------------------- 1 | # This workflow calls the GitHub API very frequently. 2 | # Can't be run as part of commits 3 | on: 4 | schedule: 5 | - cron: "0 5 * * *" # 05:00 UTC every day only run on main branch 6 | push: 7 | branches: 8 | - "cran-*" 9 | tags: 10 | - "v*" 11 | 12 | name: rcc dev 13 | 14 | jobs: 15 | matrix: 16 | runs-on: ubuntu-22.04 17 | outputs: 18 | matrix: ${{ steps.set-matrix.outputs.matrix }} 19 | 20 | name: Collect deps 21 | 22 | steps: 23 | - uses: actions/checkout@v4 24 | 25 | - uses: ./.github/workflows/rate-limit 26 | with: 27 | token: ${{ secrets.GITHUB_TOKEN }} 28 | 29 | - uses: r-lib/actions/setup-r@v2 30 | 31 | - id: set-matrix 32 | uses: ./.github/workflows/dep-matrix 33 | 34 | check-matrix: 35 | runs-on: ubuntu-22.04 36 | needs: matrix 37 | 38 | name: Check deps 39 | 40 | steps: 41 | - name: Install json2yaml 42 | run: | 43 | sudo npm install -g json2yaml 44 | 45 | - name: Check matrix definition 46 | run: | 47 | matrix='${{ needs.matrix.outputs.matrix }}' 48 | echo $matrix 49 | echo $matrix | jq . 50 | echo $matrix | json2yaml 51 | 52 | R-CMD-check-base: 53 | runs-on: ubuntu-22.04 54 | 55 | name: base 56 | 57 | # Begin custom: services 58 | # End custom: services 59 | 60 | strategy: 61 | fail-fast: false 62 | 63 | steps: 64 | - uses: actions/checkout@v4 65 | 66 | - uses: ./.github/workflows/custom/before-install 67 | if: hashFiles('.github/workflows/custom/before-install/action.yml') != '' 68 | 69 | - uses: ./.github/workflows/install 70 | with: 71 | cache-version: rcc-dev-base-1 72 | needs: build, check 73 | extra-packages: "any::rcmdcheck any::remotes ." 74 | token: ${{ secrets.GITHUB_TOKEN }} 75 | 76 | - name: Session info 77 | run: | 78 | options(width = 100) 79 | if (!requireNamespace("sessioninfo", quietly = TRUE)) install.packages("sessioninfo") 80 | pkgs <- installed.packages()[, "Package"] 81 | sessioninfo::session_info(pkgs, include_base = TRUE) 82 | shell: Rscript {0} 83 | 84 | - uses: ./.github/workflows/custom/after-install 85 | if: hashFiles('.github/workflows/custom/after-install/action.yml') != '' 86 | 87 | - uses: ./.github/workflows/update-snapshots 88 | if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository 89 | 90 | - uses: ./.github/workflows/check 91 | with: 92 | results: ${{ matrix.package }} 93 | 94 | R-CMD-check-dev: 95 | needs: 96 | - matrix 97 | - R-CMD-check-base 98 | 99 | runs-on: ubuntu-22.04 100 | 101 | name: 'rcc-dev: ${{ matrix.package }}' 102 | 103 | # Begin custom: services 104 | # End custom: services 105 | 106 | strategy: 107 | fail-fast: false 108 | matrix: ${{fromJson(needs.matrix.outputs.matrix)}} 109 | 110 | steps: 111 | - uses: actions/checkout@v4 112 | 113 | - uses: ./.github/workflows/custom/before-install 114 | if: hashFiles('.github/workflows/custom/before-install/action.yml') != '' 115 | 116 | - uses: ./.github/workflows/install 117 | with: 118 | cache-version: rcc-dev-${{ matrix.package }}-1 119 | needs: build, check 120 | extra-packages: "any::rcmdcheck any::remotes ." 121 | token: ${{ secrets.GITHUB_TOKEN }} 122 | 123 | - name: Install dev version of ${{ matrix.package }} 124 | env: 125 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} 126 | run: | 127 | remotes::install_dev("${{ matrix.package }}", "https://cloud.r-project.org", upgrade = "always") 128 | shell: Rscript {0} 129 | 130 | - name: Session info 131 | run: | 132 | options(width = 100) 133 | if (!requireNamespace("sessioninfo", quietly = TRUE)) install.packages("sessioninfo") 134 | pkgs <- installed.packages()[, "Package"] 135 | sessioninfo::session_info(pkgs, include_base = TRUE) 136 | shell: Rscript {0} 137 | 138 | - uses: ./.github/workflows/custom/after-install 139 | if: hashFiles('.github/workflows/custom/after-install/action.yml') != '' 140 | 141 | - uses: ./.github/workflows/update-snapshots 142 | if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository 143 | 144 | - uses: ./.github/workflows/check 145 | with: 146 | results: ${{ matrix.package }} 147 | -------------------------------------------------------------------------------- /.github/workflows/R-CMD-check-status.yaml: -------------------------------------------------------------------------------- 1 | # Workflow to update the status of a commit for the R-CMD-check workflow 2 | # Necessary because remote PRs cannot update the status of the commit 3 | on: 4 | workflow_run: 5 | workflows: 6 | - rcc 7 | types: 8 | - requested 9 | - completed 10 | 11 | name: rcc-status 12 | 13 | jobs: 14 | rcc-status: 15 | runs-on: ubuntu-24.04 16 | 17 | name: "Update commit status" 18 | 19 | permissions: 20 | contents: read 21 | statuses: write 22 | 23 | steps: 24 | - name: "Update commit status" 25 | # Only run if triggered by rcc workflow 26 | if: github.event.workflow_run.name == 'rcc' 27 | env: 28 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} 29 | run: | 30 | set -x 31 | 32 | if [ "${{ github.event.workflow_run.status }}" == "completed" ]; then 33 | if [ "${{ github.event.workflow_run.conclusion }}" == "success" ]; then 34 | state="success" 35 | else 36 | state="failure" 37 | fi 38 | 39 | # Read artifact ID 40 | artifact_id=$(gh api \ 41 | -H "Accept: application/vnd.github+json" \ 42 | -H "X-GitHub-Api-Version: 2022-11-28" \ 43 | repos/${{ github.repository }}/actions/runs/${{ github.event.workflow_run.id }}/artifacts | jq -r '.artifacts[] | select(.name == "rcc-smoke-sha") | .id') 44 | 45 | if [ -n "${artifact_id}" ]; then 46 | # Download artifact 47 | curl -L -o rcc-smoke-sha.zip \ 48 | -H "Accept: application/vnd.github+json" \ 49 | -H "Authorization: Bearer ${GH_TOKEN}" \ 50 | -H "X-GitHub-Api-Version: 2022-11-28" \ 51 | https://api.github.com/repos/${{ github.repository }}/actions/artifacts/${artifact_id}/zip 52 | 53 | # Unzip artifact 54 | unzip rcc-smoke-sha.zip 55 | 56 | # Read artifact 57 | sha=$(cat rcc-smoke-sha.txt) 58 | 59 | # Clean up 60 | rm rcc-smoke-sha.zip rcc-smoke-sha.txt 61 | fi 62 | else 63 | state="pending" 64 | fi 65 | 66 | if [ -z "${sha}" ]; then 67 | sha=${{ github.event.workflow_run.head_sha }} 68 | fi 69 | 70 | html_url=${{ github.event.workflow_run.html_url }} 71 | description=${{ github.event.workflow_run.name }} 72 | 73 | gh api \ 74 | --method POST \ 75 | -H "Accept: application/vnd.github+json" \ 76 | -H "X-GitHub-Api-Version: 2022-11-28" \ 77 | repos/${{ github.repository }}/statuses/${sha} \ 78 | -f "state=${state}" -f "target_url=${html_url}" -f "description=${description}" -f "context=rcc" 79 | shell: bash 80 | -------------------------------------------------------------------------------- /.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 | # 4 | # NOTE: This workflow is overkill for most R packages and 5 | # check-standard.yaml is likely a better choice. 6 | # usethis::use_github_action("check-standard") will install it. 7 | on: 8 | push: 9 | branches: 10 | - main 11 | - master 12 | - release 13 | - cran-* 14 | pull_request: 15 | branches: 16 | - main 17 | - master 18 | workflow_dispatch: 19 | inputs: 20 | ref: 21 | description: "Branch, tag, or commit to check out" 22 | required: false 23 | default: "main" 24 | versions-matrix: 25 | description: "Create a matrix of R versions" 26 | type: boolean 27 | default: false 28 | dep-suggests-matrix: 29 | description: "Create a matrix of suggested dependencies" 30 | type: boolean 31 | default: false 32 | merge_group: 33 | types: 34 | - checks_requested 35 | schedule: 36 | - cron: "10 1 * * *" 37 | 38 | concurrency: 39 | group: ${{ github.workflow }}-${{ github.ref }}-${{ inputs.ref || github.head_ref || github.sha }}-${{ github.base_ref || '' }} 40 | cancel-in-progress: true 41 | 42 | name: rcc 43 | 44 | jobs: 45 | rcc-smoke: 46 | runs-on: ubuntu-24.04 47 | 48 | outputs: 49 | sha: ${{ steps.commit.outputs.sha }} 50 | versions-matrix: ${{ steps.versions-matrix.outputs.matrix }} 51 | dep-suggests-matrix: ${{ steps.dep-suggests-matrix.outputs.matrix }} 52 | 53 | name: "Smoke test: stock R" 54 | 55 | permissions: 56 | contents: write 57 | statuses: write 58 | pull-requests: write 59 | actions: write 60 | 61 | # Begin custom: services 62 | # End custom: services 63 | 64 | steps: 65 | - uses: actions/checkout@v4 66 | with: 67 | ref: ${{ inputs.ref }} 68 | 69 | - name: Update status for rcc 70 | # FIXME: Wrap into action 71 | if: github.event_name == 'workflow_dispatch' 72 | env: 73 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} 74 | run: | 75 | # Check status of this workflow 76 | state="pending" 77 | sha=${{ inputs.ref }} 78 | if [ -z "${sha}" ]; then 79 | sha=${{ github.head_ref }} 80 | fi 81 | if [ -z "${sha}" ]; then 82 | sha=${{ github.sha }} 83 | fi 84 | sha=$(git rev-parse ${sha}) 85 | 86 | html_url=$(gh api \ 87 | -H "Accept: application/vnd.github+json" \ 88 | -H "X-GitHub-Api-Version: 2022-11-28" \ 89 | repos/${{ github.repository }}/actions/runs/${{ github.run_id }} | jq -r .html_url) 90 | 91 | description="${{ github.workflow }} / ${{ github.job }}" 92 | 93 | gh api \ 94 | --method POST \ 95 | -H "Accept: application/vnd.github+json" \ 96 | -H "X-GitHub-Api-Version: 2022-11-28" \ 97 | repos/${{ github.repository }}/statuses/${sha} \ 98 | -f "state=${state}" -f "target_url=${html_url}" -f "description=${description}" -f "context=rcc" 99 | shell: bash 100 | 101 | - uses: ./.github/workflows/rate-limit 102 | with: 103 | token: ${{ secrets.GITHUB_TOKEN }} 104 | 105 | - uses: ./.github/workflows/git-identity 106 | 107 | - uses: ./.github/workflows/custom/before-install 108 | if: hashFiles('.github/workflows/custom/before-install/action.yml') != '' 109 | 110 | - uses: ./.github/workflows/install 111 | with: 112 | token: ${{ secrets.GITHUB_TOKEN }} 113 | cache-version: rcc-smoke-2 114 | needs: build, check, website 115 | # Beware of using dev pkgdown here, has brought in dev dependencies in the past 116 | extra-packages: any::rcmdcheck r-lib/roxygen2 any::decor r-lib/styler r-lib/pkgdown deps::. 117 | 118 | - uses: ./.github/workflows/custom/after-install 119 | if: hashFiles('.github/workflows/custom/after-install/action.yml') != '' 120 | 121 | # Must come after the custom after-install workflow 122 | - name: Install package 123 | run: | 124 | _R_SHLIB_STRIP_=true R CMD INSTALL . 125 | shell: bash 126 | 127 | - id: versions-matrix 128 | # Only run for pull requests if the base repo is different from the head repo, not for workflow_dispatch if not requested, always run for other events 129 | if: (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.repository) && (github.event_name != 'workflow_dispatch' || inputs.versions-matrix) 130 | uses: ./.github/workflows/versions-matrix 131 | 132 | - id: dep-suggests-matrix 133 | # Not for workflow_dispatch if not requested, always run for other events 134 | if: github.event_name != 'workflow_dispatch' || inputs.dep-suggests-matrix 135 | uses: ./.github/workflows/dep-suggests-matrix 136 | 137 | - uses: ./.github/workflows/update-snapshots 138 | with: 139 | base: ${{ inputs.ref || github.head_ref }} 140 | 141 | - uses: ./.github/workflows/style 142 | 143 | - uses: ./.github/workflows/roxygenize 144 | 145 | - name: Remove config files from previous iteration 146 | run: | 147 | rm -f .github/dep-suggests-matrix.json .github/versions-matrix.json 148 | shell: bash 149 | 150 | - id: commit 151 | uses: ./.github/workflows/commit 152 | with: 153 | token: ${{ secrets.GITHUB_TOKEN }} 154 | 155 | - uses: ./.github/workflows/check 156 | with: 157 | results: ${{ runner.os }}-smoke-test 158 | 159 | - uses: ./.github/workflows/pkgdown-build 160 | if: github.event_name != 'push' 161 | 162 | - uses: ./.github/workflows/pkgdown-deploy 163 | if: github.event_name == 'push' 164 | 165 | # Upload sha as artifact 166 | - run: | 167 | echo -n "${{ steps.commit.outputs.sha }}" > rcc-smoke-sha.txt 168 | shell: bash 169 | 170 | - uses: actions/upload-artifact@v4 171 | with: 172 | name: rcc-smoke-sha 173 | path: rcc-smoke-sha.txt 174 | 175 | - name: Update status for rcc 176 | # FIXME: Wrap into action 177 | if: always() && github.event_name == 'workflow_dispatch' 178 | env: 179 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} 180 | run: | 181 | # Check status of this workflow 182 | if [ "${{ job.status }}" == "success" ]; then 183 | state="success" 184 | else 185 | state="failure" 186 | fi 187 | 188 | sha=${{ steps.commit.outputs.sha }} 189 | if [ -z "${sha}" ]; then 190 | sha=${{ inputs.ref }} 191 | fi 192 | if [ -z "${sha}" ]; then 193 | sha=${{ github.head_ref }} 194 | fi 195 | if [ -z "${sha}" ]; then 196 | sha=${{ github.sha }} 197 | fi 198 | sha=$(git rev-parse ${sha}) 199 | 200 | html_url=$(gh api \ 201 | -H "Accept: application/vnd.github+json" \ 202 | -H "X-GitHub-Api-Version: 2022-11-28" \ 203 | repos/${{ github.repository }}/actions/runs/${{ github.run_id }} | jq -r .html_url) 204 | 205 | description="${{ github.workflow }} / ${{ github.job }}" 206 | 207 | gh api \ 208 | --method POST \ 209 | -H "Accept: application/vnd.github+json" \ 210 | -H "X-GitHub-Api-Version: 2022-11-28" \ 211 | repos/${{ github.repository }}/statuses/${sha} \ 212 | -f "state=${state}" -f "target_url=${html_url}" -f "description=${description}" -f "context=rcc" 213 | shell: bash 214 | 215 | rcc-smoke-check-matrix: 216 | runs-on: ubuntu-24.04 217 | 218 | name: "Check matrix" 219 | 220 | needs: 221 | - rcc-smoke 222 | 223 | steps: 224 | - uses: actions/checkout@v4 225 | with: 226 | ref: ${{ needs.rcc-smoke.outputs.sha }} 227 | 228 | - uses: ./.github/workflows/matrix-check 229 | with: 230 | matrix: ${{ needs.rcc-smoke.outputs.versions-matrix }} 231 | 232 | - uses: ./.github/workflows/matrix-check 233 | with: 234 | matrix: ${{ needs.rcc-smoke.outputs.dep-suggests-matrix }} 235 | 236 | rcc-full: 237 | needs: 238 | - rcc-smoke 239 | 240 | runs-on: ${{ matrix.os }} 241 | 242 | if: ${{ needs.rcc-smoke.outputs.versions-matrix != '' }} 243 | 244 | name: 'rcc: ${{ matrix.os }} (${{ matrix.r }}) ${{ matrix.desc }}' 245 | 246 | # Begin custom: services 247 | # End custom: services 248 | 249 | strategy: 250 | fail-fast: false 251 | matrix: ${{fromJson(needs.rcc-smoke.outputs.versions-matrix)}} 252 | 253 | steps: 254 | - uses: actions/checkout@v4 255 | with: 256 | ref: ${{ needs.rcc-smoke.outputs.sha }} 257 | 258 | - uses: ./.github/workflows/custom/before-install 259 | if: hashFiles('.github/workflows/custom/before-install/action.yml') != '' 260 | 261 | - uses: ./.github/workflows/install 262 | with: 263 | r-version: ${{ matrix.r }} 264 | cache-version: rcc-full-1 265 | token: ${{ secrets.GITHUB_TOKEN }} 266 | needs: build, check 267 | 268 | - uses: ./.github/workflows/custom/after-install 269 | if: hashFiles('.github/workflows/custom/after-install/action.yml') != '' 270 | 271 | - name: Must allow NOTEs if packages are missing, even with _R_CHECK_FORCE_SUGGESTS_ 272 | run: | 273 | if (Sys.getenv("RCMDCHECK_ERROR_ON") %in% c("", "note")) { 274 | pkgs <- setdiff(desc::desc_get_deps()$package, "R") 275 | installable <- vapply(pkgs, FUN.VALUE = logical(1), requireNamespace, quietly = TRUE) 276 | if (any(!installable)) { 277 | message("Missing packages: ", paste(pkgs[!installable], collapse = ", ")) 278 | cat('RCMDCHECK_ERROR_ON="warning"\n', file = Sys.getenv("GITHUB_ENV"), append = TRUE) 279 | } 280 | } 281 | shell: Rscript {0} 282 | 283 | - uses: ./.github/workflows/update-snapshots 284 | if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository 285 | 286 | - uses: ./.github/workflows/check 287 | if: ${{ ! matrix.covr }} 288 | with: 289 | results: ${{ runner.os }}-r${{ matrix.r }} 290 | 291 | - uses: ./.github/workflows/covr 292 | if: ${{ matrix.covr }} 293 | with: 294 | token: ${{ secrets.CODECOV_TOKEN }} 295 | 296 | # The status update is taken care of by R-CMD-check-status.yaml 297 | 298 | rcc-suggests: 299 | needs: 300 | - rcc-smoke 301 | 302 | runs-on: ubuntu-22.04 303 | 304 | if: ${{ needs.rcc-smoke.outputs.dep-suggests-matrix != '' }} 305 | 306 | name: Without ${{ matrix.package }} 307 | 308 | # Begin custom: services 309 | # End custom: services 310 | 311 | strategy: 312 | fail-fast: false 313 | matrix: ${{fromJson(needs.rcc-smoke.outputs.dep-suggests-matrix)}} 314 | 315 | steps: 316 | - uses: actions/checkout@v4 317 | 318 | - uses: ./.github/workflows/custom/before-install 319 | if: hashFiles('.github/workflows/custom/before-install/action.yml') != '' 320 | 321 | - uses: ./.github/workflows/install 322 | with: 323 | cache-version: rcc-dev-${{ matrix.package }}-1 324 | needs: build, check 325 | extra-packages: "any::rcmdcheck any::remotes ." 326 | token: ${{ secrets.GITHUB_TOKEN }} 327 | 328 | - name: Remove ${{ matrix.package }} and all strong dependencies 329 | run: | 330 | pkg <- "${{ matrix.package }}" 331 | pkgs <- tools::package_dependencies(pkg, reverse = TRUE)[[1]] 332 | installed <- rownames(utils::installed.packages()) 333 | to_remove <- c(pkg, intersect(pkgs, installed)) 334 | print(to_remove) 335 | remove.packages(to_remove) 336 | shell: Rscript {0} 337 | 338 | - name: Session info 339 | run: | 340 | options(width = 100) 341 | if (!requireNamespace("sessioninfo", quietly = TRUE)) install.packages("sessioninfo") 342 | pkgs <- installed.packages()[, "Package"] 343 | sessioninfo::session_info(pkgs, include_base = TRUE) 344 | shell: Rscript {0} 345 | 346 | - uses: ./.github/workflows/custom/after-install 347 | if: hashFiles('.github/workflows/custom/after-install/action.yml') != '' 348 | 349 | - name: Must allow NOTEs, even with _R_CHECK_FORCE_SUGGESTS_ 350 | run: | 351 | if (Sys.getenv("RCMDCHECK_ERROR_ON") %in% c("", "note")) { 352 | cat('RCMDCHECK_ERROR_ON="warning"\n', file = Sys.getenv("GITHUB_ENV"), append = TRUE) 353 | } 354 | shell: Rscript {0} 355 | 356 | - name: Check env vars 357 | run: | 358 | print(Sys.getenv('_R_CHECK_FORCE_SUGGESTS_')) 359 | print(Sys.getenv('RCMDCHECK_ERROR_ON')) 360 | shell: Rscript {0} 361 | 362 | - uses: ./.github/workflows/check 363 | with: 364 | results: ${{ matrix.package }} 365 | 366 | # The status update is taken care of by R-CMD-check-status.yaml 367 | -------------------------------------------------------------------------------- /.github/workflows/check/action.yml: -------------------------------------------------------------------------------- 1 | name: "Actions to check an R package" 2 | inputs: 3 | results: 4 | description: Slug for check results 5 | required: true 6 | 7 | runs: 8 | using: "composite" 9 | steps: 10 | - uses: r-lib/actions/check-r-package@v2 11 | with: 12 | # Fails on R 3.6 on Windows, remove when this job is removed? 13 | args: 'c("--no-manual", "--as-cran", "--no-multiarch")' 14 | error-on: ${{ env.RCMDCHECK_ERROR_ON || '"note"' }} 15 | 16 | - name: Show test output 17 | if: always() 18 | run: | 19 | ## -- Show test output -- 20 | echo "::group::Test output" 21 | find check -name '*.Rout*' -exec head -n 1000000 '{}' \; || true 22 | echo "::endgroup::" 23 | shell: bash 24 | 25 | - name: Upload check results 26 | if: failure() 27 | uses: actions/upload-artifact@main 28 | with: 29 | name: ${{ inputs.results }}-results 30 | path: check 31 | -------------------------------------------------------------------------------- /.github/workflows/commit/action.yml: -------------------------------------------------------------------------------- 1 | name: "Action to commit changes to the repository" 2 | inputs: 3 | token: 4 | description: "GitHub token" 5 | required: true 6 | outputs: 7 | sha: 8 | description: "SHA of generated commit" 9 | value: ${{ steps.commit.outputs.sha }} 10 | 11 | runs: 12 | using: "composite" 13 | steps: 14 | - name: Commit if changed, create a PR if protected 15 | id: commit 16 | env: 17 | GITHUB_TOKEN: ${{ inputs.token }} 18 | run: | 19 | set -x 20 | if [ -n "$(git status --porcelain)" ]; then 21 | echo "Changed" 22 | protected=${{ github.ref_protected }} 23 | foreign=${{ github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != github.repository }} 24 | if [ "${foreign}" = "true" ]; then 25 | # https://github.com/krlmlr/actions-sync/issues/44 26 | echo "Can't push to foreign branch" 27 | elif [ "${protected}" = "true" ]; then 28 | current_branch=$(git branch --show-current) 29 | new_branch=gha-commit-$(git rev-parse --short HEAD) 30 | git checkout -b ${new_branch} 31 | git add . 32 | git commit -m "chore: Auto-update from GitHub Actions"$'\n'$'\n'"Run: ${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}" 33 | # Force-push, used in only one place 34 | # Alternative: separate branch names for each usage 35 | git push -u origin HEAD -f 36 | 37 | existing_pr=$(gh pr list --state open --base main --head ${new_branch} --json number --jq '.[] | .number') 38 | if [ -n "${existing_pr}" ]; then 39 | echo "Existing PR: ${existing_pr}" 40 | else 41 | gh pr create --base main --head ${new_branch} --title "chore: Auto-update from GitHub Actions" --body "Run: ${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}" 42 | fi 43 | 44 | gh workflow run rcc -f ref=$(git rev-parse HEAD) 45 | gh pr merge --merge --auto 46 | else 47 | git fetch 48 | if [ -n "${GITHUB_HEAD_REF}" ]; then 49 | git add . 50 | git stash save 51 | git switch ${GITHUB_HEAD_REF} 52 | git merge origin/${GITHUB_BASE_REF} --no-edit 53 | git stash pop 54 | fi 55 | git add . 56 | git commit -m "chore: Auto-update from GitHub Actions"$'\n'$'\n'"Run: ${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}" 57 | git push -u origin HEAD 58 | 59 | # Only set output if changed 60 | echo sha=$(git rev-parse HEAD) >> $GITHUB_OUTPUT 61 | fi 62 | fi 63 | shell: bash 64 | -------------------------------------------------------------------------------- /.github/workflows/covr/action.yml: -------------------------------------------------------------------------------- 1 | name: "Actions to run covr for an R package" 2 | inputs: 3 | token: 4 | description: codecov token 5 | required: false 6 | 7 | runs: 8 | using: "composite" 9 | steps: 10 | - name: Run coverage check 11 | run: | 12 | if (dir.exists("tests/testthat")) { 13 | cov <- covr::package_coverage( 14 | quiet = FALSE, 15 | clean = FALSE, 16 | install_path = file.path(normalizePath(Sys.getenv("RUNNER_TEMP"), winslash = "/"), "package") 17 | ) 18 | covr::to_cobertura(cov) 19 | } else { 20 | message("No tests found, coverage not tested.") 21 | } 22 | shell: Rscript {0} 23 | 24 | - uses: codecov/codecov-action@v5 25 | with: 26 | # Fail if token is given 27 | fail_ci_if_error: ${{ inputs.token != '' }} 28 | files: ./cobertura.xml 29 | plugins: noop 30 | disable_search: true 31 | token: ${{ inputs.token }} 32 | 33 | - name: Show testthat output 34 | if: always() 35 | run: | 36 | ## -------------------------------------------------------------------- 37 | find '${{ runner.temp }}/package' -name 'testthat.Rout*' -exec cat '{}' \; || true 38 | shell: bash 39 | 40 | - name: Upload test results 41 | if: failure() 42 | uses: actions/upload-artifact@v4 43 | with: 44 | name: coverage-test-failures 45 | path: ${{ runner.temp }}/package 46 | -------------------------------------------------------------------------------- /.github/workflows/dep-matrix/action.yml: -------------------------------------------------------------------------------- 1 | name: "Actions to compute a matrix with all dependent packages" 2 | outputs: 3 | matrix: 4 | description: "Generated matrix" 5 | value: ${{ steps.set-matrix.outputs.matrix }} 6 | 7 | runs: 8 | using: "composite" 9 | steps: 10 | - id: set-matrix 11 | run: | 12 | # Determine package dependencies 13 | # From remotes 14 | read_dcf <- function(path) { 15 | fields <- colnames(read.dcf(path)) 16 | as.list(read.dcf(path, keep.white = fields)[1, ]) 17 | } 18 | 19 | re_match <- function(text, pattern, perl = TRUE, ...) { 20 | 21 | stopifnot(is.character(pattern), length(pattern) == 1, !is.na(pattern)) 22 | text <- as.character(text) 23 | 24 | match <- regexpr(pattern, text, perl = perl, ...) 25 | 26 | start <- as.vector(match) 27 | length <- attr(match, "match.length") 28 | end <- start + length - 1L 29 | 30 | matchstr <- substring(text, start, end) 31 | matchstr[ start == -1 ] <- NA_character_ 32 | 33 | res <- data.frame( 34 | stringsAsFactors = FALSE, 35 | .text = text, 36 | .match = matchstr 37 | ) 38 | 39 | if (!is.null(attr(match, "capture.start"))) { 40 | 41 | gstart <- attr(match, "capture.start") 42 | glength <- attr(match, "capture.length") 43 | gend <- gstart + glength - 1L 44 | 45 | groupstr <- substring(text, gstart, gend) 46 | groupstr[ gstart == -1 ] <- NA_character_ 47 | dim(groupstr) <- dim(gstart) 48 | 49 | res <- cbind(groupstr, res, stringsAsFactors = FALSE) 50 | } 51 | 52 | names(res) <- c(attr(match, "capture.names"), ".text", ".match") 53 | class(res) <- c("tbl_df", "tbl", class(res)) 54 | res 55 | } 56 | 57 | dev_split_ref <- function(x) { 58 | re_match(x, "^(?[^@#]+)(?[@#].*)?$") 59 | } 60 | 61 | has_dev_dep <- function(package) { 62 | cran_url <- "https://cloud.r-project.org" 63 | 64 | refs <- dev_split_ref(package) 65 | url <- file.path(cran_url, "web", "packages", refs[["pkg"]], "DESCRIPTION") 66 | 67 | f <- tempfile() 68 | on.exit(unlink(f)) 69 | 70 | utils::download.file(url, f) 71 | desc <- read_dcf(f) 72 | 73 | url_fields <- c(desc$URL, desc$BugReports) 74 | 75 | if (length(url_fields) == 0) { 76 | return(FALSE) 77 | } 78 | 79 | pkg_urls <- unlist(strsplit(url_fields, "[[:space:]]*,[[:space:]]*")) 80 | 81 | # Remove trailing "/issues" from the BugReports URL 82 | pkg_urls <- sub("/issues$", "", pkg_urls) 83 | 84 | valid_domains <- c("github[.]com", "gitlab[.]com", "bitbucket[.]org") 85 | 86 | parts <- 87 | re_match(pkg_urls, 88 | sprintf("^https?://(?%s)/(?%s)/(?%s)(?:/(?%s))?", 89 | domain = paste0(valid_domains, collapse = "|"), 90 | username = "[^/]+", 91 | repo = "[^/@#]+", 92 | subdir = "[^/@$ ]+" 93 | ) 94 | )[c("domain", "username", "repo", "subdir")] 95 | 96 | # Remove cases which don't match and duplicates 97 | 98 | parts <- unique(stats::na.omit(parts)) 99 | 100 | nrow(parts) == 1 101 | } 102 | 103 | if (!requireNamespace("desc", quietly = TRUE)) { 104 | install.packages("desc") 105 | } 106 | 107 | deps_df <- desc::desc_get_deps() 108 | deps_df <- deps_df[deps_df$type %in% c("Depends", "Imports", "LinkingTo", "Suggests"), ] 109 | 110 | packages <- sort(deps_df$package) 111 | packages <- intersect(packages, rownames(available.packages())) 112 | 113 | valid_dev_dep <- vapply(packages, has_dev_dep, logical(1)) 114 | 115 | # https://github.com/r-lib/remotes/issues/576 116 | valid_dev_dep[packages %in% c("igraph", "duckdb", "logging")] <- FALSE 117 | 118 | deps <- packages[valid_dev_dep] 119 | if (any(!valid_dev_dep)) { 120 | msg <- paste0( 121 | "Could not determine development repository for packages: ", 122 | paste(packages[!valid_dev_dep], collapse = ", ") 123 | ) 124 | writeLines(paste0("::warning::", msg)) 125 | } 126 | 127 | json <- paste0( 128 | '{"package":[', 129 | paste0('"', deps, '"', collapse = ","), 130 | ']}' 131 | ) 132 | writeLines(json) 133 | writeLines(paste0("matrix=", json), Sys.getenv("GITHUB_OUTPUT")) 134 | shell: Rscript {0} 135 | -------------------------------------------------------------------------------- /.github/workflows/dep-suggests-matrix/action.R: -------------------------------------------------------------------------------- 1 | # FIXME: Dynamic lookup by parsing https://svn.r-project.org/R/tags/ 2 | get_deps <- function() { 3 | # Determine package dependencies 4 | if (!requireNamespace("desc", quietly = TRUE)) { 5 | install.packages("desc") 6 | } 7 | 8 | deps_df <- desc::desc_get_deps() 9 | deps_df_optional <- deps_df$package[deps_df$type %in% c("Suggests", "Enhances")] 10 | deps_df_hard <- deps_df$package[deps_df$type %in% c("Depends", "Imports", "LinkingTo")] 11 | deps_df_base <- unlist(tools::standard_package_names(), use.names = FALSE) 12 | 13 | packages <- sort(deps_df_optional) 14 | packages <- intersect(packages, rownames(available.packages())) 15 | 16 | # Too big to fail, or can't be avoided: 17 | off_limits <- c("testthat", "rmarkdown", "rcmdcheck", deps_df_hard, deps_df_base) 18 | off_limits_dep <- unlist(tools::package_dependencies(off_limits, recursive = TRUE, which = "strong")) 19 | setdiff(packages, c(off_limits, off_limits_dep)) 20 | } 21 | 22 | if (Sys.getenv("GITHUB_BASE_REF") != "") { 23 | print(Sys.getenv("GITHUB_BASE_REF")) 24 | system("git fetch origin ${GITHUB_BASE_REF}") 25 | # Use .. to avoid having to fetch the entire history 26 | # https://github.com/krlmlr/actions-sync/issues/45 27 | diff_cmd <- "git diff origin/${GITHUB_BASE_REF}.. -- R/ tests/ | egrep '^[+][^+]' | grep -q ::" 28 | diff_lines <- system(diff_cmd, intern = TRUE) 29 | if (length(diff_lines) > 0) { 30 | writeLines("Changes using :: in R/ or tests/:") 31 | writeLines(diff_lines) 32 | packages <- get_deps() 33 | } else { 34 | writeLines("No changes using :: found in R/ or tests/, not checking without suggested packages") 35 | packages <- character() 36 | } 37 | } else { 38 | writeLines("No GITHUB_BASE_REF, checking without suggested packages") 39 | packages <- get_deps() 40 | } 41 | 42 | if (length(packages) > 0) { 43 | json <- paste0( 44 | '{"package":[', 45 | paste0('"', packages, '"', collapse = ","), 46 | "]}" 47 | ) 48 | writeLines(paste0("matrix=", json), Sys.getenv("GITHUB_OUTPUT")) 49 | writeLines(json) 50 | } else { 51 | writeLines("No suggested packages found.") 52 | } 53 | -------------------------------------------------------------------------------- /.github/workflows/dep-suggests-matrix/action.yml: -------------------------------------------------------------------------------- 1 | name: "Actions to compute a matrix with all suggested packages" 2 | outputs: 3 | matrix: 4 | description: "Generated matrix" 5 | value: ${{ steps.set-matrix.outputs.matrix }} 6 | 7 | runs: 8 | using: "composite" 9 | steps: 10 | - id: set-matrix 11 | run: | 12 | Rscript ./.github/workflows/dep-suggests-matrix/action.R 13 | shell: bash 14 | -------------------------------------------------------------------------------- /.github/workflows/fledge.yaml: -------------------------------------------------------------------------------- 1 | name: fledge 2 | 3 | on: 4 | # for manual triggers 5 | workflow_dispatch: 6 | inputs: 7 | pr: 8 | description: "Create PR" 9 | required: false 10 | type: boolean 11 | default: false 12 | # daily run 13 | schedule: 14 | - cron: "30 0 * * *" 15 | 16 | concurrency: 17 | group: ${{ github.workflow }}-${{ github.ref }}-${{ github.head_ref || '' }}-${{ github.base_ref || '' }} 18 | cancel-in-progress: true 19 | 20 | jobs: 21 | check_fork: 22 | runs-on: ubuntu-24.04 23 | outputs: 24 | is_forked: ${{ steps.check.outputs.is_forked }} 25 | steps: 26 | - name: Check if the repo is forked 27 | id: check 28 | env: 29 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} 30 | run: | 31 | is_forked=$(gh api repos/${{ github.repository }} | jq .fork) 32 | echo "is_forked=${is_forked}" >> $GITHUB_OUTPUT 33 | shell: bash 34 | 35 | fledge: 36 | runs-on: ubuntu-24.04 37 | needs: check_fork 38 | if: needs.check_fork.outputs.is_forked == 'false' 39 | permissions: 40 | contents: write 41 | pull-requests: write 42 | actions: write 43 | env: 44 | FLEDGE_GHA_CI: true 45 | steps: 46 | - uses: actions/checkout@v4 47 | with: 48 | fetch-depth: 0 49 | fetch-tags: true 50 | 51 | - name: Configure Git identity 52 | run: | 53 | env | sort 54 | git config --local user.name "$GITHUB_ACTOR" 55 | git config --local user.email "$GITHUB_ACTOR@users.noreply.github.com" 56 | shell: bash 57 | 58 | - name: Update apt 59 | run: | 60 | sudo apt-get update 61 | shell: bash 62 | 63 | - uses: r-lib/actions/setup-r@v2 64 | with: 65 | use-public-rspm: true 66 | 67 | - uses: r-lib/actions/setup-r-dependencies@v2 68 | env: 69 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} 70 | with: 71 | pak-version: devel 72 | packages: cynkra/fledge 73 | cache-version: fledge-1 74 | 75 | - name: Count rulesets 76 | # Assume that branch is protected if ruleset exists 77 | id: rulesets 78 | env: 79 | GH_TOKEN: ${{ github.token }} 80 | run: | 81 | n_rulesets=$(gh api repos/${{ github.repository }}/rulesets -q length) 82 | echo "count=${n_rulesets}" >> $GITHUB_OUTPUT 83 | shell: bash 84 | 85 | - name: Switch to branch if branch protection is enabled 86 | if: github.ref_protected == 'true' || inputs.pr == 'true' || steps.rulesets.outputs.count > 0 87 | run: | 88 | git checkout -b fledge 89 | git push -f -u origin HEAD 90 | shell: bash 91 | 92 | - name: Bump version 93 | env: 94 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} 95 | run: | 96 | check_default_branch <- ("${{ github.ref_protected == 'true' || inputs.pr == 'true' || steps.rulesets.outputs.count > 0 }}" != "true") 97 | if (fledge::bump_version(which = "dev", no_change_behavior = "noop", check_default_branch = check_default_branch)) { 98 | fledge::finalize_version(push = TRUE) 99 | } 100 | shell: Rscript {0} 101 | 102 | - name: Create and merge PR if branch protection is enabled 103 | if: github.ref_protected == 'true' || inputs.pr == 'true' || steps.rulesets.outputs.count > 0 104 | env: 105 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} 106 | run: | 107 | set -ex 108 | if [ -n "$(git diff main --numstat)" ]; then 109 | gh pr create --base main --head fledge --fill-first 110 | gh workflow run rcc -f ref=$(git rev-parse HEAD) 111 | gh pr merge --squash --auto 112 | else 113 | echo "No changes." 114 | fi 115 | shell: bash 116 | 117 | - name: Check release 118 | env: 119 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} 120 | run: | 121 | fledge:::release_after_cran_built_binaries() 122 | shell: Rscript {0} 123 | -------------------------------------------------------------------------------- /.github/workflows/get-extra/action.yml: -------------------------------------------------------------------------------- 1 | name: "Action to determine extra packages to be installed" 2 | outputs: 3 | packages: 4 | description: "List of extra packages" 5 | value: ${{ steps.get-extra.outputs.packages }} 6 | 7 | runs: 8 | using: "composite" 9 | steps: 10 | - name: Get extra packages 11 | id: get-extra 12 | run: | 13 | set -x 14 | packages=$( ( grep Config/gha/extra-packages DESCRIPTION || true ) | cut -d " " -f 2) 15 | echo packages=$packages >> $GITHUB_OUTPUT 16 | shell: bash 17 | -------------------------------------------------------------------------------- /.github/workflows/git-identity/action.yml: -------------------------------------------------------------------------------- 1 | name: "Actions to set up a Git identity" 2 | 3 | runs: 4 | using: "composite" 5 | steps: 6 | - name: Configure Git identity 7 | run: | 8 | env | sort 9 | git config --local user.name "$GITHUB_ACTOR" 10 | git config --local user.email "$GITHUB_ACTOR@users.noreply.github.com" 11 | shell: bash 12 | -------------------------------------------------------------------------------- /.github/workflows/install/action.yml: -------------------------------------------------------------------------------- 1 | name: "Actions to run for installing R packages" 2 | inputs: 3 | token: 4 | description: GitHub token, set to secrets.GITHUB_TOKEN 5 | required: true 6 | r-version: 7 | description: Passed on to r-lib/actions/setup-r@v2 8 | required: false 9 | default: release 10 | install-r: 11 | description: Passed on to r-lib/actions/setup-r@v2 12 | required: false 13 | default: true 14 | needs: 15 | description: Passed on to r-lib/actions/setup-r-dependencies@v2 16 | required: false 17 | default: "" 18 | packages: 19 | description: Passed on to r-lib/actions/setup-r-dependencies@v2 20 | required: false 21 | default: deps::., any::sessioninfo 22 | extra-packages: 23 | description: Passed on to r-lib/actions/setup-r-dependencies@v2 24 | required: false 25 | default: any::rcmdcheck 26 | cache-version: 27 | description: Passed on to r-lib/actions/setup-r-dependencies@v2 28 | required: false 29 | default: 1 30 | 31 | runs: 32 | using: "composite" 33 | steps: 34 | - name: Set environment variables 35 | run: | 36 | echo "R_REMOTES_NO_ERRORS_FROM_WARNINGS=true" | tee -a $GITHUB_ENV 37 | echo "R_KEEP_PKG_SOURCE=yes" | tee -a $GITHUB_ENV 38 | echo "_R_CHECK_SYSTEM_CLOCK_=false" | tee -a $GITHUB_ENV 39 | echo "_R_CHECK_FUTURE_FILE_TIMESTAMPS_=false" | tee -a $GITHUB_ENV 40 | # prevent rgl issues because no X11 display is available 41 | echo "RGL_USE_NULL=true" | tee -a $GITHUB_ENV 42 | # from https://github.com/r-devel/r-dev-web/blob/main/CRAN/QA/Kurt/lib/R/Scripts/check_CRAN_incoming.R 43 | echo "_R_CHECK_CRAN_INCOMING_CHECK_FILE_URIS_=true" | tee -a $GITHUB_ENV 44 | echo "_R_CHECK_CRAN_INCOMING_NOTE_GNU_MAKE_=true" | tee -a $GITHUB_ENV 45 | echo "_R_CHECK_PACKAGE_DEPENDS_IGNORE_MISSING_ENHANCES_=true" | tee -a $GITHUB_ENV 46 | echo "_R_CHECK_CODE_CLASS_IS_STRING_=true" | tee -a $GITHUB_ENV 47 | echo "_R_CHECK_CODOC_VARIABLES_IN_USAGES_=true" | tee -a $GITHUB_ENV 48 | echo "_R_CHECK_CONNECTIONS_LEFT_OPEN_=true" | tee -a $GITHUB_ENV 49 | echo "_R_CHECK_DATALIST_=true" | tee -a $GITHUB_ENV 50 | echo "_R_CHECK_NEWS_IN_PLAIN_TEXT_=true" | tee -a $GITHUB_ENV 51 | echo "_R_CHECK_PACKAGES_USED_CRAN_INCOMING_NOTES_=true" | tee -a $GITHUB_ENV 52 | echo "_R_CHECK_RD_CONTENTS_KEYWORDS_=true" | tee -a $GITHUB_ENV 53 | echo "_R_CHECK_R_DEPENDS_=warn" | tee -a $GITHUB_ENV 54 | echo "_R_CHECK_S3_METHODS_SHOW_POSSIBLE_ISSUES_=true" | tee -a $GITHUB_ENV 55 | echo "_R_CHECK_THINGS_IN_TEMP_DIR_=true" | tee -a $GITHUB_ENV 56 | echo "_R_CHECK_UNDOC_USE_ALL_NAMES_=true" | tee -a $GITHUB_ENV 57 | echo "_R_CHECK_URLS_SHOW_301_STATUS_=true" | tee -a $GITHUB_ENV 58 | echo "_R_CXX_USE_NO_REMAP_=true" | tee -a $GITHUB_ENV 59 | # There is no way to disable recency and frequency checks when the incoming checks are run 60 | # echo "_R_CHECK_CRAN_INCOMING_=true" | tee -a $GITHUB_ENV 61 | echo "_R_CHECK_CRAN_INCOMING_SKIP_LARGE_VERSION_=true" | tee -a $GITHUB_ENV 62 | echo "_R_CHECK_FORCE_SUGGESTS_=false" | tee -a $GITHUB_ENV 63 | shell: bash 64 | 65 | - name: Set environment variables (non-Windows only) 66 | if: runner.os != 'Windows' 67 | run: | 68 | echo "_R_CHECK_BASHISMS_=true" | tee -a $GITHUB_ENV 69 | shell: bash 70 | 71 | - name: Update apt 72 | if: runner.os == 'Linux' 73 | run: | 74 | sudo apt-get update 75 | sudo apt-get install -y aspell 76 | echo "_R_CHECK_CRAN_INCOMING_USE_ASPELL_=true" | tee -a $GITHUB_ENV 77 | shell: bash 78 | 79 | - name: Remove pkg-config@0.29.2 80 | if: runner.os == 'macOS' 81 | run: | 82 | brew uninstall pkg-config@0.29.2 || true 83 | shell: bash 84 | 85 | - uses: r-lib/actions/setup-pandoc@v2 86 | 87 | - uses: r-lib/actions/setup-r@v2 88 | with: 89 | r-version: ${{ inputs.r-version }} 90 | install-r: ${{ inputs.install-r }} 91 | http-user-agent: ${{ matrix.config.http-user-agent }} 92 | use-public-rspm: true 93 | 94 | - id: get-extra 95 | run: | 96 | set -x 97 | packages=$( ( grep Config/gha/extra-packages DESCRIPTION || true ) | cut -d " " -f 2) 98 | echo packages=$packages >> $GITHUB_OUTPUT 99 | shell: bash 100 | 101 | - uses: r-lib/actions/setup-r-dependencies@v2 102 | env: 103 | GITHUB_PAT: ${{ inputs.token }} 104 | with: 105 | pak-version: stable 106 | needs: ${{ inputs.needs }} 107 | packages: ${{ inputs.packages }} 108 | extra-packages: ${{ inputs.extra-packages }} ${{ ( matrix.covr && 'covr xml2' ) || '' }} ${{ steps.get-extra.outputs.packages }} 109 | cache-version: ${{ inputs.cache-version }} 110 | 111 | - name: Add pkg.lock to .gitignore 112 | run: | 113 | set -x 114 | if ! [ -f .github/.gitignore ] || [ -z "$(grep '^/pkg.lock$' .github/.gitignore)" ]; then 115 | echo /pkg.lock >> .github/.gitignore 116 | fi 117 | shell: bash 118 | 119 | - name: Add fake qpdf and checkbashisms 120 | if: runner.os == 'Linux' 121 | run: | 122 | sudo ln -s $(which true) /usr/local/bin/qpdf 123 | sudo ln -s $(which true) /usr/local/bin/checkbashisms 124 | shell: bash 125 | 126 | - name: Install ccache 127 | uses: krlmlr/ccache-action@parallel-dir 128 | with: 129 | max-size: 10G 130 | verbose: 1 131 | save: false 132 | restore: false 133 | 134 | - name: Use ccache for compiling R code, and parallelize 135 | run: | 136 | mkdir -p ~/.R 137 | echo 'CC := ccache $(CC)' >> ~/.R/Makevars 138 | echo 'CXX := ccache $(CXX)' >> ~/.R/Makevars 139 | echo 'CXX11 := ccache $(CXX11)' >> ~/.R/Makevars 140 | echo 'CXX14 := ccache $(CXX14)' >> ~/.R/Makevars 141 | echo 'CXX17 := ccache $(CXX17)' >> ~/.R/Makevars 142 | echo 'MAKEFLAGS = -j2' >> ~/.R/Makevars 143 | cat ~/.R/Makevars 144 | 145 | echo 'CCACHE_SLOPPINESS=locale,time_macros' | tee -a $GITHUB_ENV 146 | 147 | # echo 'CCACHE_DEBUG=true' | tee -a $GITHUB_ENV 148 | # echo "CCACHE_DEBUGDIR=$(dirname $(pwd))/ccache-debug" | tee -a $GITHUB_ENV 149 | # mkdir -p $(dirname $(pwd))/.ccache-debug 150 | 151 | echo 'PKG_BUILD_EXTRA_FLAGS=false' | tee -a $GITHUB_ENV 152 | 153 | # Repair 154 | git rm -rf .ccache || true 155 | rm -rf .ccache 156 | shell: bash 157 | 158 | - name: Show R CMD config --all 159 | run: | 160 | R CMD config --all 161 | shell: bash 162 | -------------------------------------------------------------------------------- /.github/workflows/lock.yaml: -------------------------------------------------------------------------------- 1 | name: "Lock threads" 2 | permissions: 3 | issues: write 4 | pull-requests: write 5 | discussions: write 6 | on: 7 | workflow_dispatch: 8 | schedule: 9 | - cron: "37 2 * * *" 10 | 11 | jobs: 12 | lock: 13 | runs-on: ubuntu-24.04 14 | steps: 15 | - uses: krlmlr/lock-threads@patch-1 16 | with: 17 | github-token: ${{ github.token }} 18 | issue-inactive-days: "365" 19 | issue-lock-reason: "" 20 | issue-comment: > 21 | This old thread has been automatically locked. If you think you have 22 | found something related to this, please open a new issue and link to this 23 | old issue if necessary. 24 | -------------------------------------------------------------------------------- /.github/workflows/matrix-check/action.yml: -------------------------------------------------------------------------------- 1 | name: "Actions to check a matrix with all R and OS versions, computed with the versions-matrix action" 2 | inputs: 3 | matrix: 4 | description: "Generated matrix" 5 | required: true 6 | 7 | runs: 8 | using: "composite" 9 | steps: 10 | - name: Install json2yaml 11 | run: | 12 | sudo npm install -g json2yaml 13 | shell: bash 14 | 15 | - run: | 16 | matrix='${{ inputs.matrix }}' 17 | if [ -n "${matrix}" ]; then 18 | echo $matrix | jq . 19 | echo $matrix | json2yaml 20 | else 21 | echo "No matrix found" 22 | fi 23 | shell: bash 24 | -------------------------------------------------------------------------------- /.github/workflows/pkgdown-build/action.yml: -------------------------------------------------------------------------------- 1 | name: "Action to build a pkgdown website" 2 | 3 | runs: 4 | using: "composite" 5 | steps: 6 | - name: Build site 7 | run: | 8 | pkgdown::build_site() 9 | shell: Rscript {0} 10 | -------------------------------------------------------------------------------- /.github/workflows/pkgdown-deploy/action.yml: -------------------------------------------------------------------------------- 1 | name: "Action to deploy a pkgdown website" 2 | 3 | runs: 4 | using: "composite" 5 | steps: 6 | - name: Deploy site 7 | uses: nick-fields/retry@v3 8 | with: 9 | timeout_minutes: 15 10 | max_attempts: 10 11 | command: | 12 | R -q -e 'pkgdown::deploy_to_branch(new_process = FALSE)' 13 | -------------------------------------------------------------------------------- /.github/workflows/pkgdown.yaml: -------------------------------------------------------------------------------- 1 | # Workflow derived from https://github.com/r-lib/actions/tree/v2/examples 2 | # Also included in R-CMD-check.yaml, this workflow only listens to pushes to branches 3 | # that start with "docs*" or "cran-*" and does not need to act on pushes to the main branch. 4 | on: 5 | push: 6 | branches: 7 | - "docs*" 8 | - "cran-*" 9 | # The main branch is excluded here, it is handled by the R-CMD-check workflow. 10 | # This workflow is only for handling pushes to designated branches. 11 | workflow_dispatch: 12 | 13 | name: pkgdown 14 | 15 | concurrency: 16 | group: ${{ github.workflow }}-${{ github.ref }}-${{ github.head_ref || github.sha }}-${{ github.base_ref || '' }} 17 | cancel-in-progress: true 18 | 19 | jobs: 20 | pkgdown: 21 | runs-on: ubuntu-24.04 22 | 23 | name: "pkgdown" 24 | 25 | # Begin custom: services 26 | # End custom: services 27 | 28 | steps: 29 | - uses: actions/checkout@v4 30 | 31 | - uses: ./.github/workflows/rate-limit 32 | with: 33 | token: ${{ secrets.GITHUB_TOKEN }} 34 | 35 | - uses: ./.github/workflows/git-identity 36 | if: github.event_name == 'push' 37 | 38 | - uses: ./.github/workflows/custom/before-install 39 | if: hashFiles('.github/workflows/custom/before-install/action.yml') != '' 40 | 41 | - uses: ./.github/workflows/install 42 | with: 43 | token: ${{ secrets.GITHUB_TOKEN }} 44 | cache-version: pkgdown-2 45 | needs: website 46 | extra-packages: r-lib/pkgdown local::. 47 | 48 | - uses: ./.github/workflows/custom/after-install 49 | if: hashFiles('.github/workflows/custom/after-install/action.yml') != '' 50 | 51 | - uses: ./.github/workflows/pkgdown-build 52 | if: github.event_name != 'push' 53 | 54 | - uses: ./.github/workflows/pkgdown-deploy 55 | if: github.event_name == 'push' 56 | -------------------------------------------------------------------------------- /.github/workflows/pr-commands.yaml: -------------------------------------------------------------------------------- 1 | on: 2 | issue_comment: 3 | types: [created] 4 | name: Commands 5 | jobs: 6 | document: 7 | if: startsWith(github.event.comment.body, '/document') 8 | name: document 9 | # macos is actually better here due to native binary packages 10 | runs-on: macos-latest 11 | env: 12 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} 13 | steps: 14 | - uses: actions/checkout@v4 15 | - uses: r-lib/actions/pr-fetch@v2 16 | with: 17 | repo-token: ${{ secrets.GITHUB_TOKEN }} 18 | - uses: r-lib/actions/setup-r@v2 19 | - name: Configure Git identity 20 | run: | 21 | env | sort 22 | git config --local user.name "$GITHUB_ACTOR" 23 | git config --local user.email "$GITHUB_ACTOR@users.noreply.github.com" 24 | shell: bash 25 | - name: Install dependencies 26 | run: | 27 | install.packages(c("remotes", "roxygen2"), type = "binary") 28 | remotes::install_deps(dependencies = TRUE) 29 | shell: Rscript {0} 30 | - name: Document 31 | run: | 32 | roxygen2::roxygenise() 33 | shell: Rscript {0} 34 | - name: commit 35 | run: | 36 | if [ -n "$(git status --porcelain man/ NAMESPACE)" ]; then 37 | git add man/ NAMESPACE 38 | git commit -m 'Document' 39 | fi 40 | - uses: r-lib/actions/pr-push@v2 41 | with: 42 | repo-token: ${{ secrets.GITHUB_TOKEN }} 43 | style: 44 | if: startsWith(github.event.comment.body, '/style') 45 | name: style 46 | # macos is actually better here due to native binary packages 47 | runs-on: macos-latest 48 | env: 49 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} 50 | steps: 51 | - uses: actions/checkout@v4 52 | - uses: r-lib/actions/pr-fetch@v2 53 | with: 54 | repo-token: ${{ secrets.GITHUB_TOKEN }} 55 | - uses: r-lib/actions/setup-r@v2 56 | - name: Configure Git identity 57 | run: | 58 | env | sort 59 | git config --local user.name "$GITHUB_ACTOR" 60 | git config --local user.email "$GITHUB_ACTOR@users.noreply.github.com" 61 | shell: bash 62 | - name: Install dependencies 63 | run: | 64 | install.packages(c("styler", "roxygen2"), type = "binary") 65 | shell: Rscript {0} 66 | - name: Style 67 | run: | 68 | styler::style_pkg(strict = FALSE) 69 | shell: Rscript {0} 70 | - name: commit 71 | run: | 72 | if [ -n "$(git status --porcelain '*.R' '*.Rmd')" ]; then 73 | git add '*.R' '*.Rmd' 74 | git commit -m 'Style' 75 | fi 76 | - uses: r-lib/actions/pr-push@v2 77 | with: 78 | repo-token: ${{ secrets.GITHUB_TOKEN }} 79 | merge: 80 | if: startsWith(github.event.comment.body, '/merge') 81 | name: merge 82 | runs-on: ubuntu-22.04 83 | steps: 84 | - name: Create and merge pull request 85 | run: | 86 | set -exo pipefail 87 | PR_DETAILS=$( curl -s --header "authorization: Bearer ${{ secrets.GITHUB_TOKEN }}" https://api.github.com/repos/${{ github.repository }}/pulls/${{ github.event.issue.number }} ) 88 | echo "$PR_DETAILS" | jq . 89 | PR_BASE=$(echo "$PR_DETAILS" | jq -r .base.ref) 90 | PR_HEAD=$(echo "$PR_DETAILS" | jq -r .head.ref) 91 | PR_URL=$(curl -s -X POST --header "authorization: Bearer ${{ secrets.GITHUB_TOKEN }}" --data '{ "head": "'$PR_BASE'", "base": "'$PR_HEAD'", "title": "Merge back PR target branch", "body": "Target: #${{ github.event.issue.number }}" }' https://api.github.com/repos/${{ github.repository }}/pulls | jq -r .url ) 92 | echo $PR_URL 93 | # Merging here won't run CI/CD 94 | # curl -s -X PUT --header "authorization: Bearer ${{ secrets.GITHUB_TOKEN }}" $PR_URL/merge 95 | # A mock job just to ensure we have a successful build status 96 | finish: 97 | runs-on: ubuntu-22.04 98 | steps: 99 | - run: true 100 | -------------------------------------------------------------------------------- /.github/workflows/rate-limit/action.yml: -------------------------------------------------------------------------------- 1 | name: "Check GitHub rate limits" 2 | inputs: 3 | token: # id of input 4 | description: GitHub token, pass secrets.GITHUB_TOKEN 5 | required: true 6 | 7 | runs: 8 | using: "composite" 9 | steps: 10 | - name: Check rate limits 11 | run: | 12 | curl -s --header "authorization: Bearer ${{ inputs.token }}" https://api.github.com/rate_limit 13 | shell: bash 14 | -------------------------------------------------------------------------------- /.github/workflows/revdep.yaml: -------------------------------------------------------------------------------- 1 | # This workflow creates many jobs, run only when a branch is created 2 | on: 3 | push: 4 | branches: 5 | - "revdep*" # never run automatically on main branch 6 | 7 | name: revdep 8 | 9 | jobs: 10 | matrix: 11 | runs-on: ubuntu-22.04 12 | outputs: 13 | matrix: ${{ steps.set-matrix.outputs.matrix }} 14 | 15 | name: Collect revdeps 16 | 17 | env: 18 | R_REMOTES_NO_ERRORS_FROM_WARNINGS: true 19 | RSPM: https://packagemanager.rstudio.com/cran/__linux__/bionic/latest 20 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} 21 | # prevent rgl issues because no X11 display is available 22 | RGL_USE_NULL: true 23 | # Begin custom: env vars 24 | # End custom: env vars 25 | 26 | steps: 27 | - name: Check rate limits 28 | run: | 29 | curl -s --header "authorization: Bearer ${{ secrets.GITHUB_TOKEN }}" https://api.github.com/rate_limit 30 | shell: bash 31 | 32 | - uses: actions/checkout@v4 33 | 34 | # FIXME: Avoid reissuing succesful jobs 35 | # https://docs.github.com/en/free-pro-team@latest/rest/reference/actions#list-jobs-for-a-workflow-run 36 | # https://docs.github.com/en/free-pro-team@latest/rest/reference/actions#workflow-runs 37 | - id: set-matrix 38 | run: | 39 | package <- read.dcf("DESCRIPTION")[, "Package"][[1]] 40 | deps <- tools:::package_dependencies(package, reverse = TRUE, which = c("Depends", "Imports", "LinkingTo", "Suggests"))[[1]] 41 | json <- paste0( 42 | '{"package":[', 43 | paste0('"', deps, '"', collapse = ","), 44 | ']}' 45 | ) 46 | writeLines(json) 47 | writeLines(paste0("matrix=", json), Sys.getenv("GITHUB_OUTPUT")) 48 | shell: Rscript {0} 49 | 50 | check-matrix: 51 | runs-on: ubuntu-22.04 52 | needs: matrix 53 | steps: 54 | - name: Install json2yaml 55 | run: | 56 | sudo npm install -g json2yaml 57 | 58 | - name: Check matrix definition 59 | run: | 60 | matrix='${{ needs.matrix.outputs.matrix }}' 61 | echo $matrix 62 | echo $matrix | jq . 63 | echo $matrix | json2yaml 64 | 65 | R-CMD-check: 66 | needs: matrix 67 | 68 | runs-on: ubuntu-22.04 69 | 70 | name: 'revdep: ${{ matrix.package }}' 71 | 72 | # Begin custom: services 73 | # End custom: services 74 | 75 | strategy: 76 | fail-fast: false 77 | matrix: ${{fromJson(needs.matrix.outputs.matrix)}} 78 | 79 | env: 80 | R_REMOTES_NO_ERRORS_FROM_WARNINGS: true 81 | RSPM: https://packagemanager.rstudio.com/cran/__linux__/bionic/latest 82 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} 83 | # prevent rgl issues because no X11 display is available 84 | RGL_USE_NULL: true 85 | # Begin custom: env vars 86 | # End custom: env vars 87 | 88 | steps: 89 | - name: Check rate limits 90 | run: | 91 | curl -s --header "authorization: Bearer ${{ secrets.GITHUB_TOKEN }}" https://api.github.com/rate_limit 92 | shell: bash 93 | 94 | - uses: actions/checkout@v4 95 | 96 | # Begin custom: before install 97 | # End custom: before install 98 | 99 | - name: Use RSPM 100 | run: | 101 | mkdir -p /home/runner/work/_temp/Library 102 | echo 'local({release <- system2("lsb_release", "-sc", stdout = TRUE); options(repos=c(CRAN = paste0("https://packagemanager.rstudio.com/all/__linux__/", release, "/latest")), HTTPUserAgent = sprintf("R/%s R (%s)", getRversion(), paste(getRversion(), R.version$platform, R.version$arch, R.version$os)))}); .libPaths("/home/runner/work/_temp/Library")' | sudo tee /etc/R/Rprofile.site 103 | 104 | - name: Install remotes 105 | run: | 106 | if (!requireNamespace("curl", quietly = TRUE)) install.packages("curl") 107 | if (!requireNamespace("remotes", quietly = TRUE)) install.packages("remotes") 108 | shell: Rscript {0} 109 | 110 | - uses: r-lib/actions/setup-pandoc@v2 111 | 112 | - name: Install system dependencies 113 | if: runner.os == 'Linux' 114 | run: | 115 | sudo apt-get update -y 116 | Rscript -e 'writeLines(remotes::system_requirements("ubuntu", "22.04")); package <- "${{ matrix.package }}"; deps <- tools::package_dependencies(package, which = "Suggests")[[1]]; lapply(c(package, deps), function(x) { writeLines(remotes::system_requirements("ubuntu", "22.04", package = x)) })' | sort | uniq > .github/deps.sh 117 | cat .github/deps.sh 118 | sudo sh < .github/deps.sh 119 | 120 | - name: Install package 121 | run: | 122 | package <- "${{ matrix.package }}" 123 | install.packages(package, dependencies = TRUE) 124 | remotes::install_cran("rcmdcheck") 125 | shell: Rscript {0} 126 | 127 | - name: Session info old 128 | run: | 129 | options(width = 100) 130 | if (!requireNamespace("sessioninfo", quietly = TRUE)) install.packages("sessioninfo") 131 | pkgs <- installed.packages()[, "Package"] 132 | sessioninfo::session_info(pkgs, include_base = TRUE) 133 | shell: Rscript {0} 134 | 135 | # Begin custom: after install 136 | # End custom: after install 137 | 138 | - name: Check old 139 | env: 140 | _R_CHECK_CRAN_INCOMING_: false 141 | _R_CHECK_SYSTEM_CLOCK_: false 142 | _R_CHECK_FUTURE_FILE_TIMESTAMPS_: false 143 | # Avoid downloading binary package from RSPM 144 | run: | 145 | package <- "${{ matrix.package }}" 146 | options(HTTPUserAgent = "gha") 147 | path <- download.packages(package, destdir = ".github")[, 2] 148 | print(path) 149 | 150 | dir <- file.path("revdep", package) 151 | dir.create(dir, showWarnings = FALSE, recursive = TRUE) 152 | check <- rcmdcheck::rcmdcheck(path, args = c("--no-manual", "--as-cran"), error_on = "never", check_dir = file.path(dir, "check")) 153 | file.rename(file.path(dir, "check"), file.path(dir, "old")) 154 | saveRDS(check, file.path(dir, "old.rds")) 155 | shell: Rscript {0} 156 | 157 | - name: Install local package 158 | run: | 159 | remotes::install_local(".", force = TRUE) 160 | shell: Rscript {0} 161 | 162 | - name: Session info new 163 | run: | 164 | options(width = 100) 165 | pkgs <- installed.packages()[, "Package"] 166 | sessioninfo::session_info(pkgs, include_base = TRUE) 167 | shell: Rscript {0} 168 | 169 | - name: Check new 170 | env: 171 | _R_CHECK_CRAN_INCOMING_: false 172 | _R_CHECK_SYSTEM_CLOCK_: false 173 | _R_CHECK_FUTURE_FILE_TIMESTAMPS_: false 174 | run: | 175 | package <- "${{ matrix.package }}" 176 | path <- dir(".github", pattern = paste0("^", package), full.names = TRUE)[[1]] 177 | print(path) 178 | 179 | dir <- file.path("revdep", package) 180 | check <- rcmdcheck::rcmdcheck(path, args = c("--no-manual", "--as-cran"), error_on = "never", check_dir = file.path(dir, "check")) 181 | file.rename(file.path(dir, "check"), file.path(dir, "new")) 182 | saveRDS(check, file.path(dir, "new.rds")) 183 | shell: Rscript {0} 184 | 185 | - name: Compare 186 | run: | 187 | package <- "${{ matrix.package }}" 188 | dir <- file.path("revdep", package) 189 | old <- readRDS(file.path(dir, "old.rds")) 190 | new <- readRDS(file.path(dir, "new.rds")) 191 | compare <- rcmdcheck::compare_checks(old, new) 192 | compare 193 | cmp <- compare$cmp 194 | if (!identical(cmp[cmp$which == "old", "output"], cmp[cmp$which == "new", "output"])) { 195 | if (!requireNamespace("waldo", quietly = TRUE)) install.packages("waldo") 196 | print(waldo::compare(old, new)) 197 | 198 | stop("Check output differs.") 199 | } 200 | shell: Rscript {0} 201 | 202 | - name: Upload check results 203 | if: failure() 204 | uses: actions/upload-artifact@main 205 | with: 206 | name: ${{ matrix.package }}-results 207 | path: revdep/${{ matrix.package }} 208 | 209 | - name: Check rate limits 210 | if: always() 211 | run: | 212 | curl -s --header "authorization: Bearer ${{ secrets.GITHUB_TOKEN }}" https://api.github.com/rate_limit 213 | shell: bash 214 | -------------------------------------------------------------------------------- /.github/workflows/roxygenize/action.yml: -------------------------------------------------------------------------------- 1 | name: "Action to create documentation with roxygen2" 2 | 3 | runs: 4 | using: "composite" 5 | steps: 6 | - name: Roxygenize 7 | run: | 8 | try(roxygen2::roxygenize()) 9 | shell: Rscript {0} 10 | -------------------------------------------------------------------------------- /.github/workflows/style/action.yml: -------------------------------------------------------------------------------- 1 | name: "Action to auto-style a package" 2 | 3 | runs: 4 | using: "composite" 5 | steps: 6 | - name: Check styler options 7 | id: check 8 | run: | 9 | set -x 10 | scope=$( ( grep Config/autostyle/scope DESCRIPTION || true ) | cut -d " " -f 2) 11 | strict=$( ( grep Config/autostyle/strict DESCRIPTION || true ) | cut -d " " -f 2) 12 | rmd=$( ( grep Config/autostyle/rmd DESCRIPTION || true ) | cut -d " " -f 2) 13 | echo scope=$scope >> $GITHUB_OUTPUT 14 | echo strict=$strict >> $GITHUB_OUTPUT 15 | echo rmd=$rmd >> $GITHUB_OUTPUT 16 | shell: bash 17 | 18 | - uses: actions/cache@v4 19 | if: ${{ steps.check.outputs.scope }} 20 | with: 21 | path: | 22 | ~/.cache/R/R.cache 23 | key: ${{ runner.os }}-2-${{ github.run_id }}- 24 | restore-keys: | 25 | ${{ runner.os }}-2- 26 | 27 | - name: Imprint run ID 28 | if: ${{ steps.check.outputs.scope }} 29 | run: | 30 | mkdir -p ~/.cache/R/R.cache/styler 31 | touch ~/.cache/R/R.cache/${{ github.run_id }} 32 | shell: bash 33 | 34 | - name: Show cache 35 | if: ${{ steps.check.outputs.scope }} 36 | run: | 37 | ls -l ~/.cache/R/R.cache 38 | ls -l ~/.cache/R/R.cache/styler 39 | shell: bash 40 | 41 | - name: Enable styler cache 42 | if: ${{ steps.check.outputs.scope }} 43 | run: | 44 | styler::cache_activate(verbose = TRUE) 45 | shell: Rscript {0} 46 | 47 | - name: Run styler 48 | if: ${{ steps.check.outputs.scope }} 49 | run: | 50 | strict <- as.logical("${{ steps.check.outputs.strict }}") 51 | if (is.na(strict)) { 52 | strict <- FALSE 53 | } 54 | rmd <- as.logical("${{ steps.check.outputs.rmd }}") 55 | if (is.na(rmd)) { 56 | rmd <- TRUE 57 | } 58 | styler::style_pkg( 59 | scope = "${{ steps.check.outputs.scope }}", 60 | strict = strict, 61 | filetype = c("R", "Rprofile", if (rmd) c("Rmd", "Rmarkdown", "Rnw", "Qmd")) 62 | ) 63 | shell: Rscript {0} 64 | 65 | - name: Show cache again 66 | if: ${{ steps.check.outputs.scope }} 67 | run: | 68 | ls -l ~/.cache/R/R.cache 69 | ls -l ~/.cache/R/R.cache/styler 70 | gdu -s --inodes ~/.cache/R/R.cache/styler/* || du -s --inodes ~/.cache/R/R.cache/styler/* 71 | shell: bash 72 | -------------------------------------------------------------------------------- /.github/workflows/update-snapshots/action.yml: -------------------------------------------------------------------------------- 1 | name: "Action to create pull requests for updated testthat snapshots" 2 | description: > 3 | This action will run `testthat::test_local()` for tests that seem to use snapshots, 4 | this is determined by reading and grepping the test files. 5 | If the tests are failing, snapshots are updated, and a pull request is opened. 6 | inputs: 7 | base: 8 | description: "The base branch to create the pull request against." 9 | required: false 10 | default: "main" 11 | 12 | runs: 13 | using: "composite" 14 | steps: 15 | - name: Run tests on test files that use snapshots 16 | id: run-tests 17 | run: | 18 | ## -- Run tests on test files that use snapshots -- 19 | rx <- "^test-(.*)[.][rR]$" 20 | files <- dir("tests/testthat", pattern = rx) 21 | has_snapshot <- vapply(files, function(.x) any(grepl("snapshot", readLines(file.path("tests/testthat", .x)), fixed = TRUE)), logical(1)) 22 | if (any(has_snapshot)) { 23 | patterns <- gsub(rx, "^\\1$", files[has_snapshot]) 24 | pattern <- paste0(patterns, collapse = "|") 25 | tryCatch( 26 | { 27 | result <- as.data.frame(testthat::test_local(pattern = pattern, reporter = "silent", stop_on_failure = FALSE)) 28 | print(result) 29 | failures <- result[result$failed + result$warning > 0, ] 30 | if (nrow(failures) > 0) { 31 | writeLines("Snapshot tests failed/warned.") 32 | print(failures[names(failures) != "result"]) 33 | print(failures$result) 34 | testthat::snapshot_accept() 35 | writeLines("changed=true", Sys.getenv("GITHUB_OUTPUT")) 36 | } else { 37 | writeLines("Snapshot tests ran successfully.") 38 | } 39 | }, 40 | error = print 41 | ) 42 | } else { 43 | writeLines("No snapshots found.") 44 | } 45 | shell: Rscript {0} 46 | 47 | - name: Add snapshots to Git 48 | if: ${{ steps.run-tests.outputs.changed }} 49 | run: | 50 | ## -- Add snapshots to Git -- 51 | mkdir -p tests/testthat/_snaps 52 | git add -- tests/testthat/_snaps 53 | shell: bash 54 | 55 | - name: Check changed files 56 | if: ${{ steps.run-tests.outputs.changed }} 57 | id: check-changed 58 | run: | 59 | echo "changed=$(git status --porcelain -- tests/testthat/_snaps | head -n 1)" >> $GITHUB_OUTPUT 60 | shell: bash 61 | 62 | - name: Derive branch name 63 | if: ${{ steps.check-changed.outputs.changed }} 64 | id: matrix-desc 65 | run: | 66 | config=$(echo '${{ toJSON(matrix) }}' | jq -c .) 67 | echo "text=$(echo ${config})" >> $GITHUB_OUTPUT 68 | echo "branch=$(echo ${config} | sed -r 's/[^0-9a-zA-Z]+/-/g;s/^-//;s/-$//')" >> $GITHUB_OUTPUT 69 | shell: bash 70 | 71 | - name: Create pull request 72 | if: ${{ steps.check-changed.outputs.changed }} 73 | id: cpr 74 | uses: peter-evans/create-pull-request@v6 75 | with: 76 | base: ${{ inputs.base }} 77 | branch: snapshot-${{ inputs.base }}-${{ github.job }}-${{ steps.matrix-desc.outputs.branch }} 78 | delete-branch: true 79 | title: "test: Snapshot updates for ${{ github.job }} (${{ steps.matrix-desc.outputs.text }})" 80 | body: "Automated changes by [create-pull-request](https://github.com/peter-evans/create-pull-request) GitHub action${{ github.event.number && format(' for #{0}', github.event.number) || '' }}." 81 | add-paths: | 82 | tests/testthat/_snaps 83 | 84 | - name: Fail if pull request created 85 | if: ${{ steps.cpr.outputs.pull-request-number }} 86 | run: | 87 | false 88 | shell: bash 89 | -------------------------------------------------------------------------------- /.github/workflows/versions-matrix/action.R: -------------------------------------------------------------------------------- 1 | # Determine active versions of R to test against 2 | tags <- xml2::read_html("https://svn.r-project.org/R/tags/") 3 | 4 | bullets <- 5 | tags |> 6 | xml2::xml_find_all("//li") |> 7 | xml2::xml_text() 8 | 9 | version_bullets <- grep("^R-([0-9]+-[0-9]+-[0-9]+)/$", bullets, value = TRUE) 10 | versions <- unique(gsub("^R-([0-9]+)-([0-9]+)-[0-9]+/$", "\\1.\\2", version_bullets)) 11 | 12 | r_release <- head(sort(as.package_version(versions), decreasing = TRUE), 5) 13 | 14 | deps <- desc::desc_get_deps() 15 | r_crit <- deps$version[deps$package == "R"] 16 | if (length(r_crit) == 1) { 17 | min_r <- as.package_version(gsub("^>= ([0-9]+[.][0-9]+)(?:.*)$", "\\1", r_crit)) 18 | r_release <- r_release[r_release >= min_r] 19 | } 20 | 21 | r_versions <- c("devel", as.character(r_release)) 22 | 23 | macos <- data.frame(os = "macos-latest", r = r_versions[2:3]) 24 | windows <- data.frame(os = "windows-latest", r = r_versions[1:3]) 25 | linux_devel <- data.frame(os = "ubuntu-22.04", r = r_versions[1], `http-user-agent` = "release", check.names = FALSE) 26 | linux <- data.frame(os = "ubuntu-22.04", r = r_versions[-1]) 27 | covr <- data.frame(os = "ubuntu-22.04", r = r_versions[2], covr = "true", desc = "with covr") 28 | 29 | include_list <- list(macos, windows, linux_devel, linux, covr) 30 | 31 | if (file.exists(".github/versions-matrix.R")) { 32 | custom <- source(".github/versions-matrix.R")$value 33 | if (is.data.frame(custom)) { 34 | custom <- list(custom) 35 | } 36 | include_list <- c(include_list, custom) 37 | } 38 | 39 | print(include_list) 40 | 41 | filter <- read.dcf("DESCRIPTION")[1, ]["Config/gha/filter"] 42 | if (!is.na(filter)) { 43 | filter_expr <- parse(text = filter)[[1]] 44 | subset_fun_expr <- bquote(function(x) subset(x, .(filter_expr))) 45 | subset_fun <- eval(subset_fun_expr) 46 | include_list <- lapply(include_list, subset_fun) 47 | print(include_list) 48 | } 49 | 50 | to_json <- function(x) { 51 | if (nrow(x) == 0) return(character()) 52 | parallel <- vector("list", length(x)) 53 | for (i in seq_along(x)) { 54 | parallel[[i]] <- paste0('"', names(x)[[i]], '":"', x[[i]], '"') 55 | } 56 | paste0("{", do.call(paste, c(parallel, sep = ",")), "}") 57 | } 58 | 59 | configs <- unlist(lapply(include_list, to_json)) 60 | json <- paste0('{"include":[', paste(configs, collapse = ","), "]}") 61 | 62 | if (Sys.getenv("GITHUB_OUTPUT") != "") { 63 | writeLines(paste0("matrix=", json), Sys.getenv("GITHUB_OUTPUT")) 64 | } 65 | writeLines(json) 66 | -------------------------------------------------------------------------------- /.github/workflows/versions-matrix/action.yml: -------------------------------------------------------------------------------- 1 | name: "Actions to compute a matrix with all R and OS versions" 2 | 3 | outputs: 4 | matrix: 5 | description: "Generated matrix" 6 | value: ${{ steps.set-matrix.outputs.matrix }} 7 | 8 | runs: 9 | using: "composite" 10 | steps: 11 | - name: Install json2yaml 12 | run: | 13 | sudo npm install -g json2yaml 14 | shell: bash 15 | 16 | - id: set-matrix 17 | run: | 18 | Rscript ./.github/workflows/versions-matrix/action.R 19 | shell: bash 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .Rproj.user 2 | .Rhistory 3 | .RData 4 | /README_cache/ 5 | /docs 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # R for travis: see documentation at https://docs.travis-ci.com/user/languages/r 2 | # Default configuration for use with tic package 3 | # Usually you shouldn't need to change the first part of the file 4 | 5 | # DO NOT CHANGE THE CODE BELOW 6 | before_install: R -q -e 'install.packages(c("remotes", "curl")); remotes::install_github("ropenscilabs/tic"); tic::prepare_all_stages(); tic::before_install()' 7 | install: R -q -e 'tic::install()' 8 | after_install: R -q -e 'tic::after_install()' 9 | before_script: R -q -e 'tic::before_script()' 10 | script: R -q -e 'tic::script()' 11 | after_success: R -q -e 'tic::after_success()' 12 | after_failure: R -q -e 'tic::after_failure()' 13 | before_deploy: R -q -e 'tic::before_deploy()' 14 | deploy: 15 | provider: script 16 | script: R -q -e 'tic::deploy()' 17 | on: 18 | all_branches: true 19 | after_deploy: R -q -e 'tic::after_deploy()' 20 | after_script: R -q -e 'tic::after_script()' 21 | # DO NOT CHANGE THE CODE ABOVE 22 | 23 | # Custom parts: 24 | 25 | # Header 26 | language: r 27 | sudo: false 28 | dist: trusty 29 | cache: packages 30 | latex: false 31 | 32 | matrix: 33 | include: 34 | - r: 3.1 35 | - r: 3.2 36 | - r: oldrel 37 | - r: release 38 | env: 39 | - BUILD_PKGDOWN: true 40 | - r: devel 41 | - os: osx 42 | 43 | #env 44 | env: 45 | global: 46 | - _R_CHECK_FORCE_SUGGESTS_=false 47 | - MAKEFLAGS="-j 2" 48 | 49 | #services 50 | services: 51 | -------------------------------------------------------------------------------- /DESCRIPTION: -------------------------------------------------------------------------------- 1 | Package: bindr 2 | Title: Parametrized Active Bindings 3 | Version: 0.1.2.9012 4 | Authors@R: c( 5 | person("Kirill", "M\u00fcller", role = c("aut", "cre"), email = "kirill@cynkra.com", comment = c(ORCID = "0000-0002-1416-3412")), 6 | person("RStudio", role = c("cph", "fnd")) 7 | ) 8 | Description: Provides a simple interface for creating active bindings where the 9 | bound function accepts additional arguments. 10 | Suggests: testthat 11 | Date: 2024-12-09 12 | BugReports: https://github.com/krlmlr/bindr/issues 13 | URL: https://github.com/krlmlr/bindr, https://krlmlr.github.io/bindr/ 14 | Roxygen: list(markdown = TRUE) 15 | RoxygenNote: 7.3.2.9000 16 | License: MIT + file LICENSE 17 | Encoding: UTF-8 18 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | YEAR: 2016 2 | COPYRIGHT HOLDER: RStudio 3 | -------------------------------------------------------------------------------- /NAMESPACE: -------------------------------------------------------------------------------- 1 | # Generated by roxygen2: do not edit by hand 2 | 3 | export(create_env) 4 | export(populate_env) 5 | -------------------------------------------------------------------------------- /NEWS.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # bindr 0.1.2.9012 (2024-12-09) 4 | 5 | ## Continuous integration 6 | 7 | - Avoid failure in fledge workflow if no changes (#32). 8 | 9 | 10 | # bindr 0.1.2.9011 (2024-12-08) 11 | 12 | ## Continuous integration 13 | 14 | - Fetch tags for fledge workflow to avoid unnecessary NEWS entries (#30). 15 | 16 | 17 | # bindr 0.1.2.9010 (2024-12-07) 18 | 19 | ## Continuous integration 20 | 21 | - Use larger retry count for lock-threads workflow (#28). 22 | 23 | 24 | # bindr 0.1.2.9009 (2024-12-01) 25 | 26 | ## Continuous integration 27 | 28 | - Ignore errors when removing pkg-config on macOS (#23). 29 | 30 | - Explicit permissions (#21). 31 | 32 | - Use styler from main branch (#19). 33 | 34 | - Need to install R on Ubuntu 24.04 (#17). 35 | 36 | - Fix macOS (#16). 37 | 38 | - Use Ubuntu 24.04 and styler PR (#15). 39 | 40 | ## fledge 41 | 42 | - Bump version to 0.1.2.9002 (#14). 43 | 44 | 45 | # bindr 0.1.2.9008 (2024-11-30) 46 | 47 | ## Continuous integration 48 | 49 | - Ignore errors when removing pkg-config on macOS (#23). 50 | 51 | - Explicit permissions (#21). 52 | 53 | - Use styler from main branch (#19). 54 | 55 | - Need to install R on Ubuntu 24.04 (#17). 56 | 57 | - Fix macOS (#16). 58 | 59 | - Use Ubuntu 24.04 and styler PR (#15). 60 | 61 | ## fledge 62 | 63 | - Bump version to 0.1.2.9002 (#14). 64 | 65 | 66 | # bindr 0.1.2.9007 (2024-11-29) 67 | 68 | ## Continuous integration 69 | 70 | - Ignore errors when removing pkg-config on macOS (#23). 71 | 72 | - Explicit permissions (#21). 73 | 74 | - Use styler from main branch (#19). 75 | 76 | - Need to install R on Ubuntu 24.04 (#17). 77 | 78 | - Fix macOS (#16). 79 | 80 | - Use Ubuntu 24.04 and styler PR (#15). 81 | 82 | ## fledge 83 | 84 | - Bump version to 0.1.2.9002 (#14). 85 | 86 | 87 | # bindr 0.1.2.9006 (2024-11-28) 88 | 89 | ## Continuous integration 90 | 91 | - Ignore errors when removing pkg-config on macOS (#23). 92 | 93 | - Explicit permissions (#21). 94 | 95 | - Use styler from main branch (#19). 96 | 97 | - Need to install R on Ubuntu 24.04 (#17). 98 | 99 | - Fix macOS (#16). 100 | 101 | - Use Ubuntu 24.04 and styler PR (#15). 102 | 103 | ## fledge 104 | 105 | - Bump version to 0.1.2.9002 (#14). 106 | 107 | 108 | # bindr 0.1.2.9005 (2024-11-27) 109 | 110 | ## Continuous integration 111 | 112 | - Explicit permissions (#21). 113 | 114 | - Use styler from main branch (#19). 115 | 116 | - Need to install R on Ubuntu 24.04 (#17). 117 | 118 | - Fix macOS (#16). 119 | 120 | - Use Ubuntu 24.04 and styler PR (#15). 121 | 122 | ## fledge 123 | 124 | - Bump version to 0.1.2.9002 (#14). 125 | 126 | 127 | # bindr 0.1.2.9004 (2024-11-26) 128 | 129 | ## Continuous integration 130 | 131 | - Use styler from main branch (#19). 132 | 133 | - Need to install R on Ubuntu 24.04 (#17). 134 | 135 | - Fix macOS (#16). 136 | 137 | - Use Ubuntu 24.04 and styler PR (#15). 138 | 139 | ## fledge 140 | 141 | - Bump version to 0.1.2.9002 (#14). 142 | 143 | 144 | # bindr 0.1.2.9003 (2024-11-25) 145 | 146 | ## Continuous integration 147 | 148 | - Need to install R on Ubuntu 24.04 (#17). 149 | 150 | - Fix macOS (#16). 151 | 152 | - Use Ubuntu 24.04 and styler PR (#15). 153 | 154 | ## fledge 155 | 156 | - Bump version to 0.1.2.9002 (#14). 157 | 158 | 159 | # bindr 0.1.2.9002 (2024-11-24) 160 | 161 | ## fledge 162 | 163 | - Bump version to 0.1.2.9001 (#13). 164 | 165 | 166 | # bindr 0.1.2.9001 (2024-11-23) 167 | 168 | ## fledge 169 | 170 | - Bump version to 0.1.2.9000 (#12). 171 | 172 | 173 | # bindr 0.1.2.9000 (2024-11-22) 174 | 175 | ## Documentation 176 | 177 | - Dev mode. 178 | 179 | 180 | # bindr 0.1.2 (2024-11-21) 181 | 182 | ## Feature 183 | 184 | - The call stack for a failure in an active binding now contains a reference to `bindr_fun()` instead of `fun()`, to hint at what might have went wrong. 185 | 186 | 187 | # bindr 0.1.1 (2018-03-13) 188 | 189 | - Improve performance for very large environments for `create_env()` and `populate_env()`. 190 | 191 | - Force the `fun` argument just to be sure. 192 | 193 | 194 | # bindr 0.1 (2016-11-12) 195 | 196 | Initial release. 197 | 198 | - Functions `create_env()` and `populate_env()`. 199 | - Create or populate an environment with one or more active bindings, where the value is computed by calling a function and passing the name of the binding, and an arbitrary number of additional arguments (named or unnamed). 200 | - Not overwriting existing bindings or variables. 201 | - Names can be passed as symbols (`name`) or character strings (`character`), with warning if the conversion fails. 202 | -------------------------------------------------------------------------------- /R/bindr-package.R: -------------------------------------------------------------------------------- 1 | #' @details 2 | #' See [create_env()] for creating an environment populated with active bindings, 3 | #' and [populate_env()] for populating an existing environment. 4 | "_PACKAGE" 5 | -------------------------------------------------------------------------------- /R/populate.R: -------------------------------------------------------------------------------- 1 | #' Create or populate an environment with parametrized active bindings 2 | #' 3 | #' Leverages [makeActiveBinding()][base::bindenv] 4 | #' by allowing parametrized functions 5 | #' that take the name of the binding and an arbitrary number of additional arguments. 6 | #' 7 | #' @param names A [name], or a list of names, or a character vector; in the latter case 8 | #' the names are mangled if they are not representable in the native encoding 9 | #' @param fun A [function] with at least one argument, which will be called 10 | #' to compute the value of a binding. The function will be called with the 11 | #' binding name as first argument (unnamed), and `...` as additional arguments 12 | #' @param ... Additional arguments to `fun` 13 | #' @param .envir The [environment] in which `fun` will be executed, 14 | #' important if `fun` calls other functions that are not globally visible 15 | #' @param .enclos The enclosing environment (`parent.env`) for the newly created environment 16 | #' @export 17 | #' 18 | #' @examples 19 | #' env <- create_env(letters, paste0, "-lowercase") 20 | #' env$a 21 | #' env$c 22 | #' env$Z 23 | #' populate_env(env, LETTERS, paste0, "-uppercase") 24 | #' env$a 25 | #' env$Z 26 | create_env <- function(names, fun, ..., .envir = parent.frame(), .enclos = parent.frame()) { 27 | env <- new.env(parent = .enclos, size = length(names)) 28 | populate_env(env = env, names = names, fun = fun, ..., .envir = .envir) 29 | env 30 | } 31 | 32 | #' @param env An environment 33 | #' @rdname create_env 34 | #' @export 35 | populate_env <- function(env, names, fun, ..., .envir = parent.frame()) { 36 | names <- check_names(names) 37 | 38 | existing <- as.list(env) 39 | collisions <- fast_intersect(names, names(existing)) 40 | if (length(collisions) > 0) { 41 | stop( 42 | "Not creating bindings for existing variables: ", 43 | paste(utils::head(collisions, 6), collapse = ", ") 44 | ) 45 | } 46 | 47 | make_active_binding_fun <- make_make_active_binding_fun(.envir) 48 | 49 | lapply(names, function(name) { 50 | makeActiveBinding(name, make_active_binding_fun(name, fun, ...), env) 51 | }) 52 | 53 | invisible(env) 54 | } 55 | 56 | check_names <- function(names) { 57 | if (is.character(names)) { 58 | enc_names <- to_symbol_encoding(names) 59 | diff <- names != enc_names 60 | if (any(diff)) { 61 | warning("Mangling the following names: ", 62 | paste0(names[diff], " -> ", enc_names[diff], collapse = ", "), 63 | ". Use enc2native() to avoid the warning.", call. = FALSE) 64 | } 65 | lapply(enc_names, as.name) 66 | } else if (all(vapply(names, is.name, logical(1L)))) { 67 | names 68 | } else { 69 | stop("Expecting a list of names or a character vector", call. = FALSE) 70 | } 71 | } 72 | 73 | to_symbol_encoding <- function(x) enc2native(x) 74 | 75 | make_make_active_binding_fun <- function(.envir) { 76 | make_active_binding_fun <- function(name, fun, ...) { 77 | force(name) 78 | bindr_fun <- fun 79 | list(...) 80 | function(value) { 81 | if (!missing(value)) { 82 | stop("Binding is read-only.", call. = FALSE) 83 | } 84 | bindr_fun(name, ...) 85 | } 86 | } 87 | 88 | environment(make_active_binding_fun) <- .envir 89 | make_active_binding_fun 90 | } 91 | -------------------------------------------------------------------------------- /R/utils.R: -------------------------------------------------------------------------------- 1 | fast_intersect <- function(x, y) { 2 | if (length(x) > length(y)) { 3 | intersect(y, x) 4 | } else { 5 | intersect(x, y) 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /README.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | output: 3 | github_document: 4 | html_preview: false 5 | --- 6 | 7 | 8 | 9 | ```{r, include = FALSE} 10 | knitr::opts_chunk$set( 11 | collapse = TRUE, 12 | comment = "#>", 13 | fig.path = "README-" 14 | ) 15 | 16 | pkgload::load_all() 17 | 18 | clean_output <- function(x, options) { 19 | index <- x 20 | index <- gsub("─", "-", index) 21 | index <- strsplit(paste(index, collapse = "\n"), "\n---\n")[[1]][[2]] 22 | writeLines(index, "index.md") 23 | 24 | x <- fansi::strip_sgr(x) 25 | x 26 | } 27 | 28 | options(cli.num_colors = 256) 29 | 30 | local({ 31 | hook_source <- knitr::knit_hooks$get("document") 32 | knitr::knit_hooks$set(document = clean_output) 33 | }) 34 | ``` 35 | 36 | # bindr 37 | 38 | 39 | [![Lifecycle: stable](https://img.shields.io/badge/lifecycle-stable-brightgreen.svg)](https://lifecycle.r-lib.org/articles/stages.html) 40 | [![R build status](https://github.com/krlmlr/bindr/workflows/rcc/badge.svg)](https://github.com/krlmlr/bindr/actions) 41 | [![Coverage Status](https://img.shields.io/codecov/c/github/krlmlr/bindr/master.svg)](https://app.codecov.io/github/krlmlr/bindr?branch=master) [![CRAN_Status_Badge](https://www.r-pkg.org/badges/version/bindr)](https://cran.r-project.org/package=bindr) 42 | 43 | 44 | 45 | Active bindings in R are much like properties in other languages: 46 | They look like a variable, 47 | but querying or setting the value triggers a function call. 48 | They can be created in R via 49 | [`makeActiveBinding()`](https://www.rdocumentation.org/packages/base/versions/3.3.1/topics/bindenv), 50 | but with this API the function used to compute or change the value of a binding cannot take additional arguments. 51 | The `bindr` package faciliates the creation of active bindings that are linked to a function that receives the binding name, 52 | and an arbitrary number of additional arguments. 53 | 54 | 55 | ## Installation 56 | 57 | You can install `bindr` from GitHub with: 58 | 59 | ```{r gh-installation, eval = FALSE} 60 | # install.packages("devtools") 61 | devtools::install_github("krlmlr/bindr") 62 | ``` 63 | 64 | 65 | ## Getting started 66 | 67 | For illustration, the `append_random()` function is used. 68 | This function appends a separator (a dash by default) and a random letter 69 | to its input, and talks about it, too. 70 | 71 | ```{r append-random} 72 | set.seed(20161510) 73 | append_random <- function(x, sep = "-") { 74 | message("Evaluating append_random(sep = ", deparse(sep), ")") 75 | paste(x, sample(letters, 1), sep = sep) 76 | } 77 | 78 | append_random("a") 79 | append_random("X", sep = "+") 80 | ``` 81 | 82 | In this example, we create an environment that contains bindings 83 | for all lowercase letters, which are evaluated with `append_random()`. 84 | As a result, a dash and a random letter are appended to the name of the binding: 85 | 86 | ```{r create} 87 | library(bindr) 88 | env <- create_env(letters, append_random) 89 | ls(env) 90 | env$a 91 | env$a 92 | env$a 93 | env$c 94 | env$Z 95 | ``` 96 | 97 | Bindings can also be added to existing environments: 98 | 99 | ```{r populate} 100 | populate_env(env, LETTERS, append_random, "+") 101 | env$a 102 | env$Z 103 | ``` 104 | 105 | 106 | ## Further properties 107 | 108 | Both named and unnamed arguments are supported: 109 | 110 | ```{r named-unnamed} 111 | create_env("binding", paste, "value", sep = "-")$binding 112 | ``` 113 | 114 | A parent environment can be specified for creation: 115 | 116 | ```{r parent-env} 117 | env2 <- create_env("a", identity, .enclos = env) 118 | env2$a 119 | env2$b 120 | get("b", env2) 121 | ``` 122 | 123 | The bindings by default have access to the calling environment: 124 | 125 | ```{r env-access} 126 | create_local_env <- function(names) { 127 | paste_with_dash <- function(...) paste(..., sep = "-") 128 | binder <- function(name, append) paste_with_dash(name, append) 129 | create_env(names, binder, append = "appending") 130 | } 131 | 132 | env3 <- create_local_env("a") 133 | env3$a 134 | ``` 135 | 136 | All bindings are read-only: 137 | 138 | ```{r failing, error=TRUE} 139 | env3$a <- NA 140 | env3$a <- NULL 141 | ``` 142 | 143 | 144 | Existing variables or bindings are not overwritten: 145 | 146 | ```{r overwrite, error=TRUE} 147 | env4 <- as.environment(list(a = 5)) 148 | populate_env(env4, list(quote(b)), identity) 149 | ls(env4) 150 | populate_env(env4, letters, identity) 151 | ``` 152 | 153 | 154 | ## Active bindings and C++ 155 | 156 | Active bindings must be R functions. 157 | To interface with C++ code, one must bind against an exported Rcpp function, possibly with `rng = false` if performance matters. 158 | The [`bindrcpp`](https://github.com/krlmlr/bindrcpp#readme) package 159 | uses `bindr` to provide an easy-to-use C++ interface for parametrized active bindings, 160 | and is the recommended way to interface with C++ code. 161 | In the remainder of this section, 162 | an alternative using an exported C++ function is shown. 163 | 164 | The following C++ module exports a function `change_case(to_upper = FALSE)`, 165 | which is bound against in R code later. 166 | 167 | ```{Rcpp cpp-mod, cache = TRUE} 168 | #include 169 | 170 | #include 171 | #include 172 | 173 | using namespace Rcpp; 174 | 175 | // [[Rcpp::export(rng = FALSE)]] 176 | SEXP change_case(Symbol name, bool to_upper = false) { 177 | std::string name_string = name.c_str(); 178 | std::transform(name_string.begin(), name_string.end(), 179 | name_string.begin(), to_upper ? ::toupper : ::tolower); 180 | return CharacterVector(name_string); 181 | } 182 | ``` 183 | 184 | Binding from R: 185 | 186 | ```{r bind-cpp-from-r} 187 | env <- create_env(list(as.name("__ToLower__")), change_case) 188 | populate_env(env, list(as.name("__tOuPPER__")), change_case, TRUE) 189 | ls(env) 190 | env$`__ToLower__` 191 | get("__tOuPPER__", env) 192 | ``` 193 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | # bindr 5 | 6 | 7 | 8 | [![Lifecycle: 9 | stable](https://img.shields.io/badge/lifecycle-stable-brightgreen.svg)](https://lifecycle.r-lib.org/articles/stages.html) 10 | [![R build 11 | status](https://github.com/krlmlr/bindr/workflows/rcc/badge.svg)](https://github.com/krlmlr/bindr/actions) 12 | [![Coverage 13 | Status](https://img.shields.io/codecov/c/github/krlmlr/bindr/master.svg)](https://app.codecov.io/github/krlmlr/bindr?branch=master) 14 | [![CRAN_Status_Badge](https://www.r-pkg.org/badges/version/bindr)](https://cran.r-project.org/package=bindr) 15 | 16 | 17 | Active bindings in R are much like properties in other languages: They 18 | look like a variable, but querying or setting the value triggers a 19 | function call. They can be created in R via 20 | [`makeActiveBinding()`](https://www.rdocumentation.org/packages/base/versions/3.3.1/topics/bindenv), 21 | but with this API the function used to compute or change the value of a 22 | binding cannot take additional arguments. The `bindr` package faciliates 23 | the creation of active bindings that are linked to a function that 24 | receives the binding name, and an arbitrary number of additional 25 | arguments. 26 | 27 | ## Installation 28 | 29 | You can install `bindr` from GitHub with: 30 | 31 | ``` r 32 | # install.packages("devtools") 33 | devtools::install_github("krlmlr/bindr") 34 | ``` 35 | 36 | ## Getting started 37 | 38 | For illustration, the `append_random()` function is used. This function 39 | appends a separator (a dash by default) and a random letter to its 40 | input, and talks about it, too. 41 | 42 | ``` r 43 | set.seed(20161510) 44 | append_random <- function(x, sep = "-") { 45 | message("Evaluating append_random(sep = ", deparse(sep), ")") 46 | paste(x, sample(letters, 1), sep = sep) 47 | } 48 | 49 | append_random("a") 50 | #> Evaluating append_random(sep = "-") 51 | #> [1] "a-h" 52 | append_random("X", sep = "+") 53 | #> Evaluating append_random(sep = "+") 54 | #> [1] "X+k" 55 | ``` 56 | 57 | In this example, we create an environment that contains bindings for all 58 | lowercase letters, which are evaluated with `append_random()`. As a 59 | result, a dash and a random letter are appended to the name of the 60 | binding: 61 | 62 | ``` r 63 | library(bindr) 64 | env <- create_env(letters, append_random) 65 | ls(env) 66 | #> [1] "a" "b" "c" "d" "e" "f" "g" "h" "i" "j" "k" "l" "m" "n" "o" "p" "q" "r" "s" 67 | #> [20] "t" "u" "v" "w" "x" "y" "z" 68 | env$a 69 | #> Evaluating append_random(sep = "-") 70 | #> [1] "a-s" 71 | env$a 72 | #> Evaluating append_random(sep = "-") 73 | #> [1] "a-h" 74 | env$a 75 | #> Evaluating append_random(sep = "-") 76 | #> [1] "a-c" 77 | env$c 78 | #> Evaluating append_random(sep = "-") 79 | #> [1] "c-o" 80 | env$Z 81 | #> NULL 82 | ``` 83 | 84 | Bindings can also be added to existing environments: 85 | 86 | ``` r 87 | populate_env(env, LETTERS, append_random, "+") 88 | #> Evaluating append_random(sep = "-") 89 | #> Evaluating append_random(sep = "-") 90 | #> Evaluating append_random(sep = "-") 91 | #> Evaluating append_random(sep = "-") 92 | #> Evaluating append_random(sep = "-") 93 | #> Evaluating append_random(sep = "-") 94 | #> Evaluating append_random(sep = "-") 95 | #> Evaluating append_random(sep = "-") 96 | #> Evaluating append_random(sep = "-") 97 | #> Evaluating append_random(sep = "-") 98 | #> Evaluating append_random(sep = "-") 99 | #> Evaluating append_random(sep = "-") 100 | #> Evaluating append_random(sep = "-") 101 | #> Evaluating append_random(sep = "-") 102 | #> Evaluating append_random(sep = "-") 103 | #> Evaluating append_random(sep = "-") 104 | #> Evaluating append_random(sep = "-") 105 | #> Evaluating append_random(sep = "-") 106 | #> Evaluating append_random(sep = "-") 107 | #> Evaluating append_random(sep = "-") 108 | #> Evaluating append_random(sep = "-") 109 | #> Evaluating append_random(sep = "-") 110 | #> Evaluating append_random(sep = "-") 111 | #> Evaluating append_random(sep = "-") 112 | #> Evaluating append_random(sep = "-") 113 | #> Evaluating append_random(sep = "-") 114 | env$a 115 | #> Evaluating append_random(sep = "-") 116 | #> [1] "a-q" 117 | env$Z 118 | #> Evaluating append_random(sep = "+") 119 | #> [1] "Z+c" 120 | ``` 121 | 122 | ## Further properties 123 | 124 | Both named and unnamed arguments are supported: 125 | 126 | ``` r 127 | create_env("binding", paste, "value", sep = "-")$binding 128 | #> [1] "binding-value" 129 | ``` 130 | 131 | A parent environment can be specified for creation: 132 | 133 | ``` r 134 | env2 <- create_env("a", identity, .enclos = env) 135 | env2$a 136 | #> a 137 | env2$b 138 | #> NULL 139 | get("b", env2) 140 | #> Evaluating append_random(sep = "-") 141 | #> [1] "b-t" 142 | ``` 143 | 144 | The bindings by default have access to the calling environment: 145 | 146 | ``` r 147 | create_local_env <- function(names) { 148 | paste_with_dash <- function(...) paste(..., sep = "-") 149 | binder <- function(name, append) paste_with_dash(name, append) 150 | create_env(names, binder, append = "appending") 151 | } 152 | 153 | env3 <- create_local_env("a") 154 | env3$a 155 | #> [1] "a-appending" 156 | ``` 157 | 158 | All bindings are read-only: 159 | 160 | ``` r 161 | env3$a <- NA 162 | #> Error: Binding is read-only. 163 | env3$a <- NULL 164 | #> Error: Binding is read-only. 165 | ``` 166 | 167 | Existing variables or bindings are not overwritten: 168 | 169 | ``` r 170 | env4 <- as.environment(list(a = 5)) 171 | populate_env(env4, list(quote(b)), identity) 172 | ls(env4) 173 | #> [1] "a" "b" 174 | populate_env(env4, letters, identity) 175 | #> Error in populate_env(env4, letters, identity): Not creating bindings for existing variables: b, a 176 | ``` 177 | 178 | ## Active bindings and C++ 179 | 180 | Active bindings must be R functions. To interface with C++ code, one 181 | must bind against an exported Rcpp function, possibly with `rng = false` 182 | if performance matters. The 183 | [`bindrcpp`](https://github.com/krlmlr/bindrcpp#readme) package uses 184 | `bindr` to provide an easy-to-use C++ interface for parametrized active 185 | bindings, and is the recommended way to interface with C++ code. In the 186 | remainder of this section, an alternative using an exported C++ function 187 | is shown. 188 | 189 | The following C++ module exports a function 190 | `change_case(to_upper = FALSE)`, which is bound against in R code later. 191 | 192 | ``` cpp 193 | #include 194 | 195 | #include 196 | #include 197 | 198 | using namespace Rcpp; 199 | 200 | // [[Rcpp::export(rng = FALSE)]] 201 | SEXP change_case(Symbol name, bool to_upper = false) { 202 | std::string name_string = name.c_str(); 203 | std::transform(name_string.begin(), name_string.end(), 204 | name_string.begin(), to_upper ? ::toupper : ::tolower); 205 | return CharacterVector(name_string); 206 | } 207 | ``` 208 | 209 | Binding from R: 210 | 211 | ``` r 212 | env <- create_env(list(as.name("__ToLower__")), change_case) 213 | populate_env(env, list(as.name("__tOuPPER__")), change_case, TRUE) 214 | ls(env) 215 | #> [1] "__ToLower__" "__tOuPPER__" 216 | env$`__ToLower__` 217 | #> [1] "__tolower__" 218 | get("__tOuPPER__", env) 219 | #> [1] "__TOUPPER__" 220 | ``` 221 | -------------------------------------------------------------------------------- /_pkgdown.yml: -------------------------------------------------------------------------------- 1 | template: 2 | params: 3 | bootswatch: flatly # https://bootswatch.com/flatly/ 4 | 5 | development: 6 | mode: 7 | auto 8 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | # DO NOT CHANGE the "init" and "install" sections below 2 | 3 | # Download script file from GitHub 4 | init: 5 | ps: | 6 | $ErrorActionPreference = "Stop" 7 | Invoke-WebRequest http://raw.github.com/krlmlr/r-appveyor/master/scripts/appveyor-tool.ps1 -OutFile "..\appveyor-tool.ps1" 8 | Import-Module '..\appveyor-tool.ps1' 9 | 10 | install: 11 | ps: Bootstrap 12 | 13 | cache: 14 | - C:\RLibrary 15 | 16 | # Adapt as necessary starting from here 17 | 18 | build_script: 19 | - travis-tool.sh install_deps 20 | 21 | test_script: 22 | - travis-tool.sh run_tests 23 | 24 | on_failure: 25 | - 7z a failure.zip *.Rcheck\* 26 | - appveyor PushArtifact failure.zip 27 | 28 | artifacts: 29 | - path: '*.Rcheck\**\*.log' 30 | name: Logs 31 | 32 | - path: '*.Rcheck\**\*.out' 33 | name: Logs 34 | 35 | - path: '*.Rcheck\**\*.fail' 36 | name: Logs 37 | 38 | - path: '*.Rcheck\**\*.Rout' 39 | name: Logs 40 | 41 | - path: '\*_*.tar.gz' 42 | name: Bits 43 | 44 | - path: '\*_*.zip' 45 | name: Bits 46 | -------------------------------------------------------------------------------- /bindr.Rproj: -------------------------------------------------------------------------------- 1 | Version: 1.0 2 | 3 | RestoreWorkspace: No 4 | SaveWorkspace: No 5 | AlwaysSaveHistory: Default 6 | 7 | EnableCodeIndexing: Yes 8 | UseSpacesForTab: Yes 9 | NumSpacesForTab: 2 10 | Encoding: UTF-8 11 | 12 | RnwWeave: knitr 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 | PackageRoxygenize: rd,collate,namespace 22 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | comment: false 2 | -------------------------------------------------------------------------------- /cran-comments.md: -------------------------------------------------------------------------------- 1 | bindr 0.1.2 2 | 3 | ## Cran Repository Policy 4 | 5 | - [x] Reviewed CRP last edited 2024-08-27. 6 | -------------------------------------------------------------------------------- /index.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | # bindr 7 | 8 | 9 | [![Lifecycle: stable](https://img.shields.io/badge/lifecycle-stable-brightgreen.svg)](https://lifecycle.r-lib.org/articles/stages.html) 10 | [![R build status](https://github.com/krlmlr/bindr/workflows/rcc/badge.svg)](https://github.com/krlmlr/bindr/actions) 11 | [![Coverage Status](https://img.shields.io/codecov/c/github/krlmlr/bindr/master.svg)](https://app.codecov.io/github/krlmlr/bindr?branch=master) [![CRAN_Status_Badge](https://www.r-pkg.org/badges/version/bindr)](https://cran.r-project.org/package=bindr) 12 | 13 | 14 | 15 | Active bindings in R are much like properties in other languages: 16 | They look like a variable, 17 | but querying or setting the value triggers a function call. 18 | They can be created in R via 19 | [`makeActiveBinding()`](https://www.rdocumentation.org/packages/base/versions/3.3.1/topics/bindenv), 20 | but with this API the function used to compute or change the value of a binding cannot take additional arguments. 21 | The `bindr` package faciliates the creation of active bindings that are linked to a function that receives the binding name, 22 | and an arbitrary number of additional arguments. 23 | 24 | 25 | ## Installation 26 | 27 | You can install `bindr` from GitHub with: 28 | 29 | 30 | ``` r 31 | # install.packages("devtools") 32 | devtools::install_github("krlmlr/bindr") 33 | ``` 34 | 35 | 36 | ## Getting started 37 | 38 | For illustration, the `append_random()` function is used. 39 | This function appends a separator (a dash by default) and a random letter 40 | to its input, and talks about it, too. 41 | 42 | 43 | ``` r 44 | set.seed(20161510) 45 | append_random <- function(x, sep = "-") { 46 | message("Evaluating append_random(sep = ", deparse(sep), ")") 47 | paste(x, sample(letters, 1), sep = sep) 48 | } 49 | 50 | append_random("a") 51 | #> Evaluating append_random(sep = "-") 52 | #> [1] "a-h" 53 | append_random("X", sep = "+") 54 | #> Evaluating append_random(sep = "+") 55 | #> [1] "X+k" 56 | ``` 57 | 58 | In this example, we create an environment that contains bindings 59 | for all lowercase letters, which are evaluated with `append_random()`. 60 | As a result, a dash and a random letter are appended to the name of the binding: 61 | 62 | 63 | ``` r 64 | library(bindr) 65 | env <- create_env(letters, append_random) 66 | ls(env) 67 | #> [1] "a" "b" "c" "d" "e" "f" "g" "h" "i" "j" "k" "l" "m" "n" "o" "p" "q" "r" "s" 68 | #> [20] "t" "u" "v" "w" "x" "y" "z" 69 | env$a 70 | #> Evaluating append_random(sep = "-") 71 | #> [1] "a-s" 72 | env$a 73 | #> Evaluating append_random(sep = "-") 74 | #> [1] "a-h" 75 | env$a 76 | #> Evaluating append_random(sep = "-") 77 | #> [1] "a-c" 78 | env$c 79 | #> Evaluating append_random(sep = "-") 80 | #> [1] "c-o" 81 | env$Z 82 | #> NULL 83 | ``` 84 | 85 | Bindings can also be added to existing environments: 86 | 87 | 88 | ``` r 89 | populate_env(env, LETTERS, append_random, "+") 90 | #> Evaluating append_random(sep = "-") 91 | #> Evaluating append_random(sep = "-") 92 | #> Evaluating append_random(sep = "-") 93 | #> Evaluating append_random(sep = "-") 94 | #> Evaluating append_random(sep = "-") 95 | #> Evaluating append_random(sep = "-") 96 | #> Evaluating append_random(sep = "-") 97 | #> Evaluating append_random(sep = "-") 98 | #> Evaluating append_random(sep = "-") 99 | #> Evaluating append_random(sep = "-") 100 | #> Evaluating append_random(sep = "-") 101 | #> Evaluating append_random(sep = "-") 102 | #> Evaluating append_random(sep = "-") 103 | #> Evaluating append_random(sep = "-") 104 | #> Evaluating append_random(sep = "-") 105 | #> Evaluating append_random(sep = "-") 106 | #> Evaluating append_random(sep = "-") 107 | #> Evaluating append_random(sep = "-") 108 | #> Evaluating append_random(sep = "-") 109 | #> Evaluating append_random(sep = "-") 110 | #> Evaluating append_random(sep = "-") 111 | #> Evaluating append_random(sep = "-") 112 | #> Evaluating append_random(sep = "-") 113 | #> Evaluating append_random(sep = "-") 114 | #> Evaluating append_random(sep = "-") 115 | #> Evaluating append_random(sep = "-") 116 | env$a 117 | #> Evaluating append_random(sep = "-") 118 | #> [1] "a-q" 119 | env$Z 120 | #> Evaluating append_random(sep = "+") 121 | #> [1] "Z+c" 122 | ``` 123 | 124 | 125 | ## Further properties 126 | 127 | Both named and unnamed arguments are supported: 128 | 129 | 130 | ``` r 131 | create_env("binding", paste, "value", sep = "-")$binding 132 | #> [1] "binding-value" 133 | ``` 134 | 135 | A parent environment can be specified for creation: 136 | 137 | 138 | ``` r 139 | env2 <- create_env("a", identity, .enclos = env) 140 | env2$a 141 | #> a 142 | env2$b 143 | #> NULL 144 | get("b", env2) 145 | #> Evaluating append_random(sep = "-") 146 | #> [1] "b-t" 147 | ``` 148 | 149 | The bindings by default have access to the calling environment: 150 | 151 | 152 | ``` r 153 | create_local_env <- function(names) { 154 | paste_with_dash <- function(...) paste(..., sep = "-") 155 | binder <- function(name, append) paste_with_dash(name, append) 156 | create_env(names, binder, append = "appending") 157 | } 158 | 159 | env3 <- create_local_env("a") 160 | env3$a 161 | #> [1] "a-appending" 162 | ``` 163 | 164 | All bindings are read-only: 165 | 166 | 167 | ``` r 168 | env3$a <- NA 169 | #> Error: Binding is read-only. 170 | env3$a <- NULL 171 | #> Error: Binding is read-only. 172 | ``` 173 | 174 | 175 | Existing variables or bindings are not overwritten: 176 | 177 | 178 | ``` r 179 | env4 <- as.environment(list(a = 5)) 180 | populate_env(env4, list(quote(b)), identity) 181 | ls(env4) 182 | #> [1] "a" "b" 183 | populate_env(env4, letters, identity) 184 | #> Error in populate_env(env4, letters, identity): Not creating bindings for existing variables: b, a 185 | ``` 186 | 187 | 188 | ## Active bindings and C++ 189 | 190 | Active bindings must be R functions. 191 | To interface with C++ code, one must bind against an exported Rcpp function, possibly with `rng = false` if performance matters. 192 | The [`bindrcpp`](https://github.com/krlmlr/bindrcpp#readme) package 193 | uses `bindr` to provide an easy-to-use C++ interface for parametrized active bindings, 194 | and is the recommended way to interface with C++ code. 195 | In the remainder of this section, 196 | an alternative using an exported C++ function is shown. 197 | 198 | The following C++ module exports a function `change_case(to_upper = FALSE)`, 199 | which is bound against in R code later. 200 | 201 | 202 | ``` cpp 203 | #include 204 | 205 | #include 206 | #include 207 | 208 | using namespace Rcpp; 209 | 210 | // [[Rcpp::export(rng = FALSE)]] 211 | SEXP change_case(Symbol name, bool to_upper = false) { 212 | std::string name_string = name.c_str(); 213 | std::transform(name_string.begin(), name_string.end(), 214 | name_string.begin(), to_upper ? ::toupper : ::tolower); 215 | return CharacterVector(name_string); 216 | } 217 | ``` 218 | 219 | Binding from R: 220 | 221 | 222 | ``` r 223 | env <- create_env(list(as.name("__ToLower__")), change_case) 224 | populate_env(env, list(as.name("__tOuPPER__")), change_case, TRUE) 225 | ls(env) 226 | #> [1] "__ToLower__" "__tOuPPER__" 227 | env$`__ToLower__` 228 | #> [1] "__tolower__" 229 | get("__tOuPPER__", env) 230 | #> [1] "__TOUPPER__" 231 | ``` 232 | -------------------------------------------------------------------------------- /man/bindr-package.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/bindr-package.R 3 | \docType{package} 4 | \name{bindr-package} 5 | \alias{bindr} 6 | \alias{bindr-package} 7 | \title{bindr: Parametrized Active Bindings} 8 | \description{ 9 | Provides a simple interface for creating active bindings where the bound function accepts additional arguments. 10 | } 11 | \details{ 12 | See \code{\link[=create_env]{create_env()}} for creating an environment populated with active bindings, 13 | and \code{\link[=populate_env]{populate_env()}} for populating an existing environment. 14 | } 15 | \seealso{ 16 | Useful links: 17 | \itemize{ 18 | \item \url{https://github.com/krlmlr/bindr} 19 | \item \url{https://krlmlr.github.io/bindr/} 20 | \item Report bugs at \url{https://github.com/krlmlr/bindr/issues} 21 | } 22 | 23 | } 24 | \author{ 25 | \strong{Maintainer}: Kirill Müller \email{kirill@cynkra.com} (\href{https://orcid.org/0000-0002-1416-3412}{ORCID}) 26 | 27 | Other contributors: 28 | \itemize{ 29 | \item RStudio [copyright holder, funder] 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /man/create_env.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/populate.R 3 | \name{create_env} 4 | \alias{create_env} 5 | \alias{populate_env} 6 | \title{Create or populate an environment with parametrized active bindings} 7 | \usage{ 8 | create_env(names, fun, ..., .envir = parent.frame(), .enclos = parent.frame()) 9 | 10 | populate_env(env, names, fun, ..., .envir = parent.frame()) 11 | } 12 | \arguments{ 13 | \item{names}{A \link{name}, or a list of names, or a character vector; in the latter case 14 | the names are mangled if they are not representable in the native encoding} 15 | 16 | \item{fun}{A \link{function} with at least one argument, which will be called 17 | to compute the value of a binding. The function will be called with the 18 | binding name as first argument (unnamed), and \code{...} as additional arguments} 19 | 20 | \item{...}{Additional arguments to \code{fun}} 21 | 22 | \item{.envir}{The \link{environment} in which \code{fun} will be executed, 23 | important if \code{fun} calls other functions that are not globally visible} 24 | 25 | \item{.enclos}{The enclosing environment (\code{parent.env}) for the newly created environment} 26 | 27 | \item{env}{An environment} 28 | } 29 | \description{ 30 | Leverages \link[base:bindenv]{makeActiveBinding()} 31 | by allowing parametrized functions 32 | that take the name of the binding and an arbitrary number of additional arguments. 33 | } 34 | \examples{ 35 | env <- create_env(letters, paste0, "-lowercase") 36 | env$a 37 | env$c 38 | env$Z 39 | populate_env(env, LETTERS, paste0, "-uppercase") 40 | env$a 41 | env$Z 42 | } 43 | -------------------------------------------------------------------------------- /revdep/.gitignore: -------------------------------------------------------------------------------- 1 | cloud.noindex 2 | -------------------------------------------------------------------------------- /revdep/README.md: -------------------------------------------------------------------------------- 1 | # Revdeps 2 | 3 | -------------------------------------------------------------------------------- /revdep/cran.md: -------------------------------------------------------------------------------- 1 | ## revdepcheck results 2 | 3 | We checked 2 reverse dependencies, comparing R CMD check results across CRAN and dev versions of this package. 4 | 5 | * We saw 0 new problems 6 | * We failed to check 0 packages 7 | 8 | -------------------------------------------------------------------------------- /revdep/failures.md: -------------------------------------------------------------------------------- 1 | *Wow, no problems at all. :)* -------------------------------------------------------------------------------- /revdep/problems.md: -------------------------------------------------------------------------------- 1 | *Wow, no problems at all. :)* -------------------------------------------------------------------------------- /tests/testthat.R: -------------------------------------------------------------------------------- 1 | library(testthat) 2 | library(bindr) 3 | 4 | test_check("bindr") 5 | -------------------------------------------------------------------------------- /tests/testthat/test-create.R: -------------------------------------------------------------------------------- 1 | context("create") 2 | 3 | test_that("create_env()", { 4 | env <- create_env(lapply(letters, as.name), toupper) 5 | expect_equal(env$a, "A") 6 | expect_equal(env$x, "X") 7 | expect_null(env$X) 8 | expect_equal(length(ls(env)), length(letters)) 9 | expect_error(env$a <- "a", "read-only") 10 | }) 11 | 12 | test_that("create_env() with character", { 13 | env <- create_env(letters, toupper) 14 | expect_equal(env$a, "A") 15 | expect_equal(env$x, "X") 16 | expect_null(env$X) 17 | expect_equal(length(ls(env)), length(letters)) 18 | expect_error(env$a <- "a", "read-only") 19 | }) 20 | 21 | test_that("create_env() with inheritance", { 22 | env <- create_env(lapply(letters, as.name), toupper) 23 | env2 <- create_env(lapply(LETTERS, as.name), tolower, .enclos = env) 24 | expect_equal(get("a", env2), "A") 25 | expect_equal(get("x", env2), "X") 26 | expect_null(env2$a) 27 | expect_null(env2$x) 28 | expect_equal(env2$B, "b") 29 | expect_equal(env2$Y, "y") 30 | expect_equal(length(ls(env2)), length(letters)) 31 | expect_error(env2$B <- "B", "read-only") 32 | expect_error(env2$a <- "a", NA) 33 | expect_equal(get("a", env2), "a") 34 | }) 35 | 36 | test_that("create_env() with local function", { 37 | a <- function(x) b(x) 38 | b <- function(x) c(x) 39 | c <- function(x) toupper(x) 40 | env <- create_env(lapply(letters, as.name), a) 41 | expect_equal(env$a, "A") 42 | expect_equal(env$x, "X") 43 | expect_null(env$X) 44 | expect_equal(length(ls(env)), length(letters)) 45 | expect_error(env$a <- "a", "read-only") 46 | }) 47 | -------------------------------------------------------------------------------- /tests/testthat/test-error.R: -------------------------------------------------------------------------------- 1 | context("error") 2 | 3 | test_that("non-character raises error", { 4 | expect_error(create_env(1:3, identity)) 5 | expect_error(create_env(FALSE, identity)) 6 | }) 7 | 8 | test_that("non-native encoding causes warning", { 9 | with_mock( 10 | `bindr::to_symbol_encoding` = function(x) paste0(x, "-garbled"), 11 | expect_warning(create_env(letters[1:2], identity), 12 | "a -> a-garbled, b -> b-garbled", fixed = TRUE) 13 | ) 14 | }) 15 | -------------------------------------------------------------------------------- /tests/testthat/test-payload.R: -------------------------------------------------------------------------------- 1 | context("payload") 2 | 3 | test_that("create_env() with payload", { 4 | env <- create_env(lapply(letters, as.name), paste, "letter") 5 | expect_equal(env$a, "a letter") 6 | expect_equal(env$x, "x letter") 7 | expect_null(env$X) 8 | expect_equal(length(ls(env)), length(letters)) 9 | expect_error(env$a <- "a", "read-only") 10 | }) 11 | 12 | test_that("create_env() with named payload", { 13 | env <- create_env(lapply(letters, as.name), paste0, 1:3, collapse = "") 14 | expect_equal(env$a, "a1a2a3") 15 | expect_equal(env$x, "x1x2x3") 16 | expect_null(env$X) 17 | expect_equal(length(ls(env)), length(letters)) 18 | expect_error(env$a <- "a", "read-only") 19 | }) 20 | -------------------------------------------------------------------------------- /tests/testthat/test-populate.R: -------------------------------------------------------------------------------- 1 | context("populate") 2 | 3 | test_that("can populate existing env", { 4 | env <- new.env(parent = emptyenv()) 5 | populate_env(env, letters, identity) 6 | expect_equal(env$a, quote(a)) 7 | expect_equal(env$k, quote(k)) 8 | expect_null(env$Z) 9 | }) 10 | 11 | test_that("cannot update existing vars", { 12 | env <- new.env(parent = emptyenv()) 13 | populate_env(env, "v", identity) 14 | expect_error(populate_env(env, letters, identity), "existing") 15 | }) 16 | -------------------------------------------------------------------------------- /tic.R: -------------------------------------------------------------------------------- 1 | add_package_checks() 2 | 3 | if (Sys.getenv("BUILD_PKGDOWN") != "") { 4 | get_stage("deploy") %>% 5 | add_step(step_build_pkgdown()) 6 | 7 | if (Sys.getenv("id_rsa") != "") { 8 | get_stage("before_deploy") %>% 9 | add_step(step_setup_ssh()) 10 | 11 | # pkgdown documentation can be built optionally. Other example criteria: 12 | # - `inherits(ci(), "TravisCI")`: Only for Travis CI 13 | # - `ci()$is_tag()`: Only for tags, not for branches 14 | # - `Sys.getenv("BUILD_PKGDOWN") != ""`: If the env var "BUILD_PKGDOWN" is set 15 | # - `Sys.getenv("TRAVIS_EVENT_TYPE") == "cron"`: Only for Travis cron jobs 16 | get_stage("deploy") %>% 17 | add_step(step_push_deploy()) 18 | } 19 | } 20 | --------------------------------------------------------------------------------