├── .Rbuildignore ├── .github ├── .gitignore ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── dependabot.yaml └── workflows │ ├── R-CMD-check.yaml │ ├── end-to-end.yml │ ├── hook-dependencies-update.yml │ ├── hook-release.yml │ ├── hook-tests.yaml │ └── pkgdown.yaml ├── .gitignore ├── .pre-commit-config.yaml ├── .pre-commit-hooks.yaml ├── API ├── CONTRIBUTING.md ├── DESCRIPTION ├── LICENSE.md ├── NAMESPACE ├── NEWS.md ├── R ├── aaa.R ├── assert.R ├── cache.R ├── call.R ├── config.R ├── exec.R ├── install.R ├── open.R ├── precommit-package.R ├── purl.R ├── release.R ├── roxygen2.R ├── setup.R ├── testing.R ├── update.R ├── usethis.R ├── utils.R └── zzz.R ├── README.Rmd ├── README.md ├── _pkgdown.yml ├── cran-comments.md ├── inst ├── WORDLIST ├── hooks │ ├── exported │ │ ├── codemeta-description-updated.R │ │ ├── deps-in-desc.R │ │ ├── lintr.R │ │ ├── no-browser-statement.R │ │ ├── no-debug-statement.R │ │ ├── no-print-statement.R │ │ ├── parsable-R.R │ │ ├── parsable-roxygen.R │ │ ├── pkgdown.R │ │ ├── readme-rmd-rendered.R │ │ ├── renv-lockfile-validate.R │ │ ├── roxygenize.R │ │ ├── spell-check.R │ │ ├── style-files.R │ │ └── use-tidy-description.R │ └── local │ │ ├── consistent-release-tag.R │ │ ├── hooks-config-to-inst.R │ │ ├── spell-check-exclude-identical.R │ │ └── spell-check-ordered-exclude.R ├── pre-commit-config-pkg.yaml ├── pre-commit-config-proj.yaml ├── pre-commit-gha.yaml ├── pre-commit-hooks.yaml ├── update-dependency-graph-existing-packages.R ├── update-existing-hook-dependencies.R ├── update-ppm-url.R ├── update-renv-prepare.R └── usethis-legacy-hook ├── man ├── autoupdate.Rd ├── call_and_capture.Rd ├── call_precommit.Rd ├── communicate_captured_call.Rd ├── copy_artifacts.Rd ├── diff_requires_run_roxygenize.Rd ├── dirs_R.cache.Rd ├── empty_on_cran.Rd ├── ensure_named.Rd ├── fallback_doc.Rd ├── figures │ ├── pre-commit-meme.jpeg │ └── screenshot.png ├── generate_uninstalled_pkg_name.Rd ├── get_os.Rd ├── git_init.Rd ├── hook_state_assert.Rd ├── hook_state_create.Rd ├── install_impl.Rd ├── install_precommit.Rd ├── local_test_setup.Rd ├── may_require_permanent_cache.Rd ├── not_conda.Rd ├── open_config.Rd ├── path_derive_precommit_exec.Rd ├── path_derive_precommit_exec_conda.Rd ├── path_derive_precommit_exec_impl.Rd ├── path_derive_precommit_exec_path.Rd ├── path_derive_precommit_exec_win_python3plus_base.Rd ├── path_precommit_exec.Rd ├── precommit-package.Rd ├── precommit_docopt.Rd ├── precommit_executable_file.Rd ├── release_complete.Rd ├── release_gh.Rd ├── rev_read.Rd ├── robust_purl.Rd ├── roxygen_assert_additional_dependencies.Rd ├── roxygenize_with_cache.Rd ├── run_test.Rd ├── run_test_impl.Rd ├── set_config_source.Rd ├── snippet_generate.Rd ├── uninstall_precommit.Rd ├── update_impl.Rd ├── update_precommit.Rd ├── update_rev_in_config.Rd ├── use_ci.Rd ├── use_precommit.Rd ├── use_precommit_config.Rd └── version_precommit.Rd ├── precommit.Rproj ├── renv.lock ├── renv ├── .gitignore ├── activate.R ├── settings.dcf └── settings.json ├── revdep ├── .gitignore ├── README.md ├── cran.md ├── failures.md └── problems.md ├── tests ├── testthat.R └── testthat │ ├── in │ ├── DESCRIPTION │ ├── DESCRIPTION-no-deps.dcf │ ├── README.Rmd │ ├── README.md │ ├── Rprofile │ ├── WORDLIST │ ├── _pkgdown-articles.yml │ ├── _pkgdown-index-articles.yml │ ├── _pkgdown-index.yml │ ├── autoupdate.Rd │ ├── codemeta.json │ ├── deps-in-desc-dot3-fail.R │ ├── deps-in-desc-dot3-success.R │ ├── deps-in-desc-fail.R │ ├── deps-in-desc-fail.Rmd │ ├── deps-in-desc-fail.Rnw │ ├── deps-in-desc-success.R │ ├── deps-in-desc-success.Rmd │ ├── deps-in-desc-success.Rnw │ ├── flie-true.Rd │ ├── flie.Rd │ ├── lintr-fail.R │ ├── lintr-fail.qmd │ ├── lintr-success.R │ ├── lintr-success.qmd │ ├── no-browser-statement-fail.R │ ├── no-browser-statement-success.R │ ├── no-debug-statement-fail.R │ ├── no-debug-statement-success.R │ ├── no-print-statement-fail.R │ ├── no-print-statement-success.R │ ├── parsable-R-fail.R │ ├── parsable-R-fail.Rmd │ ├── parsable-R-success.R │ ├── parsable-R-success.Rmd │ ├── parsable-roxygen-fail.R │ ├── parsable-roxygen-fail2.R │ ├── parsable-roxygen-success.R │ ├── pkgdown.Rmd │ ├── renv-fail.lock │ ├── renv-success.lock │ ├── roxygenize-cache-success.R │ ├── roxygenize.R │ ├── spell-check-fail-2.md │ ├── spell-check-fail.md │ ├── spell-check-ignored-success.md │ ├── spell-check-language-success.md │ ├── spell-check-success.md │ ├── spell-check-wordlist-success.md │ ├── style-files-base-indention-success.R │ ├── style-files-cache-success.R │ ├── style-files-cmd-fail.R │ ├── style-files-cmd-success.R │ ├── style-files-fail-changed.R │ ├── style-files-fail-parse.R │ ├── style-files-ignore-fail.R │ ├── style-files-ignore-success.R │ ├── style-files-reindention-success.R │ ├── style-files-roxygen-success.R │ └── style-files-success.R │ ├── reference-objects │ ├── DESCRIPTION │ └── pre-commit-config.yaml │ ├── test-conda.R │ ├── test-config.R │ ├── test-docopt.R │ ├── test-exec.R │ ├── test-hook-codemeta-description-updated.R │ ├── test-hook-deps-in-desc-R.R │ ├── test-hook-lintr.R │ ├── test-hook-no-browser-statement.R │ ├── test-hook-no-debug-statement.R │ ├── test-hook-no-print-statement.R │ ├── test-hook-parsable-R.R │ ├── test-hook-parsable-roxygen.R │ ├── test-hook-pkgdown.R │ ├── test-hook-readme-rmd-rendered.R │ ├── test-hook-renv-lockfile-validate.R │ ├── test-hook-roxygenize.R │ ├── test-hook-spell-check.R │ ├── test-hook-style-files.R │ ├── test-hook-use-tidy-description.R │ ├── test-impl-hook-roxygenize.R │ ├── test-impl-hooks-regex-exclude.R │ ├── test-setup.R │ ├── test-utils.R │ └── test-zzz.R └── vignettes ├── .gitignore ├── FAQ.Rmd ├── available-hooks.Rmd ├── ci.Rmd ├── hook-order.Rmd ├── precommit.Rmd ├── testing.Rmd └── why-use-hooks.Rmd /.Rbuildignore: -------------------------------------------------------------------------------- 1 | ^.*\.Rproj$ 2 | ^API$ 3 | ^CONTRIBUTING\.md$ 4 | ^LICENSE\.md$ 5 | ^README\.Rmd$ 6 | ^\.Rproj\.user$ 7 | ^\.github$ 8 | ^\.pre-commit-config\.yaml$ 9 | ^\.pre-commit-hooks\.yaml$ 10 | ^_pkgdown\.yml$ 11 | ^cran-comments\.md$ 12 | ^docs$ 13 | ^inst/WORDLIST$ 14 | ^inst/hooks/local$ 15 | ^inst/renv-update\.R$ 16 | ^inst/update-dependency-graph-existing-packages\.R$ 17 | ^inst/update-existing-hook-dependencies\.R$ 18 | ^pkgdown$ 19 | ^renv$ 20 | ^renv\.lock$ 21 | ^revdep$ 22 | ^scratch$ 23 | -------------------------------------------------------------------------------- /.github/.gitignore: -------------------------------------------------------------------------------- 1 | *.html 2 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: lorenzwalthert 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 13 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Before filing a bug** 11 | 12 | - [ ] I have installed the latest dev version of {precommit} with `remotes::install_github('lorenzwalthert/precommit')` 13 | - [ ] I have installed the latest hook revisions (update with `precommit::autoupdate()`) 14 | - [ ] I have installed the latest release of the upstream Python framework pre-comit as described under the [update instructions](https://lorenzwalthert.github.io/precommit/dev/articles/precommit.html#update). 15 | 16 | **Describe the bug** 17 | A clear and concise description of what the bug is. 18 | 19 | **To Reproduce** 20 | Steps to reproduce the behavior: 21 | 1. Go to '...' 22 | 2. Click on '....' 23 | 3. Scroll down to '....' 24 | 4. See error 25 | 26 | **Expected behavior** 27 | A clear and concise description of what you expected to happen. 28 | 29 | **Additional context** 30 | 31 | - My operating system is: 32 | - [ ] My project uses {renv}. 33 | - [ ] the output of `packageVersion('renv')` 34 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/dependabot.yaml: -------------------------------------------------------------------------------- 1 | version: 2 2 | 3 | updates: 4 | # Keep dependencies for GitHub Actions up-to-date 5 | - package-ecosystem: "github-actions" 6 | directory: "/" 7 | schedule: 8 | interval: "weekly" 9 | -------------------------------------------------------------------------------- /.github/workflows/R-CMD-check.yaml: -------------------------------------------------------------------------------- 1 | # NOTE: This workflow is overkill for most R packages 2 | # check-standard.yaml is likely a better choice 3 | # usethis::use_github_action("check-standard") will install it. 4 | # 5 | # For help debugging build failures open an issue on the RStudio community with the 'github-actions' tag. 6 | # https://community.rstudio.com/new-topic?category=Package%20development&tags=github-actions 7 | on: 8 | push: 9 | branches: 10 | - main 11 | pull_request: 12 | branches: 13 | - main 14 | 15 | name: R-CMD-check 16 | 17 | jobs: 18 | R-CMD-check: 19 | runs-on: ${{ matrix.config.os }} 20 | 21 | name: ${{ matrix.config.os }} (${{ matrix.config.r }}, ${{ matrix.config.installation_method }}) 22 | 23 | strategy: 24 | fail-fast: false 25 | matrix: 26 | config: 27 | # macOS 28 | - {os: macOS-latest, r: 'release', installation_method: 'brew'} 29 | - {os: macOS-latest, r: 'release', installation_method: 'conda'} 30 | - {os: macOS-latest, r: 'release', installation_method: 'pip'} 31 | # Windows 32 | - {os: windows-latest, r: 'release', installation_method: 'pip'} 33 | - {os: windows-latest, r: 'release', installation_method: 'conda'} 34 | - {os: windows-latest, r: 'devel', installation_method: 'conda'} 35 | 36 | # Linux 37 | - {os: ubuntu-latest, r: 'release', installation_method: 'pip', use-public-rspm: true} 38 | - {os: ubuntu-latest, r: 'release', installation_method: 'conda', use-public-rspm: true} 39 | 40 | env: 41 | R_REMOTES_NO_ERRORS_FROM_WARNINGS: true 42 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} 43 | PRECOMMIT_INSTALLATION_METHOD: ${{ matrix.config.installation_method }} 44 | 45 | steps: 46 | - uses: actions/checkout@v4 47 | 48 | - uses: r-lib/actions/setup-r@v2 49 | with: 50 | r-version: ${{ matrix.config.r }} 51 | http-user-agent: ${{ matrix.config.http-user-agent }} 52 | 53 | - uses: r-lib/actions/setup-pandoc@v2 54 | - name: Install system dependencies (brew) 55 | if: matrix.config.installation_method == 'brew' 56 | run: brew install pre-commit 57 | - name: Install system dependencies (pip, non-macOS) 58 | if: matrix.config.installation_method == 'pip' && matrix.config.os != 'macOS-latest' 59 | run: | 60 | pip3 install pre-commit --user 61 | - name: Install system dependencies (pip, macOS) 62 | if: matrix.config.installation_method == 'pip' && matrix.config.os == 'macOS-latest' 63 | run: | 64 | pip3 install pre-commit --user --break-system-packages 65 | - uses: r-lib/actions/setup-r-dependencies@v2 66 | - name: Remove reticulate for other installation methods 67 | run: | 68 | # installation methods other than conda don't need reticulate 69 | if (Sys.getenv("PRECOMMIT_INSTALLATION_METHOD") != 'conda') { 70 | remove.packages('reticulate') 71 | } 72 | shell: Rscript {0} 73 | - name: Install system dependencies (conda) 74 | if: matrix.config.installation_method == 'conda' 75 | run: | 76 | reticulate::install_miniconda() 77 | shell: Rscript {0} 78 | - name: Session info 79 | run: | 80 | options(width = 100) 81 | pkgs <- installed.packages()[, "Package"] 82 | sessioninfo::session_info(pkgs, include_base = TRUE) 83 | shell: Rscript {0} 84 | 85 | - name: Check 86 | env: 87 | _R_CHECK_CRAN_INCOMING_: false 88 | run: | 89 | options(crayon.enabled = TRUE) 90 | if (Sys.getenv("PRECOMMIT_INSTALLATION_METHOD") != 'conda') { 91 | Sys.setenv("_R_CHECK_FORCE_SUGGESTS_" = "False") 92 | } 93 | install.packages(c("rcmdcheck", "remotes")) 94 | remotes::install_local() # circumvent https://github.com/r-lib/rcmdcheck/issues/136 95 | rcmdcheck::rcmdcheck(args = c("--no-manual", "--as-cran"), error_on = "warning", check_dir = "check") 96 | shell: Rscript {0} 97 | 98 | - name: Show testthat output 99 | if: always() 100 | run: find check -name 'testthat.Rout*' -exec cat '{}' \; || true 101 | shell: bash 102 | 103 | - name: Upload check results 104 | if: failure() 105 | uses: actions/upload-artifact@main 106 | with: 107 | name: ${{ runner.os }}-r${{ matrix.config.r }}-results 108 | path: check 109 | 110 | - name: Don't use tar from old Rtools to store the cache 111 | if: ${{ runner.os == 'Windows' && startsWith(steps.install-r.outputs.installed-r-version, '3.6' ) }} 112 | shell: bash 113 | run: echo "C:/Program Files/Git/usr/bin" >> $GITHUB_PATH 114 | -------------------------------------------------------------------------------- /.github/workflows/end-to-end.yml: -------------------------------------------------------------------------------- 1 | on: 2 | pull_request: 3 | branches: 4 | - '*' 5 | 6 | name: end-2-end 7 | 8 | jobs: 9 | end-2-end: 10 | runs-on: macOS-latest 11 | env: 12 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} 13 | steps: 14 | - uses: r-lib/actions/setup-pandoc@v2 15 | 16 | - uses: r-lib/actions/setup-r@v2 17 | 18 | - uses: actions/checkout@v4 19 | 20 | - name: Query dependencies 21 | run: | 22 | install.packages('remotes') 23 | saveRDS(remotes::dev_package_deps(dependencies = TRUE), ".github/depends.Rds", version = 2) 24 | writeLines(sprintf("R-%i.%i", getRversion()$major, getRversion()$minor), ".github/R-version") 25 | shell: Rscript {0} 26 | 27 | - name: Cache R packages 28 | uses: actions/cache@v4 29 | with: 30 | path: ${{ env.R_LIBS_USER }} 31 | key: ${{ runner.os }}-${{ hashFiles('.github/R-version') }}-1-${{ hashFiles('.github/depends.Rds') }} 32 | restore-keys: ${{ runner.os }}-${{ hashFiles('.github/R-version') }}-1- 33 | 34 | - name: Install dependencies 35 | run: | 36 | remotes::install_deps(dependencies = TRUE) 37 | remotes::install_cran('yaml') 38 | shell: Rscript {0} 39 | 40 | - name: Install package 41 | run: R CMD INSTALL . 42 | 43 | - name: Prepare pre-commit 44 | run: | 45 | config_name_test <- "pre-commit-config.yaml" 46 | config_name <- paste0(".", config_name_test) 47 | ref_config <- file.path("tests", "testthat", "reference-objects", config_name_test) 48 | len_declared <- length(yaml::read_yaml(".pre-commit-hooks.yaml")) 49 | len_testing <- length(yaml::read_yaml(ref_config)$repos[[1]]$hooks) 50 | if (len_declared != len_testing) { 51 | rlang::abort("You don't test all hooks. Add them to `test_path('reference-objects/pre-commit-config.yaml')`") 52 | } 53 | fs::file_delete(config_name) 54 | fs::file_copy(ref_config, config_name) 55 | shell: Rscript {0} 56 | - name: Update Hook revision to current push 57 | run: | 58 | # hacky, maybe can use pre-commit try-repo? 59 | # https://stackoverflow.com/questions/30871868/sed-replace-first-occurence-in-place-with-big-files 60 | sed -i '' -e "1,/rev:.*/{s/rev:.*/rev: $GITHUB_SHA/;}" .pre-commit-config.yaml 61 | git config --local user.email "github-actions[bot]@users.noreply.github.com" 62 | git config --local user.name "github-actions[bot]" 63 | git add . 64 | git commit -m 'current hash must be head' 65 | 66 | - name: Run pre-commit 67 | run: | 68 | brew install pre-commit 69 | pre-commit install --install-hooks 70 | 71 | echo 'running R/test.R' 72 | echo "#' some code\n#'\n#' @param here.\n#' @name somethings\nNULL" > R/test.R # overwrite if anything there 73 | pre-commit run --files R/test.R 74 | 75 | echo 'running DESCRIPTION' 76 | cp tests/testthat/reference-objects/DESCRIPTION . 77 | sleep 2 78 | echo 'one' >> codemeta.json 79 | pre-commit run --files DESCRIPTION 80 | 81 | echo 'running README' 82 | echo 'one' > README.Rmd 83 | sleep 1 84 | echo 'one' > README.md 85 | git add README* 86 | pre-commit run --files README.Rmd 87 | 88 | echo 'running _pkgdown.yml' 89 | cp tests/testthat/in/_pkgdown-index-articles.yml _pkgdown.yml 90 | rm -rf man/ 91 | mkdir man 92 | cp tests/testthat/in/autoupdate.Rd man/autoudpate.Rd 93 | rm -rf vignettes 94 | mkdir vignettes 95 | cp tests/testthat/in/pkgdown.Rmd vignettes/ 96 | pre-commit run --files _pkgdown.yml 97 | 98 | echo 'running man/autoupdate.Rd' 99 | git reset HEAD --hard # restore initial state 100 | git clean -f 101 | rm -rf man/ 102 | mkdir -p man 103 | cp tests/testthat/in/autoupdate.Rd man/ 104 | pre-commit run roxygenize --files man/autoupdate.Rd 105 | 106 | git reset HEAD --hard # restore initial state 107 | git clean -f 108 | 109 | env: 110 | SKIP: consistent-release-tag 111 | -------------------------------------------------------------------------------- /.github/workflows/hook-dependencies-update.yml: -------------------------------------------------------------------------------- 1 | on: 2 | schedule: 3 | - cron: '50 8 1,15 * *' 4 | workflow_dispatch: 5 | 6 | 7 | name: Hook dependency updates 8 | 9 | jobs: 10 | hook-dependencies-update: 11 | runs-on: ubuntu-20.04 12 | env: 13 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} 14 | steps: 15 | - uses: actions/checkout@v4 16 | 17 | - uses: r-lib/actions/setup-r@v2 18 | with: 19 | use-public-rspm: true 20 | - name: install runtime dependencies 21 | run: Rscript -e "install.packages(c('renv', 'jsonlite'))" 22 | - name: update PPM URL 23 | run: Rscript inst/update-ppm-url.R 24 | - name: update dependency graph among packages 25 | run: Rscript inst/update-existing-hook-dependencies.R 26 | - name: update existing packages 27 | run: Rscript inst/update-dependency-graph-existing-packages.R 28 | - name: install pre-commit 29 | run: pip3 install pre-commit 30 | - name: auto-release 31 | run: Rscript -e 'source("renv/activate.R"); renv::restore(); renv::install("."); precommit:::auto_release()' 32 | - name: Create Pull Request 33 | uses: peter-evans/create-pull-request@v7.0.7 34 | with: 35 | token: ${{ secrets.PRECOMMIT_HOOK_DEPENDENCY_UPDATE }} 36 | commit-message: Update renv dependencies 37 | branch: hook-dependencies-update 38 | delete-branch: true 39 | assignees: lorenzwalthert 40 | title: 'Hook dependencies update' 41 | body: | 42 | This PR updates the hook dependencies in `renv.lock`, auto-generated by [create-pull-request][1]. Close and re-open this to trigger `on: pull_request` events to circumvent [limitation of GitHub actions](https://github.com/peter-evans/create-pull-request/blob/master/docs/concepts-guidelines.md#push-pull-request-branches-to-a-fork). 43 | 44 | [1]: https://github.com/peter-evans/create-pull-request 45 | 46 | - name: Push latest tag 47 | run: | 48 | Rscript -e "install.packages('desc')" 49 | TAG="v$(Rscript -e 'cat(as.character(desc::desc_get_version()))')" 50 | git tag $TAG 51 | git push origin $TAG 52 | -------------------------------------------------------------------------------- /.github/workflows/hook-release.yml: -------------------------------------------------------------------------------- 1 | name: hook-release 2 | 3 | on: 4 | pull_request: 5 | types: 6 | - closed 7 | 8 | jobs: 9 | if_merged: 10 | if: github.event.pull_request.merged == true && github.head_ref == 'hook-dependencies-update' 11 | runs-on: ubuntu-latest 12 | steps: 13 | - run: | 14 | echo The PR was merged 15 | -------------------------------------------------------------------------------- /.github/workflows/pkgdown.yaml: -------------------------------------------------------------------------------- 1 | # Workflow derived from https://github.com/r-lib/actions/tree/v2/examples 2 | # Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help 3 | on: 4 | push: 5 | branches: [main, master] 6 | pull_request: 7 | branches: [main, master] 8 | release: 9 | types: [published] 10 | workflow_dispatch: 11 | 12 | name: pkgdown 13 | 14 | jobs: 15 | pkgdown: 16 | runs-on: ubuntu-latest 17 | # Only restrict concurrency for non-PR jobs 18 | concurrency: 19 | group: pkgdown-${{ github.event_name != 'pull_request' || github.run_id }} 20 | env: 21 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} 22 | steps: 23 | - uses: actions/checkout@v4 24 | 25 | - uses: r-lib/actions/setup-pandoc@v2 26 | 27 | - uses: r-lib/actions/setup-r@v2 28 | with: 29 | use-public-rspm: true 30 | 31 | - uses: r-lib/actions/setup-r-dependencies@v2 32 | with: 33 | extra-packages: any::pkgdown, local::. 34 | needs: website 35 | 36 | - name: Build site 37 | run: pkgdown::build_site_github_pages(new_process = FALSE, install = FALSE) 38 | shell: Rscript {0} 39 | 40 | - name: Deploy to GitHub pages 🚀 41 | if: github.event_name != 'pull_request' 42 | uses: JamesIves/github-pages-deploy-action@v4.7.3 43 | with: 44 | clean: false 45 | branch: gh-pages 46 | folder: docs 47 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .RData 3 | .Rhistory 4 | .Rprofile 5 | .Rproj.user 6 | .Ruserdata 7 | docs/ 8 | inst/doc 9 | scratch/ 10 | -------------------------------------------------------------------------------- /API: -------------------------------------------------------------------------------- 1 | # API for precommit package 2 | 3 | ## Exported functions 4 | 5 | autoupdate(root = here::here()) 6 | diff_requires_run_roxygenize(root = ".") 7 | dirs_R.cache(hook_id) 8 | install_precommit(force = FALSE) 9 | may_require_permanent_cache(temp_cache_is_enough = FALSE) 10 | open_config(root = here::here()) 11 | open_wordlist(root = here::here()) 12 | path_pre_commit_exec(check_if_exists = TRUE) 13 | path_precommit_exec(check_if_exists = TRUE) 14 | precommit_docopt(doc, args = commandArgs(trailingOnly = TRUE), ...) 15 | robust_purl(path) 16 | roxygen_assert_additional_dependencies() 17 | roxygenize_with_cache(key, dirs) 18 | snippet_generate(snippet = "additional-deps-roxygenize", open = rstudioapi::isAvailable(), root = here::here()) 19 | uninstall_precommit(scope = "repo", ask = "user", root = here::here()) 20 | update_precommit() 21 | use_ci(ci = getOption("precommit.ci", "native"), force = FALSE, open = rstudioapi::isAvailable(), root = here::here()) 22 | use_precommit(config_source = getOption("precommit.config_source"), force = FALSE, legacy_hooks = "forbid", open = rstudioapi::isAvailable(), install_hooks = TRUE, ci = getOption("precommit.ci", "native"), autoupdate = install_hooks, root = here::here()) 23 | use_precommit_config(config_source = getOption("precommit.config_source"), force = FALSE, open = rstudioapi::isAvailable(), verbose = FALSE, root = here::here()) 24 | version_precommit() 25 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # General 2 | 3 | Before making a pull request, discuss your ideas in an issue. 4 | 5 | # Adding new hooks 6 | 7 | To create a new hook, have a look at the [official 8 | documentation](https://pre-commit.com/#new-hooks) on creating new hooks, then have a look 9 | at existing hooks in this repo. The actual executables are defined in [`inst/hooks/`](https://github.com/lorenzwalthert/precommit/tree/main/inst/hooks). In 10 | the script, you can expect the passed command line arguments to be all options, 11 | finally the files that should be processed with the hook. 12 | 13 | For the scripts to become a hook, they need to be *registered* in 14 | [`.pre-commit-hooks.yaml`](https://github.com/lorenzwalthert/precommit/blob/main/.pre-commit-hooks.yaml). As of pre-commit 2.11, R is a [supported language of 15 | pre-commit](https://pre-commit.com/#r). Hence, it should have `language: r` in `.pre-commit-hooks.yaml` and then (for compatibility) a shebang in the `entrypoint` script. 16 | 17 | # Testing hooks 18 | 19 | Hooks should be tested by checking both the positive outcome (hook passes) and 20 | the negative outcome (hook fails) by adding two `run_test()` statements to 21 | [`./tests/testthat/test-hooks.R`](https://github.com/lorenzwalthert/precommit/blob/main/tests/testthat/test-hooks.R). Look at existing examples and [the documentation 22 | of `run_test()`](https://lorenzwalthert.github.io/precommit/reference/run_test.html). Note that this won't interact with pre-commit. It will simply 23 | run `Rscript path/to/script.R` (whereas with pre-commit, a {renv} will be activated before running the script). 24 | 25 | Also, there are [tests](https://github.com/lorenzwalthert/precommit/blob/main/.github/workflows/end-to-end.yml) to ensure that hooks are correctly registered in `.pre-commit-hooks.yaml`, which you have to adapt if you add a hook. 26 | 27 | You can also test them with `pre-commit try-repo` as described in the [documentation](https://pre-commit.com/#pre-commit-try-repo). 28 | 29 | 30 | # Summary 31 | 32 | - add your R (with extension) script in `inst/hooks/exported` and make it executable. See other scripts in `inst/hooks/exported` for a starting point for setting up your script. 33 | 34 | - register hook in `.pre-commit-hooks.yaml`. 35 | 36 | - add two unit tests, test manually with `pre-commit try-repo` and adapt the [end-to-end test](https://github.com/lorenzwalthert/precommit/blob/main/.github/workflows/end-to-end.yml). 37 | 38 | - add a description of the new hook to `vignettes/available-hooks.Rmd`. Both description and 39 | `yaml` example code. 40 | -------------------------------------------------------------------------------- /DESCRIPTION: -------------------------------------------------------------------------------- 1 | Package: precommit 2 | Title: Pre-Commit Hooks 3 | Version: 0.4.3.9010 4 | Author: Lorenz Walthert 5 | Maintainer: Lorenz Walthert 6 | Description: Useful git hooks for R building on top of the multi-language 7 | framework 'pre-commit' for hook management. This package provides git 8 | hooks for common tasks like formatting files with 'styler' or spell 9 | checking as well as wrapper functions to access the 'pre-commit' 10 | executable. 11 | License: GPL-3 12 | URL: https://lorenzwalthert.github.io/precommit/, 13 | https://github.com/lorenzwalthert/precommit 14 | Imports: 15 | cli, 16 | fs, 17 | here, 18 | magrittr, 19 | purrr, 20 | R.cache, 21 | rlang, 22 | rprojroot, 23 | withr, 24 | yaml 25 | Suggests: 26 | desc, 27 | docopt (>= 0.7.1), 28 | git2r, 29 | glue, 30 | jsonvalidate, 31 | knitr, 32 | lintr, 33 | pkgload, 34 | pkgdown, 35 | reticulate (>= 1.16), 36 | renv (>= 1.0.8), 37 | rmarkdown, 38 | roxygen2, 39 | rstudioapi, 40 | spelling, 41 | styler, 42 | testthat (>= 2.1.0), 43 | tibble, 44 | usethis (>= 2.0.0) 45 | VignetteBuilder: 46 | knitr 47 | Encoding: UTF-8 48 | Roxygen: list(markdown = TRUE, roclets = c( "rd", "namespace", "collate", 49 | if (rlang::is_installed("pkgapi")) "pkgapi::api_roclet" else { 50 | warning("Please install r-lib/pkgapi to make sure the file API is kept 51 | up to date"); NULL} ) ) 52 | RoxygenNote: 7.3.2 53 | SystemRequirements: git 54 | Config/testthat/parallel: true 55 | Config/testthat/edition: 3 56 | -------------------------------------------------------------------------------- /NAMESPACE: -------------------------------------------------------------------------------- 1 | # Generated by roxygen2: do not edit by hand 2 | 3 | export(autoupdate) 4 | export(diff_requires_run_roxygenize) 5 | export(dirs_R.cache) 6 | export(install_precommit) 7 | export(may_require_permanent_cache) 8 | export(open_config) 9 | export(open_wordlist) 10 | export(path_pre_commit_exec) 11 | export(path_precommit_exec) 12 | export(precommit_docopt) 13 | export(robust_purl) 14 | export(roxygen_assert_additional_dependencies) 15 | export(roxygenize_with_cache) 16 | export(snippet_generate) 17 | export(uninstall_precommit) 18 | export(update_precommit) 19 | export(use_ci) 20 | export(use_precommit) 21 | export(use_precommit_config) 22 | export(version_precommit) 23 | importFrom(R.cache,saveCache) 24 | importFrom(magrittr,"%>%") 25 | -------------------------------------------------------------------------------- /R/aaa.R: -------------------------------------------------------------------------------- 1 | hooks_repo <- "https://github.com/lorenzwalthert/precommit" 2 | -------------------------------------------------------------------------------- /R/assert.R: -------------------------------------------------------------------------------- 1 | assert_is_git_repo <- function(root) { 2 | if (!is_git_repo(root = root)) { 3 | rlang::abort(paste0( 4 | "The directory ", root, " is not a git repo. Please navigate to ", 5 | root, " and init git in ", 6 | "this directory with `$ git init` from the command line or ", 7 | "`> usethis::use_git()` from the R prompt." 8 | )) 9 | } 10 | } 11 | 12 | assert_is_installed <- function() { 13 | if (!is_installed()) { 14 | rlang::abort(paste0( 15 | "pre-commit is not installed on your system (or we can't find it).\n\n", 16 | "If you have it installed and you know where it is, please set the R option ", 17 | "`precommit.executable` to this ", 18 | "path so it can be used to perform various pre-commit commands from R. ", 19 | "If you think this is a standard location, please open an issue on GitHub ", 20 | "so we can auto-detect this location in the future and spare new users some ", 21 | "set-up troubles.\n\n", 22 | "If you don't know where the executable is stored, go back to the log output ", 23 | "that resulted from the installation of pre-commit for hints. If you found ", 24 | "it and you think it's a standard location, please open an issue on GitHub ", 25 | "so we can auto-detect this location in the future and spare unexpereienced ", 26 | "users some trouble.\n\n", 27 | "In case you are totally lost with these messages, you can most likely ", 28 | "solve the problems with just using the conda installation method, see ", 29 | "https://lorenzwalthert.github.io/precommit/ for how to do this." 30 | )) 31 | } 32 | } 33 | 34 | assert_correct_upstream_repo_url <- function() { 35 | if (upstream_repo_url_is_outdated()) { 36 | cli::cli_alert_info(c( 37 | "The repo https://github.com/lorenzwalthert/pre-commit-hooks ", 38 | "has moved to ", hooks_repo, ". ", 39 | "Please fix the URL in .pre-commit-config.yaml, ", 40 | "most confortably with `precommit::open_config()`." 41 | )) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /R/cache.R: -------------------------------------------------------------------------------- 1 | #' Issue a warning if \{R.cache\} uses temporary cache only 2 | #' 3 | #' This function used to check if a permanent cache was available and issue a 4 | #' warning if not, but since \{R.cache\} version `0.15.0` (release date 5 | #' 2021-04-27), a permanent directory will be used automatically, so this check 6 | #' if redundant. the function is kept in the package for compatibility, i.e. 7 | #' if someone updates the R package \{precommit\} but not the hook revisions. 8 | #' @param temp_cache_is_enough ignored. 9 | #' @family hook script helpers 10 | #' @keywords internal 11 | #' @export 12 | may_require_permanent_cache <- function(temp_cache_is_enough = FALSE) { 13 | if (temp_cache_is_enough) { 14 | rlang::warn(paste0( 15 | "The argument `--no-warn-cache` is deprecated and will be removed in a ", 16 | "future release. Please remove it from your `.pre-commit-config.yaml`." 17 | )) 18 | } 19 | return() 20 | } 21 | -------------------------------------------------------------------------------- /R/call.R: -------------------------------------------------------------------------------- 1 | #' Make a call with [system2()] and capture the effects. 2 | #' @param command The command to issue. A character string of length one. 3 | #' @param args The command line arguments. 4 | #' @param ... Arguments passed to [system2()]. 5 | #' @param wait Passed to [system2()]. 6 | #' @return 7 | #' A list with: 8 | #' * content of stderr 9 | #' * content of stdout 10 | #' * exit status 11 | #' @keywords internal 12 | call_and_capture <- function(command, args, ..., wait = TRUE) { 13 | if (length(command) != 1 | !inherits(command, "character")) { 14 | rlang::abort("The command must be a character string of length 1.") 15 | } 16 | stdout <- tempfile() 17 | writeLines("", stdout) 18 | stderr <- tempfile() 19 | writeLines("", stderr) 20 | if (!is.character(args)) { 21 | rlang::abort(paste0( 22 | "command line arguments must be a character vector, not of class `", 23 | class(args)[1], "`." 24 | )) 25 | } 26 | exit_status <- suppressWarnings( 27 | system2(command, args, ..., wait = wait, stdout = stdout, stderr = stderr) 28 | ) 29 | # on Windows, there is no output when wait = FALSE 30 | if (is_windows() && !wait) { 31 | out <- list( 32 | stdout = "[cannot recover stdout for Windows when R option `precommit.block_install_hooks` is FALSE]", 33 | stderr = "[cannot recover stderr for Windows when R option `precommit.block_install_hooks` is FALSE]", 34 | exit_status = exit_status 35 | ) 36 | return(out) 37 | } 38 | stderr <- readLines(stderr) 39 | if (isTRUE(any(grepl("error", stderr, ignore.case = TRUE)))) { 40 | # conda run has exit status 0 but stderr with ERROR, we need to set exit 41 | # code in that case. 42 | exit_status <- -999 43 | } 44 | 45 | if (exit_status != 0) { 46 | if (length(stderr) < 1) { 47 | stderr <- paste0( 48 | "Could not recover stderr. Run the following command to get the error", 49 | paste(...) 50 | ) 51 | } 52 | } 53 | list( 54 | stdout = readLines(stdout), 55 | stderr = stderr, 56 | exit_status = exit_status 57 | ) 58 | } 59 | 60 | #' Call pre-commit 61 | #' 62 | #' Either via `conda run` (because conda env needs to be activated in general to 63 | #' ensure an executable to runs successfully) or, if the installation method was 64 | #' not conda, as a plain bash command. 65 | #' @param ... Arguments passed to the command line call `pre-commit`. 66 | #' @param wait Passed to [base::system2()]. 67 | #' @keywords internal 68 | call_precommit <- function(..., wait = TRUE) { 69 | if (is_conda_installation()) { 70 | call_and_capture( 71 | reticulate::conda_binary(), 72 | c("run", "-n", "r-precommit", path_precommit_exec(), ...), 73 | wait = wait 74 | ) 75 | } else { 76 | call_and_capture(path_precommit_exec(), c(...), wait = wait) 77 | } 78 | } 79 | 80 | #' Communicate a captured call 81 | #' 82 | #' Communicates a captured call. 83 | #' @param x The output of [call_and_capture()]. 84 | #' @keywords internal 85 | communicate_captured_call <- function(x, preamble = "") { 86 | if (x$exit_status != 0) { 87 | trans <- rlang::abort 88 | } else { 89 | trans <- rlang::warn 90 | } 91 | trans(paste0(preamble, 92 | "\nstderr: ", 93 | paste0(x$stderr, collapse = "\n"), "\n\nstdout: ", 94 | paste0(x$stdout, collapse = "\n"), 95 | collapse = "\n" 96 | )) 97 | x$exit_status 98 | } 99 | -------------------------------------------------------------------------------- /R/open.R: -------------------------------------------------------------------------------- 1 | #' Open pre-commit related files 2 | #' 3 | #' @details 4 | #' * `open_config()`: opens the pre-commit config file. 5 | #' * `open_wordlist()`: opens the the WORDLIST file for the check-spelling hook 6 | #' in inst/WORDLIST. 7 | #' @inheritParams fallback_doc 8 | #' @return 9 | #' `NULL` (invisibly). The function is called for its side effects. 10 | #' @family helpers 11 | #' @examples 12 | #' \dontrun{ 13 | #' open_config() 14 | #' } 15 | #' @export 16 | open_config <- function(root = here::here()) { 17 | if (rstudioapi::isAvailable()) { 18 | rstudioapi::navigateToFile(fs::path(root, ".pre-commit-config.yaml")) 19 | } else { 20 | rlang::abort("Can't open if you don't have RStudio running.") 21 | } 22 | invisible(NULL) 23 | } 24 | 25 | #' @rdname open_config 26 | #' @examples 27 | #' \dontrun{ 28 | #' open_wordlist() 29 | #' } 30 | #' @export 31 | open_wordlist <- function(root = here::here()) { 32 | rstudioapi::navigateToFile(fs::path(root, "inst", "WORDLIST")) 33 | invisible(NULL) 34 | } 35 | -------------------------------------------------------------------------------- /R/precommit-package.R: -------------------------------------------------------------------------------- 1 | #' @keywords internal 2 | #' @importFrom magrittr %>% 3 | "_PACKAGE" 4 | 5 | # The following block is used by usethis to automatically manage 6 | # roxygen namespace tags. Modify with care! 7 | ## usethis namespace: start 8 | ## usethis namespace: end 9 | NULL 10 | 11 | #' Fallback doc 12 | #' @param root The path to the root directory of your project. 13 | #' @name fallback_doc 14 | #' @keywords internal 15 | NULL 16 | 17 | 18 | if (getRversion() >= "2.15.1") { 19 | utils::globalVariables(c( 20 | ".", 21 | NULL 22 | )) 23 | } 24 | 25 | if (FALSE) { 26 | # {here} is only used as a dependency for default arguments. 27 | # this is to avoid a false positive CRAN note. 28 | here::here 29 | } 30 | -------------------------------------------------------------------------------- /R/purl.R: -------------------------------------------------------------------------------- 1 | #' Run [knitr::purl()], setting the chunk option `purl` to `TRUE` if it's not 2 | #' already set to a literal value. 3 | #' 4 | #' This function is only exported for use in hook scripts, but it's not intended 5 | #' to be called by the end-user directly. 6 | #' @param path The path to the file you want to [knitr::purl()]. 7 | #' @family hook script helpers 8 | #' @keywords internal 9 | #' @export 10 | robust_purl <- function(path) { 11 | ext <- fs::path_ext(path) 12 | path_rmd <- tempfile(fileext = paste0(".", ext)) 13 | file.copy(path, path_rmd) 14 | lines <- readLines(path_rmd) 15 | has_purl <- grepl("purl.*=.*(TRUE|FALSE|T|F)", lines) 16 | has_eval <- grepl("eval.*=.*(TRUE|FALSE|T|F)", lines) 17 | should_not_override <- has_purl | has_eval 18 | 19 | if (tolower(ext) == "rmd") { 20 | lines[!should_not_override] <- gsub( 21 | "^```\\{ *[Rr] *.*\\}.*", "```{r purl = TRUE}", lines[!should_not_override] 22 | ) 23 | } else if (tolower(ext) == "rnw") { 24 | lines[!should_not_override] <- gsub( 25 | "^<<>>=.*", "<>=", lines[!should_not_override] 26 | ) 27 | } 28 | writeLines(lines, path_rmd) 29 | path_ <- knitr::purl( 30 | input = path_rmd, 31 | output = tempfile(fileext = ".R"), 32 | quiet = TRUE, 33 | documentation = FALSE 34 | ) 35 | path_ 36 | } 37 | -------------------------------------------------------------------------------- /R/roxygen2.R: -------------------------------------------------------------------------------- 1 | #' Check if we should run roxygen. 2 | #' 3 | #' This is the case if a new or replaced/removed line contains a roxygen2 4 | #' comment in a file that is staged. 5 | #' This function is only exported for use in hook scripts, but it's not intended 6 | #' to be called by the end-user directly. 7 | #' @param root The root of the git repo. 8 | #' @return 9 | #' A logical vector of length 1. 10 | #' @keywords internal 11 | #' @family hook script helpers 12 | #' @examples 13 | #' \dontrun{ 14 | #' diff_requires_run_roxygenize() 15 | #' } 16 | #' @export 17 | diff_requires_run_roxygenize <- function(root = ".") { 18 | git_raw_diff <- withr::with_dir( 19 | root, call_and_capture("git", c("diff", "--cached")) 20 | )$stdout 21 | is_roxygen <- grepl("^(\\+|-)#'", git_raw_diff) 22 | if (any(is_roxygen)) { 23 | return(TRUE) 24 | } else { 25 | # check if formals were changed 26 | # we invalidate the cache on formal change, even if it is not sure they are 27 | # documented with roxygen. This is easy, cheap and safe. Might give false 28 | # positive (invalidates in cases where it's not necessary). 29 | without_comments <- gsub("#.*", "", git_raw_diff) 30 | any(grep("function(", without_comments, fixed = TRUE)) 31 | } 32 | } 33 | 34 | #' Assert if all dependencies are installed 35 | #' 36 | #' This function is only exported for use in hook scripts, but it's not intended 37 | #' to be called by the end-user directly. 38 | #' @family hook script helpers 39 | #' @keywords internal 40 | #' @export 41 | roxygen_assert_additional_dependencies <- function() { 42 | out <- rlang::try_fetch( 43 | # roxygen2 will load: https://github.com/r-lib/roxygen2/issues/771 44 | pkgload::load_all(quiet = TRUE), 45 | error = function(e) { 46 | e 47 | } 48 | ) 49 | if ( 50 | inherits(out, "packageNotFoundError") || 51 | ("message" %in% names(out) && ( 52 | grepl("Dependency package(\\(s\\))? .* not available", out$message) || 53 | grepl("Failed to load", out$message) 54 | )) 55 | ) { 56 | # case used in package but not installed 57 | rlang::abort(paste0( 58 | "The roxygenize hook requires all dependencies of your package to be listed in ", 59 | "the file `.pre-commit-config.yaml`. ", 60 | "Call `precommit::snippet_generate('additional-deps-roxygenize')` ", 61 | "to generate that list or, to completely deactivate the hook, ", 62 | "comment out all lines corresponding to that hook under \n\n", 63 | " - id: roxygenize\n", 64 | " some_key_under_id_roxygenize: comment-out-as-well\n", 65 | " - id: some-other-hook-do-not-comment-out\n\n", 66 | "The initial error (from `pkgload::load_all()`) was: ", 67 | conditionMessage(out), ".\n\n" 68 | )) 69 | } 70 | } 71 | 72 | #' Roxygen and add a cache entry 73 | #' 74 | #' This function is only exported for use in hook scripts, but it's not intended 75 | #' to be called by the end-user directly. 76 | #' @inheritParams R.cache::saveCache 77 | #' @family hook script helpers 78 | #' @keywords internal 79 | #' @export 80 | #' @importFrom R.cache saveCache 81 | # fails if accessed with R.cache::saveCache()! 82 | roxygenize_with_cache <- function(key, dirs) { 83 | out <- rlang::try_fetch( 84 | roxygen2::roxygenise(), 85 | error = function(e) e 86 | ) 87 | if ( 88 | inherits(out, c("packageNotFoundError", "rlib_error_package_not_found")) || 89 | ("message" %in% names(out) && grepl("The package .* is required\\.", conditionMessage(out))) 90 | ) { 91 | rlang::abort(paste0( 92 | conditionMessage(out), 93 | " Please add the package as a dependency to ", 94 | "`.pre-commit-config.yaml` -> `id: roxygenize` -> ", 95 | "`additional_dependencies` and try again. The package must be ", 96 | "specified so `renv::install()` understands it, e.g. like this:\n\n", 97 | " - id: roxygenize", 98 | " 99 | additional_dependencies: 100 | - r-lib/pkgapi # replace `r-lib/pkgapi` with the package required\n\n" 101 | )) 102 | } else if (inherits(out, "error")) { 103 | rlang::abort(conditionMessage(out)) 104 | } else if (inherits(out, "warning")) { 105 | rlang::warn(conditionMessage(out)) 106 | } 107 | saveCache(object = Sys.time(), key = key, dirs = dirs) 108 | } 109 | -------------------------------------------------------------------------------- /R/update.R: -------------------------------------------------------------------------------- 1 | #' Update the pre-commit executable 2 | #' 3 | #' Updates the conda installation of the upstream framework pre-commit. This 4 | #' does not update the R package `{precommit}` and only works if you choose 5 | #' conda as your installation method. If you have problems updating, we suggest 6 | #' deleting the conda environment `r-precommit` (if you are sure nothing but 7 | #' pre-commit depend on it) and do a fresh installation with 8 | #' [install_precommit()]. 9 | #' @return 10 | #' The exit status of the conda update command (invisible). 11 | #' @family executable managers 12 | #' @export 13 | update_precommit <- function() { 14 | old <- version_precommit() 15 | assert_reticulate_is_installed() 16 | if (!is_conda_installation()) { 17 | rlang::abort(paste0( 18 | "You can only update your pre-commit executable via the R API if you ", 19 | "chose the installation method via conda ", 20 | "(see https://lorenzwalthert.github.io/precommit/#installation). It ", 21 | "does not seem you installed via conda, because the path to the ", 22 | "executable in use is ", path_precommit_exec(), ". Please use the ", 23 | "update utilities of the installation method you chose. Alternatively, ", 24 | "you can uninstall with the utility of your installation method / ", 25 | "delete the executable and ", 26 | "run `precommit::install_precommit()` to switch to the conda ", 27 | "installation method." 28 | )) 29 | } 30 | exit_status <- update_impl() 31 | new <- version_precommit() 32 | if (new == old) { 33 | cli::cli_alert_info("Nothing to update, your version {old} is the latest available.") 34 | } else { 35 | cli::cli_alert_success("Successfully updated pre-commit from version {old} to {new}.") 36 | } 37 | invisible(exit_status) 38 | } 39 | 40 | #' Retrieve the version of the pre-commit executable used 41 | #' 42 | #' Retrieves the version of the pre-commit executable used. 43 | #' @family executable managers 44 | #' @export 45 | version_precommit <- function() { 46 | out <- call_precommit("--version") 47 | if (out$exit_status == 0) { 48 | out <- trimws(gsub("pre-commit", "", out$stdout[1])) 49 | } else { 50 | communicate_captured_call( 51 | out, 52 | preamble = "Running `pre-commit --version` failed." 53 | ) 54 | } 55 | out 56 | } 57 | -------------------------------------------------------------------------------- /R/usethis.R: -------------------------------------------------------------------------------- 1 | write_union <- function(path, lines) { 2 | stopifnot(is.character(lines)) 3 | if (file.exists(path)) { 4 | existing_lines <- read_utf8(path) 5 | } else { 6 | existing_lines <- character() 7 | } 8 | new <- setdiff(lines, existing_lines) 9 | if (length(new) == 0) { 10 | return(invisible(FALSE)) 11 | } 12 | cli::cli_alert_success("Adding {new} to {path}.") 13 | all <- c(existing_lines, new) 14 | write_utf8(path, all) 15 | } 16 | 17 | 18 | write_utf8 <- function(path, text) { 19 | opts <- options(encoding = "native.enc") 20 | withr::defer(options(opts)) 21 | writeLines(enc2utf8(text), path, useBytes = TRUE) 22 | } 23 | 24 | read_utf8 <- function(path) { 25 | opts <- options(encoding = "native.enc") 26 | withr::defer(options(opts)) 27 | readLines(path, encoding = "UTF-8", warn = FALSE) 28 | } 29 | -------------------------------------------------------------------------------- /R/utils.R: -------------------------------------------------------------------------------- 1 | is_windows <- function() { 2 | identical(.Platform$OS.type, "windows") 3 | } 4 | 5 | is_url <- function(text) { 6 | if (length(text) < 1) { 7 | return(FALSE) 8 | } 9 | conn <- file(text) 10 | on.exit(close(conn)) 11 | conn %>% 12 | inherits("url") 13 | } 14 | 15 | file_exists <- function(...) { 16 | fs::file_exists(fs::path_expand(...)) 17 | } 18 | 19 | 20 | path_if_exist <- function(...) { 21 | path <- c(...) 22 | path[file_exists(path)] 23 | } 24 | 25 | is_conda_installation <- function() { 26 | grepl( 27 | "(r-miniconda-.*|conda3?)/envs/r-precommit/(bin|Scripts)/pre-commit(\\.exe)?", 28 | getOption("precommit.executable") 29 | ) 30 | } 31 | 32 | is_package <- function(root = here::here()) { 33 | rlang::try_fetch( 34 | rprojroot::find_package_root_file(path = root), 35 | error = function(e) NULL 36 | ) %>% 37 | is.null() %>% 38 | magrittr::not() 39 | } 40 | 41 | #' Name the input 42 | #' 43 | #' @param x A vector. 44 | #' @param f How to transform the input `x` into a name. 45 | #' @keywords internal 46 | ensure_named <- function(x, candidate_name = NULL, f = identity) { 47 | if (is.null(names(x))) { 48 | if (is.null(candidate_name)) { 49 | names(x) <- f(x) 50 | } else { 51 | names(x) <- candidate_name 52 | } 53 | } 54 | x 55 | } 56 | 57 | 58 | #' Create the path to the precommit R.cache cache 59 | #' 60 | #' This function is only exported for use in hook scripts, but it's not intended 61 | #' to be called by the end-user directly. 62 | #' @param hook_id The id of the hook for which we want the relative cache 63 | #' directory. 64 | #' @family hook script helpers 65 | #' @keywords internal 66 | #' @export 67 | dirs_R.cache <- function(hook_id) { 68 | file.path("precommit", hook_id) 69 | } 70 | 71 | #' Initiate git and configure it 72 | #' 73 | #' In particular, to avoid CRAN errors 74 | #' [lorenzwalthert/precommit#320](https://github.com/lorenzwalthert/precommit/issues/320). 75 | #' @param path The root of the repo. 76 | #' @keywords internal 77 | git_init <- function(path = ".") { 78 | git2r::init(path = path) 79 | git2r::config( 80 | user.name = "testthat", 81 | user.email = "agent@testthat.com", 82 | core.autocrlf = "true" 83 | ) 84 | } 85 | 86 | #' Provide a singular interface for hook calls to docopt 87 | #' 88 | #' docopt provides different processing for a single string 89 | #' than an array/vector. As `"string"`` and `c("string")` 90 | #' are semantically equivalent in R, this can create problems 91 | #' when a single parameter is provided. Thus, this function 92 | #' wraps docopt to ensure that the args will always be 93 | #' interpreted as a vector. 94 | #' 95 | #' This function is only exported for use in hook scripts, but it's not intended 96 | #' to be called by the end-user directly. 97 | #' @param doc `character` vector with command line specification. 98 | #' @param args `character` vector of command line arguments. 99 | #' Defaults to `commandArgs(trailingOnly=TRUE)`. 100 | #' @param ... Additional parameters passed to `docopt`. 101 | #' @family hook script helpers 102 | #' @keywords internal 103 | #' @export 104 | precommit_docopt <- function(doc, args = commandArgs(trailingOnly = TRUE), ...) { 105 | docopt::docopt(doc, c(args, ""), ...) 106 | } 107 | 108 | #' Read the refs corresponding to a hooks repo 109 | #' @keywords internal 110 | rev_read <- function(path = ".pre-commit-config.yaml", repo = hooks_repo) { 111 | config <- yaml::read_yaml(path) 112 | idx <- purrr::map_chr(config$repos, ~ .x$repo) %>% 113 | grep(repo, ., fixed = TRUE) 114 | if (length(idx) < 1) { 115 | return(NA) 116 | } else { 117 | config$repos[[idx]]$rev 118 | } 119 | } 120 | 121 | rev_as_pkg_version <- function(rev) { 122 | package_version(gsub("^v", "", rev)) 123 | } 124 | 125 | has_git <- function() { 126 | nzchar(Sys.which("git")) 127 | } 128 | 129 | is_git_repo <- function(root = here::here()) { 130 | withr::local_dir(root) 131 | if (has_git()) { 132 | rlang::try_fetch( 133 | { 134 | output <- call_and_capture( 135 | "git", 136 | c("rev-parse", "--is-inside-work-tree") 137 | ) 138 | output$exit_status == 0 && as.logical(toupper(output$stdout)) 139 | }, 140 | erorr = function(...) FALSE 141 | ) 142 | } else { 143 | dir.exists(".git") 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /R/zzz.R: -------------------------------------------------------------------------------- 1 | .onLoad <- function(libname, pkgname) { 2 | op <- options() 3 | op.precommit <- list( 4 | precommit.executable = path_derive_precommit_exec(), 5 | precommit.block_install_hooks = FALSE, 6 | precommit.ci = "native" 7 | ) 8 | toset <- !(names(op.precommit) %in% names(op)) 9 | if (any(toset)) options(op.precommit[toset]) 10 | if (interactive()) ensure_renv_precommit_compat() 11 | invisible() 12 | } 13 | 14 | # here is only used as a default argument, hence the R CMD check warning 15 | # Namespace in Imports field not imported from 16 | if (FALSE) { 17 | here::here() 18 | } 19 | -------------------------------------------------------------------------------- /README.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | output: github_document 3 | --- 4 | 5 | 6 | 7 | ```{r, include = FALSE} 8 | knitr::opts_chunk$set( 9 | fig.path = "man/figures/", 10 | eval = FALSE 11 | ) 12 | ``` 13 | 14 | # Useful git pre-commit hooks for R 15 | 16 | 17 | 18 | [![CRAN status](https://www.r-pkg.org/badges/version/precommit)](https://CRAN.R-project.org/package=precommit) [![Lifecycle: stable](https://img.shields.io/badge/lifecycle-stable-green.svg)](https://lifecycle.r-lib.org/articles/stages.html#stable) [![R build status](https://github.com/lorenzwalthert/precommit/workflows/R-CMD-check/badge.svg)](https://github.com/lorenzwalthert/precommit/actions) 19 | 20 | 21 | 22 | [Pre-commit hooks](https://pre-commit.com) are tests that run each time you attempt to commit. If the tests pass, the commit will be made, otherwise not. A very basic test is to check if the code is parsable, making sure you have not forgotten a comma, brace or quote. You can run hooks locally and/or in the cloud: 23 | 24 | - As a check before local commits: This requires installing pre-commit. 25 | 26 | - As a CI check with : If you want to enforce passing hooks on pull requests (and auto-fix trivial problems like styling) even if the committer does not have a local installation. 27 | 28 | ## Goals of the package 29 | 30 | The goal of this package is to twofold: 31 | 32 | - Provide a [set of hooks](https://lorenzwalthert.github.io/precommit/articles/available-hooks.html) that are useful when your git repo contains R code. 33 | 34 | - Provide [usethis](https://github.com/r-lib/usethis)-like functionality for common tasks such as installation and set-up and config file modification. 35 | 36 | ## Why do I need pre-commit hooks? 37 | 38 |
39 | 40 |
By Mara Averick
41 |
42 | 43 | For a more in-depth explanation and even more reasons, see `vignette("why-use-hooks")`. 44 | 45 | ## Documentation 46 | 47 | The following online docs are available: 48 | 49 | - [latest CRAN release](https://lorenzwalthert.github.io/precommit/). 50 | 51 | - [GitHub development version](https://lorenzwalthert.github.io/precommit/dev/). 52 | 53 | - [UseR 2022 talk on pre-commit for R](https://www.youtube.com/watch?v=r3QXwfxQBLM&t=1280s) 54 | 55 | These only cover the functionality added on top of the pre-commit framework by this package. Everything else is covered in the extensive [online documentation](https://pre-commit.com) of the pre-commit framework itself, including how to create hooks for actions like `git push` or `git checkout`, create local hooks etc. 56 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | --- 2 | editor_options: 3 | markdown: 4 | wrap: 72 5 | --- 6 | 7 | 8 | 9 | # Useful git pre-commit hooks for R 10 | 11 | 12 | 13 | [![CRAN 14 | status](https://www.r-pkg.org/badges/version/precommit)](https://CRAN.R-project.org/package=precommit) 15 | [![Lifecycle: 16 | stable](https://img.shields.io/badge/lifecycle-stable-green.svg)](https://lifecycle.r-lib.org/articles/stages.html#stable) 17 | [![R build 18 | status](https://github.com/lorenzwalthert/precommit/workflows/R-CMD-check/badge.svg)](https://github.com/lorenzwalthert/precommit/actions) 19 | 20 | 21 | 22 | [Pre-commit hooks](https://pre-commit.com) are tests that run each time 23 | you attempt to commit. If the tests pass, the commit will be made, 24 | otherwise not. A very basic test is to check if the code is parsable, 25 | making sure you have not forgotten a comma, brace or quote. You can run 26 | hooks locally and/or in the cloud: 27 | 28 | - As a check before local commits: This requires installing 29 | pre-commit. 30 | 31 | - As a CI check with : If you want to enforce 32 | passing hooks on pull requests (and auto-fix trivial problems like 33 | styling) even if the committer does not have a local installation. 34 | 35 | ## Goals of the package 36 | 37 | The goal of this package is to twofold: 38 | 39 | - Provide a [set of 40 | hooks](https://lorenzwalthert.github.io/precommit/articles/available-hooks.html) 41 | that are useful when your git repo contains R code. 42 | 43 | - Provide [usethis](https://github.com/r-lib/usethis)-like 44 | functionality for common tasks such as installation and set-up and 45 | config file modification. 46 | 47 | ## Why do I need pre-commit hooks? 48 | 49 |
50 | 51 | 52 | 53 |
By 54 | Mara 55 | Averick
56 | 57 |
58 | 59 | For a more in-depth explanation and even more reasons, see 60 | `vignette("why-use-hooks")`. 61 | 62 | ## Documentation 63 | 64 | The following online docs are available: 65 | 66 | - [latest CRAN release](https://lorenzwalthert.github.io/precommit/). 67 | 68 | - [GitHub development 69 | version](https://lorenzwalthert.github.io/precommit/dev/). 70 | 71 | - [UseR 2022 talk on pre-commit for 72 | R](https://www.youtube.com/watch?v=r3QXwfxQBLM&t=1280s) 73 | 74 | These only cover the functionality added on top of the pre-commit 75 | framework by this package. Everything else is covered in the extensive 76 | [online documentation](https://pre-commit.com) of the pre-commit 77 | framework itself, including how to create hooks for actions like 78 | `git push` or `git checkout`, create local hooks etc. 79 | -------------------------------------------------------------------------------- /_pkgdown.yml: -------------------------------------------------------------------------------- 1 | reference: 2 | - title: "Setup tools" 3 | desc: > 4 | Get started 5 | - contents: 6 | - install_precommit 7 | - use_precommit 8 | - use_precommit_config 9 | - use_ci 10 | - title: "Workflow" 11 | desc: > 12 | Edit the pre-commit configuration 13 | - contents: 14 | - open_config 15 | - open_wordlist 16 | - autoupdate 17 | - snippet_generate 18 | - title: "Manage the pre-commit executable" 19 | - contents: 20 | - uninstall_precommit 21 | - update_precommit 22 | - version_precommit 23 | - path_precommit_exec 24 | 25 | destination: docs 26 | articles: 27 | - title: Get started 28 | description: > 29 | All you need to know as a {precommit} (power) user. 30 | navbar: ~ 31 | contents: 32 | - precommit 33 | - why-use-hooks 34 | - available-hooks 35 | - ci 36 | - FAQ 37 | - title: Developer Docs 38 | description: > 39 | You won't need this unless you want to contribute to that repo. 40 | contents: 41 | - hook-order 42 | - testing 43 | 44 | template: 45 | params: 46 | docsearch: 47 | api_key: 'ae5363c86f48f4117b4f2b9beaefa28f' 48 | index_name: 'lorenzwalthert_precommit' 49 | 50 | authors: 51 | Lorenz Walthert: 52 | href: https://lorenzwalthert.com 53 | 54 | development: 55 | mode: auto 56 | 57 | url: https://lorenzwalthert.github.io/precommit/ 58 | -------------------------------------------------------------------------------- /cran-comments.md: -------------------------------------------------------------------------------- 1 | This is a submission due to enable the pkgdown release since they now check 2 | URL consistency between `DESCRIPTION` and `_pkgdown.yml`. 3 | message changes. 4 | 5 | ## Test environments 6 | 7 | - ubuntu 18.04 (on GitHub Actions): R 4.4 8 | - Windows (on GitHub Actions): R 4.4 9 | - macOS (on GitHub Actions): R 4.4 10 | - win-builder: R devel 11 | 12 | ## R CMD check results 13 | 14 | 0 errors \| 0 warnings \| 0 notes 15 | 16 | ## Downstream Dependencies 17 | 18 | None. 19 | -------------------------------------------------------------------------------- /inst/WORDLIST: -------------------------------------------------------------------------------- 1 | ae 2 | Affero 3 | api 4 | appveyor 5 | arg 6 | args 7 | artific 8 | autoupdate 9 | Averick 10 | bd 11 | beaefa 12 | behaviour 13 | bioconductor 14 | bliblablupp 15 | cfe 16 | Changelog 17 | ci 18 | CLI 19 | cli 20 | CMD 21 | cmd 22 | codemeta 23 | codemetar 24 | codetools 25 | comit 26 | commandArgs 27 | comparator 28 | conda 29 | conditionMessage 30 | config 31 | copyrightable 32 | cp 33 | cran 34 | csv 35 | Ctrl 36 | dcf 37 | deps 38 | desc 39 | dev 40 | dir 41 | dirname 42 | docopt 43 | docsearch 44 | docType 45 | dontrun 46 | donttest 47 | Ei 48 | Eic 49 | elif 50 | emph 51 | entrypoint 52 | env 53 | EOF 54 | eval 55 | exlucded 56 | fi 57 | fs 58 | fsf 59 | fsssile 60 | getOption 61 | getParseData 62 | getwd 63 | gh 64 | github 65 | gitignore 66 | Gitlab 67 | grDevices 68 | grepl 69 | gsub 70 | Homebrew 71 | href 72 | http 73 | https 74 | icloud 75 | idx 76 | impl 77 | init 78 | io 79 | isAvailable 80 | IssueHunt 81 | issuehunt 82 | isTRUE 83 | jpeg 84 | json 85 | jsonvalidate 86 | KernSmooth 87 | knitr 88 | Ko 89 | ko 90 | lang 91 | lapply 92 | LF 93 | lgpl 94 | Liberapay 95 | liberapay 96 | licensors 97 | Lifecycle 98 | LinkingTo 99 | lintr 100 | loadCache 101 | lockfile 102 | lorenz 103 | lorenzwalthert 104 | lt 105 | macOS 106 | magrittr 107 | maxkb 108 | md 109 | MERCHANTABILITY 110 | mgcv 111 | miniconda 112 | mis 113 | modef 114 | msg 115 | mtime 116 | navbar 117 | netlify 118 | nlme 119 | nnet 120 | noncommercially 121 | npm 122 | nrow 123 | nt 124 | num 125 | oneliner 126 | os 127 | Otechie 128 | otechie 129 | overscope 130 | packageVersion 131 | pandoc 132 | params 133 | parsable 134 | Patreon 135 | patreon 136 | pkgapi 137 | pkgdown 138 | pkgload 139 | png 140 | pre 141 | prec 142 | precommit 143 | precommithooks 144 | proj 145 | purrr 146 | py 147 | qmd 148 | Rbuildignore 149 | rc 150 | RData 151 | Rdata 152 | Rds 153 | rds 154 | readLines 155 | readme 156 | recognised 157 | relicensing 158 | renv 159 | Renviron 160 | repo 161 | repos 162 | Resubmission 163 | reticulate 164 | Rhistory 165 | rlang 166 | rmarkdown 167 | rmd 168 | Rnw 169 | rnw 170 | roclet 171 | roclets 172 | ropenscilabs 173 | roxygen 174 | roxygenise 175 | roxygenize 176 | RoxygenNote 177 | rpart 178 | Rprofile 179 | Rproj 180 | rprojroot 181 | Rr 182 | rR 183 | Rscript 184 | RSMP 185 | RStudio 186 | rstudio 187 | rstudioapi 188 | saveCache 189 | seealso 190 | sep 191 | setdiff 192 | SHA 193 | stderr 194 | stdout 195 | sterr 196 | stopifnot 197 | styler 198 | styler's 199 | sublicenses 200 | Sublicensing 201 | Sys 202 | SystemRequirements 203 | tcltk 204 | tempfile 205 | testthat 206 | tibble 207 | Tidelift 208 | tidelift 209 | tidyverse 210 | toolchain 211 | trailingOnly 212 | travis 213 | tryCatch 214 | ubuntu 215 | UCRT 216 | uninitialised 217 | Uninstallation 218 | unlist 219 | unname 220 | Unstaged 221 | Upvote 222 | UseR 223 | usethis 224 | usr 225 | vcs 226 | VignetteBuilder 227 | Walthert 228 | walthert 229 | wd 230 | WIPO 231 | withr 232 | wordlist 233 | writeLines 234 | www 235 | yaml 236 | yihui 237 | yml 238 | Za 239 | -------------------------------------------------------------------------------- /inst/hooks/exported/codemeta-description-updated.R: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env Rscript 2 | 3 | "A hook to make sure DESCRIPTION hasn’t been edited more recently than 4 | codemeta.json. 5 | 6 | Usage: 7 | codemeta-description-updated [--root=] ... 8 | 9 | Options: 10 | --root= Path relative to the git root that contains the R package 11 | root [default: .]. 12 | 13 | " -> doc 14 | 15 | 16 | arguments <- precommit::precommit_docopt(doc) 17 | setwd(arguments$root) 18 | 19 | # adapted from https://github.com/lorenzwalthert/precommit/blob/f4413cfe6282c84f7176160d06e1560860c8bd3d/inst/hooks/exported/readme-rmd-rendered 20 | if (!file.exists("DESCRIPTION")) { 21 | rlang::abort("No `DESCRIPTION` found in repository.") 22 | } 23 | 24 | if (!file.exists("codemeta.json")) { 25 | rlang::abort("No `codemeta.json` found in repository.") 26 | } 27 | 28 | 29 | codemeta_outdated <- file.info("DESCRIPTION")$mtime > file.info("codemeta.json")$mtime 30 | if (codemeta_outdated) { 31 | rlang::abort("codemeta.json is out of date; please re-run codemetar::write_codemeta().") 32 | } 33 | -------------------------------------------------------------------------------- /inst/hooks/exported/deps-in-desc.R: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env Rscript 2 | 3 | "Ensure all dependencies of the form pkg::fun are in DESCRIPTION 4 | Usage: 5 | deps-in-desc [--allow_private_imports] [--root=] ... 6 | 7 | Options: 8 | --allow_private_imports Whether or not to allow the use of ::: on imported functions. 9 | --root= Path relative to the git root that contains the R package root [default: .]. 10 | 11 | " -> doc 12 | pre_installed <- c( 13 | "base", "boot", "class", "cluster", "codetools", "compiler", 14 | "datasets", "foreign", "graphics", "grDevices", "grid", "KernSmooth", 15 | "lattice", "MASS", "Matrix", "methods", "mgcv", "nlme", "nnet", 16 | "parallel", "rpart", "spatial", "splines", "stats", "stats4", 17 | "survival", "tcltk", "tools", "utils" 18 | ) 19 | 20 | arguments <- precommit::precommit_docopt(doc) 21 | arguments$files <- normalizePath(arguments$files) # because working directory changes to root 22 | setwd(normalizePath(arguments$root)) 23 | deps_in_desc <- function(file, arguments) { 24 | if (basename(file) == "README.Rmd") { 25 | # is .Rbuildignored, dependencies irrelevant 26 | return() 27 | } 28 | if (grepl("(\\.Rmd|\\.Rnw|\\.rmd|\\.rnw)$", file)) { 29 | file_ <- precommit::robust_purl(file) 30 | } else { 31 | file_ <- file 32 | } 33 | parse_data <- getParseData(parse(file = file_, keep.source = TRUE)) 34 | used <- parse_data[parse_data$token == "SYMBOL_PACKAGE", ]$text 35 | local_pkg_name <- unname(desc::desc_get("Package")) 36 | unregistered <- setdiff( 37 | unique(used), 38 | c(pre_installed, desc::desc_get_deps(file = ".")$package, local_pkg_name) 39 | ) 40 | out <- TRUE 41 | if (length(unregistered) > 0) { 42 | cat( 43 | "Not all packages used in your code are listed in DESCRIPTION. ", 44 | "The following are missing in file `", file, "`: ", 45 | paste(unregistered, collapse = ", "), ".\n", 46 | "You can add them with `usethis::use_package()` or ", 47 | "`usethis::use_dev_package()`.\n", 48 | sep = "" 49 | ) 50 | out <- FALSE 51 | } 52 | if (isTRUE(arguments$allow_private_imports)) { 53 | return(out) 54 | } 55 | private <- setdiff( 56 | parse_data[c(parse_data$token[-1], FALSE) == "NS_GET_INT", ]$text, 57 | local_pkg_name 58 | ) 59 | if (length(private) > 0) { 60 | cat( 61 | "You should generally not call any function with `:::` unless it's a function you ", 62 | "define yourself in this package. The following files have such function calls: ", 63 | paste0(file, collapse = ", "), "\n\n", 64 | "If you still think `:::` is a good idea, you can ", 65 | "allow private imports in your `.pre-commit-config.yaml` like this: 66 | 67 | - id: deps-in-desc 68 | args: [--allow_private_imports] 69 | 70 | ", 71 | sep = "" 72 | ) 73 | out <- FALSE 74 | } 75 | out 76 | } 77 | 78 | out <- lapply(arguments$files, deps_in_desc, arguments = arguments) 79 | if (!all(unlist(out))) { 80 | stop("Dependency check failed", call. = FALSE) 81 | } 82 | -------------------------------------------------------------------------------- /inst/hooks/exported/lintr.R: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env Rscript 2 | 3 | 4 | "Run lintr on R files during a precommit. 5 | Usage: 6 | lintr [options] ... 7 | 8 | Options: 9 | --warn_only Print lint warnings instead of blocking the commit. Should be 10 | used with `verbose: True` in `.pre-commit-config.yaml`. 11 | Otherwise, lints will never be shown to the user. 12 | --load_package Use `pkgload::load_all()` to load subject package prior to 13 | running lintr. 14 | 15 | " -> doc 16 | 17 | arguments <- precommit::precommit_docopt(doc) 18 | 19 | lintr_staged <- grepl( 20 | "modified:.*\\.lintr", system2("git", "status", stdout = TRUE) 21 | ) 22 | if (any(lintr_staged)) { 23 | stop( 24 | "Unstaged changes to .lintr file. Stage the .lintr file or discard ", 25 | "the changes to it. ", 26 | call. = FALSE 27 | ) 28 | } 29 | 30 | if (arguments$load_package) { 31 | cat("Attempting to load package\n") 32 | pkgload::load_all() 33 | } 34 | 35 | for (path in arguments$files) { 36 | lints <- lintr::lint(path) 37 | if (length(lints) > 0) { 38 | cat("File `", path, "` is not lint free\n", sep = "") 39 | rendered_lints <- capture.output(print(lints)) 40 | cat(rendered_lints, sep = "\n") 41 | if (!arguments$warn_only) { 42 | stop("File ", path, " is not lint free", call. = FALSE) 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /inst/hooks/exported/no-browser-statement.R: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env Rscript 2 | 3 | files <- commandArgs(trailing = TRUE) 4 | no_browser_statement <- function(path) { 5 | pd <- getParseData(parse(path, keep.source = TRUE)) 6 | if (any(pd$text[pd$token == "SYMBOL_FUNCTION_CALL"] == "browser")) { 7 | stop("File `", path, "` contains a `browser()` statement.", call. = FALSE) 8 | } 9 | } 10 | 11 | for (file in files) { 12 | temp <- no_browser_statement(file) 13 | } 14 | -------------------------------------------------------------------------------- /inst/hooks/exported/no-debug-statement.R: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env Rscript 2 | 3 | files <- commandArgs(trailing = TRUE) 4 | no_debug_statement <- function(path) { 5 | pd <- getParseData(parse(path, keep.source = TRUE)) 6 | if (any(pd$text[pd$token == "SYMBOL_FUNCTION_CALL"] %in% c("debug", "debugonce"))) { 7 | stop("File `", path, "` contains a `debug()` or `debugonce()` statement.", call. = FALSE) 8 | } 9 | } 10 | 11 | for (file in files) { 12 | temp <- no_debug_statement(file) 13 | } 14 | -------------------------------------------------------------------------------- /inst/hooks/exported/no-print-statement.R: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env Rscript 2 | 3 | files <- commandArgs(trailing = TRUE) 4 | no_print_statement <- function(path) { 5 | pd <- getParseData(parse(path, keep.source = TRUE)) 6 | if (any(pd$text[pd$token == "SYMBOL_FUNCTION_CALL"] == "print")) { 7 | stop("File `", path, "` contains a `print()` statement.", call. = FALSE) 8 | } 9 | } 10 | 11 | for (file in files) { 12 | temp <- no_print_statement(file) 13 | } 14 | -------------------------------------------------------------------------------- /inst/hooks/exported/parsable-R.R: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env Rscript 2 | files <- commandArgs(trailing = TRUE) 3 | 4 | out <- lapply(files, function(path) { 5 | is_rmd <- grepl("\\.[rR]md$", path) 6 | if (is_rmd) { 7 | path_ <- precommit::robust_purl(path) 8 | } else { 9 | path_ <- path 10 | } 11 | 12 | tryCatch( 13 | parse(path_), 14 | error = function(error) { 15 | cat(c("File ", path, " is not parsable. Full context:\n")) 16 | stop(conditionMessage(error), call. = FALSE) 17 | } 18 | ) 19 | }) 20 | -------------------------------------------------------------------------------- /inst/hooks/exported/parsable-roxygen.R: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env Rscript 2 | 3 | "Check whether roxygen comments within files are valid 4 | Usage: 5 | parsable-roxygen [--eval] ... 6 | 7 | Options: 8 | --eval Evaluate file contents after parsing - this is required if `@eval` tags must be evaluated 9 | 10 | " -> doc 11 | 12 | arguments <- precommit::precommit_docopt(doc) 13 | 14 | if (packageVersion("roxygen2") < package_version("7.3.0")) { 15 | rlang::abort("You need at least version 7.3.0 of {roxygen2} to run this hook.") 16 | } 17 | 18 | out <- lapply(arguments$files, function(path) { 19 | tryCatch( 20 | # Capture any messages from roxygen2:::warn_roxy() 21 | msg <- capture.output( 22 | roxygen2::parse_file( 23 | path, 24 | env = if (isTRUE(arguments$eval)) { 25 | roxygen2::env_file(path) 26 | } else { 27 | NULL 28 | } 29 | ), 30 | type = "message" 31 | ), 32 | 33 | # In case we encounter a more general file parsing problem 34 | error = function(e) { 35 | cat(c("File ", path, " is not parsable. Full context:\n")) 36 | stop(conditionMessage(e), call. = FALSE) 37 | } 38 | ) 39 | 40 | if (length(msg) > 0) { 41 | cat(c("Roxygen commentary in file ", path, " is not parsable. Full context:\n")) 42 | stop(paste0(msg, collapse = "\n"), call. = FALSE) 43 | } 44 | }) 45 | -------------------------------------------------------------------------------- /inst/hooks/exported/pkgdown.R: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env Rscript 2 | 3 | if (is.null(pkgdown:::pkgdown_config_path("."))) { 4 | rlang::inform(paste0( 5 | "{pkgdown} seems not configured, the remainder of the check is skipped. ", 6 | "For this hook to not even be invoked, remove `id: pkgdown` from ", 7 | "`.pre-commit-config.yaml`." 8 | )) 9 | quit() 10 | } 11 | 12 | if (!require(pkgdown, quietly = TRUE)) { 13 | stop("{pkgdown} could not be loaded, please install it.") 14 | } 15 | if (packageVersion("pkgdown") < package_version("2.0.4")) { 16 | rlang::abort("You need at least version 2.0.4 of {pkgdown} to run this hook.") 17 | } 18 | check_pkgdown() 19 | -------------------------------------------------------------------------------- /inst/hooks/exported/readme-rmd-rendered.R: -------------------------------------------------------------------------------- 1 | if (file.exists("README.Rmd") & file.exists("README.md")) { 2 | if (file.info("README.md")$mtime < file.info("README.Rmd")$mtime) { 3 | rlang::abort("README.md is out of date; please re-knit README.Rmd.") 4 | } 5 | if (!nzchar(Sys.which("git"))) { 6 | rlang::abort("git not found on `$PATH`, hook can't be ran.") 7 | } 8 | file_names_staged <- system2( 9 | "git", c("diff --cached --name-only"), 10 | stdout = TRUE 11 | ) 12 | num_readmes <- length(grepl("^README\\.[R]?md$", file_names_staged)) 13 | 14 | if (num_readmes == 1) { 15 | rlang::abort("README.Rmd and README.md should be both staged.") 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /inst/hooks/exported/renv-lockfile-validate.R: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env Rscript 2 | 3 | "Validate renv lockfiles 4 | See `?renv::lockfile_validate()`. 5 | Usage: 6 | lockfile_validate [--schema=] [--greedy --error --verbose --strict] ... 7 | Options: 8 | --schema Path. Path to a custom schema. 9 | --greedy Continue after first error? 10 | --error Throw an error on parse failure? 11 | --verbose If `TRUE`, then an attribute `errors` will list validation failures as a `data.frame`. 12 | --strict Set whether the schema should be parsed strictly or not. 13 | " -> doc 14 | 15 | if (!require(renv, quietly = TRUE)) { 16 | stop("{renv} could not be loaded, please install it.") 17 | } 18 | if (packageVersion("renv") < package_version("1.0.8")) { 19 | rlang::abort("You need at least version 1.0.8 of {renv} to run this hook.") 20 | } 21 | if (!require(jsonvalidate, quietly = TRUE)) { 22 | stop("{jsonvalidate} could not be loaded, please install it.") 23 | } 24 | 25 | arguments <- precommit::precommit_docopt(doc) 26 | arguments$files <- normalizePath(arguments$files) 27 | if (!is.null(arguments$schema)) { 28 | arguments$schema <- normalizePath(arguments$schema) 29 | } 30 | 31 | renv::lockfile_validate( 32 | lockfile = arguments$files, 33 | schema = arguments$schema, 34 | greedy = arguments$greedy, 35 | error = arguments$error, 36 | verbose = arguments$verbose, 37 | strict = arguments$strict 38 | ) 39 | -------------------------------------------------------------------------------- /inst/hooks/exported/roxygenize.R: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env Rscript 2 | 3 | "Run roxygen2::roxygenize() 4 | 5 | Obviously, this hook is only activated when any staged file passes the filter 6 | specified in .pre-commit-hooks.yaml. Then, we check the time stamp of the last 7 | time we ran the hook. If any of our R files is younger than that, we consider 8 | running the hook. We next use {git2r} to inspect the cached diff of all .R files 9 | in the R/ directory, and not the files passed to this hook. If we find any 10 | roxygen2 comment in the diff, we run `roxygen2::roxygenize(). The preliminary 11 | use case for this is when we previously attempted to commit but check failed, so 12 | on the second try without any other files changed, it will succeed. 13 | This check should run *after* check that modify the files that are passed to 14 | them (like styler) because they will never modify their input .R files. 15 | 16 | Usage: 17 | roxygenize [--no-warn-cache] [--root=] ... 18 | 19 | Options: 20 | --no-warn-cache Suppress the warning about a missing permanent cache. 21 | --root= Path relative to the git root that contains the R package root [default: .]. 22 | 23 | " -> doc 24 | arguments <- precommit::precommit_docopt(doc) 25 | arguments$files <- normalizePath(arguments$files) # because working directory changes to root 26 | setwd(arguments$root) 27 | 28 | if (packageVersion("precommit") < "0.1.3.9010") { 29 | rlang::abort(paste( 30 | "This hooks only works with the R package {precommit} >= 0.1.3.9010", 31 | 'Please upgrade with `remotes::install_github("lorenzwalthert/precommit@v0.1.3.9010")`.' 32 | )) 33 | } else { 34 | precommit::may_require_permanent_cache(arguments$no_warn_cache) 35 | precommit::roxygen_assert_additional_dependencies() 36 | } 37 | 38 | path_relative_cache <- precommit::dirs_R.cache("roxygenize") 39 | wd <- list(getwd()) 40 | cache <- R.cache::loadCache(key = wd, dirs = path_relative_cache) 41 | rd_files_before_roxygen <- list.files("man", pattern = "\\.Rd$") 42 | 43 | if (!is.null(cache)) { 44 | candidates <- intersect( 45 | normalizePath(list.files(c("R", "man"), full.names = TRUE)), 46 | arguments$files 47 | ) 48 | all_files <- file.info(candidates) 49 | last_modified <- max(all_files$mtime) 50 | if (last_modified > cache[[1]]) { 51 | if (precommit::diff_requires_run_roxygenize()) { 52 | precommit::roxygenize_with_cache(key = wd, dirs = path_relative_cache) 53 | } 54 | } 55 | } else { 56 | precommit::roxygenize_with_cache(key = wd, dirs = path_relative_cache) 57 | } 58 | 59 | rd_files_after_roxygen <- list.files("man", pattern = "\\.Rd$") 60 | 61 | if (length(setdiff(rd_files_after_roxygen, rd_files_before_roxygen)) > 0) { 62 | rlang::abort("Please commit the new `.Rd` files in `man/`.") 63 | } 64 | -------------------------------------------------------------------------------- /inst/hooks/exported/spell-check.R: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env Rscript 2 | 3 | "Spell check for files 4 | Usage: 5 | spell-check [--lang=] [--read-only] ... 6 | 7 | Options: 8 | --lang= Passed to `spelling::spell_check_files()` [default: en_US] 9 | --read-only Don't update inst/WORDLIST with errors (new words) 10 | 11 | " -> doc 12 | 13 | arguments <- precommit::precommit_docopt(doc) 14 | path_wordlist <- file.path("inst", "WORDLIST") 15 | files <- arguments$files 16 | if (file.exists(path_wordlist)) { 17 | ignore <- readLines(path_wordlist, encoding = "UTF-8") 18 | action <- "update" 19 | } else { 20 | if (isFALSE(arguments$read_only)) { 21 | if (!dir.exists(dirname(path_wordlist))) { 22 | dir.create(dirname(path_wordlist)) 23 | } 24 | file.create(path_wordlist) 25 | } 26 | ignore <- character() 27 | action <- "create" 28 | } 29 | 30 | 31 | spelling_errors <- spelling::spell_check_files( 32 | files, 33 | ignore = ignore, 34 | lang = arguments$lang 35 | ) 36 | 37 | if (nrow(spelling_errors) > 0) { 38 | cat("The following spelling errors were found:\n") 39 | print(spelling_errors) 40 | if (isTRUE(arguments$read_only)) { 41 | cat("Hint: you can enable an automatic updating of WORDLIST with errors (new words) by removing the --read-only flag.\n") 42 | } else { 43 | ignore_df <- data.frame( 44 | original = unique(c(ignore, spelling_errors$word)) 45 | ) 46 | ignore_df$lower <- tolower(ignore_df$original) 47 | ignore_df <- ignore_df[order(ignore_df$lower, method = "radix"), ] 48 | ignore <- ignore_df$original[ignore_df$lower != ""] # drop blanks if any 49 | writeLines(ignore, path_wordlist) 50 | cat( 51 | "All spelling errors found were copied to inst/WORDLIST assuming they were", 52 | "not spelling errors and will be ignored in the future. Please", 53 | "review the above list and for each word that is an actual typo:\n", 54 | "- fix it in the source code.\n", 55 | "- remove it again manually from inst/WORDLIST to make sure it's not\n", 56 | " ignored in the future.\n", 57 | "Then, try committing again.\n", 58 | "Hint: you can disable this behavior by providing a --read-only flag.\n" 59 | ) 60 | } 61 | stop("Spell check failed", call. = FALSE) 62 | } 63 | -------------------------------------------------------------------------------- /inst/hooks/exported/style-files.R: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env Rscript 2 | 'style files. 3 | Usage: 4 | style_files [--style_pkg=] [--style_fun=] [--cache-root=] [--no-warn-cache] [--ignore-start=] [--ignore-stop=] ... 5 | 6 | Options: 7 | --style_pkg= Package where the style guide is stored [default: styler]. 8 | --style_fun= The styling function in style_pkg [default: tidyverse_style]. 9 | --no-warn-cache Suppress the warning about a missing permanent cache. 10 | --cache-root= Passed to `options("styler.cache_root")` [default: styler-perm]. 11 | --ignore-start= Passed to `options("styler.ignore_start")`. 12 | --ignore-stop= Passed to `options("styler.ignore_stop")`. 13 | ' -> doc 14 | 15 | if (packageVersion("precommit") < "0.1.3.9010") { 16 | rlang::abort(paste( 17 | "This hooks only works with the R package {precommit} >= 0.1.3.9010", 18 | 'Please upgrade with `remotes::install_github("lorenzwalthert/precommit@v0.1.3.9010")`.' 19 | )) 20 | } 21 | 22 | # the code bellow will basically expand `doc` with the additional key values 23 | # provided passed to `style_text(...), passed as --key=value. This way, we 24 | # can rely on {docopt} to do the parsing for us afterwards. 25 | # We'll use yaml.load() to convert from string to numeric/logical. 26 | library(styler) 27 | args <- commandArgs(trailingOnly = TRUE) 28 | non_file_args <- args[!grepl("^[^-][^-]", args)] 29 | keys <- setdiff( 30 | gsub("(^--[0-9A-Za-z_-]+).*", "\\1", non_file_args), 31 | c("--style_pkg", "--style_fun", "--cache-root", "--no-warn-cache", "--ignore-start", "--ignore-stop") 32 | ) 33 | if (length(keys) > 0) { 34 | bare_keys <- gsub("^--", "", keys) 35 | key_value_pairs <- paste0(" ", keys, "= non_file_args ", bare_keys, ".") 36 | insert <- paste(paste0("[", keys, "=]", collapse = " "), "...") 37 | 38 | doc <- gsub("...", insert, paste0(doc, paste(key_value_pairs, collapse = "\n"))) 39 | } 40 | 41 | arguments <- precommit::precommit_docopt(doc, args) 42 | if (packageVersion("styler") < "1.3.2") { 43 | stop( 44 | "Your styler version is outdated. ", 45 | "Please install a newer version of styler with ", 46 | "`install.packages('styler')`. This will get you big speed-ups." 47 | ) 48 | } else { 49 | precommit::may_require_permanent_cache(arguments$no_warn_cache) 50 | } 51 | options("styler.cache_root" = arguments$cache_root) 52 | if (!is.null(arguments$ignore_start)) { 53 | options("styler.ignore_start" = arguments$ignore_start) 54 | } 55 | if (!is.null(arguments$ignore_stop)) { 56 | options("styler.ignore_stop" = arguments$ignore_stop) 57 | } 58 | print(c("cache root set to ", arguments$cache_root)) 59 | if (!rlang::is_installed(arguments$style_pkg)) { 60 | rlang::abort(paste0( 61 | "{", arguments$style_pkg, 62 | "} must be listed in `additional_dependencies:` with a syntax that `renv::install()` understands, e.g. for a public GitHub package, adapt your `.pre-commit-config.yaml` file: 63 | 64 | - id: style-files 65 | args: [--style_pkg=", arguments$style_pkg, ", --style_fun=", arguments$style_fun, "] 66 | additional_dependencies: 67 | - github-org/repo-with-package 68 | 69 | See 'Examples' in `help(\"install\", package = \"renv\")` for other supported ways of specifying the dependency. 70 | " 71 | )) 72 | } 73 | 74 | style <- eval(parse(text = paste(arguments$style_pkg, "::", arguments$style_fun))) 75 | 76 | tryCatch( 77 | { 78 | dot_args <- list(path = arguments$files, style = style) 79 | if (length(keys) > 0) { 80 | dot_args <- append(dot_args, lapply(arguments[bare_keys], function(x) { 81 | tryCatch( 82 | tryCatch( 83 | # 1 try yaml.load() -> parse 84 | eval(parse(text = tryCatch(yaml::yaml.load(x), 85 | # 2 try parse 86 | error = function(...) x 87 | ))), 88 | # 3 try yaml.load 89 | error = yaml::yaml.load(x) 90 | # 4 try as is. 91 | ), 92 | error = function(...) x 93 | ) 94 | })) 95 | } 96 | do.call(style_file, dot_args) 97 | }, 98 | warning = function(w) { 99 | msg <- conditionMessage(w) 100 | if (grepl("Unknown or uninitialised column", msg, ignore.case = TRUE)) { 101 | warning(msg, call. = FALSE) 102 | } else { 103 | stop(msg, call. = FALSE) 104 | } 105 | } 106 | ) 107 | -------------------------------------------------------------------------------- /inst/hooks/exported/use-tidy-description.R: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env Rscript 2 | 3 | "A hook to run usethis::use_tidy_description() to ensure dependencies are 4 | ordered alphabetically and fields are in standard order. 5 | 6 | Usage: 7 | use-tidy-description [--root=] ... 8 | 9 | Options: 10 | --root= Path relative to the git root that contains the R package root [default: .]. 11 | 12 | " -> doc 13 | 14 | 15 | arguments <- precommit::precommit_docopt(doc) 16 | setwd(arguments$root) 17 | 18 | if (!file.exists("DESCRIPTION")) { 19 | rlang::abort("No `DESCRIPTION` found in repository.") 20 | } 21 | 22 | description <- desc::description$new() 23 | description_old <- description$clone(deep = TRUE) 24 | deps <- description$get_deps() 25 | deps <- deps[order(deps$type, deps$package), , drop = FALSE] 26 | description$del_deps() 27 | description$set_deps(deps) 28 | remotes <- description$get_remotes() 29 | if (length(remotes) > 0) { 30 | description$set_remotes(sort(remotes)) 31 | } 32 | description$set(Encoding = "UTF-8") 33 | try(description$normalize(), silent = TRUE) 34 | if (!all(capture.output(description) == capture.output(description_old))) { 35 | description$write() 36 | } 37 | -------------------------------------------------------------------------------- /inst/hooks/local/consistent-release-tag.R: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env Rscript 2 | "This hook checks that all versions in config files and the git tag that 3 | is used by precommit to clone the repo is identical. 4 | 5 | Usage: 6 | consistent-release-tag [--release-mode] [...] 7 | 8 | " -> doc 9 | arguments <- precommit::precommit_docopt(doc) 10 | 11 | 12 | # This hook checks that all versions in config files and the git tag that 13 | # is used by precommit to clone the repo is identical. 14 | # DESCRIPTION is allowed to have higher version because the process is: 15 | # - release a version e.g. 0.1.0 on CRAN, all example configs should match DESCRIPTION 16 | # - continue development: Should bump DESCRIPTION to dev, e.g. 0.1.0.9000, 17 | # but not git tag or config examples. These should remain at 0.1.0. 18 | # - release new version on CRAN, make sure all tags correspond to 0.2.0. 19 | 20 | path_config <- c( 21 | fs::path("inst", "pre-commit-config-pkg.yaml"), 22 | fs::path("inst", "pre-commit-config-proj.yaml") 23 | ) 24 | 25 | 26 | 27 | assert_config_has_rev <- function(path_config, latest_tag) { 28 | file <- yaml::read_yaml(path_config) 29 | repo <- purrr::map(file$repos, "repo") 30 | 31 | lorenzwalthert_precommit_idx <- which(repo == "https://github.com/lorenzwalthert/precommit") 32 | stopifnot(length(lorenzwalthert_precommit_idx) == 1) 33 | rev <- file$repos[[lorenzwalthert_precommit_idx]]$rev 34 | 35 | if (latest_tag != rev) { 36 | rlang::abort(glue::glue( 37 | "latest git tag is `{latest_tag}`, but in `{path_config}`, you the ", 38 | "revision is set to `{rev}` Please make the two correspond." 39 | )) 40 | } 41 | } 42 | 43 | get_latest_tag <- function() { 44 | system2("git", c("fetch", "--tags")) 45 | system2("git", c("describe", "--tags", "--abbrev=0"), stdout = TRUE) 46 | } 47 | 48 | latest_tag <- get_latest_tag() 49 | 50 | purrr::walk(path_config, assert_config_has_rev, latest_tag = latest_tag) 51 | latest_tag_without_prefix <- gsub("^v", "", latest_tag) 52 | 53 | if (!(latest_tag_without_prefix < desc::desc_get_field("Version"))) { 54 | if (latest_tag_without_prefix > desc::desc_get_field("Version")) { 55 | rlang::abort(paste( 56 | "git tag should never be greater than description. At most they should", 57 | "be equal." 58 | )) 59 | } else if (!arguments$release_mode) { 60 | rlang::abort(paste( 61 | "DESCRIPTION version must be larger than git tag unless the check is", 62 | " performed during a release. Then turn this off with --release-mode" 63 | )) 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /inst/hooks/local/hooks-config-to-inst.R: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env Rscript 2 | 3 | # because we need access of .pre-commit-hooks.yaml in the vignette 4 | # available-hooks and maybe more general in other places, we must put this 5 | # file under inst/ because otherwise it cannot be accessed (leaving it on top 6 | # level will fail r cmd check, the same when used as hidden file in inst/) 7 | fs::file_copy( 8 | ".pre-commit-hooks.yaml", "inst/pre-commit-hooks.yaml", 9 | overwrite = TRUE 10 | ) 11 | -------------------------------------------------------------------------------- /inst/hooks/local/spell-check-exclude-identical.R: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env Rscript 2 | library(magrittr) 3 | read_config <- function(path) { 4 | yaml::read_yaml(path) %>% 5 | purrr::pluck("repos") %>% 6 | purrr::keep(~ .x$repo == "https://github.com/lorenzwalthert/precommit") %>% 7 | purrr::pluck(1, "hooks") %>% 8 | purrr::keep(~ .x$id == "spell-check") %>% 9 | purrr::pluck(1, "exclude") 10 | } 11 | hooks_pkg <- read_config(here::here("inst/pre-commit-config-pkg.yaml")) 12 | hooks_proj <- read_config(here::here("inst/pre-commit-config-proj.yaml")) 13 | if (!identical(hooks_proj, hooks_pkg)) { 14 | rlang::abort(paste0( 15 | "The `exclude` key from the spell-check hook should be the same for all templates in `inst/`." 16 | )) 17 | } 18 | -------------------------------------------------------------------------------- /inst/hooks/local/spell-check-ordered-exclude.R: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env Rscript 2 | # This hook checks that the `exclude:` key in the pre-commit config files 3 | # are alphabetically ordered. This is helpful for manual searching plus also 4 | # groups the patterns, i.e. extensions, files everywhere, directories 5 | library(magrittr) 6 | args <- commandArgs(trailingOnly = TRUE) 7 | run_one_file <- function(file) { 8 | config <- yaml::read_yaml(file) # pre-commit filter 9 | ours <- which(purrr::map_chr(config$repos, "repo") == "https://github.com/lorenzwalthert/precommit") 10 | nme <- purrr::map_chr(config$repos[[ours]]$hooks, "id") 11 | 12 | regex <- config$repos[[1]]$hooks[[which(nme == "spell-check")]]$exclude %>% 13 | strsplit(" +") %>% 14 | unlist() 15 | 16 | without_mask <- regex[c(-1, -length(regex))] 17 | if (any(without_mask != sort(without_mask))) { 18 | cat(paste0( 19 | "regular expressions in file ", file, " not sorted for spell check hook, paste this ", 20 | "into the pre-commit config file: under the `exclude:` key:" 21 | )) 22 | 23 | cat(c("", regex[1], sort(without_mask), regex[length(regex)]), sep = "\n ") 24 | rlang::abort("Execution halted.") 25 | } 26 | } 27 | 28 | purrr::walk(args, run_one_file) 29 | -------------------------------------------------------------------------------- /inst/pre-commit-config-pkg.yaml: -------------------------------------------------------------------------------- 1 | # All available hooks: https://pre-commit.com/hooks.html 2 | # R specific hooks: https://github.com/lorenzwalthert/precommit 3 | repos: 4 | - repo: https://github.com/lorenzwalthert/precommit 5 | rev: v0.4.3.9010 6 | hooks: 7 | - id: style-files 8 | args: [--style_pkg=styler, --style_fun=tidyverse_style] 9 | - id: roxygenize 10 | # codemeta must be above use-tidy-description when both are used 11 | # - id: codemeta-description-updated 12 | - id: use-tidy-description 13 | - id: spell-check 14 | exclude: > 15 | (?x)^( 16 | .*\.[rR]| 17 | .*\.feather| 18 | .*\.jpeg| 19 | .*\.pdf| 20 | .*\.png| 21 | .*\.py| 22 | .*\.RData| 23 | .*\.rds| 24 | .*\.Rds| 25 | .*\.Rproj| 26 | .*\.sh| 27 | (.*/|)\.gitignore| 28 | (.*/|)\.gitlab-ci\.yml| 29 | (.*/|)\.lintr| 30 | (.*/|)\.pre-commit-.*| 31 | (.*/|)\.Rbuildignore| 32 | (.*/|)\.Renviron| 33 | (.*/|)\.Rprofile| 34 | (.*/|)\.travis\.yml| 35 | (.*/|)appveyor\.yml| 36 | (.*/|)NAMESPACE| 37 | (.*/|)renv/settings\.dcf| 38 | (.*/|)renv\.lock| 39 | (.*/|)WORDLIST| 40 | \.github/workflows/.*| 41 | data/.*| 42 | )$ 43 | - id: lintr 44 | - id: readme-rmd-rendered 45 | - id: parsable-R 46 | - id: no-browser-statement 47 | - id: no-print-statement 48 | - id: no-debug-statement 49 | - id: deps-in-desc 50 | - id: pkgdown 51 | - repo: https://github.com/pre-commit/pre-commit-hooks 52 | rev: v1.2.3 53 | hooks: 54 | - id: check-added-large-files 55 | args: ['--maxkb=200'] 56 | - id: file-contents-sorter 57 | files: '^\.Rbuildignore$' 58 | - id: end-of-file-fixer 59 | exclude: '\.Rd' 60 | - repo: https://github.com/pre-commit-ci/pre-commit-ci-config 61 | rev: v1.5.1 62 | hooks: 63 | # Only required when https://pre-commit.ci is used for config validation 64 | - id: check-pre-commit-ci-config 65 | - repo: local 66 | hooks: 67 | - id: forbid-to-commit 68 | name: Don't commit common R artifacts 69 | entry: Cannot commit .Rhistory, .RData, .Rds or .rds. 70 | language: fail 71 | files: '\.(Rhistory|RData|Rds|rds)$' 72 | # `exclude: ` to allow committing specific files 73 | 74 | ci: 75 | autoupdate_schedule: monthly 76 | skip: [pkgdown] 77 | -------------------------------------------------------------------------------- /inst/pre-commit-config-proj.yaml: -------------------------------------------------------------------------------- 1 | # All available hooks: https://pre-commit.com/hooks.html 2 | # R specific hooks: https://github.com/lorenzwalthert/precommit 3 | repos: 4 | - repo: https://github.com/lorenzwalthert/precommit 5 | rev: v0.4.3.9010 6 | hooks: 7 | - id: style-files 8 | args: [--style_pkg=styler, --style_fun=tidyverse_style] 9 | - id: spell-check 10 | exclude: > 11 | (?x)^( 12 | .*\.[rR]| 13 | .*\.feather| 14 | .*\.jpeg| 15 | .*\.pdf| 16 | .*\.png| 17 | .*\.py| 18 | .*\.RData| 19 | .*\.rds| 20 | .*\.Rds| 21 | .*\.Rproj| 22 | .*\.sh| 23 | (.*/|)\.gitignore| 24 | (.*/|)\.gitlab-ci\.yml| 25 | (.*/|)\.lintr| 26 | (.*/|)\.pre-commit-.*| 27 | (.*/|)\.Rbuildignore| 28 | (.*/|)\.Renviron| 29 | (.*/|)\.Rprofile| 30 | (.*/|)\.travis\.yml| 31 | (.*/|)appveyor\.yml| 32 | (.*/|)NAMESPACE| 33 | (.*/|)renv/settings\.dcf| 34 | (.*/|)renv\.lock| 35 | (.*/|)WORDLIST| 36 | \.github/workflows/.*| 37 | data/.*| 38 | )$ 39 | - id: lintr 40 | - id: readme-rmd-rendered 41 | - id: parsable-R 42 | - id: no-browser-statement 43 | - id: no-debug-statement 44 | - repo: https://github.com/pre-commit/pre-commit-hooks 45 | rev: v1.2.3 46 | hooks: 47 | - id: check-added-large-files 48 | args: ['--maxkb=200'] 49 | - id: end-of-file-fixer 50 | exclude: '\.Rd' 51 | - repo: https://github.com/pre-commit-ci/pre-commit-ci-config 52 | rev: v1.5.1 53 | hooks: 54 | # Only required when https://pre-commit.ci is used for config validation 55 | - id: check-pre-commit-ci-config 56 | - repo: local 57 | hooks: 58 | - id: forbid-to-commit 59 | name: Don't commit common R artifacts 60 | entry: Cannot commit .Rhistory, .RData, .Rds or .rds. 61 | language: fail 62 | files: '\.(Rhistory|RData|Rds|rds)$' 63 | # `exclude: ` to allow committing specific files 64 | 65 | ci: 66 | autoupdate_schedule: monthly 67 | -------------------------------------------------------------------------------- /inst/pre-commit-gha.yaml: -------------------------------------------------------------------------------- 1 | name: pre-commit 2 | on: 3 | push: 4 | branches-ignore: 5 | - 'master' 6 | - 'main' 7 | pull_request: 8 | types: [opened, synchronize, reopened, ready_for_review] 9 | 10 | jobs: 11 | pre-commit: 12 | runs-on: ubuntu-latest 13 | if: >- 14 | !contains(github.event.head_commit.message, 'ci skip') && 15 | ( 16 | startsWith(github.ref, 'refs/heads') || 17 | github.event.pull_request.draft == false 18 | ) 19 | steps: 20 | - name: Cancel Previous Runs 21 | uses: styfle/cancel-workflow-action@0.6.0 22 | with: 23 | access_token: ${{ github.token }} 24 | - uses: actions/checkout@v3 25 | with: 26 | fetch-depth: 0 27 | - name: Install system dependencies 28 | if: runner.os == 'Linux' 29 | run: | 30 | # your system installation code here 31 | # sudo apt-get install -y libcurl4-openssl-dev 32 | - name: Set up Python 33 | uses: actions/setup-python@v2 34 | with: 35 | python-version: "3.8" 36 | architecture: "x64" 37 | - name: Run pre-commit 38 | uses: pre-commit/action@v2.0.3 39 | - name: Commit files 40 | if: failure() && startsWith(github.ref, 'refs/heads') 41 | run: | 42 | if [[ `git status --porcelain --untracked-files=no` ]]; then 43 | git config --local user.email "github-actions[bot]@users.noreply.github.com" 44 | git config --local user.name "github-actions[bot]" 45 | git checkout -- .github/workflows 46 | git commit -m "pre-commit" -a 47 | fi 48 | - name: Push changes 49 | if: failure() && startsWith(github.ref, 'refs/heads') 50 | uses: ad-m/github-push-action@master 51 | with: 52 | github_token: ${{ secrets.GITHUB_TOKEN }} 53 | branch: ${{ github.ref }} 54 | env: 55 | RENV_CONFIG_CACHE_ENABLED: FALSE 56 | -------------------------------------------------------------------------------- /inst/update-dependency-graph-existing-packages.R: -------------------------------------------------------------------------------- 1 | hook_deps <- function(root) { 2 | out <- renv::dependencies("inst/hooks/exported/")$Package 3 | desc <- desc::desc() 4 | deps <- desc$get_deps() 5 | dont <- c( 6 | "yaml", "usethis", "withr", "rstudioapi", "precommit", 7 | "pkgdown", 8 | "httr" 9 | ) 10 | out <- c(out, "docopt", "roxygen2", "spelling", "styler", "pkgload", "lintr", "knitr", "desc", "jsonvalidate") 11 | out <- setdiff(c(unique(c(out, deps[deps$type == "Imports", ]$package))), dont) 12 | out <- names(renv:::renv_package_dependencies(out)) 13 | return(sort(out)) 14 | } 15 | 16 | source("inst/update-renv-prepare.R") 17 | source("renv/activate.R") 18 | renv::restore() 19 | options(renv.snapshot.filter = hook_deps) 20 | 21 | # TODO snapshot looks up from which repo the packages were installed. 22 | # Hence, setting the repo option only affects new installs. Solution: 23 | # - manually replace cran with rspm in renv.lock 24 | # - first activate renv, restore renv and then run this script 25 | renv::snapshot(type = "custom", prompt = FALSE) 26 | -------------------------------------------------------------------------------- /inst/update-existing-hook-dependencies.R: -------------------------------------------------------------------------------- 1 | source("inst/update-renv-prepare.R") 2 | renv_deps <- names(jsonlite::read_json("renv.lock")$Packages) 3 | source("renv/activate.R") 4 | renv::load() 5 | renv::restore(prompt = FALSE) 6 | can_be_updated <- renv::update(renv_deps, prompt = FALSE, check = FALSE) 7 | renv::snapshot(packages = renv_deps, prompt = FALSE) 8 | -------------------------------------------------------------------------------- /inst/update-ppm-url.R: -------------------------------------------------------------------------------- 1 | # old binaries only guaranteed to be available for frozen snapshot 2 | # https://community.rstudio.com/t/binary-packages-removed-once-new-package-version-released-on-ppm/177282/2 3 | lockfile <- renv::lockfile_read() 4 | rspm_url <- lockfile$R$Repositories$RSPM 5 | current_date <- Sys.Date() - 3 # available for sure 6 | 7 | to_substract <- max(0, as.integer(strftime(current_date, "%u")) - 5) 8 | ensured_weekday <- current_date - to_substract 9 | updated_url <- gsub("[0-9]{4}-[0-9]{2}-[0-9]{2}$", ensured_weekday, rspm_url) 10 | lockfile$R$Repositories$RSPM <- updated_url 11 | renv::lockfile_write(lockfile = lockfile) 12 | cat(paste0("Snapshot day for PPM moved to ", ensured_weekday)) 13 | -------------------------------------------------------------------------------- /inst/update-renv-prepare.R: -------------------------------------------------------------------------------- 1 | options( 2 | install.packages.check.source = "no", # don't check if source packages are available 3 | install.packages.compile.from.source = "never" # probably redundant with the above 'no': If source package is available, only use source if no code needs to be compiled (needs compilation flag on CRAN). 4 | ) 5 | -------------------------------------------------------------------------------- /inst/usethis-legacy-hook: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | README=($(git diff --cached --name-only | grep -Ei '^README\.[R]?md$')) 3 | MSG="use 'git commit --no-verify' to override this check" 4 | 5 | if [[ ${#README[@]} == 0 ]]; then 6 | exit 0 7 | fi 8 | 9 | if [[ README.Rmd -nt README.md ]]; then 10 | echo -e "README.md is out of date; please re-knit README.Rmd\n$MSG" 11 | exit 1 12 | elif [[ ${#README[@]} -lt 2 ]]; then 13 | echo -e "README.Rmd and README.md should be both staged\n$MSG" 14 | exit 1 15 | fi 16 | -------------------------------------------------------------------------------- /man/autoupdate.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/setup.R 3 | \name{autoupdate} 4 | \alias{autoupdate} 5 | \title{Auto-update your hooks} 6 | \usage{ 7 | autoupdate(root = here::here()) 8 | } 9 | \arguments{ 10 | \item{root}{The path to the root directory of your project.} 11 | } 12 | \value{ 13 | The exit status from \verb{pre-commit autoupdate} (invisibly). 14 | } 15 | \description{ 16 | Runs \href{https://pre-commit.com/#pre-commit-autoupdate}{\verb{pre-commit autoupdate}}. 17 | } 18 | \examples{ 19 | \dontrun{ 20 | autoupdate() 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /man/call_and_capture.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/call.R 3 | \name{call_and_capture} 4 | \alias{call_and_capture} 5 | \title{Make a call with \code{\link[=system2]{system2()}} and capture the effects.} 6 | \usage{ 7 | call_and_capture(command, args, ..., wait = TRUE) 8 | } 9 | \arguments{ 10 | \item{command}{The command to issue. A character string of length one.} 11 | 12 | \item{args}{The command line arguments.} 13 | 14 | \item{...}{Arguments passed to \code{\link[=system2]{system2()}}.} 15 | 16 | \item{wait}{Passed to \code{\link[=system2]{system2()}}.} 17 | } 18 | \value{ 19 | A list with: 20 | \itemize{ 21 | \item content of stderr 22 | \item content of stdout 23 | \item exit status 24 | } 25 | } 26 | \description{ 27 | Make a call with \code{\link[=system2]{system2()}} and capture the effects. 28 | } 29 | \keyword{internal} 30 | -------------------------------------------------------------------------------- /man/call_precommit.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/call.R 3 | \name{call_precommit} 4 | \alias{call_precommit} 5 | \title{Call pre-commit} 6 | \usage{ 7 | call_precommit(..., wait = TRUE) 8 | } 9 | \arguments{ 10 | \item{...}{Arguments passed to the command line call \code{pre-commit}.} 11 | 12 | \item{wait}{Passed to \code{\link[base:system2]{base::system2()}}.} 13 | } 14 | \description{ 15 | Either via \verb{conda run} (because conda env needs to be activated in general to 16 | ensure an executable to runs successfully) or, if the installation method was 17 | not conda, as a plain bash command. 18 | } 19 | \keyword{internal} 20 | -------------------------------------------------------------------------------- /man/communicate_captured_call.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/call.R 3 | \name{communicate_captured_call} 4 | \alias{communicate_captured_call} 5 | \title{Communicate a captured call} 6 | \usage{ 7 | communicate_captured_call(x, preamble = "") 8 | } 9 | \arguments{ 10 | \item{x}{The output of \code{\link[=call_and_capture]{call_and_capture()}}.} 11 | } 12 | \description{ 13 | Communicates a captured call. 14 | } 15 | \keyword{internal} 16 | -------------------------------------------------------------------------------- /man/copy_artifacts.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/testing.R 3 | \name{copy_artifacts} 4 | \alias{copy_artifacts} 5 | \title{Copy some file to the test directory that must be present, but are not 6 | passed to the hook as a file argument.} 7 | \usage{ 8 | copy_artifacts(artifacts, tempdir) 9 | } 10 | \arguments{ 11 | \item{artifacts}{Artifacts to copy.} 12 | 13 | \item{tempdir}{The temporary directory.} 14 | } 15 | \description{ 16 | Copy some file to the test directory that must be present, but are not 17 | passed to the hook as a file argument. 18 | } 19 | \keyword{internal} 20 | -------------------------------------------------------------------------------- /man/diff_requires_run_roxygenize.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/roxygen2.R 3 | \name{diff_requires_run_roxygenize} 4 | \alias{diff_requires_run_roxygenize} 5 | \title{Check if we should run roxygen.} 6 | \usage{ 7 | diff_requires_run_roxygenize(root = ".") 8 | } 9 | \arguments{ 10 | \item{root}{The root of the git repo.} 11 | } 12 | \value{ 13 | A logical vector of length 1. 14 | } 15 | \description{ 16 | This is the case if a new or replaced/removed line contains a roxygen2 17 | comment in a file that is staged. 18 | This function is only exported for use in hook scripts, but it's not intended 19 | to be called by the end-user directly. 20 | } 21 | \examples{ 22 | \dontrun{ 23 | diff_requires_run_roxygenize() 24 | } 25 | } 26 | \seealso{ 27 | Other hook script helpers: 28 | \code{\link{dirs_R.cache}()}, 29 | \code{\link{may_require_permanent_cache}()}, 30 | \code{\link{precommit_docopt}()}, 31 | \code{\link{robust_purl}()}, 32 | \code{\link{roxygen_assert_additional_dependencies}()}, 33 | \code{\link{roxygenize_with_cache}()} 34 | } 35 | \concept{hook script helpers} 36 | \keyword{internal} 37 | -------------------------------------------------------------------------------- /man/dirs_R.cache.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/utils.R 3 | \name{dirs_R.cache} 4 | \alias{dirs_R.cache} 5 | \title{Create the path to the precommit R.cache cache} 6 | \usage{ 7 | dirs_R.cache(hook_id) 8 | } 9 | \arguments{ 10 | \item{hook_id}{The id of the hook for which we want the relative cache 11 | directory.} 12 | } 13 | \description{ 14 | This function is only exported for use in hook scripts, but it's not intended 15 | to be called by the end-user directly. 16 | } 17 | \seealso{ 18 | Other hook script helpers: 19 | \code{\link{diff_requires_run_roxygenize}()}, 20 | \code{\link{may_require_permanent_cache}()}, 21 | \code{\link{precommit_docopt}()}, 22 | \code{\link{robust_purl}()}, 23 | \code{\link{roxygen_assert_additional_dependencies}()}, 24 | \code{\link{roxygenize_with_cache}()} 25 | } 26 | \concept{hook script helpers} 27 | \keyword{internal} 28 | -------------------------------------------------------------------------------- /man/empty_on_cran.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/testing.R 3 | \name{empty_on_cran} 4 | \alias{empty_on_cran} 5 | \title{Reduce a check to the empty string on CRAN} 6 | \usage{ 7 | empty_on_cran(string) 8 | } 9 | \arguments{ 10 | \item{string}{The string to test.} 11 | } 12 | \description{ 13 | In testing, we don't want to rely on exact error messages or stdout on CRAN 14 | for third-party packages, since this would complicate the release process for 15 | dependencies of \{precommit\}. 16 | } 17 | \keyword{internal} 18 | -------------------------------------------------------------------------------- /man/ensure_named.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/utils.R 3 | \name{ensure_named} 4 | \alias{ensure_named} 5 | \title{Name the input} 6 | \usage{ 7 | ensure_named(x, candidate_name = NULL, f = identity) 8 | } 9 | \arguments{ 10 | \item{x}{A vector.} 11 | 12 | \item{f}{How to transform the input \code{x} into a name.} 13 | } 14 | \description{ 15 | Name the input 16 | } 17 | \keyword{internal} 18 | -------------------------------------------------------------------------------- /man/fallback_doc.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/precommit-package.R 3 | \name{fallback_doc} 4 | \alias{fallback_doc} 5 | \title{Fallback doc} 6 | \arguments{ 7 | \item{root}{The path to the root directory of your project.} 8 | } 9 | \description{ 10 | Fallback doc 11 | } 12 | \keyword{internal} 13 | -------------------------------------------------------------------------------- /man/figures/pre-commit-meme.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lorenzwalthert/precommit/1ffd55b30522dcfe4750bd9e6c868361b2c0ef47/man/figures/pre-commit-meme.jpeg -------------------------------------------------------------------------------- /man/figures/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lorenzwalthert/precommit/1ffd55b30522dcfe4750bd9e6c868361b2c0ef47/man/figures/screenshot.png -------------------------------------------------------------------------------- /man/generate_uninstalled_pkg_name.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/testing.R 3 | \name{generate_uninstalled_pkg_name} 4 | \alias{generate_uninstalled_pkg_name} 5 | \title{Generate a random package name that is not installed} 6 | \usage{ 7 | generate_uninstalled_pkg_name(n = 10) 8 | } 9 | \arguments{ 10 | \item{n}{The number of times we should try} 11 | } 12 | \description{ 13 | Generate a random package name that is not installed 14 | } 15 | \keyword{internal} 16 | -------------------------------------------------------------------------------- /man/get_os.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/exec.R 3 | \name{get_os} 4 | \alias{get_os} 5 | \title{Get the operating System} 6 | \usage{ 7 | get_os() 8 | } 9 | \description{ 10 | Can't mock base package (either because it's an \code{.Internal} or for some other 11 | reason). 12 | } 13 | \keyword{internal} 14 | -------------------------------------------------------------------------------- /man/git_init.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/utils.R 3 | \name{git_init} 4 | \alias{git_init} 5 | \title{Initiate git and configure it} 6 | \usage{ 7 | git_init(path = ".") 8 | } 9 | \arguments{ 10 | \item{path}{The root of the repo.} 11 | } 12 | \description{ 13 | In particular, to avoid CRAN errors 14 | \href{https://github.com/lorenzwalthert/precommit/issues/320}{lorenzwalthert/precommit#320}. 15 | } 16 | \keyword{internal} 17 | -------------------------------------------------------------------------------- /man/hook_state_assert.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/testing.R 3 | \name{hook_state_assert} 4 | \alias{hook_state_assert} 5 | \title{Check if the hook produced what you want} 6 | \usage{ 7 | hook_state_assert( 8 | path_candidate, 9 | tempdir, 10 | path_candidate_temp, 11 | file_transformer, 12 | path_stdout, 13 | path_stderr, 14 | expect_success, 15 | std_err, 16 | std_out, 17 | exit_status 18 | ) 19 | } 20 | \description{ 21 | Match the resulting state after the hook run with the expected state 22 | } 23 | \keyword{internal} 24 | -------------------------------------------------------------------------------- /man/hook_state_create.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/testing.R 3 | \name{hook_state_create} 4 | \alias{hook_state_create} 5 | \title{Create a hook state} 6 | \usage{ 7 | hook_state_create( 8 | tempdir, 9 | path_candidate_temp, 10 | path_executable, 11 | cmd_args, 12 | path_stdout, 13 | path_stderr, 14 | env 15 | ) 16 | } 17 | \description{ 18 | Runs the hook script to create a hook state, i.e. exit code, transformed 19 | files and emitted messages of the hook run. 20 | } 21 | \keyword{internal} 22 | -------------------------------------------------------------------------------- /man/install_impl.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/install.R 3 | \name{install_impl} 4 | \alias{install_impl} 5 | \title{Install pre-commit on your system with conda} 6 | \usage{ 7 | install_impl() 8 | } 9 | \description{ 10 | Install pre-commit on your system with conda 11 | } 12 | \keyword{internal} 13 | -------------------------------------------------------------------------------- /man/install_precommit.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/install.R 3 | \name{install_precommit} 4 | \alias{install_precommit} 5 | \title{Install pre-commit on your system} 6 | \usage{ 7 | install_precommit(force = FALSE) 8 | } 9 | \arguments{ 10 | \item{force}{Whether or not to force a re-installation.} 11 | } 12 | \value{ 13 | The path to the pre-commit executable (invisibly). 14 | } 15 | \description{ 16 | This installs pre-commit in the conda environment r-precommit. It 17 | will be available to use across different git repositories. To update, 18 | refer to \code{\link[=update_precommit]{update_precommit()}}. 19 | } 20 | \examples{ 21 | \dontrun{ 22 | install_precommit() 23 | } 24 | } 25 | \seealso{ 26 | Other executable managers: 27 | \code{\link{uninstall_precommit}()}, 28 | \code{\link{update_precommit}()}, 29 | \code{\link{version_precommit}()} 30 | } 31 | \concept{executable managers} 32 | -------------------------------------------------------------------------------- /man/local_test_setup.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/testing.R 3 | \name{local_test_setup} 4 | \alias{local_test_setup} 5 | \title{Testing utilities} 6 | \usage{ 7 | local_test_setup( 8 | git = TRUE, 9 | use_precommit = FALSE, 10 | package = FALSE, 11 | quiet = TRUE, 12 | autoupdate = FALSE, 13 | ..., 14 | .local_envir = parent.frame() 15 | ) 16 | } 17 | \arguments{ 18 | \item{git}{Whether or not to init git in the local directory.} 19 | 20 | \item{use_precommit}{Whether or not to \code{\link[=use_precommit]{use_precommit()}}.} 21 | 22 | \item{autoupdate}{Whether or not to run \code{\link[=autoupdate]{autoupdate()}} as part of this 23 | fixture.} 24 | 25 | \item{.local_envir}{\verb{[environment]}\cr The environment to use for scoping.} 26 | } 27 | \description{ 28 | Similar to the \code{local_()} family from \code{{withr}}, this function creates a 29 | temporary directory and optionally initiates git and pre-commit in it. 30 | } 31 | \keyword{internal} 32 | -------------------------------------------------------------------------------- /man/may_require_permanent_cache.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/cache.R 3 | \name{may_require_permanent_cache} 4 | \alias{may_require_permanent_cache} 5 | \title{Issue a warning if \{R.cache\} uses temporary cache only} 6 | \usage{ 7 | may_require_permanent_cache(temp_cache_is_enough = FALSE) 8 | } 9 | \arguments{ 10 | \item{temp_cache_is_enough}{ignored.} 11 | } 12 | \description{ 13 | This function used to check if a permanent cache was available and issue a 14 | warning if not, but since \{R.cache\} version \verb{0.15.0} (release date 15 | 2021-04-27), a permanent directory will be used automatically, so this check 16 | if redundant. the function is kept in the package for compatibility, i.e. 17 | if someone updates the R package \{precommit\} but not the hook revisions. 18 | } 19 | \seealso{ 20 | Other hook script helpers: 21 | \code{\link{diff_requires_run_roxygenize}()}, 22 | \code{\link{dirs_R.cache}()}, 23 | \code{\link{precommit_docopt}()}, 24 | \code{\link{robust_purl}()}, 25 | \code{\link{roxygen_assert_additional_dependencies}()}, 26 | \code{\link{roxygenize_with_cache}()} 27 | } 28 | \concept{hook script helpers} 29 | \keyword{internal} 30 | -------------------------------------------------------------------------------- /man/not_conda.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/testing.R 3 | \name{not_conda} 4 | \alias{not_conda} 5 | \title{The testing environment does not use a conda environment if the env variable 6 | PRECOMMIT_INSTALLATION_METHOD is not 'conda'.} 7 | \usage{ 8 | not_conda() 9 | } 10 | \description{ 11 | The testing environment does not use a conda environment if the env variable 12 | PRECOMMIT_INSTALLATION_METHOD is not 'conda'. 13 | } 14 | \keyword{internal} 15 | -------------------------------------------------------------------------------- /man/open_config.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/open.R 3 | \name{open_config} 4 | \alias{open_config} 5 | \alias{open_wordlist} 6 | \title{Open pre-commit related files} 7 | \usage{ 8 | open_config(root = here::here()) 9 | 10 | open_wordlist(root = here::here()) 11 | } 12 | \arguments{ 13 | \item{root}{The path to the root directory of your project.} 14 | } 15 | \value{ 16 | \code{NULL} (invisibly). The function is called for its side effects. 17 | } 18 | \description{ 19 | Open pre-commit related files 20 | } 21 | \details{ 22 | \itemize{ 23 | \item \code{open_config()}: opens the pre-commit config file. 24 | \item \code{open_wordlist()}: opens the the WORDLIST file for the check-spelling hook 25 | in inst/WORDLIST. 26 | } 27 | } 28 | \examples{ 29 | \dontrun{ 30 | open_config() 31 | } 32 | \dontrun{ 33 | open_wordlist() 34 | } 35 | } 36 | \seealso{ 37 | Other helpers: 38 | \code{\link{use_precommit}()} 39 | } 40 | \concept{helpers} 41 | -------------------------------------------------------------------------------- /man/path_derive_precommit_exec.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/exec.R 3 | \name{path_derive_precommit_exec} 4 | \alias{path_derive_precommit_exec} 5 | \title{Derive the path to the pre-commit executable} 6 | \usage{ 7 | path_derive_precommit_exec() 8 | } 9 | \description{ 10 | Returns "" if search was not successful, the path otherwise. 11 | } 12 | \section{Heuristic}{ 13 | 14 | \itemize{ 15 | \item First check if there is an executable on the \verb{$PATH} using 16 | \code{\link[=path_derive_precommit_exec_path]{path_derive_precommit_exec_path()}} 17 | \item Search os dependent for other possible locations for common installation 18 | methods. 19 | \item If not, check if we can find one in a conda environment with 20 | \code{\link[=path_derive_precommit_exec_conda]{path_derive_precommit_exec_conda()}}. Do this last as it's the slowest. 21 | } 22 | } 23 | 24 | \keyword{internal} 25 | -------------------------------------------------------------------------------- /man/path_derive_precommit_exec_conda.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/exec.R 3 | \name{path_derive_precommit_exec_conda} 4 | \alias{path_derive_precommit_exec_conda} 5 | \title{Derive the path to the conda pre-commit executable} 6 | \usage{ 7 | path_derive_precommit_exec_conda() 8 | } 9 | \description{ 10 | Only checks the conda env \code{r-precommit}. 11 | If we can't find the executable, the empty string is returned. 12 | } 13 | \keyword{internal} 14 | -------------------------------------------------------------------------------- /man/path_derive_precommit_exec_impl.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/exec.R 3 | \name{path_derive_precommit_exec_impl} 4 | \alias{path_derive_precommit_exec_impl} 5 | \title{Find an executable} 6 | \usage{ 7 | path_derive_precommit_exec_impl(candidate) 8 | } 9 | \arguments{ 10 | \item{candidate}{A directory to check for the pre-commit executable. The 11 | directory may also not exist.} 12 | } 13 | \description{ 14 | Evaluates if the pre-commit executable exists in one or more candidate 15 | locations. If so, return one, else return the empty string 16 | } 17 | \keyword{internal} 18 | -------------------------------------------------------------------------------- /man/path_derive_precommit_exec_path.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/exec.R 3 | \name{path_derive_precommit_exec_path} 4 | \alias{path_derive_precommit_exec_path} 5 | \title{Derive the pre-commit executable from the path} 6 | \usage{ 7 | path_derive_precommit_exec_path() 8 | } 9 | \description{ 10 | Tries to derive the \code{pre-commit} executable from the \verb{$PATH}. 11 | Returns \code{""} if no executable is found. 12 | } 13 | \keyword{internal} 14 | -------------------------------------------------------------------------------- /man/path_derive_precommit_exec_win_python3plus_base.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/exec.R 3 | \name{path_derive_precommit_exec_win_python3plus_base} 4 | \alias{path_derive_precommit_exec_win_python3plus_base} 5 | \title{Where are executables on Windows for Python 3 and higher?} 6 | \usage{ 7 | path_derive_precommit_exec_win_python3plus_base() 8 | } 9 | \description{ 10 | Heuristic to determine the directory where the pre-commit executable on 11 | Windows lives for Python versions 3 and above. 12 | } 13 | \keyword{internal} 14 | -------------------------------------------------------------------------------- /man/path_precommit_exec.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/exec.R 3 | \name{path_precommit_exec} 4 | \alias{path_precommit_exec} 5 | \alias{path_pre_commit_exec} 6 | \title{Locate the pre-commit executable} 7 | \usage{ 8 | path_precommit_exec(check_if_exists = TRUE) 9 | 10 | path_pre_commit_exec(check_if_exists = TRUE) 11 | } 12 | \arguments{ 13 | \item{check_if_exists}{Whether or not to make sure the returned path also 14 | exists.} 15 | } 16 | \value{ 17 | A character vector of length one with the path to the pre-commit executable. 18 | } 19 | \description{ 20 | \code{\link[=path_precommit_exec]{path_precommit_exec()}} simply reads the R option \code{precommit.executable}, 21 | \code{\link[=path_pre_commit_exec]{path_pre_commit_exec()}} is the old spelling and deprecated. 22 | } 23 | \examples{ 24 | \dontrun{ 25 | path_precommit_exec() 26 | } 27 | \dontrun{ 28 | path_pre_commit_exec() 29 | } 30 | } 31 | \seealso{ 32 | \code{\link[=path_derive_precommit_exec]{path_derive_precommit_exec()}} for the heuristic to derive it from scratch. 33 | } 34 | -------------------------------------------------------------------------------- /man/precommit-package.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/precommit-package.R 3 | \docType{package} 4 | \name{precommit-package} 5 | \alias{precommit} 6 | \alias{precommit-package} 7 | \title{precommit: Pre-Commit Hooks} 8 | \description{ 9 | Useful git hooks for R building on top of the multi-language framework 'pre-commit' for hook management. This package provides git hooks for common tasks like formatting files with 'styler' or spell checking as well as wrapper functions to access the 'pre-commit' executable. 10 | } 11 | \seealso{ 12 | Useful links: 13 | \itemize{ 14 | \item \url{https://lorenzwalthert.github.io/precommit/} 15 | \item \url{https://github.com/lorenzwalthert/precommit} 16 | } 17 | 18 | } 19 | \keyword{internal} 20 | -------------------------------------------------------------------------------- /man/precommit_docopt.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/utils.R 3 | \name{precommit_docopt} 4 | \alias{precommit_docopt} 5 | \title{Provide a singular interface for hook calls to docopt} 6 | \usage{ 7 | precommit_docopt(doc, args = commandArgs(trailingOnly = TRUE), ...) 8 | } 9 | \arguments{ 10 | \item{doc}{\code{character} vector with command line specification.} 11 | 12 | \item{args}{\code{character} vector of command line arguments. 13 | Defaults to \code{commandArgs(trailingOnly=TRUE)}.} 14 | 15 | \item{...}{Additional parameters passed to \code{docopt}.} 16 | } 17 | \description{ 18 | docopt provides different processing for a single string 19 | than an array/vector. As \verb{"string"`` and }c("string")` 20 | are semantically equivalent in R, this can create problems 21 | when a single parameter is provided. Thus, this function 22 | wraps docopt to ensure that the args will always be 23 | interpreted as a vector. 24 | } 25 | \details{ 26 | This function is only exported for use in hook scripts, but it's not intended 27 | to be called by the end-user directly. 28 | } 29 | \seealso{ 30 | Other hook script helpers: 31 | \code{\link{diff_requires_run_roxygenize}()}, 32 | \code{\link{dirs_R.cache}()}, 33 | \code{\link{may_require_permanent_cache}()}, 34 | \code{\link{robust_purl}()}, 35 | \code{\link{roxygen_assert_additional_dependencies}()}, 36 | \code{\link{roxygenize_with_cache}()} 37 | } 38 | \concept{hook script helpers} 39 | \keyword{internal} 40 | -------------------------------------------------------------------------------- /man/precommit_executable_file.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/exec.R 3 | \name{precommit_executable_file} 4 | \alias{precommit_executable_file} 5 | \title{The name of the executable file} 6 | \usage{ 7 | precommit_executable_file() 8 | } 9 | \description{ 10 | This is platform dependent. 11 | } 12 | \keyword{internal} 13 | -------------------------------------------------------------------------------- /man/release_complete.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/release.R 3 | \name{release_complete} 4 | \alias{release_complete} 5 | \title{Complete the release} 6 | \usage{ 7 | release_complete(ask = TRUE, is_cran = ask, tag = NULL) 8 | } 9 | \arguments{ 10 | \item{tag}{The tag to push. \code{NULL} will derive the tag from \code{DESCRIPTION}.} 11 | } 12 | \description{ 13 | Bumps the version to devel. 14 | } 15 | \keyword{internal} 16 | -------------------------------------------------------------------------------- /man/release_gh.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/release.R 3 | \name{release_gh} 4 | \alias{release_gh} 5 | \title{Create a new release on GitHub} 6 | \usage{ 7 | release_gh(bump = "dev", is_cran = bump != "dev") 8 | } 9 | \arguments{ 10 | \item{bump}{The bump increment, either "dev", "patch", "minor" or "major".} 11 | 12 | \item{is_cran}{Is this release a CRAN release?} 13 | } 14 | \description{ 15 | This must be done \strong{before} a CRAN release. 16 | } 17 | \details{ 18 | This function does the following: 19 | \itemize{ 20 | \item bump description. 21 | \item update default config in inst/ 22 | \item commit 23 | \item git tag 24 | \item run \code{inst/hooks/local/consistent-release-tag.R} hook with --release-mode (passing args to hooks 25 | not possible interactively, hence we run in advance). 26 | \item commit and push with skipping \code{inst/hooks/local/consistent-release-tag.R}. 27 | \item autoupdate own config file 28 | \item bump description with dev 29 | \item commit and push DESCRIPTION and .pre-commit-config.yaml 30 | } 31 | } 32 | \section{CRAN release}{ 33 | 34 | If \code{is_cran} is \code{TRUE}, the workflow is changed slightly: 35 | \itemize{ 36 | \item push to release branch, not main. 37 | \item doesn't run \code{\link[=release_complete]{release_complete()}}. This must be done manually after accepted 38 | on CRAN. 39 | } 40 | } 41 | 42 | \keyword{internal} 43 | -------------------------------------------------------------------------------- /man/rev_read.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/utils.R 3 | \name{rev_read} 4 | \alias{rev_read} 5 | \title{Read the refs corresponding to a hooks repo} 6 | \usage{ 7 | rev_read(path = ".pre-commit-config.yaml", repo = hooks_repo) 8 | } 9 | \description{ 10 | Read the refs corresponding to a hooks repo 11 | } 12 | \keyword{internal} 13 | -------------------------------------------------------------------------------- /man/robust_purl.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/purl.R 3 | \name{robust_purl} 4 | \alias{robust_purl} 5 | \title{Run \code{\link[knitr:knit]{knitr::purl()}}, setting the chunk option \code{purl} to \code{TRUE} if it's not 6 | already set to a literal value.} 7 | \usage{ 8 | robust_purl(path) 9 | } 10 | \arguments{ 11 | \item{path}{The path to the file you want to \code{\link[knitr:knit]{knitr::purl()}}.} 12 | } 13 | \description{ 14 | This function is only exported for use in hook scripts, but it's not intended 15 | to be called by the end-user directly. 16 | } 17 | \seealso{ 18 | Other hook script helpers: 19 | \code{\link{diff_requires_run_roxygenize}()}, 20 | \code{\link{dirs_R.cache}()}, 21 | \code{\link{may_require_permanent_cache}()}, 22 | \code{\link{precommit_docopt}()}, 23 | \code{\link{roxygen_assert_additional_dependencies}()}, 24 | \code{\link{roxygenize_with_cache}()} 25 | } 26 | \concept{hook script helpers} 27 | \keyword{internal} 28 | -------------------------------------------------------------------------------- /man/roxygen_assert_additional_dependencies.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/roxygen2.R 3 | \name{roxygen_assert_additional_dependencies} 4 | \alias{roxygen_assert_additional_dependencies} 5 | \title{Assert if all dependencies are installed} 6 | \usage{ 7 | roxygen_assert_additional_dependencies() 8 | } 9 | \description{ 10 | This function is only exported for use in hook scripts, but it's not intended 11 | to be called by the end-user directly. 12 | } 13 | \seealso{ 14 | Other hook script helpers: 15 | \code{\link{diff_requires_run_roxygenize}()}, 16 | \code{\link{dirs_R.cache}()}, 17 | \code{\link{may_require_permanent_cache}()}, 18 | \code{\link{precommit_docopt}()}, 19 | \code{\link{robust_purl}()}, 20 | \code{\link{roxygenize_with_cache}()} 21 | } 22 | \concept{hook script helpers} 23 | \keyword{internal} 24 | -------------------------------------------------------------------------------- /man/roxygenize_with_cache.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/roxygen2.R 3 | \name{roxygenize_with_cache} 4 | \alias{roxygenize_with_cache} 5 | \title{Roxygen and add a cache entry} 6 | \usage{ 7 | roxygenize_with_cache(key, dirs) 8 | } 9 | \arguments{ 10 | \item{key}{An optional object from which a hexadecimal hash 11 | code will be generated and appended to the filename.} 12 | 13 | \item{dirs}{A \code{\link[base]{character}} \code{\link[base]{vector}} constituting the path to the 14 | cache subdirectory (of the \emph{cache root directory} 15 | as returned by \code{\link[R.cache]{getCacheRootPath}}()) to be used. 16 | If \code{\link[base]{NULL}}, the path will be the cache root path.} 17 | } 18 | \description{ 19 | This function is only exported for use in hook scripts, but it's not intended 20 | to be called by the end-user directly. 21 | } 22 | \seealso{ 23 | Other hook script helpers: 24 | \code{\link{diff_requires_run_roxygenize}()}, 25 | \code{\link{dirs_R.cache}()}, 26 | \code{\link{may_require_permanent_cache}()}, 27 | \code{\link{precommit_docopt}()}, 28 | \code{\link{robust_purl}()}, 29 | \code{\link{roxygen_assert_additional_dependencies}()} 30 | } 31 | \concept{hook script helpers} 32 | \keyword{internal} 33 | -------------------------------------------------------------------------------- /man/run_test.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/testing.R 3 | \name{run_test} 4 | \alias{run_test} 5 | \title{Run a test} 6 | \usage{ 7 | run_test( 8 | hook_name, 9 | file_name = hook_name, 10 | suffix = ".R", 11 | std_err = NULL, 12 | std_out = NULL, 13 | cmd_args = NULL, 14 | artifacts = NULL, 15 | file_transformer = function(files) files, 16 | env = character(), 17 | expect_success = is.null(std_err), 18 | read_only = FALSE 19 | ) 20 | } 21 | \arguments{ 22 | \item{hook_name}{The name of the hook in \verb{inst/hooks/exported/}, without 23 | file extension.} 24 | 25 | \item{file_name}{The file to test in \verb{tests/in} (without extension). Can be 26 | a named vector of length one where the name is the target location relative 27 | to the temporary location and the value is the source of the file.} 28 | 29 | \item{suffix}{The suffix of \code{file_name}.} 30 | 31 | \item{std_err}{An expected error message. If no error is expected, this 32 | can be \code{NULL}. In that case, the \code{comparator} is applied.} 33 | 34 | \item{std_out}{The expected stdout message. If \code{NULL}, this check is omitted.} 35 | 36 | \item{cmd_args}{More arguments passed to the file. Pre-commit handles it as 37 | described \href{https://pre-commit.com/#arguments-pattern-in-hooks}{here}.} 38 | 39 | \item{artifacts}{Path with artifact files to copy to the temp directory root where 40 | the test is run. If you don't target the root, this can be a named vector 41 | of length one where the name is the target location relative to the 42 | temporary location and the value is the source of the file.} 43 | 44 | \item{file_transformer}{A function that takes the file names as input and is 45 | ran right before the hook script is invoked, returning the path to the 46 | files, potentially modified (if renamed). This can be useful if you need to 47 | make in-place modifications to the file, e.g. to test hooks that operate on 48 | \code{.Rprofile}. You can't have different names for different tests on that 49 | file because it must be called \code{.Rprofile} all the time. And R CMD check 50 | seems to remove hidden files, so we must also rename it. The transformation 51 | is also applied to a temp copy of the reference file before a comparison is 52 | made.} 53 | 54 | \item{env}{The environment variables to set with \code{\link[base:system2]{base::system2()}}.} 55 | 56 | \item{expect_success}{Whether or not an exit code 0 is expected. This can 57 | be derived from \code{std_err}, but sometimes, non-empty stderr does not mean 58 | error, but just a message.} 59 | 60 | \item{read_only}{If \code{TRUE}, then assert that no new files were created. 61 | Additionally, if \code{artifacts} are not \code{NULL}, then assert that hook did not 62 | modify the artifacts.} 63 | } 64 | \description{ 65 | Tests for the executables used as pre-commit hooks via \code{entrypoint} in 66 | \code{.pre-commit-config.yaml}. Set's the env variable \code{R_PRECOMMIT_HOOK_ENV} to 67 | when running. 68 | } 69 | \details{ 70 | Two potential outcomes of a hooks are pass or fail. This is reflected on the 71 | level of the executable: Fail means the executable fails or the file is 72 | changed. Pass means the executable succeeds and the file is unchanged. 73 | We check if the executable passes as follows: 74 | \itemize{ 75 | \item If we expect success (by setting \code{std_err} to \code{NULL}), we make sure 76 | nothing was written to sterr and the file content does not change. 77 | \item If we expect failure, it can be due to changed file or due to failed 78 | executable. To check for failed executable, we set \code{std_err} to 79 | the message we expect. To check changed file content, we set \code{std_err} to 80 | \code{NA}. 81 | } 82 | } 83 | \keyword{internal} 84 | -------------------------------------------------------------------------------- /man/run_test_impl.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/testing.R 3 | \name{run_test_impl} 4 | \alias{run_test_impl} 5 | \title{Implement a test run} 6 | \usage{ 7 | run_test_impl( 8 | path_executable, 9 | path_candidate, 10 | std_err, 11 | std_out, 12 | cmd_args, 13 | artifacts, 14 | file_transformer, 15 | env, 16 | expect_success, 17 | read_only 18 | ) 19 | } 20 | \arguments{ 21 | \item{path_executable}{The path to the executable bash script.} 22 | 23 | \item{path_candidate}{The path to a file that should be modified by the 24 | executable.} 25 | 26 | \item{std_err}{An expected error message. If no error is expected, this 27 | can be \code{NULL}. In that case, the \code{comparator} is applied.} 28 | 29 | \item{std_out}{The expected stdout message. If \code{NULL}, this check is omitted.} 30 | 31 | \item{cmd_args}{More arguments passed to the file. Pre-commit handles it as 32 | described \href{https://pre-commit.com/#arguments-pattern-in-hooks}{here}.} 33 | 34 | \item{artifacts}{Path with artifact files to copy to the temp directory root where 35 | the test is run. If you don't target the root, this can be a named vector 36 | of length one where the name is the target location relative to the 37 | temporary location and the value is the source of the file.} 38 | 39 | \item{env}{The environment variables to set with \code{\link[base:system2]{base::system2()}}.} 40 | 41 | \item{expect_success}{Whether or not an exit code 0 is expected. This can 42 | be derived from \code{std_err}, but sometimes, non-empty stderr does not mean 43 | error, but just a message.} 44 | 45 | \item{read_only}{If \code{TRUE}, then assert that no new files were created. 46 | Additionally, if \code{artifacts} are not \code{NULL}, then assert that hook did not 47 | modify the artifacts.} 48 | } 49 | \description{ 50 | Implement a test run 51 | } 52 | \keyword{internal} 53 | -------------------------------------------------------------------------------- /man/set_config_source.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/config.R 3 | \name{set_config_source} 4 | \alias{set_config_source} 5 | \title{Set the location to a config file} 6 | \usage{ 7 | set_config_source(config_source, root, verbose = TRUE) 8 | } 9 | \description{ 10 | If a remote location is specified, the file is downloaded to a temporary 11 | location and the path to this location is returned. If \code{NULL}, we'll resort 12 | to a default config. We'll perform some checks on the existence of the file 13 | too. 14 | } 15 | \keyword{internal} 16 | -------------------------------------------------------------------------------- /man/snippet_generate.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/setup.R 3 | \name{snippet_generate} 4 | \alias{snippet_generate} 5 | \title{Generate code snippets} 6 | \usage{ 7 | snippet_generate( 8 | snippet = "additional-deps-roxygenize", 9 | open = rstudioapi::isAvailable(), 10 | root = here::here() 11 | ) 12 | } 13 | \arguments{ 14 | \item{snippet}{Name of the snippet.} 15 | 16 | \item{open}{Whether or not to open the .pre-commit-config.yaml. The default 17 | is \code{TRUE} when working in RStudio. Otherwise, we recommend manually opening 18 | the file.} 19 | 20 | \item{root}{The path to the root directory of your project.} 21 | } 22 | \description{ 23 | Utility function to generate code snippets: 24 | } 25 | \details{ 26 | Currently supported: 27 | \itemize{ 28 | \item additional-deps-roxygenize: Code to paste into 29 | \code{.pre-commit-config.yaml} for the additional dependencies required by 30 | the roxygenize hook. 31 | \item additional-deps-lintr: Code to paste into 32 | \code{.pre-commit-config.yaml} for the additional dependencies required by 33 | the lintr hook if you use \code{--load-package}. 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /man/uninstall_precommit.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/install.R 3 | \name{uninstall_precommit} 4 | \alias{uninstall_precommit} 5 | \title{Uninstall pre-commit} 6 | \usage{ 7 | uninstall_precommit(scope = "repo", ask = "user", root = here::here()) 8 | } 9 | \arguments{ 10 | \item{scope}{Either "repo" or "user". "repo" removes pre-commit from your 11 | project, but you will be able to use it in other projects. With "user", 12 | you remove the pre-commit executable in the virtual python environment 13 | r-precommit so it won't be available in any project. When you want to do 14 | the latter, you should first do the former.} 15 | 16 | \item{ask}{Either "user", "repo" or "none" to determine in which case 17 | a prompt should show up to let the user confirm his action.} 18 | 19 | \item{root}{The path to the root directory of your project.} 20 | } 21 | \value{ 22 | \code{NULL} (invisibly). The function is called for its side effects. 23 | } 24 | \description{ 25 | Remove pre-commit from a repo or from your system. 26 | } 27 | \examples{ 28 | \dontrun{ 29 | uninstall_precommit() 30 | } 31 | } 32 | \seealso{ 33 | Other executable managers: 34 | \code{\link{install_precommit}()}, 35 | \code{\link{update_precommit}()}, 36 | \code{\link{version_precommit}()} 37 | } 38 | \concept{executable managers} 39 | -------------------------------------------------------------------------------- /man/update_impl.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/install.R 3 | \name{update_impl} 4 | \alias{update_impl} 5 | \title{Updates pre-commit on your system with conda} 6 | \usage{ 7 | update_impl() 8 | } 9 | \description{ 10 | Updates pre-commit on your system with conda 11 | } 12 | \keyword{internal} 13 | -------------------------------------------------------------------------------- /man/update_precommit.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/update.R 3 | \name{update_precommit} 4 | \alias{update_precommit} 5 | \title{Update the pre-commit executable} 6 | \usage{ 7 | update_precommit() 8 | } 9 | \value{ 10 | The exit status of the conda update command (invisible). 11 | } 12 | \description{ 13 | Updates the conda installation of the upstream framework pre-commit. This 14 | does not update the R package \code{{precommit}} and only works if you choose 15 | conda as your installation method. If you have problems updating, we suggest 16 | deleting the conda environment \code{r-precommit} (if you are sure nothing but 17 | pre-commit depend on it) and do a fresh installation with 18 | \code{\link[=install_precommit]{install_precommit()}}. 19 | } 20 | \seealso{ 21 | Other executable managers: 22 | \code{\link{install_precommit}()}, 23 | \code{\link{uninstall_precommit}()}, 24 | \code{\link{version_precommit}()} 25 | } 26 | \concept{executable managers} 27 | -------------------------------------------------------------------------------- /man/update_rev_in_config.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/release.R 3 | \name{update_rev_in_config} 4 | \alias{update_rev_in_config} 5 | \title{Updates the hook version ref of \{precommit\} in a \code{.pre-commit-config} file} 6 | \usage{ 7 | update_rev_in_config(new_version, path = "inst/pre-commit-config.yaml") 8 | } 9 | \arguments{ 10 | \item{new_version}{The version string of the new version.} 11 | 12 | \item{path}{The path to a pre-commit config file.} 13 | } 14 | \description{ 15 | This is useful in the release process because when releasing a new version, 16 | we must make sure the template that is used with \code{precommit::use_precommit()} 17 | is up-to date. Also, after we pushed the release to GitHub, we want to update 18 | the hooks from our own hook repo in the source repo too (we could also do that 19 | with \code{precommit::autoupdate()} though). 20 | } 21 | \keyword{internal} 22 | -------------------------------------------------------------------------------- /man/use_ci.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/setup.R 3 | \name{use_ci} 4 | \alias{use_ci} 5 | \title{Use continuous integration with pre-commit} 6 | \usage{ 7 | use_ci( 8 | ci = getOption("precommit.ci", "native"), 9 | force = FALSE, 10 | open = rstudioapi::isAvailable(), 11 | root = here::here() 12 | ) 13 | } 14 | \arguments{ 15 | \item{ci}{Specifies which continuous integration service to use. See 16 | \code{vignette("ci", package = "precommit")} for details. Defaults to 17 | \code{getOption("precommit.ci", "native")}, which is set to 18 | \code{"native"} on package loading (if unset). \code{"native"} sets up 19 | \href{https://pre-commit.ci}{pre-commit.ci}. Alternatively, \code{"gha"} can be used 20 | to set up \href{https://github.com/features/actions}{GitHub Actions}. Set value 21 | to \code{NA} if you don't want to use a continuous integration.} 22 | 23 | \item{force}{Whether or not to overwrite an existing ci config file (only 24 | relevant for \code{ci = "gha"}).} 25 | 26 | \item{open}{Whether or not to open \href{https://pre-commit.ci}{pre-commit.ci} 27 | (if \code{ci = "native"}). The default is \code{TRUE} when working in RStudio.} 28 | 29 | \item{root}{The path to the root directory of your project.} 30 | } 31 | \description{ 32 | Sets up continuous integration, or prompts the user to do it manually. 33 | } 34 | -------------------------------------------------------------------------------- /man/use_precommit.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/setup.R 3 | \name{use_precommit} 4 | \alias{use_precommit} 5 | \title{Get started with pre-commit} 6 | \usage{ 7 | use_precommit( 8 | config_source = getOption("precommit.config_source"), 9 | force = FALSE, 10 | legacy_hooks = "forbid", 11 | open = rstudioapi::isAvailable(), 12 | install_hooks = TRUE, 13 | ci = getOption("precommit.ci", "native"), 14 | autoupdate = install_hooks, 15 | root = here::here() 16 | ) 17 | } 18 | \arguments{ 19 | \item{config_source}{Path or URL to a \code{.pre-commit-config.yaml}. This 20 | config file will be hard-copied into \code{root}. If \code{NULL}, we check if 21 | \code{root} is a package or project directory using 22 | \code{\link[rprojroot:find_root_file]{rprojroot::find_package_root_file()}}, and resort to an appropriate default 23 | config. See section 'Copying an existing config file'.} 24 | 25 | \item{force}{Whether or not to overwrite an existing ci config file (only 26 | relevant for \code{ci = "gha"}).} 27 | 28 | \item{legacy_hooks}{How to treat hooks already in the repo which are not 29 | managed by pre-commit. "forbid", the default, will cause \code{use_precommit()} 30 | to fail if there are such hooks. "allow" will run these along with 31 | pre-commit. "remove" will delete them.} 32 | 33 | \item{open}{Whether or not to open \code{.pre-commit-config.yaml} after 34 | it's been placed in your repo as well as 35 | \href{https://pre-commit.ci}{pre-commit.ci} (if \code{ci = "native"}). The default is 36 | \code{TRUE} when working in RStudio.} 37 | 38 | \item{install_hooks}{Whether to install environments for all available hooks. 39 | If \code{FALSE}, environments are installed with first commit.} 40 | 41 | \item{ci}{Specifies which continuous integration service to use. See 42 | \code{vignette("ci", package = "precommit")} for details. Defaults to 43 | \code{getOption("precommit.ci", "native")}, which is set to 44 | \code{"native"} on package loading (if unset). \code{"native"} sets up 45 | \href{https://pre-commit.ci}{pre-commit.ci}. Alternatively, \code{"gha"} can be used 46 | to set up \href{https://github.com/features/actions}{GitHub Actions}. Set value 47 | to \code{NA} if you don't want to use a continuous integration.} 48 | 49 | \item{autoupdate}{Whether or not to run \code{\link[=autoupdate]{autoupdate()}} as part of this 50 | function call.} 51 | 52 | \item{root}{The path to the root directory of your project.} 53 | } 54 | \value{ 55 | \code{NULL} (invisibly). The function is called for its side effects. 56 | } 57 | \description{ 58 | This function sets up pre-commit for your git repo. 59 | } 60 | \section{When to call this function?}{ 61 | 62 | \itemize{ 63 | \item You want to add pre-commit support to a git repo which does not have a 64 | \code{.pre-commit-config.yaml}. This involves adding 65 | a pre-commit config file and making sure git will call the hooks before 66 | the next commit. 67 | \item You cloned a repo that has a \code{.pre-commit-config.yaml} already. You need 68 | to make sure git calls the hooks before the next commit. 69 | } 70 | } 71 | 72 | \section{What does the function do?}{ 73 | 74 | \itemize{ 75 | \item Sets up a template \code{.pre-commit-config.yaml}. 76 | \item Autoupdates the template to make sure you get the latest versions of the 77 | hooks. 78 | \item Installs the pre-commit script along with the hook environments with 79 | \verb{$ pre-commit install --install-hooks}. 80 | \item Opens the config file if RStudio is running. 81 | } 82 | } 83 | 84 | \section{Copying an existing config file}{ 85 | 86 | You can use an existing \code{.pre-commit-config.yaml} file when initializing 87 | pre-commit with \code{\link[=use_precommit]{use_precommit()}} using the argument \code{config_source} to 88 | copy an existing config file into your repo. This argument defaults to the R 89 | option \code{precommit.config_source}, so you may want to set this option in 90 | your \code{.Rprofile} for convenience. Note that this is \strong{not} equivalent to the 91 | \code{--config} option in the CLI command \verb{pre-commit install} and similar, 92 | which do \emph{not} copy a config file into a project root (and allow to put it 93 | under version control), but rather link it in some more or less transparent 94 | way. 95 | } 96 | 97 | \examples{ 98 | \dontrun{ 99 | use_precommit() 100 | } 101 | } 102 | \seealso{ 103 | Other helpers: 104 | \code{\link{open_config}()} 105 | } 106 | \concept{helpers} 107 | -------------------------------------------------------------------------------- /man/use_precommit_config.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/config.R 3 | \name{use_precommit_config} 4 | \alias{use_precommit_config} 5 | \title{Initiate a pre-commit config file} 6 | \usage{ 7 | use_precommit_config( 8 | config_source = getOption("precommit.config_source"), 9 | force = FALSE, 10 | open = rstudioapi::isAvailable(), 11 | verbose = FALSE, 12 | root = here::here() 13 | ) 14 | } 15 | \arguments{ 16 | \item{config_source}{Path or URL to a \code{.pre-commit-config.yaml}. This 17 | config file will be hard-copied into \code{root}. If \code{NULL}, we check if 18 | \code{root} is a package or project directory using 19 | \code{\link[rprojroot:find_root_file]{rprojroot::find_package_root_file()}}, and resort to an appropriate default 20 | config. See section 'Copying an existing config file'.} 21 | 22 | \item{force}{Whether to replace an existing config file.} 23 | 24 | \item{open}{Whether or not to open the .pre-commit-config.yaml after 25 | it's been placed in your repo. The default is \code{TRUE} when working in 26 | RStudio. Otherwise, we recommend manually inspecting the file.} 27 | 28 | \item{verbose}{Whether or not to communicate what's happening.} 29 | 30 | \item{root}{The path to the root directory of your project.} 31 | } 32 | \value{ 33 | Character vector of length one with the path to the config file used. 34 | } 35 | \description{ 36 | Initiate a pre-commit config file 37 | } 38 | \section{Copying an existing config file}{ 39 | 40 | You can use an existing \code{.pre-commit-config.yaml} file when initializing 41 | pre-commit with \code{\link[=use_precommit]{use_precommit()}} using the argument \code{config_source} to 42 | copy an existing config file into your repo. This argument defaults to the R 43 | option \code{precommit.config_source}, so you may want to set this option in 44 | your \code{.Rprofile} for convenience. Note that this is \strong{not} equivalent to the 45 | \code{--config} option in the CLI command \verb{pre-commit install} and similar, 46 | which do \emph{not} copy a config file into a project root (and allow to put it 47 | under version control), but rather link it in some more or less transparent 48 | way. 49 | } 50 | 51 | \examples{ 52 | \dontrun{ 53 | use_precommit_config() 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /man/version_precommit.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/update.R 3 | \name{version_precommit} 4 | \alias{version_precommit} 5 | \title{Retrieve the version of the pre-commit executable used} 6 | \usage{ 7 | version_precommit() 8 | } 9 | \description{ 10 | Retrieves the version of the pre-commit executable used. 11 | } 12 | \seealso{ 13 | Other executable managers: 14 | \code{\link{install_precommit}()}, 15 | \code{\link{uninstall_precommit}()}, 16 | \code{\link{update_precommit}()} 17 | } 18 | \concept{executable managers} 19 | -------------------------------------------------------------------------------- /precommit.Rproj: -------------------------------------------------------------------------------- 1 | Version: 1.0 2 | ProjectId: 3e63e6ad-9f4f-48c7-9a5f-2cbad1d4915d 3 | 4 | RestoreWorkspace: Default 5 | SaveWorkspace: Default 6 | AlwaysSaveHistory: Default 7 | 8 | EnableCodeIndexing: Yes 9 | UseSpacesForTab: Yes 10 | NumSpacesForTab: 2 11 | Encoding: UTF-8 12 | 13 | RnwWeave: knitr 14 | LaTeX: pdfLaTeX 15 | 16 | BuildType: Package 17 | PackageUseDevtools: Yes 18 | PackageInstallArgs: --no-multiarch --with-keep.source 19 | PackageRoxygenize: rd,collate,namespace 20 | -------------------------------------------------------------------------------- /renv/.gitignore: -------------------------------------------------------------------------------- 1 | cellar/ 2 | library/ 3 | local/ 4 | lock/ 5 | python/ 6 | sandbox/ 7 | staging/ 8 | -------------------------------------------------------------------------------- /renv/settings.dcf: -------------------------------------------------------------------------------- 1 | external.libraries: 2 | ignored.packages: 3 | package.dependency.fields: Imports, Depends, LinkingTo 4 | r.version: 5 | snapshot.type: implicit 6 | use.cache: TRUE 7 | vcs.ignore.library: TRUE 8 | vcs.ignore.local: TRUE 9 | -------------------------------------------------------------------------------- /renv/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "bioconductor.version": null, 3 | "external.libraries": [], 4 | "ignored.packages": [], 5 | "package.dependency.fields": [ 6 | "Imports", 7 | "Depends", 8 | "LinkingTo" 9 | ], 10 | "r.version": [], 11 | "snapshot.type": "implicit", 12 | "use.cache": true, 13 | "vcs.ignore.cellar": true, 14 | "vcs.ignore.library": true, 15 | "vcs.ignore.local": true, 16 | "vcs.manage.ignores": true 17 | } 18 | -------------------------------------------------------------------------------- /revdep/.gitignore: -------------------------------------------------------------------------------- 1 | *.html 2 | checks 3 | checks.noindex 4 | cloud.noindex 5 | data.sqlite 6 | library 7 | library.noindex 8 | -------------------------------------------------------------------------------- /revdep/README.md: -------------------------------------------------------------------------------- 1 | # Platform 2 | 3 | |field |value | 4 | |:--------|:------------------------------------------------------------------------------------------| 5 | |version |R version 4.3.0 (2023-04-21) | 6 | |os |macOS 14.2.1 | 7 | |system |aarch64, darwin20 | 8 | |ui |RStudio | 9 | |language |(EN) | 10 | |collate |en_US.UTF-8 | 11 | |ctype |en_US.UTF-8 | 12 | |tz |Europe/Zurich | 13 | |date |2024-01-21 | 14 | |rstudio |2023.12.0+369 Ocean Storm (desktop) | 15 | |pandoc |3.1.1 @ /Applications/RStudio.app/Contents/Resources/app/quarto/bin/tools/ (via rmarkdown) | 16 | 17 | # Dependencies 18 | 19 | |package |old |new |Δ | 20 | |:---------|:-----|:------|:--| 21 | |precommit |0.3.2 |0.4.0 |* | 22 | |cli |NA |3.6.2 |* | 23 | |digest |NA |0.6.34 |* | 24 | |glue |NA |1.7.0 |* | 25 | |lifecycle |NA |1.0.4 |* | 26 | |purrr |NA |1.0.2 |* | 27 | |R.utils |NA |2.12.3 |* | 28 | |rlang |NA |1.1.3 |* | 29 | |rprojroot |NA |2.0.4 |* | 30 | |vctrs |NA |0.6.5 |* | 31 | |withr |NA |3.0.0 |* | 32 | |yaml |NA |2.3.8 |* | 33 | 34 | # Revdeps 35 | -------------------------------------------------------------------------------- /revdep/cran.md: -------------------------------------------------------------------------------- 1 | ## revdepcheck results 2 | 3 | We checked 1 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 | -------------------------------------------------------------------------------- /revdep/failures.md: -------------------------------------------------------------------------------- 1 | *Wow, no problems at all. :)* 2 | -------------------------------------------------------------------------------- /revdep/problems.md: -------------------------------------------------------------------------------- 1 | *Wow, no problems at all. :)* 2 | -------------------------------------------------------------------------------- /tests/testthat.R: -------------------------------------------------------------------------------- 1 | library(testthat) 2 | library(precommit) 3 | test_check("precommit") 4 | -------------------------------------------------------------------------------- /tests/testthat/in/DESCRIPTION: -------------------------------------------------------------------------------- 1 | Package: prec3 2 | Title: Pre commit hooks 3 | Version: 1.0.0 4 | Author: Lorenz Walthert 5 | Maintainer: Lorenz Walthert 6 | Imports: 7 | artific, 8 | bliblablupp 9 | Remotes: 10 | ropenscilabs/tic 11 | Encoding: UTF-8 12 | URL: https://example.com 13 | -------------------------------------------------------------------------------- /tests/testthat/in/DESCRIPTION-no-deps.dcf: -------------------------------------------------------------------------------- 1 | Package: prec3 2 | Title: Pre commit hooks 3 | Version: 1.0.0 4 | Author: Lorenz Walthert 5 | Maintainer: Lorenz Walthert 6 | Encoding: UTF-8 7 | -------------------------------------------------------------------------------- /tests/testthat/in/README.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | output: github_document 3 | --- 4 | 5 | # This is a readme 6 | 7 | ```{r} 8 | knitr::opts_chunk$set( 9 | fig.path = "man/figures/", 10 | eval = FALSE 11 | ) 12 | ``` 13 | -------------------------------------------------------------------------------- /tests/testthat/in/README.md: -------------------------------------------------------------------------------- 1 | 2 | # This is a readme 3 | -------------------------------------------------------------------------------- /tests/testthat/in/Rprofile: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lorenzwalthert/precommit/1ffd55b30522dcfe4750bd9e6c868361b2c0ef47/tests/testthat/in/Rprofile -------------------------------------------------------------------------------- /tests/testthat/in/WORDLIST: -------------------------------------------------------------------------------- 1 | fsssile 2 | -------------------------------------------------------------------------------- /tests/testthat/in/_pkgdown-articles.yml: -------------------------------------------------------------------------------- 1 | articles: 2 | - title: Get started 3 | navbar: ~ 4 | contents: 5 | - why-use-hooks 6 | - some-more 7 | url: https://example.com 8 | -------------------------------------------------------------------------------- /tests/testthat/in/_pkgdown-index-articles.yml: -------------------------------------------------------------------------------- 1 | reference: 2 | - title: "Workflow" 3 | desc: > 4 | Edit the pre-commit configuration 5 | - contents: 6 | - autoupdate 7 | 8 | articles: 9 | - title: Get started 10 | navbar: ~ 11 | contents: 12 | - pkgdown 13 | url: https://example.com 14 | -------------------------------------------------------------------------------- /tests/testthat/in/_pkgdown-index.yml: -------------------------------------------------------------------------------- 1 | reference: 2 | - title: "Workflow" 3 | desc: > 4 | Edit the pre-commit configuration 5 | - contents: 6 | - autoupdate 7 | url: https://example.com 8 | -------------------------------------------------------------------------------- /tests/testthat/in/autoupdate.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/setup.R 3 | \name{autoupdate} 4 | \alias{autoupdate} 5 | \title{Auto-update your hooks} 6 | \usage{ 7 | autoupdate(root = here::here()) 8 | } 9 | \arguments{ 10 | \item{root}{The path to the root directory of your project.} 11 | } 12 | \value{ 13 | The exit status from \verb{pre-commit autoupdate} (invisibly). 14 | } 15 | \description{ 16 | Runs \href{https://pre-commit.com/#pre-commit-autoupdate}{\verb{pre-commit autoupdate}}. 17 | } 18 | \examples{ 19 | \dontrun{ 20 | autoupdate() 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /tests/testthat/in/codemeta.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lorenzwalthert/precommit/1ffd55b30522dcfe4750bd9e6c868361b2c0ef47/tests/testthat/in/codemeta.json -------------------------------------------------------------------------------- /tests/testthat/in/deps-in-desc-dot3-fail.R: -------------------------------------------------------------------------------- 1 | bliblablupp:::select() 2 | 3 | artific:::filter() 4 | -------------------------------------------------------------------------------- /tests/testthat/in/deps-in-desc-dot3-success.R: -------------------------------------------------------------------------------- 1 | prec3:::dfjk() 2 | -------------------------------------------------------------------------------- /tests/testthat/in/deps-in-desc-fail.R: -------------------------------------------------------------------------------- 1 | xzywdj::kk 2 | -------------------------------------------------------------------------------- /tests/testthat/in/deps-in-desc-fail.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Available Hooks" 3 | output: rmarkdown::html_vignette 4 | vignette: > 5 | %\VignetteIndexEntry{available-hooks} 6 | %\VignetteEngine{knitr::rmarkdown} 7 | %\VignetteEncoding{UTF-8} 8 | --- 9 | 10 | ```{r} 11 | ttyzp::something 12 | ``` 13 | -------------------------------------------------------------------------------- /tests/testthat/in/deps-in-desc-fail.Rnw: -------------------------------------------------------------------------------- 1 | \documentclass{article} 2 | 3 | \begin{document} 4 | 5 | <<>>= 6 | bliblabpu::head(mtcars) 7 | @ 8 | 9 | 10 | 11 | \end{document} 12 | -------------------------------------------------------------------------------- /tests/testthat/in/deps-in-desc-success.R: -------------------------------------------------------------------------------- 1 | bliblablupp::xx 2 | 3 | stats::acf() # should be excluded 4 | -------------------------------------------------------------------------------- /tests/testthat/in/deps-in-desc-success.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Available Hooks" 3 | output: rmarkdown::html_vignette 4 | vignette: > 5 | %\VignetteIndexEntry{available-hooks} 6 | %\VignetteEngine{knitr::rmarkdown} 7 | %\VignetteEncoding{UTF-8} 8 | --- 9 | 10 | ```{r, eval = FALSE} 11 | R.cache::clearCache() 12 | ``` 13 | -------------------------------------------------------------------------------- /tests/testthat/in/deps-in-desc-success.Rnw: -------------------------------------------------------------------------------- 1 | \documentclass{article} 2 | 3 | \begin{document} 4 | 5 | <<>>= 6 | utils::head(mtcars) 7 | @ 8 | 9 | 10 | 11 | \end{document} 12 | -------------------------------------------------------------------------------- /tests/testthat/in/flie-true.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/roxygenize.R 3 | \name{flie} 4 | \alias{flie} 5 | \title{This is a file} 6 | \arguments{ 7 | \item{text}{This is a parameter.} 8 | } 9 | \description{ 10 | This is a file 11 | } 12 | -------------------------------------------------------------------------------- /tests/testthat/in/flie.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/roxygenize.R 3 | -------------------------------------------------------------------------------- /tests/testthat/in/lintr-fail.R: -------------------------------------------------------------------------------- 1 | sum(1,2) 2 | -------------------------------------------------------------------------------- /tests/testthat/in/lintr-fail.qmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Test doc: fail lintr" 3 | format: html 4 | --- 5 | 6 | ```{r} 7 | sum(1,2) 8 | ``` 9 | -------------------------------------------------------------------------------- /tests/testthat/in/lintr-success.R: -------------------------------------------------------------------------------- 1 | sum(1, 2) 2 | -------------------------------------------------------------------------------- /tests/testthat/in/lintr-success.qmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Test doc: success lintr" 3 | format: html 4 | --- 5 | 6 | ```{r} 7 | sum(1, 2) 8 | ``` 9 | -------------------------------------------------------------------------------- /tests/testthat/in/no-browser-statement-fail.R: -------------------------------------------------------------------------------- 1 | browser() 2 | -------------------------------------------------------------------------------- /tests/testthat/in/no-browser-statement-success.R: -------------------------------------------------------------------------------- 1 | "browser()" 2 | -------------------------------------------------------------------------------- /tests/testthat/in/no-debug-statement-fail.R: -------------------------------------------------------------------------------- 1 | debugonce() 2 | -------------------------------------------------------------------------------- /tests/testthat/in/no-debug-statement-success.R: -------------------------------------------------------------------------------- 1 | "debugonce()" 2 | -------------------------------------------------------------------------------- /tests/testthat/in/no-print-statement-fail.R: -------------------------------------------------------------------------------- 1 | print() 2 | -------------------------------------------------------------------------------- /tests/testthat/in/no-print-statement-success.R: -------------------------------------------------------------------------------- 1 | "print()" 2 | -------------------------------------------------------------------------------- /tests/testthat/in/parsable-R-fail.R: -------------------------------------------------------------------------------- 1 | 1 1j1j öj1 2 | -------------------------------------------------------------------------------- /tests/testthat/in/parsable-R-fail.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Parsable Rmd Fail" 3 | output: rmarkdown::html_document 4 | --- 5 | 6 | ```{r} 7 | 1 1j1j öj1 8 | ``` 9 | -------------------------------------------------------------------------------- /tests/testthat/in/parsable-R-success.R: -------------------------------------------------------------------------------- 1 | call() 2 | -------------------------------------------------------------------------------- /tests/testthat/in/parsable-R-success.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Parsable Rmd Success" 3 | output: rmarkdown::html_document 4 | --- 5 | 6 | ```{r} 7 | call() 8 | is_nice <- FALSE 9 | ``` 10 | 11 | ```{r, eval = is_nice} 12 | parsable + code 13 | ``` 14 | 15 | ```{r, purl=FALSE} 16 | fas / fk 12 - 17 | ``` 18 | 19 | 20 | ```{nonR chunk} 21 | this is 22 | ``` 23 | 24 | ```{f , x= 2} 25 | this is 26 | ``` 27 | 28 | ```{f, x= 2} 29 | this is 30 | ``` 31 | 32 | ```{ f, x= 2} 33 | this is 34 | ``` 35 | -------------------------------------------------------------------------------- /tests/testthat/in/parsable-roxygen-fail.R: -------------------------------------------------------------------------------- 1 | #' Some function 2 | #' 3 | #' This function is great! But \code{oh dear, a missing brace... 4 | #' 5 | #' And isn't that a multi-line example? We should probably use @examples... 6 | #' 7 | #' @param x A parameter. 8 | #' 9 | #' @returns Invisible `NULL`. 10 | #' 11 | #' @example 12 | #' some_function(10) 13 | #' some_function(11) 14 | #' 15 | #' @export 16 | some_function <- function(x) { 17 | x 18 | } 19 | -------------------------------------------------------------------------------- /tests/testthat/in/parsable-roxygen-fail2.R: -------------------------------------------------------------------------------- 1 | #' Minimal roxygen docs, but there's a problem with our R code! 2 | #' 3 | #' @export 4 | some_function <- function(x) { 5 | (x 6 | } 7 | -------------------------------------------------------------------------------- /tests/testthat/in/parsable-roxygen-success.R: -------------------------------------------------------------------------------- 1 | #' Some function 2 | #' 3 | #' This function is great! 4 | #' 5 | #' @param x A parameter. 6 | #' 7 | #' @returns Invisible `NULL`. 8 | #' 9 | #' @examples 10 | #' some_function(10) 11 | #' 12 | #' @export 13 | some_function <- function(x) { 14 | x 15 | } 16 | 17 | # To check whether code was evaluated 18 | print("A random print statement") 19 | -------------------------------------------------------------------------------- /tests/testthat/in/pkgdown.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: "pkgdown" 3 | output: rmarkdown::html_vignette 4 | vignette: > 5 | %\VignetteIndexEntry{pkgdown} 6 | %\VignetteEngine{knitr::rmarkdown} 7 | %\VignetteEncoding{UTF-8} 8 | --- 9 | 10 | ```{r, include = FALSE} 11 | knitr::opts_chunk$set( 12 | collapse = TRUE, 13 | comment = "#>" 14 | ) 15 | ``` 16 | 17 | ```{r setup} 18 | library(precommit) 19 | ``` 20 | -------------------------------------------------------------------------------- /tests/testthat/in/renv-fail.lock: -------------------------------------------------------------------------------- 1 | { 2 | "R": { 3 | "Version": "4.2.3", 4 | "Repositories": [ 5 | { 6 | "Name": "CRAN", 7 | "URL": "https://cloud.r-project.org" 8 | } 9 | ] 10 | }, 11 | "Packages": { 12 | "markdown": { 13 | "Package": "markdown", 14 | "Version": "1.0", 15 | "Source": "Repository", 16 | "Repository": "CRAN", 17 | "Hash": "2324" 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /tests/testthat/in/renv-success.lock: -------------------------------------------------------------------------------- 1 | { 2 | "R": { 3 | "Version": "4.2.3", 4 | "Repositories": [ 5 | { 6 | "Name": "CRAN", 7 | "URL": "https://cloud.r-project.org" 8 | } 9 | ] 10 | }, 11 | "Packages": { 12 | "markdown": { 13 | "Package": "markdown", 14 | "Version": "1.0", 15 | "Source": "Repository", 16 | "Repository": "CRAN" 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /tests/testthat/in/roxygenize-cache-success.R: -------------------------------------------------------------------------------- 1 | #' Title 2 | #' 3 | #' text 4 | NULL 5 | -------------------------------------------------------------------------------- /tests/testthat/in/roxygenize.R: -------------------------------------------------------------------------------- 1 | #' This is a file 2 | #' 3 | #' @param text This is a parameter. 4 | #' @name flie 5 | NULL 6 | -------------------------------------------------------------------------------- /tests/testthat/in/spell-check-fail-2.md: -------------------------------------------------------------------------------- 1 | # a ttle 2 | 3 | This is a fsssile. 4 | -------------------------------------------------------------------------------- /tests/testthat/in/spell-check-fail.md: -------------------------------------------------------------------------------- 1 | # a title 2 | 3 | This is a fsssile. 4 | -------------------------------------------------------------------------------- /tests/testthat/in/spell-check-ignored-success.md: -------------------------------------------------------------------------------- 1 | # a title 2 | 3 | This is a file. 4 | -------------------------------------------------------------------------------- /tests/testthat/in/spell-check-language-success.md: -------------------------------------------------------------------------------- 1 | # a title 2 | 3 | behaviour 4 | -------------------------------------------------------------------------------- /tests/testthat/in/spell-check-success.md: -------------------------------------------------------------------------------- 1 | # a title 2 | 3 | This is a file. 4 | -------------------------------------------------------------------------------- /tests/testthat/in/spell-check-wordlist-success.md: -------------------------------------------------------------------------------- 1 | # a title 2 | 3 | This is a fsssile. 4 | -------------------------------------------------------------------------------- /tests/testthat/in/style-files-base-indention-success.R: -------------------------------------------------------------------------------- 1 | 1 + 1 2 | -------------------------------------------------------------------------------- /tests/testthat/in/style-files-cache-success.R: -------------------------------------------------------------------------------- 1 | 1 + 1 2 | -------------------------------------------------------------------------------- /tests/testthat/in/style-files-cmd-fail.R: -------------------------------------------------------------------------------- 1 | 1+1 2 | -------------------------------------------------------------------------------- /tests/testthat/in/style-files-cmd-success.R: -------------------------------------------------------------------------------- 1 | 1 + 1 2 | -------------------------------------------------------------------------------- /tests/testthat/in/style-files-fail-changed.R: -------------------------------------------------------------------------------- 1 | 1+ 1 2 | -------------------------------------------------------------------------------- /tests/testthat/in/style-files-fail-parse.R: -------------------------------------------------------------------------------- 1 | 1+ ) 2 | -------------------------------------------------------------------------------- /tests/testthat/in/style-files-ignore-fail.R: -------------------------------------------------------------------------------- 1 | # styler: off 2 | 1+1 3 | # styler: on 4 | 5 | 12 + 39 6 | 7 | # 1 styler: on 8 | -------------------------------------------------------------------------------- /tests/testthat/in/style-files-ignore-success.R: -------------------------------------------------------------------------------- 1 | # styler: off 2 | 1+1 3 | # styler: on 4 | 5 | 12 + 97 6 | 7 | # 1 styler: on 8 | -------------------------------------------------------------------------------- /tests/testthat/in/style-files-reindention-success.R: -------------------------------------------------------------------------------- 1 | for (i in 1:3) { 2 | ##### comment 3 | i 4 | } 5 | -------------------------------------------------------------------------------- /tests/testthat/in/style-files-roxygen-success.R: -------------------------------------------------------------------------------- 1 | #' Code 2 | #' 3 | #' @examples 4 | #' 1+1 5 | NULL 6 | -------------------------------------------------------------------------------- /tests/testthat/in/style-files-success.R: -------------------------------------------------------------------------------- 1 | 1 + 1 2 | 3 | # styler: off 4 | 1+1 5 | -------------------------------------------------------------------------------- /tests/testthat/reference-objects/DESCRIPTION: -------------------------------------------------------------------------------- 1 | Package: precommit 2 | Title: Pre commit hooks 3 | Version: 0.0.0.9007 4 | Author: Lorenz Walthert 5 | Maintainer: Lorenz Walthert 6 | Imports: 7 | docopt, 8 | fs, 9 | here, 10 | oneliner (>= 0.1.0), 11 | roxygen2, 12 | spelling, 13 | styler (>= 1.1.1.9002), 14 | testthat, 15 | tic, 16 | usethis, 17 | withr 18 | Remotes: 19 | lorenzwalthert/oneliner, 20 | r-lib/styler, 21 | ropenscilabs/tic 22 | Encoding: UTF-8 23 | Roxygen: list(markdown = TRUE) 24 | RoxygenNote: 6.1.1 25 | URL: https://example.com 26 | -------------------------------------------------------------------------------- /tests/testthat/reference-objects/pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | # config used to run all hooks we have defined in this repo in .github/workflows/end-2-end.yml 2 | # All available hooks: https://pre-commit.com/hooks.html 3 | # R specific hooks: https://github.com/lorenzwalthert/precommit 4 | default_stages: ["commit"] 5 | repos: 6 | - repo: https://github.com/lorenzwalthert/precommit 7 | rev: v0.1.3.9008 8 | hooks: 9 | - id: style-files 10 | args: [--style_pkg=styler, --style_fun=tidyverse_style] 11 | exclude: '^tests/testthat/in/.*\.R' 12 | - id: roxygenize 13 | additional_dependencies: 14 | - cli 15 | - fs 16 | - here 17 | - magrittr 18 | - purrr 19 | - R.cache 20 | - rlang 21 | - rprojroot 22 | - rstudioapi 23 | - withr 24 | - yaml 25 | - r-lib/pkgapi 26 | # codemeta must be above use-tidy-description when both are used 27 | - id: codemeta-description-updated 28 | - id: lintr 29 | - id: use-tidy-description 30 | - id: spell-check 31 | exclude: > 32 | (?x)^( 33 | .*\.[rR]| 34 | .*\.feather| 35 | .*\.jpeg| 36 | .*\.pdf| 37 | .*\.png| 38 | .*\.py| 39 | .*\.RData| 40 | .*\.rds| 41 | .*\.Rds| 42 | .*\.Rproj| 43 | .*\.sh| 44 | (.*/|)\.gitignore| 45 | (.*/|)\.pre-commit-.*| 46 | (.*/|)\.Rbuildignore| 47 | (.*/|)\.Renviron| 48 | (.*/|)\.Rprofile| 49 | (.*/|)\.travis\.yml| 50 | (.*/|)appveyor\.yml| 51 | (.*/|)NAMESPACE| 52 | (.*/|)renv/settings\.dcf| 53 | (.*/|)renv\.lock| 54 | (.*/|)WORDLIST| 55 | \.github/workflows/.*| 56 | data/.*| 57 | inst/hooks/.*| 58 | inst/pre-commit-.*| 59 | )$ 60 | - id: readme-rmd-rendered 61 | - id: parsable-R 62 | - id: parsable-roxygen 63 | - id: no-browser-statement 64 | - id: no-print-statement 65 | - id: no-debug-statement 66 | - id: deps-in-desc 67 | - id: pkgdown 68 | - id: renv-lockfile-validate 69 | args: [--error] 70 | - repo: local 71 | hooks: 72 | - id: consistent-release-tag 73 | name: consistent-release-tag 74 | entry: inst/hooks/local/consistent-release-tag.R 75 | language: script 76 | stages: [commit, push] 77 | - id: hooks-config-to-inst 78 | name: hooks-config-to-inst 79 | entry: inst/hooks/local/hooks-config-to-inst.R 80 | language: script 81 | stages: [commit, push] 82 | - id: spell-check-exclude-identical 83 | name: spell-check-exclude-identical 84 | entry: inst/hooks/local/spell-check-exclude-identical.R 85 | language: script 86 | stages: [commit, push] 87 | - id: forbid-to-commit 88 | name: Don't commit common R artifacts 89 | entry: Cannot commit .Rhistory, .Rdata, .csv and similar. 90 | language: fail 91 | files: '\.(Rhistory|csv|RData|Rds|rds)$' 92 | # `exclude: ` to allow committing specific files. 93 | - id: spell-check-ordered-exclude 94 | name: Ordered regex pattern for spell-check exclusion 95 | description: Ensure alphabetical order in `exclude:` key of spell check. 96 | entry: inst/hooks/local/spell-check-ordered-exclude.R 97 | language: script 98 | files: '^(.*/|)\.?pre-commit-config.*\.yaml$' 99 | -------------------------------------------------------------------------------- /tests/testthat/test-config.R: -------------------------------------------------------------------------------- 1 | test_that("can set path to local config", { 2 | tmp <- tempdir() 3 | test.pkg <- fs::dir_create(tmp, "test.proj") 4 | expect_equal( 5 | set_config_source(NULL, root = test.pkg), 6 | system.file("pre-commit-config-proj.yaml", package = "precommit") 7 | ) 8 | expect_error( 9 | set_config_source(tempfile()), 10 | "does not exist" 11 | ) 12 | }) 13 | 14 | test_that("can set path to remote config", { 15 | skip_on_cran() 16 | path <- set_config_source( 17 | example_remote_config() 18 | ) 19 | expect_equal(fs::path_ext(path), "yaml") 20 | expect_silent(yaml::read_yaml(path)) 21 | 22 | expect_error(set_config_source("https://apple.com"), "valid yaml") 23 | }) 24 | 25 | test_that("defaults to right config depending on whether or not root is a pkg", { 26 | tmp <- tempdir() 27 | test.pkg <- fs::dir_create(tmp, "test.pkg") 28 | withr::with_dir(test.pkg, { 29 | desc <- desc::description$new("!new") 30 | desc$set(Package = "test.pkg") 31 | desc$write("DESCRIPTION") 32 | }) 33 | expect_message( 34 | set_config_source(NULL, root = test.pkg), 35 | "pkg\\.yaml" 36 | ) 37 | fs::file_delete(fs::path(test.pkg, "DESCRIPTION")) 38 | expect_message( 39 | set_config_source(NULL, root = test.pkg), 40 | "proj\\.yaml" 41 | ) 42 | }) 43 | 44 | test_that(".Rbuildignore is written to the right directory when root is relative", { 45 | root <- tempfile() 46 | fs::dir_create(root) 47 | 48 | withr::with_dir(root, { 49 | desc <- desc::description$new("!new") 50 | desc$set(Package = "test.pkg") 51 | desc$write("DESCRIPTION") 52 | }) 53 | withr::with_dir( 54 | fs::path_dir(root), 55 | use_precommit_config(root = fs::path_file(root)) 56 | ) 57 | expect_true(file_exists(fs::path(root, ".Rbuildignore"))) 58 | }) 59 | 60 | test_that(".Rbuildignore is written to the right directory when root is absolute", { 61 | root <- tempfile() 62 | fs::dir_create(root) 63 | 64 | withr::with_dir(root, { 65 | desc <- desc::description$new("!new") 66 | desc$set(Package = "test.pkg") 67 | desc$write("DESCRIPTION") 68 | }) 69 | use_precommit_config(root = root) 70 | expect_true(file_exists(fs::path(root, ".Rbuildignore"))) 71 | }) 72 | -------------------------------------------------------------------------------- /tests/testthat/test-docopt.R: -------------------------------------------------------------------------------- 1 | test_that("custom docopt interface parses as expected", { 2 | args_variants <- list( 3 | "file a.R", 4 | c("file a.R", "file-B.R"), 5 | c("File A.R", "File c.R"), 6 | c("Another file with spaces.R", "--warn_only"), 7 | c("Another file with spaces.R", "Yet another file with spaces (YAFWS).R", "--warn_only"), 8 | c("--warn_only", "Another file with spaces.R"), 9 | c("--warn_only", "Another file with spaces.R", "Yet another file with spaces (YAFWS).R"), 10 | c("Another file with spaces.R", "--warn_only", "Yet another file with spaces (YAFWS).R") 11 | ) 12 | 13 | "Run lintr on R files during a precommit. 14 | Usage: 15 | cmdtest [--warn_only] ... 16 | Options: 17 | --warn_only Placeholder for test. 18 | " -> doc 19 | 20 | for (args in args_variants) { 21 | new_args <- precommit_docopt(doc, args) 22 | 23 | # to show failures in vanilla docopt, use this: 24 | # new_args <- docopt::docopt(doc, args) 25 | 26 | expect_equal(length(new_args), 4) 27 | if ("--warn_only" %in% args) { 28 | expect_equal(length(new_args$files), length(args) - 1) 29 | expect_equal(length(new_args$``), length(args) - 1) 30 | expect_true(new_args$warn_only) 31 | expect_true(new_args$`--warn_only`) 32 | } else { 33 | expect_equal(length(new_args$files), length(args)) 34 | expect_equal(length(new_args$``), length(args)) 35 | expect_false(new_args$warn_only) 36 | expect_false(new_args$`--warn_only`) 37 | } 38 | } 39 | }) 40 | -------------------------------------------------------------------------------- /tests/testthat/test-exec.R: -------------------------------------------------------------------------------- 1 | test_that("Path can be derived for windows Python >= 3.0 (mocked)", { 2 | local_mocked_bindings( 3 | path_derive_precommit_exec_win_python3plus_candidates = function() { 4 | c( 5 | fs::path_home("AppData/Roaming/Python/Python35"), 6 | fs::path_home("AppData/Roaming/Python/Python37") 7 | ) 8 | } 9 | ) 10 | 11 | expect_equal( 12 | path_derive_precommit_exec_win_python3plus_base(), 13 | c( 14 | fs::path(fs::path_home(), "AppData/Roaming/Python/Python37/Scripts"), 15 | fs::path(fs::path_home(), "AppData/Roaming/Python/Python35/Scripts") 16 | ), 17 | ignore_attr = TRUE 18 | ) 19 | }) 20 | 21 | 22 | test_that("Path can be derived for windows Python >= 3.0 (actual)", { 23 | skip_if(!is_windows()) 24 | skip_if(!not_conda()) 25 | skip_if(on_cran()) 26 | expect_match(path_derive_precommit_exec_win_python3plus_base(), "AppData/Roaming") 27 | expect_equal( 28 | fs::path_file(path_derive_precommit_exec_win()), 29 | precommit_executable_file() 30 | ) 31 | }) 32 | 33 | 34 | test_that("Warns when there are multiple installations found (2x os)", { 35 | local_mocked_bindings( 36 | path_derive_precommit_exec_path = function(candidate) { 37 | fs::path_home("AppData/Roaming/Python/Python35") 38 | }, 39 | get_os = function(...) { 40 | c(sysname = "windows") 41 | }, 42 | path_derive_precommit_exec_win = function() { 43 | c( 44 | fs::path_home("AppData/Roaming/Python/Python34"), 45 | fs::path_home("AppData/Roaming/Python/Python37") 46 | ) 47 | } 48 | ) 49 | 50 | expect_warning( 51 | path_derive_precommit_exec(), 52 | "We detected multiple pre-commit executables" 53 | ) 54 | }) 55 | 56 | test_that("Warns when there are multiple installations found (2x path)", { 57 | local_mocked_bindings( 58 | path_derive_precommit_exec_path = function(candidate) { 59 | c( 60 | fs::path_home("AppData/Roaming/Python/Python35"), 61 | fs::path_home("AppData/Roaming/Python/Python37") 62 | ) 63 | }, 64 | get_os = function(...) { 65 | c(sysname = "windows") 66 | }, 67 | path_derive_precommit_exec_win = function() { 68 | fs::path_home("AppData/Roaming/Python/Python34") 69 | } 70 | ) 71 | expect_warning( 72 | path_derive_precommit_exec(), 73 | "We detected multiple pre-commit executables" 74 | ) 75 | }) 76 | 77 | test_that("Warns when there are multiple installations found (path and os)", { 78 | local_mocked_bindings( 79 | path_derive_precommit_exec_path = function(candidate) { 80 | fs::path_home("AppData/Roaming/Python/Python35") 81 | }, 82 | path_derive_precommit_exec_win = function() { 83 | fs::path_home("AppData/Roaming/Python/Python34") 84 | }, 85 | get_os = function(...) { 86 | c(sysname = "windows") 87 | }, 88 | ) 89 | 90 | expect_warning( 91 | path_derive_precommit_exec(), 92 | "We detected multiple pre-commit executables" 93 | ) 94 | }) 95 | -------------------------------------------------------------------------------- /tests/testthat/test-hook-codemeta-description-updated.R: -------------------------------------------------------------------------------- 1 | run_test("codemeta-description-update", 2 | file_name = c("codemeta.json"), 3 | suffix = "", 4 | std_err = "No `DESCRIPTION` found in repository.", 5 | std_out = NULL, 6 | ) 7 | 8 | # succeed 9 | run_test("codemeta-description-update", 10 | file_name = c("DESCRIPTION", "codemeta.json"), 11 | suffix = "", 12 | file_transformer = function(files) { 13 | if (length(files) > 1) { 14 | # transformer is called once on all files and once per file 15 | content_2 <- readLines(files[2]) 16 | Sys.sleep(2) 17 | writeLines(content_2, files[2]) 18 | } 19 | files 20 | } 21 | ) 22 | 23 | if (!on_cran()) { 24 | # succeed in correct root 25 | run_test("codemeta-description-update", 26 | file_name = c( 27 | "rpkg/DESCRIPTION" = "DESCRIPTION", 28 | "rpkg/codemeta.json" = "codemeta.json" 29 | ), 30 | cmd_args = "--root=rpkg", 31 | suffix = "", 32 | file_transformer = function(files) { 33 | if (length(files) > 1) { 34 | # transformer is called once on all files and once per file 35 | content_2 <- readLines(files[2]) 36 | Sys.sleep(2) 37 | writeLines(content_2, files[2]) 38 | } 39 | files 40 | } 41 | ) 42 | 43 | # # fail in wrong root 44 | run_test("codemeta-description-update", 45 | file_name = c( 46 | "rpkg/DESCRIPTION" = "DESCRIPTION", 47 | "rpkg/codemeta.json" = "codemeta.json", 48 | "rpkg2/codemeta.json" = "README.md" 49 | ), 50 | cmd_args = "--root=rpkg2", 51 | std_err = "No `DESCRIPTION` found in repository.", 52 | suffix = "", 53 | file_transformer = function(files) { 54 | if (length(files) > 1) { 55 | # transformer is called once on all files and once per file 56 | content_2 <- readLines(files[2]) 57 | Sys.sleep(2) 58 | writeLines(content_2, files[2]) 59 | } 60 | files 61 | } 62 | ) 63 | } 64 | 65 | if (!on_windows_on_cran()) { 66 | run_test("codemeta-description-update", 67 | file_name = c("DESCRIPTION"), 68 | suffix = "", 69 | std_err = "No `codemeta.json` found in repository.", 70 | std_out = NULL, 71 | ) 72 | 73 | 74 | # outdated 75 | run_test("codemeta-description-update", 76 | file_name = c("DESCRIPTION", "codemeta.json"), 77 | suffix = "", 78 | std_err = "out of date", 79 | std_out = NULL, 80 | file_transformer = function(files) { 81 | if (length(files) > 1) { 82 | # transformer is called once on all files and once per file 83 | content_2 <- readLines(files[1]) 84 | Sys.sleep(2) 85 | writeLines(content_2, files[1]) 86 | } 87 | files 88 | } 89 | ) 90 | } 91 | -------------------------------------------------------------------------------- /tests/testthat/test-hook-deps-in-desc-R.R: -------------------------------------------------------------------------------- 1 | # succeed (call to library that is in description) 2 | run_test("deps-in-desc", 3 | suffix = "-success.R", std_err = NULL, 4 | artifacts = c("DESCRIPTION" = test_path("in/DESCRIPTION")) 5 | ) 6 | 7 | # fail (call to library that is not in description) 8 | run_test("deps-in-desc", 9 | suffix = "-fail.R", std_err = "Dependency check failed", 10 | artifacts = c("DESCRIPTION" = test_path("in/DESCRIPTION")) 11 | ) 12 | 13 | 14 | if (!on_cran()) { 15 | # in sub directory with wrong root 16 | run_test("deps-in-desc", 17 | suffix = "-fail.R", std_err = "Could not find R package", 18 | file_transformer = function(files) { 19 | fs::path_abs(fs::file_move(files, "rpkg")) 20 | }, 21 | artifacts = c("rpkg/DESCRIPTION" = test_path("in/DESCRIPTION")) 22 | ) 23 | # in sub directory with correct root 24 | run_test("deps-in-desc", 25 | cmd_args = "--root=rpkg", 26 | suffix = "-fail.R", std_err = "Dependency check failed", 27 | file_transformer = function(files) { 28 | fs::path_abs(fs::file_move(files, "rpkg")) 29 | }, 30 | artifacts = c("rpkg/DESCRIPTION" = test_path("in/DESCRIPTION")) 31 | ) 32 | # in sub directory with correct root 33 | run_test("deps-in-desc", 34 | cmd_args = "--root=rpkg", 35 | suffix = "-success.R", std_err = NULL, 36 | file_transformer = function(files) { 37 | fs::path_abs(fs::file_move(files, "rpkg")) 38 | }, 39 | artifacts = c("rpkg/DESCRIPTION" = test_path("in/DESCRIPTION")) 40 | ) 41 | } 42 | 43 | 44 | if (!on_windows_on_cran()) { 45 | # with ::: 46 | run_test("deps-in-desc", 47 | "deps-in-desc-dot3", 48 | suffix = "-fail.R", std_err = "Dependency check failed", 49 | artifacts = c("DESCRIPTION" = test_path("in/DESCRIPTION")) 50 | ) 51 | 52 | run_test("deps-in-desc", 53 | "deps-in-desc-dot3", 54 | suffix = "-success.R", std_err = NULL, 55 | artifacts = c("DESCRIPTION" = test_path("in/DESCRIPTION")) 56 | ) 57 | 58 | run_test("deps-in-desc", 59 | "deps-in-desc-dot3", 60 | suffix = "-fail.R", std_err = NULL, 61 | artifacts = c("DESCRIPTION" = test_path("in/DESCRIPTION")), 62 | cmd_args = "--allow_private_imports" 63 | ) 64 | 65 | # Rmd 66 | run_test("deps-in-desc", 67 | "deps-in-desc", 68 | suffix = "-fail.Rmd", std_err = "Dependency check failed", 69 | std_out = "deps-in-desc-fail.Rmd`: ttyzp", 70 | artifacts = c("DESCRIPTION" = test_path("in/DESCRIPTION")) 71 | ) 72 | 73 | run_test("deps-in-desc", 74 | "deps-in-desc", 75 | suffix = "-success.Rmd", std_err = NULL, 76 | artifacts = c("DESCRIPTION" = test_path("in/DESCRIPTION")) 77 | ) 78 | 79 | # README.Rmd is excluded 80 | run_test("deps-in-desc", 81 | "README.Rmd", 82 | suffix = "", std_err = NULL, 83 | artifacts = c("DESCRIPTION" = test_path("in/DESCRIPTION-no-deps.dcf")) 84 | ) 85 | 86 | 87 | 88 | # Rnw 89 | run_test("deps-in-desc", 90 | "deps-in-desc", 91 | suffix = "-fail.Rnw", std_err = "Dependency check failed", 92 | artifacts = c("DESCRIPTION" = test_path("in/DESCRIPTION")) 93 | ) 94 | 95 | run_test("deps-in-desc", 96 | "deps-in-desc", 97 | suffix = "-success.Rnw", std_err = NULL, 98 | artifacts = c("DESCRIPTION" = test_path("in/DESCRIPTION")) 99 | ) 100 | 101 | # Rprofile 102 | # because .Rprofile is executed on startup, this must be an installed 103 | # package (to not give an error staight away) not listed in 104 | # test_path("in/DESCRIPTION") 105 | if (Sys.getenv("GITHUB_WORKFLOW") != "Hook tests") { 106 | # seems like .Rprofile with renv activation does not get executed when 107 | # argument to Rscript contains Rprofile ?! Skip this 108 | expect_true(rlang::is_installed("R.cache")) 109 | run_test("deps-in-desc", 110 | "Rprofile", 111 | suffix = "", std_err = "Dependency check failed", 112 | artifacts = c("DESCRIPTION" = test_path("in/DESCRIPTION")), 113 | file_transformer = function(files) { 114 | writeLines("R.cache::findCache", files) 115 | fs::file_move( 116 | files, 117 | fs::path(fs::path_dir(files), paste0(".", fs::path_file(files))) 118 | ) 119 | } 120 | ) 121 | 122 | run_test("deps-in-desc", 123 | "Rprofile", 124 | suffix = "", std_err = NULL, 125 | artifacts = c("DESCRIPTION" = test_path("in/DESCRIPTION")), 126 | file_transformer = function(files) { 127 | writeLines("utils::head", files) 128 | fs::file_move( 129 | files, 130 | fs::path(fs::path_dir(files), paste0(".", fs::path_file(files))) 131 | ) 132 | } 133 | ) 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /tests/testthat/test-hook-lintr.R: -------------------------------------------------------------------------------- 1 | # .R ---- 2 | 3 | # success 4 | run_test("lintr", 5 | suffix = "-success.R", 6 | std_err = NULL 7 | ) 8 | 9 | # failure 10 | run_test("lintr", suffix = "-fail.R", std_err = "not lint free") 11 | 12 | if (!on_windows_on_cran()) { 13 | # warning 14 | run_test( 15 | "lintr", 16 | suffix = "-fail.R", cmd_args = "--warn_only", std_err = NULL 17 | ) 18 | 19 | # .qmd ---- 20 | 21 | # success 22 | run_test("lintr", suffix = "-success.qmd", std_err = NULL) 23 | 24 | # failure 25 | run_test("lintr", suffix = "-fail.qmd", std_err = "not lint free") 26 | } 27 | -------------------------------------------------------------------------------- /tests/testthat/test-hook-no-browser-statement.R: -------------------------------------------------------------------------------- 1 | # success 2 | run_test( 3 | "no-browser-statement", 4 | suffix = "-success.R", 5 | std_err = NULL 6 | ) 7 | 8 | # failure 9 | run_test( 10 | "no-browser-statement", 11 | suffix = "-fail.R", 12 | std_err = "contains a `browser()` statement." 13 | ) 14 | -------------------------------------------------------------------------------- /tests/testthat/test-hook-no-debug-statement.R: -------------------------------------------------------------------------------- 1 | # success 2 | run_test( 3 | "no-debug-statement", 4 | suffix = "-success.R", 5 | std_err = NULL 6 | ) 7 | 8 | # failure 9 | run_test( 10 | "no-debug-statement", 11 | suffix = "-fail.R", 12 | std_err = "contains a `debug()` or `debugonce()` statement." 13 | ) 14 | -------------------------------------------------------------------------------- /tests/testthat/test-hook-no-print-statement.R: -------------------------------------------------------------------------------- 1 | # success 2 | run_test( 3 | "no-print-statement", 4 | suffix = "-success.R", 5 | std_err = NULL 6 | ) 7 | 8 | # failure 9 | run_test( 10 | "no-print-statement", 11 | suffix = "-fail.R", 12 | std_err = "contains a `print()` statement." 13 | ) 14 | -------------------------------------------------------------------------------- /tests/testthat/test-hook-parsable-R.R: -------------------------------------------------------------------------------- 1 | run_test("parsable-R", 2 | suffix = "-success.R", 3 | std_err = NULL 4 | ) 5 | # failure 6 | run_test("parsable-R", suffix = "-fail.R", std_out = "Full context", std_err = "1 1") 7 | 8 | if (!on_windows_on_cran()) { 9 | run_test("parsable-R", 10 | suffix = "-success.Rmd", 11 | std_err = NULL 12 | ) 13 | 14 | run_test( 15 | "parsable-R", 16 | suffix = "-fail.Rmd", 17 | std_out = "parsable-R-fail.Rmd", 18 | std_err = "1 1" 19 | ) 20 | } 21 | -------------------------------------------------------------------------------- /tests/testthat/test-hook-parsable-roxygen.R: -------------------------------------------------------------------------------- 1 | # success - code not evaluated 2 | run_test( 3 | "parsable-roxygen", 4 | suffix = "-success.R", 5 | std_out = NULL, 6 | std_err = NULL, 7 | read_only = TRUE 8 | ) 9 | 10 | # success - code evaluated 11 | run_test( 12 | "parsable-roxygen", 13 | suffix = "-success.R", 14 | cmd_args = "--eval", 15 | std_out = "A random print statement", 16 | std_err = NULL, 17 | read_only = TRUE 18 | ) 19 | 20 | # failure - roxygen problem 21 | run_test( 22 | "parsable-roxygen", 23 | suffix = "-fail.R", 24 | std_out = "Roxygen commentary", 25 | std_err = "@description has mismatched braces or quotes", 26 | read_only = TRUE 27 | ) 28 | 29 | # failure - R problem 30 | run_test( 31 | "parsable-roxygen", 32 | suffix = "-fail2.R", 33 | std_out = "File ", 34 | std_err = "unexpected '}'", 35 | read_only = TRUE 36 | ) 37 | -------------------------------------------------------------------------------- /tests/testthat/test-hook-pkgdown.R: -------------------------------------------------------------------------------- 1 | # success index 2 | run_test("pkgdown", 3 | file_name = c( 4 | "man/autoudpate.Rd" = "autoupdate.Rd", 5 | "_pkgdown.yml" = "_pkgdown-index.yml", 6 | "DESCRIPTION" = "DESCRIPTION" 7 | ), 8 | suffix = "", std_err = NULL 9 | ) 10 | 11 | # failed index 12 | run_test("pkgdown", 13 | file_name = c( 14 | "man/flie-true.Rd" = "flie-true.Rd", 15 | "_pkgdown.yml" = "_pkgdown-index.yml", 16 | "DESCRIPTION" = "DESCRIPTION" 17 | ), 18 | suffix = "", 19 | std_err = empty_on_cran("must be a known") 20 | ) 21 | 22 | # failed articles 23 | run_test("pkgdown", 24 | file_name = c( 25 | "vignettes/pkgdown.Rmd" = "pkgdown.Rmd", 26 | "_pkgdown.yml" = "_pkgdown-articles.yml", 27 | "DESCRIPTION" = "DESCRIPTION" 28 | ), 29 | suffix = "", 30 | std_err = "why-use-hooks" 31 | ) 32 | 33 | # success index and article 34 | run_test("pkgdown", 35 | file_name = c( 36 | "man/autoudpate.Rd" = "autoupdate.Rd", 37 | "vignettes/pkgdown.Rmd" = "pkgdown.Rmd", 38 | "_pkgdown.yml" = "_pkgdown-index-articles.yml", 39 | "DESCRIPTION" = "DESCRIPTION" 40 | ), 41 | suffix = "", 42 | std_err = NULL 43 | ) 44 | -------------------------------------------------------------------------------- /tests/testthat/test-hook-readme-rmd-rendered.R: -------------------------------------------------------------------------------- 1 | if (has_git()) { 2 | run_test("readme-rmd-rendered", 3 | file_name = c("README.md", "README.Rmd"), 4 | suffix = "", 5 | std_err = "out of date", 6 | std_out = NULL, 7 | file_transformer = function(files) { 8 | if (length(files) > 1) { 9 | # transformer is called once on all files and once per file 10 | content_2 <- readLines(files[2]) 11 | Sys.sleep(2) 12 | writeLines(content_2, files[2]) 13 | git_init() 14 | git2r::add(path = files) 15 | } 16 | files 17 | } 18 | ) 19 | 20 | # only has Rmd 21 | run_test("readme-rmd-rendered", 22 | file_name = "README.Rmd", 23 | suffix = "", 24 | std_err = NULL, 25 | std_out = NULL, 26 | file_transformer = function(files) { 27 | git_init() 28 | git2r::add(path = files[1]) 29 | files 30 | } 31 | ) 32 | if (!on_windows_on_cran()) { 33 | # only one file staged 34 | run_test("readme-rmd-rendered", 35 | file_name = c("README.Rmd", "README.md"), 36 | suffix = "", 37 | std_err = "should be both staged", 38 | std_out = NULL, 39 | file_transformer = function(files) { 40 | if (length(files) > 1) { 41 | # transformer is called once on all files and once per file 42 | content_2 <- readLines(files[2]) 43 | Sys.sleep(2) 44 | writeLines(content_2, files[2]) 45 | git_init() 46 | git2r::add(path = files[1]) 47 | } 48 | files 49 | } 50 | ) 51 | 52 | # only has md 53 | run_test("readme-rmd-rendered", 54 | file_name = "README.md", 55 | suffix = "", 56 | std_err = NULL, 57 | std_out = NULL, 58 | file_transformer = function(files) { 59 | git_init() 60 | git2r::add(path = files[1]) 61 | files 62 | } 63 | ) 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /tests/testthat/test-hook-renv-lockfile-validate.R: -------------------------------------------------------------------------------- 1 | # success 2 | run_test("renv-lockfile-validate", 3 | file_name = "renv-success", 4 | suffix = ".lock", cmd_args = c("--error"), 5 | std_err = NULL 6 | ) 7 | # fail 8 | run_test("renv-lockfile-validate", 9 | file_name = "renv-fail", 10 | suffix = ".lock", cmd_args = c("--error"), 11 | std_err = "error validating json" 12 | ) 13 | -------------------------------------------------------------------------------- /tests/testthat/test-hook-roxygenize.R: -------------------------------------------------------------------------------- 1 | # with outdated Rd present 2 | run_test("roxygenize", 3 | file_name = c("man/flie.Rd" = "flie.Rd"), 4 | suffix = "", 5 | std_err = NA, 6 | std_out = empty_on_cran("Writing NAMESPACE"), 7 | artifacts = c( 8 | "DESCRIPTION" = test_path("in/DESCRIPTION-no-deps.dcf"), 9 | "R/roxygenize.R" = test_path("in/roxygenize.R") 10 | ), 11 | file_transformer = function(files) { 12 | git_init() 13 | git2r::add(path = files) 14 | # hack to add artifact to trigger diff_requires_roxygenize() 15 | git2r::add(path = fs::path(fs::path_dir(fs::path_dir(files[1])), "R")) 16 | files 17 | } 18 | ) 19 | 20 | if (!on_cran()) { 21 | # with outdated Rd present in correct root 22 | run_test("roxygenize", 23 | file_name = c("rpkg/man/flie.Rd" = "flie.Rd"), 24 | suffix = "", 25 | std_err = NA, 26 | cmd_args = "--root=rpkg", 27 | std_out = empty_on_cran("Writing NAMESPACE"), 28 | artifacts = c( 29 | "rpkg/DESCRIPTION" = test_path("in/DESCRIPTION-no-deps.dcf"), 30 | "rpkg/R/roxygenize.R" = test_path("in/roxygenize.R") 31 | ), 32 | file_transformer = function(files) { 33 | withr::local_dir("rpkg") 34 | git_init() 35 | git2r::add(path = files) 36 | # hack to add artifact to trigger diff_requires_roxygenize() 37 | git2r::add(path = fs::path(fs::path_dir(fs::path_dir(files[1])), "R")) 38 | files 39 | } 40 | ) 41 | # without Rd present 42 | run_test("roxygenize", 43 | file_name = c("rpkg1/R/roxygenize.R" = "roxygenize.R"), 44 | suffix = "", 45 | cmd_args = "--root=rpkg1", 46 | std_err = "Please commit the new `.Rd` files", 47 | artifacts = c( 48 | "rpkg1/DESCRIPTION" = test_path("in/DESCRIPTION-no-deps.dcf"), 49 | "rpkg2/R/roxygenize.R" = test_path("in/roxygenize.R") 50 | ), 51 | file_transformer = function(files) { 52 | withr::local_dir("rpkg1") 53 | git_init() 54 | git2r::add(path = files) 55 | files 56 | } 57 | ) 58 | 59 | # with Rd present in wrong root 60 | run_test("roxygenize", 61 | file_name = c("R/roxygenize.R" = "roxygenize.R"), 62 | suffix = "", 63 | std_err = "Please commit the new `.Rd` files", 64 | artifacts = c( 65 | "DESCRIPTION" = test_path("in/DESCRIPTION-no-deps.dcf") 66 | ), 67 | file_transformer = function(files) { 68 | git_init() 69 | git2r::add(path = files) 70 | files 71 | } 72 | ) 73 | } 74 | 75 | 76 | 77 | 78 | 79 | # with up to date rd present 80 | run_test("roxygenize", 81 | file_name = c("man/flie.Rd" = "flie-true.Rd"), 82 | suffix = "", 83 | std_err = empty_on_cran("Writing NAMESPACE"), 84 | artifacts = c( 85 | "DESCRIPTION" = test_path("in/DESCRIPTION-no-deps.dcf"), 86 | "R/roxygenize.R" = test_path("in/roxygenize.R") 87 | ), 88 | file_transformer = function(files) { 89 | git_init() 90 | git2r::add(path = files) 91 | # hack to add artifact to trigger diff_requires_roxygenize() 92 | git2r::add(path = fs::path(fs::path_dir(fs::path_dir(files[1])), "R")) 93 | files 94 | }, 95 | expect_success = TRUE 96 | ) 97 | -------------------------------------------------------------------------------- /tests/testthat/test-hook-spell-check.R: -------------------------------------------------------------------------------- 1 | # success 2 | run_test("spell-check", suffix = "-success.md", std_err = NULL) 3 | 4 | # failure with --read-only does not create WORDLIST 5 | run_test( 6 | "spell-check", 7 | suffix = "-fail.md", 8 | std_err = "Spell check failed", 9 | cmd_args = "--read-only", 10 | read_only = TRUE 11 | ) 12 | 13 | if (!on_windows_on_cran()) { 14 | # failure with --read-only does not update WORDLIST 15 | run_test( 16 | "spell-check", 17 | suffix = "-fail-2.md", 18 | std_err = "Spell check failed", 19 | cmd_args = "--read-only", 20 | artifacts = c("inst/WORDLIST" = test_path("in/WORDLIST")), 21 | read_only = TRUE 22 | ) 23 | 24 | # success with wordlist 25 | run_test("spell-check", 26 | suffix = "-wordlist-success.md", 27 | std_err = NULL, 28 | artifacts = c("inst/WORDLIST" = test_path("in/WORDLIST")) 29 | ) 30 | 31 | # success with ignored files 32 | # uses lang argument 33 | run_test("spell-check", suffix = "-language-success.md", cmd_args = "--lang=en_GB") 34 | } 35 | -------------------------------------------------------------------------------- /tests/testthat/test-hook-style-files.R: -------------------------------------------------------------------------------- 1 | # success 2 | run_test("style-files", 3 | suffix = "-success.R", cmd_args = c("--cache-root=styler") 4 | ) 5 | # fail 6 | run_test("style-files", 7 | suffix = "-fail-changed.R", cmd_args = c("--cache-root=styler"), 8 | std_err = NA 9 | ) 10 | 11 | 12 | if (!on_windows_on_cran()) { 13 | run_test("style-files", 14 | suffix = "-fail-parse.R", cmd_args = c("--cache-root=styler"), 15 | std_err = "" 16 | ) 17 | 18 | # success with cmd args 19 | run_test("style-files", 20 | file_name = "style-files-cmd", 21 | suffix = "-success.R", 22 | cmd_args = c("--style_pkg=styler", "--style_fun=tidyverse_style", "--cache-root=styler") 23 | ) 24 | 25 | run_test("style-files", 26 | file_name = "style-files-cmd", 27 | suffix = "-success.R", 28 | cmd_args = c("--scope=spaces", "--cache-root=styler") 29 | ) 30 | 31 | run_test("style-files", 32 | file_name = "style-files-cmd", 33 | suffix = "-success.R", 34 | cmd_args = c('--scope="I(\'spaces\')"', "--cache-root=styler") 35 | ) 36 | 37 | run_test("style-files", 38 | file_name = "style-files-cmd", 39 | suffix = "-success.R", 40 | cmd_args = c( 41 | '--scope="I(\'spaces\')"', 42 | "--base_indention=0", 43 | "--include_roxygen_examples=TRUE", 44 | "--cache-root=styler" 45 | ) 46 | ) 47 | 48 | run_test("style-files", 49 | file_name = "style-files-reindention", 50 | suffix = "-success.R", 51 | cmd_args = c( 52 | '--scope="I(\'spaces\')"', 53 | "--base_indention=0", 54 | "--include_roxygen_examples=TRUE", 55 | '--reindention="specify_reindention(\'#\')"', 56 | "--cache-root=styler" 57 | ) 58 | ) 59 | 60 | run_test("style-files", 61 | file_name = "style-files", 62 | suffix = "-base-indention-success.R", 63 | cmd_args = c("--base_indention=4", "--cache-root=styler") 64 | ) 65 | 66 | run_test("style-files", 67 | file_name = "style-files", 68 | suffix = "-roxygen-success.R", 69 | cmd_args = c("--include_roxygen_examples=FALSE", "--cache-root=styler") 70 | ) 71 | 72 | run_test("style-files", 73 | file_name = "style-files", 74 | suffix = "-ignore-success.R", 75 | cmd_args = c( 76 | "--cache-root=styler", 77 | '--ignore-start="^# styler: off$"', 78 | '--ignore-stop="^# styler: on$"' 79 | ) 80 | ) 81 | 82 | run_test("style-files", 83 | file_name = "style-files", 84 | suffix = "-ignore-fail.R", 85 | cmd_args = "--cache-root=styler", 86 | std_err = "" 87 | ) 88 | 89 | 90 | # fail with cmd args 91 | run_test("style-files", 92 | file_name = "style-files-cmd", 93 | suffix = "-success.R", 94 | cmd_args = c("--scope=space", "--cache-root=styler"), 95 | std_err = "", 96 | expect_success = FALSE 97 | ) 98 | 99 | run_test("style-files", 100 | file_name = "style-files-cmd", 101 | suffix = "-fail.R", 102 | std_err = NA, 103 | cmd_args = c( 104 | "--style_pkg=styler", "--style_fun=tidyverse_style", "--cache-root=styler" 105 | ) 106 | ) 107 | 108 | run_test("style-files", 109 | file_name = "style-files-cmd", 110 | suffix = "-fail.R", 111 | std_err = "must be listed in `additional_dependencies:`", 112 | cmd_args = c( 113 | "--style_pkg=blubliblax", "--style_fun=tidyverse_style", "--cache-root=styler" 114 | ) 115 | ) 116 | } 117 | -------------------------------------------------------------------------------- /tests/testthat/test-hook-use-tidy-description.R: -------------------------------------------------------------------------------- 1 | # success 2 | run_test("use-tidy-description", "DESCRIPTION", suffix = "") 3 | 4 | 5 | 6 | if (!on_cran()) { 7 | # in sub directory with correct root 8 | run_test("use-tidy-description", 9 | "DESCRIPTION", 10 | suffix = "", 11 | cmd_args = "--root=rpkg", 12 | artifacts = c("rpkg/DESCRIPTION" = test_path("in/DESCRIPTION")) 13 | ) 14 | # in sub directory with incorrect root 15 | # Need to generate the directoy `rpkg` but without DESCRIPTION file. 16 | run_test("use-tidy-description", 17 | "DESCRIPTION", 18 | suffix = "", 19 | cmd_args = "--root=rpkg", 20 | std_err = "No `DESCRIPTION` found in repository.", 21 | artifacts = c("rpkg/README.md" = test_path("in/README.md")) 22 | ) 23 | } 24 | -------------------------------------------------------------------------------- /tests/testthat/test-impl-hooks-regex-exclude.R: -------------------------------------------------------------------------------- 1 | test_that("exclude regex for spell check hook matches expected files", { 2 | skip_if(not_conda()) 3 | skip_if(on_cran()) 4 | 5 | # declare 6 | re <- reticulate::import("re") 7 | 8 | pattern_read <- function(file) { 9 | readLines(file) %>% 10 | gsub("^ *exclude *: *>", " exclude: |", .) %>% 11 | yaml::yaml.load() %>% 12 | purrr::keep(~ .x$id == "spell-check") %>% 13 | magrittr::extract2(1) %>% 14 | magrittr::extract2("exclude") 15 | } 16 | 17 | is_match <- function(pattern, x) { 18 | matches <- purrr::map_lgl( 19 | x, 20 | ~ !is.null(re$match(pattern, .x, flags = re$VERBOSE)) 21 | ) 22 | if (any(!matches)) { 23 | rlang::abort(paste( 24 | "The following expressions don't match the pattern: ", 25 | paste0(x[!matches], collapse = ", "), ". Did you forget a trailing |?" 26 | )) 27 | } 28 | } 29 | 30 | files_to_match <- c( 31 | "data/x", 32 | ".Rprofile", 33 | ".Renviron", 34 | "renv.lock", 35 | "renv/settings.dcf", 36 | "vignettes/.gitignore", 37 | "NAMESPACE", 38 | "inst/WORDLIST", 39 | ".travis.yml", 40 | "appveyor.yml", 41 | "file.RData", 42 | ".Rbuildignore", 43 | "analysis.R", 44 | "linked/yids/gg.r", 45 | "things.py", 46 | "data/data.feather", 47 | "more/data.rds", 48 | "things/xx.Rds", 49 | ".pre-commit-", 50 | ".Rproj", 51 | "some/X.pdf", 52 | "figure.png", 53 | "pciture.jpeg", 54 | ".github/workflows/r-cmd-check.yaml" 55 | ) 56 | 57 | # run 58 | pattern <- pattern_read(system.file("pre-commit-hooks.yaml", package = "precommit")) 59 | 60 | individual_patterns <- setdiff( 61 | unlist(strsplit(pattern, "\n", fixed = TRUE)), 62 | c("(?x)^(", ")$") 63 | ) 64 | 65 | expect_equal(length(individual_patterns), length(files_to_match)) 66 | expect_silent(all(is_match( 67 | pattern, 68 | files_to_match 69 | ))) 70 | }) 71 | -------------------------------------------------------------------------------- /tests/testthat/test-setup.R: -------------------------------------------------------------------------------- 1 | test_that("snippet generation works for roxygen", { 2 | local_test_setup( 3 | git = FALSE, use_precommit = FALSE, package = TRUE, install_hooks = FALSE 4 | ) 5 | usethis::use_package("R", "Depends", "3.6.0") 6 | expect_error( 7 | out <- capture_output(snippet_generate("additional-deps-roxygenize")), 8 | NA, 9 | ) 10 | expect_equal(out, "") 11 | usethis::use_package("styler") 12 | expect_error( 13 | out <- capture_output(snippet_generate("additional-deps-roxygenize")), 14 | NA, 15 | ) 16 | 17 | expect_match( 18 | out, " - id: roxygenize\n.* - styler\n$", 19 | ) 20 | desc::desc_set("Remotes", "r-lib/styler") 21 | expect_warning( 22 | out <- capture_output(snippet_generate("additional-deps-roxygenize")), 23 | "you have remote dependencies " 24 | ) 25 | expect_match( 26 | out, " - id: roxygenize\n.* - styler\n$", 27 | ) 28 | }) 29 | 30 | 31 | test_that("snippet generation works for lintr", { 32 | local_test_setup( 33 | git = FALSE, use_precommit = FALSE, package = TRUE, install_hooks = FALSE 34 | ) 35 | usethis::use_package("R", "Depends", "3.6.0") 36 | expect_error( 37 | out <- capture_output(snippet_generate("additional-deps-lintr")), 38 | NA, 39 | ) 40 | expect_equal(out, "") 41 | usethis::use_package("styler") 42 | expect_error( 43 | out <- capture_output(snippet_generate("additional-deps-lintr")), 44 | NA, 45 | ) 46 | 47 | expect_match( 48 | out, " - id: lintr\n.* - styler\n$", 49 | ) 50 | desc::desc_set("Remotes", "r-lib/styler") 51 | expect_warning( 52 | out <- capture_output(snippet_generate("additional-deps-roxygenize")), 53 | "you have remote dependencies " 54 | ) 55 | expect_match( 56 | out, " - id: roxygenize\n.* - styler\n$", 57 | ) 58 | }) 59 | 60 | test_that("snippet generation only includes hard dependencies", { 61 | local_test_setup( 62 | git = FALSE, use_precommit = FALSE, package = TRUE, 63 | install_hooks = FALSE, open = FALSE 64 | ) 65 | usethis::use_package("styler") 66 | usethis::use_package("lintr", type = "Suggest") 67 | expect_warning( 68 | out <- capture_output(snippet_generate("additional-deps-roxygenize")), 69 | NA 70 | ) 71 | expect_match( 72 | out, " - id: roxygenize\n.* - styler\n$", 73 | ) 74 | }) 75 | 76 | 77 | test_that("GitHub Action CI setup works", { 78 | local_test_setup( 79 | git = FALSE, use_precommit = FALSE, package = TRUE, install_hooks = FALSE 80 | ) 81 | use_precommit_config( 82 | root = getwd(), 83 | open = FALSE, verbose = FALSE 84 | ) 85 | expect_error(use_ci("stuff", root = getwd()), "must be one of") 86 | use_ci("gha", root = getwd()) 87 | expect_true(file_exists(".github/workflows/pre-commit.yaml")) 88 | }) 89 | 90 | test_that("Pre-commit CI GitHub Action template is parsable", { 91 | expect_error( 92 | yaml::read_yaml(system.file("pre-commit-gha.yaml", package = "precommit")), 93 | NA 94 | ) 95 | }) 96 | 97 | test_that("Pre-commit CI setup works", { 98 | local_test_setup( 99 | git = FALSE, use_precommit = FALSE, package = TRUE, install_hooks = FALSE 100 | ) 101 | use_precommit_config( 102 | root = getwd(), 103 | open = FALSE, verbose = FALSE 104 | ) 105 | use_ci(root = getwd(), open = FALSE) 106 | expect_false(file_exists(".github/workflows/pre-commit.yaml")) 107 | }) 108 | 109 | test_that("Pre-commit CI setup works", { 110 | local_test_setup( 111 | git = FALSE, use_precommit = FALSE, package = TRUE, install_hooks = FALSE 112 | ) 113 | expect_error(use_ci(root = getwd(), open = FALSE), "o `.pre-commit-config.yaml`") 114 | }) 115 | -------------------------------------------------------------------------------- /tests/testthat/test-utils.R: -------------------------------------------------------------------------------- 1 | test_that("call capturing for pass", { 2 | expect_silent(captured <- call_and_capture("echo", "1")) 3 | expect_true( 4 | captured$exit_status == 0 5 | ) 6 | expect_true( 7 | length(captured$stdout) == 1 8 | ) 9 | 10 | expect_true( 11 | length(captured$stderr) == 0 12 | ) 13 | expect_warning( 14 | communicate_captured_call(captured), 15 | "stdout" 16 | ) 17 | }) 18 | 19 | test_that("git repo status can be evaluated", { 20 | tmpdir <- local_test_setup(git = FALSE) 21 | expect_false(is_git_repo(root = tmpdir)) 22 | git_init(tmpdir) 23 | expect_true(is_git_repo(root = tmpdir)) 24 | }) 25 | 26 | test_that("call capturing for error (command that does not exist)", { 27 | expect_silent(captured <- call_and_capture("j23lkjsdi", "1")) 28 | expect_true( 29 | captured$exit_status != 0 30 | ) 31 | expect_true( 32 | length(captured$stdout) == 0 33 | ) 34 | 35 | expect_true( 36 | length(captured$stderr) == 1 37 | ) 38 | if (is_windows()) { 39 | expected <- "Could not recover stderr." 40 | } else { 41 | expected <- "not found" 42 | } 43 | expect_error( 44 | communicate_captured_call(captured), 45 | expected 46 | ) 47 | }) 48 | 49 | test_that("call capturing for error fo command that exists (but arguments that don't)", { 50 | expect_silent(captured <- call_and_capture("ls", "djdfjdoidj")) 51 | expect_true( 52 | captured$exit_status != 0 53 | ) 54 | expect_true( 55 | length(captured$stdout) == 0 56 | ) 57 | 58 | expect_true( 59 | length(captured$stderr) > 0 60 | ) 61 | expect_error( 62 | communicate_captured_call(captured), 63 | "No such file or " 64 | ) 65 | }) 66 | 67 | test_that("inputs meet requirements", { 68 | expect_error(captured <- call_and_capture("echo", 1), "character vector.") 69 | expect_error(captured <- call_and_capture("echo", list("x")), "character vector.") 70 | }) 71 | -------------------------------------------------------------------------------- /tests/testthat/test-zzz.R: -------------------------------------------------------------------------------- 1 | test_that("tests don't write to styler-perm", { 2 | skip_on_cran() 3 | expect_false(file_exists( 4 | fs::path(R.cache::getCacheRootPath(), "styler-perm") 5 | )) 6 | }) 7 | -------------------------------------------------------------------------------- /vignettes/.gitignore: -------------------------------------------------------------------------------- 1 | *.R 2 | *.html 3 | -------------------------------------------------------------------------------- /vignettes/hook-order.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: "hook-order" 3 | output: rmarkdown::html_vignette 4 | vignette: > 5 | %\VignetteIndexEntry{hook-order} 6 | %\VignetteEngine{knitr::rmarkdown} 7 | %\VignetteEncoding{UTF-8} 8 | --- 9 | 10 | ```{r, include = FALSE} 11 | knitr::opts_chunk$set( 12 | collapse = TRUE, 13 | comment = "#>" 14 | ) 15 | ``` 16 | 17 | Read only hooks should generally run only after write hooks. 18 | 19 | ## Rules for editing this document 20 | 21 | - Only add a dependency once, i.e. if styler must run before roxygen, add the requirement to styler or roxygen, not both. This makes keeping track easier. 22 | - The hooks must appear in an order that meets all constraints, not just randomly order constraints. 23 | 24 | ## Hooks with dependencies 25 | 26 | ### Read and write 27 | 28 | - style-files - caches; should run before roxygenize because of the caching 29 | - roxygenize - caches 30 | - codemeta-description-updated - must be before use-tidy-description 31 | - use-tidy-description 32 | - spell-check - updates `inst/WORDLIST` (unless `--read-only` arg is specified); should run after roxygenize 33 | 34 | ### Read only 35 | 36 | - lintr - should run after styler 37 | - readme-rmd-rendered - must run after styler 38 | - parsable-R 39 | - no-browser-statement 40 | - no-print-statement 41 | - no-debug-statement 42 | - deps-in-desc 43 | - pkgdown 44 | -------------------------------------------------------------------------------- /vignettes/testing.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: "testing" 3 | output: rmarkdown::html_vignette 4 | vignette: > 5 | %\VignetteIndexEntry{testing} 6 | %\VignetteEncoding{UTF-8} 7 | %\VignetteEngine{knitr::rmarkdown} 8 | editor_options: 9 | markdown: 10 | wrap: 72 11 | --- 12 | 13 | ```{r, include = FALSE} 14 | knitr::opts_chunk$set( 15 | collapse = TRUE, 16 | comment = "#>" 17 | ) 18 | ``` 19 | 20 | pre-commit \>= 2.11.0 supports R as a language and each hook repo has 21 | its own virtual environment with package versions locked. For that 22 | reason, we should also test with exactly these versions. 23 | 24 | This package has five testing workflows: 25 | 26 | - hook testing. Tests the hooks (as well as hook script helpers?) in 27 | the renv in which they'll be shipped. No R CMD Check or pre-commit 28 | executable needed. All platforms. To avoid convolution of the 29 | testing environment (that contains testthat and other packages) and 30 | the hook environment (specified in `renv.lock`), we must only 31 | activate the hook environment right when the script is called, but 32 | `run_test()` must run in the testing environment. Since `--vanilla` 33 | is inherited in the child process initiated from `run_test()`, the 34 | only way to do this is to set an env variable when running 35 | `run_test()` and check in the user R profile if it is set, and then 36 | activate the renv. This is done with `R_PRECOMMIT_HOOK_ENV`. 37 | 38 | - complete testing: Tests hooks as well as usethis like access 39 | functionality with latest CRAN packages, on all platforms, with all 40 | installation methods. 41 | 42 | - CRAN testing. On CRAN, complete testing is ran, tests that check 43 | pre-commit executable access are disabled. 44 | 45 | - end-2-end testing: This simulates closely what happens when you run 46 | `git commit` with pre-commit activated: We update the 47 | `.pre-commit-config.yaml` to the pushed SHA and run 48 | `pre-commit run`, covering all hooks, to ensure in particular file 49 | linking, file permissions etc. in `.pre-commit-hooks.yaml` are 50 | correct for exported hooks. 51 | -------------------------------------------------------------------------------- /vignettes/why-use-hooks.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Why is this useful?" 3 | output: rmarkdown::html_vignette 4 | description: > 5 | Understand why we build this and why we think it's useful. 6 | vignette: > 7 | %\VignetteIndexEntry{why-use-hooks} 8 | %\VignetteEncoding{UTF-8} 9 | %\VignetteEngine{knitr::rmarkdown} 10 | editor_options: 11 | markdown: 12 | wrap: 72 13 | --- 14 | 15 | # Why use pre-commit hooks? 16 | 17 | **Better commit quality = better code quality** 18 | 19 | The goal of pre-commit hooks is to improve the quality of commits. This 20 | is achieved by making sure your commits meet some (formal) requirements, 21 | e.g: 22 | 23 | - that they comply to a certain coding style (with the hook 24 | `style-files`). 25 | 26 | - that you commit derivatives such as `README.md` or `.Rd` files with 27 | their source instead of spreading them over multiple commits. 28 | 29 | - and so on. 30 | 31 | # Why use the pre-commit framework? 32 | 33 | Using hooks from a framework like 34 | [pre-commit.com](https://pre-commit.com) has multiple benefits compared 35 | to using simple bash scripts locally in `.git/hooks` or use boilerplate 36 | code in other CI services to perform these tasks: 37 | 38 | - **Focus on your code**. Hooks are maintained, tested and documented 39 | *outside* of your repo, all you need a `.pre-commit-config.yaml` 40 | file to invoke them. No need to c/p hooks from one project to 41 | another or maintain boilerplate code. 42 | 43 | - **A declarative configuration file for routine checks**. File 44 | filtering for specific hooks, language version of hooks, when to 45 | trigger them (push, commit, merge), configuration options - all 46 | controlled via a single configuration file: 47 | `.pre-commit-config.yaml`. 48 | 49 | - **Locally and remotely. Or just one of the two**. You can use 50 | pre-commit locally and in the cloud with 51 | [pre-commit.ci](https://pre-commit.ci), where hooks can auto-fix 52 | issues like styling and push them back to GitHub. Exact same 53 | execution and configuration. 54 | 55 | - **Dependency isolation.** {precommit} leverages {renv} and hence 56 | ensures that anyone who uses the hooks uses the same version of the 57 | underlying tools, producing the same results, and does not touch 58 | your global R library or anything else unrelated to the hooks. 59 | 60 | - **No git history convolution**. Pre-commit detects problems before 61 | they enter your version control system, let's you fix them, or fixes 62 | them automatically. 63 | 64 | - **The power of the crowd.** Easily use hooks other people have 65 | created in bash, R, Python and other languages. There are a wealth 66 | of useful hooks available, most listed 67 | [here](https://pre-commit.com/hooks.html). For example, 68 | `check-added-large-files` prevents you from committing big files, 69 | other hooks validate json or yaml files and so on. 70 | 71 | - **Extensible.**. You can write your own R code to run as a hook, 72 | very easily. 73 | 74 | - **Standing on the shoulders of giants.** Leveraging 75 | [pre-commit.com](https://pre-commit.com) drastically reduces 76 | complexity and abstracts away a lot of logic that is not R specific 77 | for the maintainers of {precommit}. 78 | 79 | - **Independent.** pre-commit is not bound to GitHub, but runs on your 80 | local computer upon commit, and 81 | [pre-commit.ci](https://pre-commit.ci) will support on other git 82 | hosts than GitHub in the future. 83 | 84 | Have an idea for a hook? Please [file an 85 | issue](https://github.com/lorenzwalthert/precommit/issues). 86 | --------------------------------------------------------------------------------