├── .Rbuildignore ├── .Rprofile ├── .gitattributes ├── .github ├── .gitignore └── workflows │ ├── R-CMD-check.yaml │ ├── lint.yaml │ ├── pkgdown.yaml │ ├── pr-commands.yaml │ └── test-coverage.yaml ├── .gitignore ├── CODE_OF_CONDUCT.md ├── DESCRIPTION ├── LICENSE ├── LICENSE.md ├── NAMESPACE ├── NEWS.md ├── R └── add.R ├── README.Rmd ├── README.md ├── _pkgdown.yml ├── codecov.yml ├── man ├── add.Rd └── figures │ ├── 3.10.1.png │ ├── 3.11.1.png │ ├── 3.11.2.png │ ├── 3.11.3.png │ ├── 3.12.1.png │ ├── 3.2.1.png │ ├── 3.2.2.png │ ├── 3.2.3.png │ ├── 3.2.4.png │ ├── 3.3.1.png │ ├── 3.4.1.png │ ├── 3.5.1.png │ ├── 3.5.2.png │ ├── 3.7.1.png │ └── 3.8.1.png ├── rPackageTutorial.Rproj ├── renv.lock ├── renv ├── .gitignore ├── activate.R └── settings.dcf └── tests ├── testthat.R └── testthat └── test-add.R /.Rbuildignore: -------------------------------------------------------------------------------- 1 | ^renv$ 2 | ^renv\.lock$ 3 | ^rPackageTutorial\.Rproj$ 4 | ^\.Rproj\.user$ 5 | ^README\.Rmd$ 6 | ^\.github$ 7 | ^LICENSE\.md$ 8 | ^_pkgdown\.yml$ 9 | ^docs$ 10 | ^pkgdown$ 11 | ^codecov\.yml$ 12 | ^CODE_OF_CONDUCT\.md$ 13 | -------------------------------------------------------------------------------- /.Rprofile: -------------------------------------------------------------------------------- 1 | source("renv/activate.R") 2 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.github/.gitignore: -------------------------------------------------------------------------------- 1 | *.html 2 | -------------------------------------------------------------------------------- /.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 | - master 12 | pull_request: 13 | branches: 14 | - main 15 | - master 16 | 17 | name: R-CMD-check 18 | 19 | jobs: 20 | R-CMD-check: 21 | runs-on: ${{ matrix.config.os }} 22 | 23 | name: ${{ matrix.config.os }} (${{ matrix.config.r }}) 24 | 25 | strategy: 26 | fail-fast: false 27 | matrix: 28 | config: 29 | - {os: macOS-latest, r: 'release'} 30 | - {os: windows-latest, r: 'release'} 31 | - {os: windows-latest, r: '3.6'} 32 | # We explicitly set the user agent for R devel to the current release version of R so RSPM serves the release binaries. 33 | - {os: ubuntu-16.04, r: 'devel', rspm: "https://packagemanager.rstudio.com/cran/__linux__/xenial/latest", http-user-agent: "R/4.0.0 (ubuntu-16.04) R (4.0.0 x86_64-pc-linux-gnu x86_64 linux-gnu) on GitHub Actions" } 34 | - {os: ubuntu-16.04, r: 'release', rspm: "https://packagemanager.rstudio.com/cran/__linux__/xenial/latest"} 35 | - {os: ubuntu-16.04, r: 'oldrel', rspm: "https://packagemanager.rstudio.com/cran/__linux__/xenial/latest"} 36 | - {os: ubuntu-16.04, r: '3.5', rspm: "https://packagemanager.rstudio.com/cran/__linux__/xenial/latest"} 37 | - {os: ubuntu-16.04, r: '3.4', rspm: "https://packagemanager.rstudio.com/cran/__linux__/xenial/latest"} 38 | - {os: ubuntu-16.04, r: '3.3', rspm: "https://packagemanager.rstudio.com/cran/__linux__/xenial/latest"} 39 | 40 | env: 41 | R_REMOTES_NO_ERRORS_FROM_WARNINGS: true 42 | RSPM: ${{ matrix.config.rspm }} 43 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} 44 | 45 | steps: 46 | - uses: actions/checkout@v2 47 | 48 | - uses: r-lib/actions/setup-r@v1 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@v1 54 | 55 | - name: Query dependencies 56 | run: | 57 | install.packages('remotes') 58 | saveRDS(remotes::dev_package_deps(dependencies = TRUE), ".github/depends.Rds", version = 2) 59 | writeLines(sprintf("R-%i.%i", getRversion()$major, getRversion()$minor), ".github/R-version") 60 | shell: Rscript {0} 61 | 62 | - name: Cache R packages 63 | if: runner.os != 'Windows' 64 | uses: actions/cache@v2 65 | with: 66 | path: ${{ env.R_LIBS_USER }} 67 | key: ${{ runner.os }}-${{ hashFiles('.github/R-version') }}-1-${{ hashFiles('.github/depends.Rds') }} 68 | restore-keys: ${{ runner.os }}-${{ hashFiles('.github/R-version') }}-1- 69 | 70 | - name: Install system dependencies 71 | if: runner.os == 'Linux' 72 | run: | 73 | while read -r cmd 74 | do 75 | eval sudo $cmd 76 | done < <(Rscript -e 'writeLines(remotes::system_requirements("ubuntu", "16.04"))') 77 | 78 | - name: Install dependencies 79 | run: | 80 | remotes::install_deps(dependencies = TRUE) 81 | remotes::install_cran("rcmdcheck") 82 | shell: Rscript {0} 83 | 84 | - name: Session info 85 | run: | 86 | options(width = 100) 87 | pkgs <- installed.packages()[, "Package"] 88 | sessioninfo::session_info(pkgs, include_base = TRUE) 89 | shell: Rscript {0} 90 | 91 | - name: Check 92 | env: 93 | _R_CHECK_CRAN_INCOMING_: false 94 | run: | 95 | options(crayon.enabled = TRUE) 96 | rcmdcheck::rcmdcheck(args = c("--no-manual", "--as-cran"), error_on = "warning", check_dir = "check") 97 | shell: Rscript {0} 98 | 99 | - name: Show testthat output 100 | if: always() 101 | run: find check -name 'testthat.Rout*' -exec cat '{}' \; || true 102 | shell: bash 103 | 104 | - name: Upload check results 105 | if: failure() 106 | uses: actions/upload-artifact@main 107 | with: 108 | name: ${{ runner.os }}-r${{ matrix.config.r }}-results 109 | path: check 110 | -------------------------------------------------------------------------------- /.github/workflows/lint.yaml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: 4 | - main 5 | - master 6 | pull_request: 7 | branches: 8 | - main 9 | - master 10 | 11 | name: lint 12 | 13 | jobs: 14 | lint: 15 | runs-on: macOS-latest 16 | env: 17 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} 18 | steps: 19 | - uses: actions/checkout@v2 20 | 21 | - uses: r-lib/actions/setup-r@v1 22 | 23 | - name: Query dependencies 24 | run: | 25 | install.packages('remotes') 26 | saveRDS(remotes::dev_package_deps(dependencies = TRUE), ".github/depends.Rds", version = 2) 27 | writeLines(sprintf("R-%i.%i", getRversion()$major, getRversion()$minor), ".github/R-version") 28 | shell: Rscript {0} 29 | 30 | - name: Cache R packages 31 | uses: actions/cache@v2 32 | with: 33 | path: ${{ env.R_LIBS_USER }} 34 | key: ${{ runner.os }}-${{ hashFiles('.github/R-version') }}-1-${{ hashFiles('.github/depends.Rds') }} 35 | restore-keys: ${{ runner.os }}-${{ hashFiles('.github/R-version') }}-1- 36 | 37 | - name: Install dependencies 38 | run: | 39 | install.packages(c("remotes")) 40 | remotes::install_deps(dependencies = TRUE) 41 | remotes::install_cran("lintr") 42 | shell: Rscript {0} 43 | 44 | - name: Install package 45 | run: R CMD INSTALL . 46 | 47 | - name: Lint 48 | run: lintr::lint_package() 49 | shell: Rscript {0} 50 | -------------------------------------------------------------------------------- /.github/workflows/pkgdown.yaml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: 4 | - main 5 | - master 6 | 7 | name: pkgdown 8 | 9 | jobs: 10 | pkgdown: 11 | runs-on: macOS-latest 12 | env: 13 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} 14 | steps: 15 | - uses: actions/checkout@v2 16 | 17 | - uses: r-lib/actions/setup-r@v1 18 | 19 | - uses: r-lib/actions/setup-pandoc@v1 20 | 21 | - name: Query dependencies 22 | run: | 23 | install.packages('remotes') 24 | saveRDS(remotes::dev_package_deps(dependencies = TRUE), ".github/depends.Rds", version = 2) 25 | writeLines(sprintf("R-%i.%i", getRversion()$major, getRversion()$minor), ".github/R-version") 26 | shell: Rscript {0} 27 | 28 | - name: Cache R packages 29 | uses: actions/cache@v2 30 | with: 31 | path: ${{ env.R_LIBS_USER }} 32 | key: ${{ runner.os }}-${{ hashFiles('.github/R-version') }}-1-${{ hashFiles('.github/depends.Rds') }} 33 | restore-keys: ${{ runner.os }}-${{ hashFiles('.github/R-version') }}-1- 34 | 35 | - name: Install dependencies 36 | run: | 37 | remotes::install_deps(dependencies = TRUE) 38 | install.packages("pkgdown", type = "binary") 39 | shell: Rscript {0} 40 | 41 | - name: Install package 42 | run: R CMD INSTALL . 43 | 44 | - name: Deploy package 45 | run: | 46 | git config --local user.email "actions@github.com" 47 | git config --local user.name "GitHub Actions" 48 | Rscript -e 'pkgdown::deploy_to_branch(new_process = FALSE)' 49 | -------------------------------------------------------------------------------- /.github/workflows/pr-commands.yaml: -------------------------------------------------------------------------------- 1 | on: 2 | issue_comment: 3 | types: [created] 4 | name: Commands 5 | jobs: 6 | document: 7 | if: startsWith(github.event.comment.body, '/document') 8 | name: document 9 | runs-on: macOS-latest 10 | env: 11 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} 12 | steps: 13 | - uses: actions/checkout@v2 14 | - uses: r-lib/actions/pr-fetch@v1 15 | with: 16 | repo-token: ${{ secrets.GITHUB_TOKEN }} 17 | - uses: r-lib/actions/setup-r@v1 18 | - name: Install dependencies 19 | run: Rscript -e 'install.packages(c("remotes", "roxygen2"))' -e 'remotes::install_deps(dependencies = TRUE)' 20 | - name: Document 21 | run: Rscript -e 'roxygen2::roxygenise()' 22 | - name: commit 23 | run: | 24 | git config --local user.email "actions@github.com" 25 | git config --local user.name "GitHub Actions" 26 | git add man/\* NAMESPACE 27 | git commit -m 'Document' 28 | - uses: r-lib/actions/pr-push@v1 29 | with: 30 | repo-token: ${{ secrets.GITHUB_TOKEN }} 31 | style: 32 | if: startsWith(github.event.comment.body, '/style') 33 | name: style 34 | runs-on: macOS-latest 35 | env: 36 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} 37 | steps: 38 | - uses: actions/checkout@v2 39 | - uses: r-lib/actions/pr-fetch@v1 40 | with: 41 | repo-token: ${{ secrets.GITHUB_TOKEN }} 42 | - uses: r-lib/actions/setup-r@v1 43 | - name: Install dependencies 44 | run: Rscript -e 'install.packages("styler")' 45 | - name: Style 46 | run: Rscript -e 'styler::style_pkg()' 47 | - name: commit 48 | run: | 49 | git config --local user.email "actions@github.com" 50 | git config --local user.name "GitHub Actions" 51 | git add \*.R 52 | git commit -m 'Style' 53 | - uses: r-lib/actions/pr-push@v1 54 | with: 55 | repo-token: ${{ secrets.GITHUB_TOKEN }} 56 | -------------------------------------------------------------------------------- /.github/workflows/test-coverage.yaml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: 4 | - main 5 | - master 6 | pull_request: 7 | branches: 8 | - main 9 | - master 10 | 11 | name: test-coverage 12 | 13 | jobs: 14 | test-coverage: 15 | runs-on: macOS-latest 16 | env: 17 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} 18 | steps: 19 | - uses: actions/checkout@v2 20 | 21 | - uses: r-lib/actions/setup-r@v1 22 | 23 | - uses: r-lib/actions/setup-pandoc@v1 24 | 25 | - name: Query dependencies 26 | run: | 27 | install.packages('remotes') 28 | saveRDS(remotes::dev_package_deps(dependencies = TRUE), ".github/depends.Rds", version = 2) 29 | writeLines(sprintf("R-%i.%i", getRversion()$major, getRversion()$minor), ".github/R-version") 30 | shell: Rscript {0} 31 | 32 | - name: Cache R packages 33 | uses: actions/cache@v2 34 | with: 35 | path: ${{ env.R_LIBS_USER }} 36 | key: ${{ runner.os }}-${{ hashFiles('.github/R-version') }}-1-${{ hashFiles('.github/depends.Rds') }} 37 | restore-keys: ${{ runner.os }}-${{ hashFiles('.github/R-version') }}-1- 38 | 39 | - name: Install dependencies 40 | run: | 41 | install.packages(c("remotes")) 42 | remotes::install_deps(dependencies = TRUE) 43 | remotes::install_cran("covr") 44 | shell: Rscript {0} 45 | 46 | - name: Test coverage 47 | run: covr::codecov() 48 | shell: Rscript {0} 49 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .Rproj.user 2 | docs 3 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, religion, or sexual identity and 10 | orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | * Demonstrating empathy and kindness toward other people 21 | * Being respectful of differing opinions, viewpoints, and experiences 22 | * Giving and gracefully accepting constructive feedback 23 | * Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | * Focusing on what is best not just for us as individuals, but for the overall 26 | community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | * The use of sexualized language or imagery, and sexual attention or 31 | advances of any kind 32 | * Trolling, insulting or derogatory comments, and personal or political attacks 33 | * Public or private harassment 34 | * Publishing others' private information, such as a physical or email 35 | address, without their explicit permission 36 | * Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards 42 | of acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies 54 | when an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official e-mail 56 | address, posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the community leaders responsible for enforcement at [INSERT CONTACT 63 | METHOD]. All complaints will be reviewed and investigated promptly and fairly. 64 | 65 | All community leaders are obligated to respect the privacy and security of the 66 | reporter of any incident. 67 | 68 | ## Enforcement Guidelines 69 | 70 | Community leaders will follow these Community Impact Guidelines in determining 71 | the consequences for any action they deem in violation of this Code of Conduct: 72 | 73 | ### 1. Correction 74 | 75 | **Community Impact**: Use of inappropriate language or other behavior deemed 76 | unprofessional or unwelcome in the community. 77 | 78 | **Consequence**: A private, written warning from community leaders, providing 79 | clarity around the nature of the violation and an explanation of why the 80 | behavior was inappropriate. A public apology may be requested. 81 | 82 | ### 2. Warning 83 | 84 | **Community Impact**: A violation through a single incident or series of 85 | actions. 86 | 87 | **Consequence**: A warning with consequences for continued behavior. No 88 | interaction with the people involved, including unsolicited interaction with 89 | those enforcing the Code of Conduct, for a specified period of time. This 90 | includes avoiding interactions in community spaces as well as external channels 91 | like social media. Violating these terms may lead to a temporary or permanent 92 | ban. 93 | 94 | ### 3. Temporary Ban 95 | 96 | **Community Impact**: A serious violation of community standards, including 97 | sustained inappropriate behavior. 98 | 99 | **Consequence**: A temporary ban from any sort of interaction or public 100 | communication with the community for a specified period of time. No public or 101 | private interaction with the people involved, including unsolicited interaction 102 | with those enforcing the Code of Conduct, is allowed during this period. 103 | Violating these terms may lead to a permanent ban. 104 | 105 | ### 4. Permanent Ban 106 | 107 | **Community Impact**: Demonstrating a pattern of violation of community 108 | standards, including sustained inappropriate behavior, harassment of an 109 | individual, or aggression toward or disparagement of classes of individuals. 110 | 111 | **Consequence**: A permanent ban from any sort of public interaction within the 112 | community. 113 | 114 | ## Attribution 115 | 116 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 117 | version 2.0, 118 | available at https://www.contributor-covenant.org/version/2/0/ 119 | code_of_conduct.html. 120 | 121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct 122 | enforcement ladder](https://github.com/mozilla/diversity). 123 | 124 | [homepage]: https://www.contributor-covenant.org 125 | 126 | For answers to common questions about this code of conduct, see the FAQ at 127 | https://www.contributor-covenant.org/faq. Translations are available at https:// 128 | www.contributor-covenant.org/translations. 129 | -------------------------------------------------------------------------------- /DESCRIPTION: -------------------------------------------------------------------------------- 1 | Package: rPackageTutorial 2 | Title: Create R Package In A Professional Way Tutorial 3 | Version: 0.1.0 4 | Authors@R: 5 | person(given = "Wei", 6 | family = "Su", 7 | role = c("aut", "cre"), 8 | email = "swsoyee@gmail.com") 9 | Description: Show an example of how to create a r package in a professional way. 10 | License: MIT + file LICENSE 11 | Encoding: UTF-8 12 | LazyData: true 13 | Roxygen: list(markdown = TRUE) 14 | RoxygenNote: 7.1.1 15 | Suggests: 16 | testthat (>= 3.0.0), 17 | covr, 18 | renv 19 | Config/testthat/edition: 3 20 | URL: https://github.com/swsoyee/rPackageTutorial 21 | BugReports: https://github.com/swsoyee/rPackageTutorial/issues 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | YEAR: 2021 2 | COPYRIGHT HOLDER: rPackageTutorial authors 3 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # MIT License 2 | 3 | Copyright (c) 2021 rPackageTutorial authors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /NAMESPACE: -------------------------------------------------------------------------------- 1 | # Generated by roxygen2: do not edit by hand 2 | 3 | export(add) 4 | -------------------------------------------------------------------------------- /NEWS.md: -------------------------------------------------------------------------------- 1 | # rPackageTutorial 0.1.0 2 | 3 | * Added a `NEWS.md` file to track changes to the package. 4 | -------------------------------------------------------------------------------- /R/add.R: -------------------------------------------------------------------------------- 1 | #' Returns the sum of two numbers 2 | #' 3 | #' @param a Number one. 4 | #' @param b Number two. 5 | #' 6 | #' @return Sum of two number. 7 | #' @export 8 | #' 9 | #' @examples 10 | #' library(rPackageTutorial) 11 | #' add(1, 2) 12 | add <- function(a, b) { 13 | a + b 14 | } 15 | -------------------------------------------------------------------------------- /README.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | output: github_document 3 | --- 4 | 5 | 6 | 7 | ```{r, include = FALSE} 8 | knitr::opts_chunk$set( 9 | collapse = TRUE, 10 | comment = "#>", 11 | fig.path = "man/figures/README-", 12 | out.width = "100%" 13 | ) 14 | ``` 15 | 16 | # rPackageTutorial 17 | 18 | 19 | [![Codecov test coverage](https://codecov.io/gh/swsoyee/rPackageTutorial/branch/main/graph/badge.svg)](https://codecov.io/gh/swsoyee/rPackageTutorial?branch=main) 20 | [![R-CMD-check](https://github.com/swsoyee/rPackageTutorial/workflows/R-CMD-check/badge.svg)](https://github.com/swsoyee/rPackageTutorial/actions) 21 | 22 | 23 | `{rPackageTutorial}` is an easy-to-understand tutorial about how to create an R package with some basic settings in a professional way for those R beginner to quickly experience the development process. 24 | 25 | ## 1. 前言 26 | 27 | 现在的中文网络上其实并不缺乏教新手如何去创建和开发一个 R 包,大致有基于命令行和 RStudio 截图的方式两种方式手把手的把每一步都很好地传授给读者。 28 | 例如,只要很随意的搜索一下,就能找到不少可以参考的资料: 29 | 30 | 1. [开发 R 程序包之忍者篇](https://cosx.org/2011/05/write-r-packages-like-a-ninja/); 31 | 2. [极简 R 包建立方法](https://cosx.org/2013/11/building-r-packages-easily/); 32 | 3. [R包开发](https://r-packages-zh-cn.readthedocs.io/zh_CN/latest/index.html); 33 | ...... 34 | 35 | 还有数不胜数的学习笔记和知识分享类的文章。那么问题来了,为什么我还要再写一篇来分享如何创建 R 包的文章呢? 36 | 动机其实也很简单,由于 R 社区的不断发展,`{usethis}`、`{testthat}`、`{styler}`、`{lintr}`、`{pkgdown}` 等等各类便于开发的工具层出不穷,在这些工具的帮助下,开发一个包的学习曲线愈发地降低。 37 | 而现在中文网络中基于这些更为现代化工具的开发新手级入门教程还是尚且偏少。 38 | 即使有(如上述参考链接中的 [3. R包开发](https://r-packages-zh-cn.readthedocs.io/zh_CN/latest/index.html)),略微过于详细,没有花上个把两天是很难体验到全流程的。 39 | 40 | 因此,本文的主要目的就是用最短的时间,用专业的(自称)方式创建一个高逼格的 R 包并且托管在 GitHub 上。 41 | 也因此,会对很多局部的细节不做过多的讲解,先把流程跑通,对全流程有个大致的理解后,想了解细节时再去做具体调查即可。 42 | 43 | ## 2. 准备工作 44 | 45 | 在开发前,你只需要完成 2 项工作: 46 | 47 | 1. 为了使用最简单易懂的方式进行操作,下载并且安装好 RStudio(R环境当然是默认就具备了)就不用说了; 48 | 2. 一个 GitHub 账号(相关教程太多,这里也不展开讲解); 49 | 3. GitHub Desktop(Git操作的图形界面,非必需但是方便了解代码发生了什么变化)。 50 | 51 | 此外,在 GitHub 上也放了一个和本教程相关的仓库([swsoyee/rPackageTutorial](https://github.com/swsoyee/rPackageTutorial))。 52 | 也可以参看[每一次 Commit(提交)](https://github.com/swsoyee/rPackageTutorial/commits/main)来了解每一步操作具体发生了什么变化,从而加深理解。 53 | 每一步操作都没有唯一答案,所写的内容也只是其中的一个可供参考的步骤而已。 54 | 55 | > 部分界面截图如 RStudio 和 GitHub 的界面可能会有所不同是因为我个人进行了相关主题或者插件设置,不影响文章内容理解。 56 | 57 | ## 3. 创建 R 包 58 | 59 | 在文章中,我将展示编写一个只包含有两个数加法函数的包作为例子。 60 | 包的功能本身不是关键,关键是构建一个专业的 R 包其拥有的功能以外的整个外部环境。 61 | 再次重复一下前言中所提到的,很多细节部分不会过多讲解,但是会提点到关键词,因此有需要的可以根据关键词去搜索即可补充了解所需知识。 62 | 好,废话不再多说,那就正式开始了。 63 | 64 | ### 3.1 用 `{usethis}` 开始创建 65 | 66 | 为了使得重现步骤更为简单,在这里我们基本全部使用代码的方式来完成创建工作。首先随便打开一个 R 的进程,默认或者在 RStudio 中都可以。 67 | 68 | ```r 69 | # 如果没有 {usethis} 包的话,先下载安装。 70 | install.packages("usethis") 71 | 72 | # 命名一个 {rPackageTutorial} 包,并且创建,path 可以填写你想创建在哪个文件夹中。 73 | # 这里我们选择在当前路径创建该包 74 | usethis::create_package(path = "rPackageTutorial") 75 | ``` 76 | 77 | 输入完毕后,自动会弹出一个新的 RStudio 窗口,并且自动设定了 `rPackageTutorial` 为当前工作文件夹,名为 `{rPackageTutorial}` 就创建好了。 78 | ~~本期教程结束,谢谢!~~ 79 | 80 | ### 3.2 加入版本控制(Git) 81 | 82 | 这一步的方法有很多种,这里写的是本人用的最简单的一种。 83 | 打开 GitHub Desktop(默认已经完成了登陆 GitHub 账号的操作),点击 `Add > Add Existing Repository`,在弹出的 `Add Local Repository` 中选择所创建好的 `{rPackageTutorial}` 路径。 84 | 85 | ![3.2.1 使用 GitHub Desktop 添加本地项目。](man/figures/3.2.1.png) 86 | 87 | 会提示您选择的文件夹还没有包含 Git 的相关设定,因此只需要继续点击 `create a repository` 创建相关设定即可。 88 | 89 | ![3.2.2 选择本地项目地址后点击 `create a repository` 创建相关设定。](man/figures/3.2.2.png) 90 | 91 | 在下一步的 `Create a New Repository` 中可以什么都不改,直接点击右下角的 `Create Repository` 按钮完成项目的创建。 92 | 93 | ![3.2.3 填写云端项目信息内容后按 `Create a New Repository` 创建。](man/figures/3.2.3.png) 94 | 95 | 至此,本地的设定就完成了,只需要点击 `Publish repository`,然后设置公不公开后即可将项目推送到你的 GitHub 中去了。 96 | 可以点击这里 [`d78d245`](https://github.com/swsoyee/rPackageTutorial/commit/d78d245c641b4208de2ed726f764e4b4a38ab3ed) 查看第一次推送时候的变更内容。 97 | 98 | ![3.2.4 点击 `Publish repository` 推送到云端 GitHub 上。](man/figures/3.2.4.png) 99 | 100 | ### 3.3 用 `{renv}` 进行依赖(加载包)的版本控制 101 | 102 | 让我们回到 RStudio,这次我们引入的是 [`{renv}`](https://rstudio.github.io/renv/index.html) 这个包来进行依赖的版本控制。 103 | 用最简单的话来说,就是为了自己写的包不会因为所使用的一些别人写的包发生大规模改变的时候自己的包也跟着受到影响,和让合作开发者能迅速构建和你同样的开发环境,减少由于所使用的包版本不同导致的问题发生。 104 | 105 | ```r 106 | # 安装 CRAN 版的 {renv} 107 | install.packages("renv") 108 | 109 | # 初始化环境 110 | renv::init() 111 | ``` 112 | 113 | 初始化时候的信息: 114 | 115 | ``` 116 | * Initializing project ... 117 | * Discovering package dependencies ... Done! 118 | * Copying packages into the cache ... Done! 119 | The following package(s) will be updated in the lockfile: 120 | 121 | # CRAN =============================== 122 | - renv [* -> 0.12.5] 123 | 124 | * Lockfile written to '~/Documents/GitHub/rPackageTutorial/renv.lock'. 125 | 126 | Restarting R session... 127 | 128 | * Project '~/Documents/GitHub/rPackageTutorial' loaded. [renv 0.12.5] 129 | ``` 130 | 131 | ```r 132 | # 保存当前所用的包环境,当然我们才刚刚开始开发,别的包都没有引入 133 | renv::snapshot() 134 | ``` 135 | 136 | 用于记录依赖的 `lockfile` 已经更新完毕: 137 | 138 | ``` 139 | * The lockfile is already up to date. 140 | ``` 141 | 142 | 在持续开发的过程中,只要用到下述几个常用命令即可,更多的可以到[文档](https://rstudio.github.io/renv/index.html)中了解: 143 | 144 | - 如果有引入新的包,运行 `renv::snapshot()` 进行 `lockfile` 的更新; 145 | - 如果想更新所使用的包,运行 `renv::update()` 进行包本身的更新; 146 | - 如果换了电脑进行开发,运行 `renv::restore()` 恢复到开发时用的包环境; 147 | 148 | 完成这一步后,不要忘记进行 Git 的提交工作。 149 | 让我们回到 GitHub Desktop,在 `Summary (required)` 中提交本次改动的内容(在这里我写了 `使用renv进行环境初始化`),点击 `Commit to main` 完成本地提交后,点击 `Push origin` 推送到云端(GitHub)上([`2493654`](https://github.com/swsoyee/rPackageTutorial/commit/229365403eadbf8ed4ff49171a3f298a16e3c12e))。 150 | 151 | ![3.3.1 使用 GitHub Desktop 进行 Git 操作对于新手较为友好。](man/figures/3.3.1.png) 152 | 153 | 至于如何写好提交信息可以参考[Angular提交信息规范](https://zj-git-guide.readthedocs.io/zh_CN/latest/message/Angular%E6%8F%90%E4%BA%A4%E4%BF%A1%E6%81%AF%E8%A7%84%E8%8C%83/),如果要显得包较为专业的话,那么这些小细节也是不容忽视的。 154 | 在这之后将不再赘述每一步操作的提交工作,可以参考示例项目的提交记录即可。 155 | 156 | > 注:在本次教程中没有每次都适时的进行 `renv::snapshot()` 的包更新操作,在开发当中,每次引入或者使用新包的适合都建议进行一次 `lockfile` 的更新([`bc5051d`](https://github.com/swsoyee/rPackageTutorial/commit/bc5051d1ed25a9d1ec36d139686acc84cfdf9f38))。 157 | 158 | 除了使用 `{renv}` 进行版本控制以外,还有一种在开发 R 包时更为普遍的方式,就是在 `DESCRIPTION` 中用添加会使用或者是建议使用的包的信息。 159 | 我们可以使用下述命令进行快速添加: 160 | ```r 161 | # 如果包为使用时必须的,则需要设置 type = "Imports" 162 | # {renv} 在这里只是开发必备,而非使用所开发的包必备,因此选择 "Suggests" 即可 163 | usethis::use_package(package = "renv", type = "Suggests") 164 | ``` 165 | 166 | 变更结果可参考提交[`65e4fc4`](https://github.com/swsoyee/rPackageTutorial/commit/65e4fc4371d433fe1e883cea73e97bc692dcbf40)。 167 | 对于非 R 包的项目(比如说用 `{shiny}` 来开发一个仪表盘应用)来说,用 `{renv}` 进行依赖管理就会显得非常重要。 168 | 而在 R 包的开发中,标准的做法也就是用 `DESCRIPTION` 来记录依赖的版本信息。 169 | 不过 `renv::restore()` 一条命令恢复开发环境的力量还是很香的,推荐使用度 + 1。 170 | 171 | ### 3.4 创建 README 172 | 173 | README 是一个项目的入口,因此拥有一个(至少看起来)专业的 README 是必不可少的。 174 | 在这里我们直接用命令来进行创建: 175 | 176 | ```r 177 | # 引入必备包 {rmarkdown} 178 | install.packages("rmarkdown") 179 | 180 | # 虽然也可以使用 usethis::use_readme_md(),看个人需求(不详细讲解区别) 181 | usethis::use_readme_rmd() 182 | ``` 183 | 一个默认的 `README.Rmd` 模版就创建完成了,你可以自由的编写内容后点击 `Knit` 从而生成所需要的 `README.md` 文档。 184 | 185 | > 注:由于设定,每一次编写后必须要点击 `Knit` 进行更新才能提交本次变更。 186 | 187 | 第一次 `Knit` 的时候,在 `README.Rmd` 会有你的包加载的这一条命令,因此需要先将包成功打包一次才能成功执行,否则会报错。 188 | 或者你也可以先把自动生成的模版中的 `library(rPackageTutorial)` 先注释掉即可。 189 | 190 | ![3.4.1 由于图中选择的加载包的代码存在,因此需要先将包成功打包一次才能成功执行。可以选择右侧面板中的 `Build` 标签,按 `Install and Restart` 即可。](man/figures/3.4.1.png) 191 | 192 | 完成后回到 GitHub Desktop 进行提交([`658b233`](https://github.com/swsoyee/rPackageTutorial/commit/658b2338783bd1a7ec2f8fcf75c2eb675cd66049))。 193 | 194 | ### 3.5 编写函数 195 | 196 | 来到 R 包的函数编写部分了,让我们直接在根目录的 R 文件夹下建立一个 `add.R` 文件来保存我们写好的函数。 197 | 一个函数一个文件的管理方式是比较稳妥的,如果是每一个函数都很简单的同一个类函数的话也可以放到同一个文件中。 198 | 199 | ```r 200 | # 通过 RStudio 的 File > New File > R Script 也一样 201 | file.create("R/add.R") 202 | 203 | # 打开文件开始编写,写入下列内容 204 | add<-function(a,b) { 205 | a+b 206 | } 207 | ``` 208 | 209 | 至此,我们只是在一个文件里写了一个函数,但是还没有让这个函数包含到我们的包中。 210 | 这时候,就需要插入一些文档让打包的时候能够识别这个函数。 211 | 这一步我们可以点击图中左上的 **魔术棒** 按钮(放大镜图标的右侧),点击 `Insert Roxygen Skeleton` 快速生成文档骨架。 212 | 213 | ![3.5.1 在 `R` 文件夹h中创建 `add.R` 文件用于保存函数,点击 `Insert Roxygen Skeleton` 快速生成。](man/figures/3.5.1.png) 214 | 215 | ```r 216 | # 点击 Insert Roxygen Skeleton 后就会出现文档结构 217 | #' Title 218 | #' 219 | #' @param a 220 | #' @param b 221 | #' 222 | #' @return 223 | #' @export 224 | #' 225 | #' @examples 226 | add<-function(a,b) { 227 | a+b 228 | } 229 | 230 | # 让我们完成编写 231 | #' Returns the sum of two numbers 232 | #' 233 | #' @param a Number one. 234 | #' @param b Number two. 235 | #' 236 | #' @return Sum of two number. 237 | #' @export 238 | #' 239 | #' @examples 240 | #' library(rPackageTutorial) 241 | #' add(1, 2) 242 | add<-function(a,b) { 243 | a+b 244 | } 245 | 246 | # 写完后,需要生成文档才能够真正使用 247 | devtools::document() 248 | ``` 249 | 250 | 输出结果: 251 | 252 | ``` 253 | Updating rPackageTutorial documentation 254 | Loading rPackageTutorial 255 | Writing NAMESPACE 256 | Writing add.Rd 257 | ``` 258 | 259 | 之后再按一下 `Install and Restart` 就可以使用了。也可以在命令行中使用 `?add` 来查看一下自己定义的帮助文档。 260 | 261 | ![3.5.2 输入 `devtools::document()` 更新文档,然后点击 `Install and Restart` 重新打包并自动重新加载包,可用 `?add` 来确认没有问题。](man/figures/3.5.2.png) 262 | 263 | 最后,回到 GitHub Desktop 中提交本次更改([`88f0c57`](https://github.com/swsoyee/rPackageTutorial/commit/88f0c57f538d6b71bda138acc76ffd09aa7b1b18))。 264 | 至此,你的包就创建完成啦!~~本期教程结束,再次感谢!~~ 265 | 266 | ### 3.6 用 `{styler}` 来美化代码 267 | 268 | 包是完成了,但是对于具有强迫症的程序员来说,代码似乎并不美观? 269 | 那这个时候就交由 [`{styler}`](https://styler.r-lib.org/) 来替我们美化代码吧。 270 | 271 | ```r 272 | # 安装 {styler} 273 | install.packages("styler") 274 | 275 | # 对整个包进行代码美化 276 | styler::style_pkg() 277 | ``` 278 | 279 | 结果显示,一个文件被进行了代码格式上的修改,一些空格被自动添加到了里面: 280 | 281 | ``` 282 | Styling 2 files: 283 | R/add.R ℹ 284 | .Rprofile ✓ 285 | ──────────────────────────────────────── 286 | Status Count Legend 287 | ✓ 1 File unchanged. 288 | ℹ 1 File changed. 289 | x 0 Styling threw an error. 290 | ──────────────────────────────────────── 291 | Please review the changes carefully! 292 | ``` 293 | 294 | 由于我们的函数很简单,当开发一个较大的、较为复杂的包的时候,那么就能看到很明显的变化了。 295 | 如果别人帮你改进了部分代码(提交了一个 Pull Request)却没有进行代码优化那怎么办呢?难道每次都要手动提醒对方吗注意修改格式?别着急,后续将会告诉你答案。 296 | 还是那句话,不要忘记提交本次更改([`08df7c2`](https://github.com/swsoyee/rPackageTutorial/commit/08df7c21909d4005c788fe1f101a1441bd1e5f90))。 297 | 298 | ### 3.7 用 `{lintr}` 来规范代码 299 | 300 | 命名法一直都是码农界一个经常讨论的问题,不同语言有不同的较为规范的命名法则,有些人喜欢用驼峰式,有些喜欢下划线式。 301 | 如果在别人好心帮你改进了代码但是没有遵循一定的法则,作为项目创建者的你要在格式上面指指点点,Pull Request 的作者也需要回应你的指点,很有可能搞得双方都不是特别舒服。 302 | 如果在项目建立当初就设定了规范,让规则来说话,就能很好地避免这种冲突。这里我们使用 [`{lintr}`](https://github.com/jimhester/lintr) 来快速帮我们解决这个问题。 303 | 304 | ```r 305 | # 安装 {lintr} 306 | install.packages("lintr") 307 | 308 | # 对整个包进行不符合规范的代码查询(当然,都没有写多少代码,当然不会出现什么错误结果) 309 | lintr::lint_package() 310 | 311 | # 比如我们在 add 函数中增加一个超过80字符的注释 312 | add <- function(a, b) { 313 | # aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa 314 | a + b 315 | } 316 | 317 | # 再次执行 318 | lintr::lint_package() 319 | ``` 320 | 321 | 命令完成后会在 RStudio 中弹出一个 Markers 的面板,显示: 322 | ``` 323 | Line 13 lines should not be more than 80 characters. 324 | ``` 325 | 326 | 嗯,当然太长的代码会不利于阅读,因此默认会限制了代码长度。 327 | 各种设置都可以在 [`{lintr}`](https://github.com/jimhester/lintr) 中进行参考,一般我们都使用默认规则即可。 328 | 如果需要自定义规则,则需要在项目根目录下创建一个 `.lintr` 文件,这部分可参考官方文档进行设置,这里不做赘述。 329 | 我们可以在利用 GitHub Action 来对提交的代码进行规范检查,这样一来如果有不规范代码被提交的话会自动提示错误,这样你就可以要求代码提交这进行更改了。 330 | 331 | ```r 332 | # 在 GitHub Action 中设置 lintr 333 | usethis::use_github_action("lint") 334 | ``` 335 | 运行结果: 336 | ``` 337 | ✓ Setting active project to '/Users/suwei/Documents/GitHub/rPackageTutorial' 338 | ✓ Creating '.github/' 339 | ✓ Adding '^\\.github$' to '.Rbuildignore' 340 | ✓ Adding '*.html' to '.github/.gitignore' 341 | ✓ Creating '.github/workflows/' 342 | ✓ Writing '.github/workflows/lint.yaml' 343 | ``` 344 | 345 | 老样子,提交并推送代码到 GitHub 上([`454c7f6`](https://github.com/swsoyee/rPackageTutorial/commit/454c7f6bf604d6ecb84c04848b1c923750193cd2))。 346 | 在这之后的每次当你提交的代码通过检查的时候,就会有一个绿色的小勾表示通过,是不是稍微显得专业一点了呢? 347 | 348 | ![3.7.1 多出了一个小~~狗狗~~勾勾来显示我们的代码完全没问题,已通过检查。](man/figures/3.7.1.png) 349 | 350 | ### 3.8 用 `{testthat}` 来测试代码 351 | 352 | 在编写一个具有可靠性的包的时候,特别是当函数变得复杂的时候,测试是必不可少的。 353 | 我们可以用 `{usethis}` 来快速设定编写测试所需要的 `{testthat}` 的环境。 354 | 355 | ```r 356 | # 设定环境 357 | usethis::use_testthat() 358 | ``` 359 | 弹出运行结果: 360 | ``` 361 | ✓ Adding 'testthat' to Suggests field in DESCRIPTION 362 | ✓ Setting Config/testthat/edition field in DESCRIPTION to '3' 363 | ✓ Creating 'tests/testthat/' 364 | ✓ Writing 'tests/testthat.R' 365 | ● Call `use_test()` to initialize a basic test file and open it for editing. 366 | ``` 367 | 368 | ```r 369 | # 安装 {testthat} 370 | install.packages("testthat") 371 | 372 | # 快速创建测试文件 373 | > usethis::use_test() 374 | ``` 375 | 弹出运行结果: 376 | ``` 377 | ✓ Writing 'tests/testthat/test-add.R' 378 | ● Modify 'tests/testthat/test-add.R' 379 | ``` 380 | 381 | ```r 382 | # 在自动生成的测试文件中包含了下述代码 383 | test_that("multiplication works", { 384 | expect_equal(2 * 2, 4) 385 | }) 386 | 387 | # 将其修改成有意义的测试 388 | test_that("add() function return the sum of two number", { 389 | expect_equal(add(1, 2), 3) 390 | }) 391 | ``` 392 | 393 | 在写完测试后,可以点击编写面板的右上角 `Run Tests` 进行单文件测试,也可以选择 `Addins > Report test coverage for a package` 进行整个包的函数测试覆盖率的测算。 394 | 395 | ![3.8.1 运行测试的方法多种多样,最简单的单文件测试是点击编辑界面右上角的 `Run Tests`。](man/figures/3.8.1.png) 396 | 397 | 当然还有其他办法来达成同样的目的,这只是其中一种方法而已。 398 | 但如果他人对你的代码进行修改或者实现了功能时,通过什么办法确保对方修改的代码不会造成 Bug 呢? 399 | 同样是通过 GitHub Action 来进行实现,稍后将会讲解到。回到 GitHub Desktop 提交我们本次的修改([`2655b2a`](https://github.com/swsoyee/rPackageTutorial/commit/2655b2a9b936148a33ae50c30d7e8c6bb7ea7095))。 400 | 401 | ### 3.9 编写包的说明 402 | 403 | 在别的教程中,一般都会把包的说明放到前面,说明这也是很重要的一步。 404 | 在这里,为了把说明部分都汇总到一起,就将其放到了偏后的部分。 405 | 406 | 打开根目录下的 `DESCRIPTION` 文件,对包说明进行一些修改,主要是 Title、Authors 和 Description 部分,别的都可以原封不动: 407 | 408 | ``` 409 | Package: rPackageTutorial 410 | Title: Create R Package In A Professional Way Tutorial 411 | Version: 0.0.0.9000 412 | Authors@R: 413 | person(given = "Wei", 414 | family = "Su", 415 | role = c("aut", "cre"), 416 | email = "swsoyee@gmail.com") 417 | Description: Show an example of how to create a r package in a professional way. 418 | License: `use_mit_license()`, `use_gpl3_license()` or friends to 419 | pick a license 420 | Encoding: UTF-8 421 | LazyData: true 422 | Roxygen: list(markdown = TRUE) 423 | RoxygenNote: 7.1.1 424 | Suggests: 425 | testthat (>= 3.0.0) 426 | Config/testthat/edition: 3 427 | ``` 428 | 429 | 通过 `{usethis}` 来修改 License 和 Version 信息: 430 | ```r 431 | # 如果没有依赖到别的具有不同版权的第三方包的话,一般选择最为广泛使用的 MIT 即可 432 | usethis::use_mit_license() 433 | ``` 434 | 结果显示: 435 | ``` 436 | ✓ Setting active project to '/Users/suwei/Documents/GitHub/rPackageTutorial' 437 | ✓ Setting License field in DESCRIPTION to 'MIT + file LICENSE' 438 | ✓ Writing 'LICENSE' 439 | ✓ Writing 'LICENSE.md' 440 | ✓ Adding '^LICENSE\\.md$' to '.Rbuildignore' 441 | ``` 442 | 443 | ```r 444 | # 升级版本号 445 | usethis::use_version() 446 | ``` 447 | ``` 448 | There are uncommitted changes and you're about to bump version 449 | Do you want to proceed anyway? 450 | 451 | 1: Negative 452 | 2: No 453 | 3: Yeah 454 | 455 | Selection: 3 456 | Current version is 0.0.0.9000. 457 | Which part to increment? (0 to exit) 458 | 459 | 1: major --> 1.0.0 460 | 2: minor --> 0.1.0 461 | 3: patch --> 0.0.1 462 | 4: dev --> 0.0.0.9001 463 | 464 | Selection: 2 465 | ✓ Setting Version field in DESCRIPTION to '0.1.0' 466 | There is 1 uncommitted file: 467 | * 'DESCRIPTION' 468 | Is it ok to commit it? 469 | 470 | 1: No way 471 | 2: I agree 472 | 3: Absolutely not 473 | 474 | Selection: 3 475 | ``` 476 | 477 | 最后,提交本次变更([`7052d68`](https://github.com/swsoyee/rPackageTutorial/commit/7052d684f41e6cf36a6c9f1d9527743456f6ecaa))。 478 | 只有在完成 `DESCRIPTION` 的合理编写后,运行所编写的 R 包的检查时,才能够无错误通过。 479 | 480 | ```r 481 | # 轻量版检查 482 | devtools::check() 483 | ``` 484 | 省略中途的信息: 485 | ``` 486 | ── R CMD check results ───────────────────────────── rPackageTutorial 0.1.0 ──── 487 | Duration: 36.8s 488 | 489 | 0 errors ✓ | 0 warnings ✓ | 0 notes ✓ 490 | 491 | R CMD check succeeded 492 | ``` 493 | 494 | 一切安好。做到这个地步,你的包已经逐渐流露出专业的味道了。 495 | 496 | ### 3.10 用 `{pkgdown}` 制作包的说明书 497 | 498 | 在很多较为专业的包中,经常能看到会有一个包的专门的网站(在线版)说明书以供用户查阅。 499 | 就算我们只创建了一个 1 + 1 = 2 的函数,该有的逼格可必须得有。让我们同样使用 [`{pkgdown}`](https://pkgdown.r-lib.org/) 来完成创建。 500 | 501 | ```r 502 | # 安装 {pkgdown} 503 | install.packages("pkgdown") 504 | 505 | # 初始化你的用户手册网站 506 | usethis::use_pkgdown() 507 | ``` 508 | 结果显示: 509 | ``` 510 | ✓ Adding '^_pkgdown\\.yml$', '^docs$' to '.Rbuildignore' 511 | ✓ Adding '^pkgdown$' to '.Rbuildignore' 512 | ✓ Adding 'docs' to '.gitignore' 513 | ● Record your site's url in the pkgdown config file (optional, but recommended) 514 | ● Modify '_pkgdown.yml' 515 | ``` 516 | 517 | ```r 518 | # 让我们来试一下看创建我们的网站 519 | pkgdown::build_site() 520 | ``` 521 | 522 | ![3.10.1 嗯,有内味儿了。](man/figures/3.10.1.png) 523 | 524 | 要对网站进行定制化,只需要给 `_pkgdown.yml` 中添加配置即可。 525 | 具体可以参考文档说明,因此在这里也不赘述了。 526 | 还是别忘了提交本次变更([`18b5d45`](https://github.com/swsoyee/rPackageTutorial/commit/18b5d4539bc3ce738532633c65901aa1f0b49957))。 527 | 528 | ### 3.11 用 Github Action 自动检查 529 | 530 | 当包越来越复杂,代码和文档都大量增加的时候,特别是如果还是想搞开源,期待他人能更好地参与到由你主导的 R 包开发时,就需要设定一些自动检查的流程,从而减少一些不必要的“摩擦”。 531 | 同时,如果每次小更新都要执行一大堆的文档网站更新,运行测试等等操作的话实在是太烦了,作为一个程序员,就好好利用自动化的力量吧。 532 | 533 | ```r 534 | # 如无特殊需求,或者新手入门下述命令直接无脑设置即可 535 | usethis::use_tidy_github_actions() 536 | ``` 537 | 显示结果: 538 | ``` 539 | ✓ Adding 'covr' to Suggests field in DESCRIPTION 540 | ✓ Writing 'codecov.yml' 541 | ✓ Adding '^codecov\\.yml$' to '.Rbuildignore' 542 | ✓ Adding Codecov test coverage badge to 'README.Rmd' 543 | ● Re-knit 'README.Rmd' 544 | ✓ Writing '.github/workflows/R-CMD-check.yaml' 545 | ✓ Adding R-CMD-check badge to 'README.Rmd' 546 | ● Re-knit 'README.Rmd' 547 | ✓ Writing '.github/workflows/pr-commands.yaml' 548 | ✓ Writing '.github/workflows/pkgdown.yaml' 549 | ✓ Writing '.github/workflows/test-coverage.yaml' 550 | ``` 551 | 552 | 很好,直接返回 GitHub Desktop 保存提交。 553 | 嗯?是否无法提交了?弹出错误提示: 554 | 555 | ``` 556 | Commit failed - exit code 1 received, with output: 'README.md is out of date; please re-knit README.Rmd 557 | use 'git commit --no-verify' to override this check' 558 | ``` 559 | 560 | 还记得上文提到的备注吗? 561 | 562 | > 注:由于设定,每一次编写后必须要点击 `Knit` 进行更新才能提交本次变更。 563 | 564 | 所以在此,由于引入的设置命令中对 `README.Rmd` 进行了修改(加了 2 个徽章),因此首先需要更新我们的 `README.md` 才行。 565 | 打开 `README.Rmd` 点击编辑面板上方的 `Knit` 进行同步更新。或者使用下述命令更新也可。 566 | 567 | ```r 568 | devtools::build_rmd() 569 | ``` 570 | 571 | 提交本次变更推送到 GitHub 中([`e78b035`](https://github.com/swsoyee/rPackageTutorial/commit/e78b03590faf9ed4281a7506f35bc73e85d48ce7))。 572 | 点开检查的标记,发现多出了非常多的流程。 573 | 574 | ![3.11.1 运行中是黄色小圈圈,通过是绿色的勾,有错误是红色的叉。](man/figures/3.11.1.png) 575 | 576 | 包括 `{lintr}` 的代码规范性检查、 `test-coverage` 的测试覆盖度计算、 `{pkgdown}` 的帮助文档网站生成、包的构建结果检查 `R-CMD-check` 等等一些列的操作都会在合适的时候自动执行。 577 | 其结果会反映到 README 和网站中,是不是整个就显得专业起来了呢? 578 | 579 | 不过等下,我们还需要在 GitHub 上完成一点小设置: 580 | 581 | 首先在 GitHub 上设置激活我们的帮助手册托管页面。 582 | 来到项目的 `Setting`,向下滚动到 `GitHub Pages` 一项。 583 | 584 | ![3.11.2 来到设定页面。并且滚动到 `GitHub Pages` 一项。](man/figures/3.11.2.png) 585 | 586 | 按图中设置分支 `Branch:gh-pages / root` 后按保存即可。 587 | 588 | ![3.11.3 按图中设置即可。](man/figures/3.11.3.png) 589 | 590 | 保存成功后会自动返回页面最上方,还是向下滚动到刚才的 `GitHub Pages`,能看到多了一行字,没几十秒就能看到我们的网址被激活了: 591 | ``` 592 | Your site is ready to be published at https://swsoyee.github.io/rPackageTutorial/. 593 | ``` 594 | 595 | 然后让我们回到 GitHub 仓库的首页,你可能会看到一个 [`codecov | unknown`](https://codecov.io/gh/swsoyee/rPackageTutorial?branch=main) 的小徽章。 596 | 但不用着急,只需要后续再提交内容的话,unknown 就会自动更新为测试覆盖度了。 597 | 598 | ### 3.12 丰富文档 599 | 600 | 当我们完成上面步骤的设置后,整体环境也搭建的差不多了,剩下就是丰富我们的各种文档,让专业程度更上一层楼。 601 | 602 | ```r 603 | # 增加 NEWS 页面,用于记录每一次升级所做出的变更 604 | usethis::use_news_md() 605 | ``` 606 | 显示结果 607 | 608 | ``` 609 | ✓ Writing 'NEWS.md' 610 | ● Modify 'NEWS.md' 611 | There is 1 uncommitted file: 612 | * 'NEWS.md' 613 | Is it ok to commit it? 614 | 615 | 1: Yeah 616 | 2: No 617 | 3: Negative 618 | 619 | Selection: 2 620 | ``` 621 | 由于默认的提交信息没有遵循我们的规范,所以我选择了不自动提交而手动修改提交信息后用 GitHub Desktop 自己提交[`06b3cd5`](https://github.com/swsoyee/rPackageTutorial/commit/06b3cd50ff9ee969b17ebdc6aa761dbe6f0110b8)。 622 | 623 | ```r 624 | # 添加 Code of Conduct 625 | usethis::use_code_of_conduct() 626 | ``` 627 | 根据弹出的提示,把自动生成的内容添加到 `README.Rmd` 中并且重新生成 `README.md` 后提交本次变更[`c5834f3`](https://github.com/swsoyee/rPackageTutorial/commit/c5834f3ba6c6b5eb88e104166026433a37c0d8bc)。 628 | 629 | 接着手动在 `DESCRIPTION` 中最后的位置添加项目地址和 Bug 报告链接: 630 | ``` 631 | URL: https://github.com/swsoyee/rPackageTutorial 632 | BugReports: https://github.com/swsoyee/rPackageTutorial/issues 633 | ``` 634 | 635 | > 注:在这里同样可以通过 `usethis::use_github_links()` 来达成相同目的,但是首先要进行 PAT 的设置。 636 | > 有兴趣的可以自己设置一下,这里就采取最简单易懂的复制粘贴形式来达成相同目的了。 637 | 638 | 提交本次变更并且推送后[`8166dfa`](https://github.com/swsoyee/rPackageTutorial/commit/8166dface73993a7fe6be0c7e6d67bf3512eba68),等通过 GitHub Action 调用 `{pkgdown}` 自动执行完操作后,一个页面信息更丰富的专业包说明网站就被自动更新了。 639 | 640 | ![3.12.1 整体框架搭建完成。](man/figures/3.12.1.png) 641 | 642 | ## 4. 结语 643 | 644 | 在洋洋洒洒地完成了上述所有步骤后,一个能“唬人”的专业 R 包开发环境就建立完毕了。 645 | 接下来只需要继续的在 `R` 文件夹中添加自己编写的函数,和 `tests/testthat` 中持续编写追加你的内容,每次更新一点内容就即使进行提交,以记录你的每一步变更(推荐函数和测试同时提交,见 **TDD原则**)。 646 | 本地编写时候,时常用 `devtools::check()` 进行检查,确保每一次变更都不会造成你的包出现问题。 647 | 648 | 即使是个人开发,对于大范围的改动也尽量通过使用 `Pull Request` 的方式进行,而不是直接推送到 `master` 或者 `main` 的主分支中,方便搞砸时候的回退操作等等。 649 | 只有构建了一个较为专业的环境,用户在搜索的时候才会对你的包略有信赖,从而获取用户。 650 | 而也只有遵循了开源社区的规范时,才更方便他人来对你的包进行改进,降低了阅读源码的难度。 651 | 652 | 除了以上列举的步骤外,其实还有很多能用的东西,单就 `{usethis}` 一个包中能用的函数就好几十个,如果写起来真是没完没了。 653 | 如果感兴趣的话可以去查看 **Hadley Wickham** 编写的 [R Packages](https://r-pkgs.org/)和各个工具包的说明文档来了解更多详细内容和具体的说明。 654 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | # rPackageTutorial 5 | 6 | 7 | 8 | [![Codecov test 9 | coverage](https://codecov.io/gh/swsoyee/rPackageTutorial/branch/main/graph/badge.svg)](https://codecov.io/gh/swsoyee/rPackageTutorial?branch=main) 10 | [![R-CMD-check](https://github.com/swsoyee/rPackageTutorial/workflows/R-CMD-check/badge.svg)](https://github.com/swsoyee/rPackageTutorial/actions) 11 | 12 | 13 | `{rPackageTutorial}` is an easy-to-understand tutorial about how to 14 | create an R package with some basic settings in a professional way for 15 | those R beginner to quickly experience the development process. 16 | 17 | ## 1\. 前言 18 | 19 | 现在的中文网络上其实并不缺乏教新手如何去创建和开发一个 R 包,大致有基于命令行和 RStudio 20 | 截图的方式两种方式手把手的把每一步都很好地传授给读者。 21 | 例如,只要很随意的搜索一下,就能找到不少可以参考的资料: 22 | 23 | 1. [开发 R 24 | 程序包之忍者篇](https://cosx.org/2011/05/write-r-packages-like-a-ninja/); 25 | 2. [极简 R 包建立方法](https://cosx.org/2013/11/building-r-packages-easily/); 26 | 3. [R包开发](https://r-packages-zh-cn.readthedocs.io/zh_CN/latest/index.html); 27 | …… 28 | 29 | 还有数不胜数的学习笔记和知识分享类的文章。那么问题来了,为什么我还要再写一篇来分享如何创建 R 包的文章呢? 动机其实也很简单,由于 R 30 | 社区的不断发展,`{usethis}`、`{testthat}`、`{styler}`、`{lintr}`、`{pkgdown}` 31 | 等等各类便于开发的工具层出不穷,在这些工具的帮助下,开发一个包的学习曲线愈发地降低。 32 | 而现在中文网络中基于这些更为现代化工具的开发新手级入门教程还是尚且偏少。 33 | 即使有(如上述参考链接中的 [3. 34 | R包开发](https://r-packages-zh-cn.readthedocs.io/zh_CN/latest/index.html)),略微过于详细,没有花上个把两天是很难体验到全流程的。 35 | 36 | 因此,本文的主要目的就是用最短的时间,用专业的(自称)方式创建一个高逼格的 R 包并且托管在 GitHub 上。 37 | 也因此,会对很多局部的细节不做过多的讲解,先把流程跑通,对全流程有个大致的理解后,想了解细节时再去做具体调查即可。 38 | 39 | ## 2\. 准备工作 40 | 41 | 在开发前,你只需要完成 2 项工作: 42 | 43 | 1. 为了使用最简单易懂的方式进行操作,下载并且安装好 RStudio(R环境当然是默认就具备了)就不用说了; 44 | 2. 一个 GitHub 账号(相关教程太多,这里也不展开讲解); 45 | 3. GitHub Desktop(Git操作的图形界面,非必需但是方便了解代码发生了什么变化)。 46 | 47 | 此外,在 GitHub 48 | 上也放了一个和本教程相关的仓库([swsoyee/rPackageTutorial](https://github.com/swsoyee/rPackageTutorial))。 49 | 也可以参看[每一次 50 | Commit(提交)](https://github.com/swsoyee/rPackageTutorial/commits/main)来了解每一步操作具体发生了什么变化,从而加深理解。 51 | 每一步操作都没有唯一答案,所写的内容也只是其中的一个可供参考的步骤而已。 52 | 53 | > 部分界面截图如 RStudio 和 GitHub 的界面可能会有所不同是因为我个人进行了相关主题或者插件设置,不影响文章内容理解。 54 | 55 | ## 3\. 创建 R 包 56 | 57 | 在文章中,我将展示编写一个只包含有两个数加法函数的包作为例子。 包的功能本身不是关键,关键是构建一个专业的 R 58 | 包其拥有的功能以外的整个外部环境。 59 | 再次重复一下前言中所提到的,很多细节部分不会过多讲解,但是会提点到关键词,因此有需要的可以根据关键词去搜索即可补充了解所需知识。 60 | 好,废话不再多说,那就正式开始了。 61 | 62 | ### 3.1 用 `{usethis}` 开始创建 63 | 64 | 为了使得重现步骤更为简单,在这里我们基本全部使用代码的方式来完成创建工作。首先随便打开一个 R 的进程,默认或者在 RStudio 中都可以。 65 | 66 | ``` r 67 | # 如果没有 {usethis} 包的话,先下载安装。 68 | install.packages("usethis") 69 | 70 | # 命名一个 {rPackageTutorial} 包,并且创建,path 可以填写你想创建在哪个文件夹中。 71 | # 这里我们选择在当前路径创建该包 72 | usethis::create_package(path = "rPackageTutorial") 73 | ``` 74 | 75 | 输入完毕后,自动会弹出一个新的 RStudio 窗口,并且自动设定了 `rPackageTutorial` 为当前工作文件夹,名为 76 | `{rPackageTutorial}` 就创建好了。 ~~本期教程结束,谢谢!~~ 77 | 78 | ### 3.2 加入版本控制(Git) 79 | 80 | 这一步的方法有很多种,这里写的是本人用的最简单的一种。 打开 GitHub Desktop(默认已经完成了登陆 GitHub 账号的操作),点击 81 | `Add > Add Existing Repository`,在弹出的 `Add Local Repository` 中选择所创建好的 82 | `{rPackageTutorial}` 路径。 83 | 84 | ![3.2.1 使用 GitHub Desktop 添加本地项目。](man/figures/3.2.1.png) 85 | 86 | 会提示您选择的文件夹还没有包含 Git 的相关设定,因此只需要继续点击 `create a repository` 创建相关设定即可。 87 | 88 | ![3.2.2 选择本地项目地址后点击 `create a repository` 89 | 创建相关设定。](man/figures/3.2.2.png) 90 | 91 | 在下一步的 `Create a New Repository` 中可以什么都不改,直接点击右下角的 `Create Repository` 92 | 按钮完成项目的创建。 93 | 94 | ![3.2.3 填写云端项目信息内容后按 `Create a New Repository` 95 | 创建。](man/figures/3.2.3.png) 96 | 97 | 至此,本地的设定就完成了,只需要点击 `Publish repository`,然后设置公不公开后即可将项目推送到你的 GitHub 中去了。 98 | 可以点击这里 99 | [`d78d245`](https://github.com/swsoyee/rPackageTutorial/commit/d78d245c641b4208de2ed726f764e4b4a38ab3ed) 100 | 查看第一次推送时候的变更内容。 101 | 102 | ![3.2.4 点击 `Publish repository` 推送到云端 GitHub 上。](man/figures/3.2.4.png) 103 | 104 | ### 3.3 用 `{renv}` 进行依赖(加载包)的版本控制 105 | 106 | 让我们回到 RStudio,这次我们引入的是 107 | [`{renv}`](https://rstudio.github.io/renv/index.html) 这个包来进行依赖的版本控制。 108 | 用最简单的话来说,就是为了自己写的包不会因为所使用的一些别人写的包发生大规模改变的时候自己的包也跟着受到影响,和让合作开发者能迅速构建和你同样的开发环境,减少由于所使用的包版本不同导致的问题发生。 109 | 110 | ``` r 111 | # 安装 CRAN 版的 {renv} 112 | install.packages("renv") 113 | 114 | # 初始化环境 115 | renv::init() 116 | ``` 117 | 118 | 初始化时候的信息: 119 | 120 | * Initializing project ... 121 | * Discovering package dependencies ... Done! 122 | * Copying packages into the cache ... Done! 123 | The following package(s) will be updated in the lockfile: 124 | 125 | # CRAN =============================== 126 | - renv [* -> 0.12.5] 127 | 128 | * Lockfile written to '~/Documents/GitHub/rPackageTutorial/renv.lock'. 129 | 130 | Restarting R session... 131 | 132 | * Project '~/Documents/GitHub/rPackageTutorial' loaded. [renv 0.12.5] 133 | 134 | ``` r 135 | # 保存当前所用的包环境,当然我们才刚刚开始开发,别的包都没有引入 136 | renv::snapshot() 137 | ``` 138 | 139 | 用于记录依赖的 `lockfile` 已经更新完毕: 140 | 141 | * The lockfile is already up to date. 142 | 143 | 在持续开发的过程中,只要用到下述几个常用命令即可,更多的可以到[文档](https://rstudio.github.io/renv/index.html)中了解: 144 | 145 | - 如果有引入新的包,运行 `renv::snapshot()` 进行 `lockfile` 的更新; 146 | - 如果想更新所使用的包,运行 `renv::update()` 进行包本身的更新; 147 | - 如果换了电脑进行开发,运行 `renv::restore()` 恢复到开发时用的包环境; 148 | 149 | 完成这一步后,不要忘记进行 Git 的提交工作。 让我们回到 GitHub Desktop,在 `Summary (required)` 150 | 中提交本次改动的内容(在这里我写了 `使用renv进行环境初始化`),点击 `Commit to main` 151 | 完成本地提交后,点击 `Push origin` 152 | 推送到云端(GitHub)上([`2493654`](https://github.com/swsoyee/rPackageTutorial/commit/229365403eadbf8ed4ff49171a3f298a16e3c12e))。 153 | 154 | ![3.3.1 使用 GitHub Desktop 进行 Git 操作对于新手较为友好。](man/figures/3.3.1.png) 155 | 156 | 至于如何写好提交信息可以参考[Angular提交信息规范](https://zj-git-guide.readthedocs.io/zh_CN/latest/message/Angular%E6%8F%90%E4%BA%A4%E4%BF%A1%E6%81%AF%E8%A7%84%E8%8C%83/),如果要显得包较为专业的话,那么这些小细节也是不容忽视的。 157 | 在这之后将不再赘述每一步操作的提交工作,可以参考示例项目的提交记录即可。 158 | 159 | > 注:在本次教程中没有每次都适时的进行 `renv::snapshot()` 160 | > 的包更新操作,在开发当中,每次引入或者使用新包的适合都建议进行一次 161 | > `lockfile` 162 | > 的更新([`bc5051d`](https://github.com/swsoyee/rPackageTutorial/commit/bc5051d1ed25a9d1ec36d139686acc84cfdf9f38))。 163 | 164 | 除了使用 `{renv}` 进行版本控制以外,还有一种在开发 R 包时更为普遍的方式,就是在 `DESCRIPTION` 165 | 中用添加会使用或者是建议使用的包的信息。 我们可以使用下述命令进行快速添加: 166 | 167 | ``` r 168 | # 如果包为使用时必须的,则需要设置 type = "Imports" 169 | # {renv} 在这里只是开发必备,而非使用所开发的包必备,因此选择 "Suggests" 即可 170 | usethis::use_package(package = "renv", type = "Suggests") 171 | ``` 172 | 173 | 变更结果可参考提交[`65e4fc4`](https://github.com/swsoyee/rPackageTutorial/commit/65e4fc4371d433fe1e883cea73e97bc692dcbf40)。 174 | 对于非 R 包的项目(比如说用 `{shiny}` 来开发一个仪表盘应用)来说,用 `{renv}` 进行依赖管理就会显得非常重要。 而在 R 175 | 包的开发中,标准的做法也就是用 `DESCRIPTION` 来记录依赖的版本信息。 不过 `renv::restore()` 176 | 一条命令恢复开发环境的力量还是很香的,推荐使用度 + 1。 177 | 178 | ### 3.4 创建 README 179 | 180 | README 是一个项目的入口,因此拥有一个(至少看起来)专业的 README 是必不可少的。 在这里我们直接用命令来进行创建: 181 | 182 | ``` r 183 | # 引入必备包 {rmarkdown} 184 | install.packages("rmarkdown") 185 | 186 | # 虽然也可以使用 usethis::use_readme_md(),看个人需求(不详细讲解区别) 187 | usethis::use_readme_rmd() 188 | ``` 189 | 190 | 一个默认的 `README.Rmd` 模版就创建完成了,你可以自由的编写内容后点击 `Knit` 从而生成所需要的 `README.md` 191 | 文档。 192 | 193 | > 注:由于设定,每一次编写后必须要点击 `Knit` 进行更新才能提交本次变更。 194 | 195 | 第一次 `Knit` 的时候,在 `README.Rmd` 会有你的包加载的这一条命令,因此需要先将包成功打包一次才能成功执行,否则会报错。 196 | 或者你也可以先把自动生成的模版中的 `library(rPackageTutorial)` 先注释掉即可。 197 | 198 | ![3.4.1 由于图中选择的加载包的代码存在,因此需要先将包成功打包一次才能成功执行。可以选择右侧面板中的 `Build` 标签,按 199 | `Install and Restart` 即可。](man/figures/3.4.1.png) 200 | 201 | 完成后回到 GitHub Desktop 202 | 进行提交([`658b233`](https://github.com/swsoyee/rPackageTutorial/commit/658b2338783bd1a7ec2f8fcf75c2eb675cd66049))。 203 | 204 | ### 3.5 编写函数 205 | 206 | 来到 R 包的函数编写部分了,让我们直接在根目录的 R 文件夹下建立一个 `add.R` 文件来保存我们写好的函数。 207 | 一个函数一个文件的管理方式是比较稳妥的,如果是每一个函数都很简单的同一个类函数的话也可以放到同一个文件中。 208 | 209 | ``` r 210 | # 通过 RStudio 的 File > New File > R Script 也一样 211 | file.create("R/add.R") 212 | 213 | # 打开文件开始编写,写入下列内容 214 | add<-function(a,b) { 215 | a+b 216 | } 217 | ``` 218 | 219 | 至此,我们只是在一个文件里写了一个函数,但是还没有让这个函数包含到我们的包中。 这时候,就需要插入一些文档让打包的时候能够识别这个函数。 220 | 这一步我们可以点击图中左上的 **魔术棒** 按钮(放大镜图标的右侧),点击 `Insert Roxygen Skeleton` 221 | 快速生成文档骨架。 222 | 223 | ![3.5.1 在 `R` 文件夹h中创建 `add.R` 文件用于保存函数,点击 `Insert Roxygen Skeleton` 224 | 快速生成。](man/figures/3.5.1.png) 225 | 226 | ``` r 227 | # 点击 Insert Roxygen Skeleton 后就会出现文档结构 228 | #' Title 229 | #' 230 | #' @param a 231 | #' @param b 232 | #' 233 | #' @return 234 | #' @export 235 | #' 236 | #' @examples 237 | add<-function(a,b) { 238 | a+b 239 | } 240 | 241 | # 让我们完成编写 242 | #' Returns the sum of two numbers 243 | #' 244 | #' @param a Number one. 245 | #' @param b Number two. 246 | #' 247 | #' @return Sum of two number. 248 | #' @export 249 | #' 250 | #' @examples 251 | #' library(rPackageTutorial) 252 | #' add(1, 2) 253 | add<-function(a,b) { 254 | a+b 255 | } 256 | 257 | # 写完后,需要生成文档才能够真正使用 258 | devtools::document() 259 | ``` 260 | 261 | 输出结果: 262 | 263 | Updating rPackageTutorial documentation 264 | Loading rPackageTutorial 265 | Writing NAMESPACE 266 | Writing add.Rd 267 | 268 | 之后再按一下 `Install and Restart` 就可以使用了。也可以在命令行中使用 `?add` 来查看一下自己定义的帮助文档。 269 | 270 | ![3.5.2 输入 `devtools::document()` 更新文档,然后点击 `Install and Restart` 271 | 重新打包并自动重新加载包,可用 `?add` 来确认没有问题。](man/figures/3.5.2.png) 272 | 273 | 最后,回到 GitHub Desktop 274 | 中提交本次更改([`88f0c57`](https://github.com/swsoyee/rPackageTutorial/commit/88f0c57f538d6b71bda138acc76ffd09aa7b1b18))。 275 | 至此,你的包就创建完成啦!~~本期教程结束,再次感谢!~~ 276 | 277 | ### 3.6 用 `{styler}` 来美化代码 278 | 279 | 包是完成了,但是对于具有强迫症的程序员来说,代码似乎并不美观? 那这个时候就交由 280 | [`{styler}`](https://styler.r-lib.org/) 来替我们美化代码吧。 281 | 282 | ``` r 283 | # 安装 {styler} 284 | install.packages("styler") 285 | 286 | # 对整个包进行代码美化 287 | styler::style_pkg() 288 | ``` 289 | 290 | 结果显示,一个文件被进行了代码格式上的修改,一些空格被自动添加到了里面: 291 | 292 | Styling 2 files: 293 | R/add.R ℹ 294 | .Rprofile ✓ 295 | ──────────────────────────────────────── 296 | Status Count Legend 297 | ✓ 1 File unchanged. 298 | ℹ 1 File changed. 299 | x 0 Styling threw an error. 300 | ──────────────────────────────────────── 301 | Please review the changes carefully! 302 | 303 | 由于我们的函数很简单,当开发一个较大的、较为复杂的包的时候,那么就能看到很明显的变化了。 如果别人帮你改进了部分代码(提交了一个 Pull 304 | Request)却没有进行代码优化那怎么办呢?难道每次都要手动提醒对方吗注意修改格式?别着急,后续将会告诉你答案。 305 | 还是那句话,不要忘记提交本次更改([`08df7c2`](https://github.com/swsoyee/rPackageTutorial/commit/08df7c21909d4005c788fe1f101a1441bd1e5f90))。 306 | 307 | ### 3.7 用 `{lintr}` 来规范代码 308 | 309 | 命名法一直都是码农界一个经常讨论的问题,不同语言有不同的较为规范的命名法则,有些人喜欢用驼峰式,有些喜欢下划线式。 310 | 如果在别人好心帮你改进了代码但是没有遵循一定的法则,作为项目创建者的你要在格式上面指指点点,Pull 311 | Request 的作者也需要回应你的指点,很有可能搞得双方都不是特别舒服。 312 | 如果在项目建立当初就设定了规范,让规则来说话,就能很好地避免这种冲突。这里我们使用 313 | [`{lintr}`](https://github.com/jimhester/lintr) 来快速帮我们解决这个问题。 314 | 315 | ``` r 316 | # 安装 {lintr} 317 | install.packages("lintr") 318 | 319 | # 对整个包进行不符合规范的代码查询(当然,都没有写多少代码,当然不会出现什么错误结果) 320 | lintr::lint_package() 321 | 322 | # 比如我们在 add 函数中增加一个超过80字符的注释 323 | add <- function(a, b) { 324 | # aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa 325 | a + b 326 | } 327 | 328 | # 再次执行 329 | lintr::lint_package() 330 | ``` 331 | 332 | 命令完成后会在 RStudio 中弹出一个 Markers 的面板,显示: 333 | 334 | Line 13 lines should not be more than 80 characters. 335 | 336 | 嗯,当然太长的代码会不利于阅读,因此默认会限制了代码长度。 各种设置都可以在 337 | [`{lintr}`](https://github.com/jimhester/lintr) 中进行参考,一般我们都使用默认规则即可。 338 | 如果需要自定义规则,则需要在项目根目录下创建一个 `.lintr` 文件,这部分可参考官方文档进行设置,这里不做赘述。 339 | 我们可以在利用 GitHub Action 340 | 来对提交的代码进行规范检查,这样一来如果有不规范代码被提交的话会自动提示错误,这样你就可以要求代码提交这进行更改了。 341 | 342 | ``` r 343 | # 在 GitHub Action 中设置 lintr 344 | usethis::use_github_action("lint") 345 | ``` 346 | 347 | 运行结果: 348 | 349 | ✓ Setting active project to '/Users/suwei/Documents/GitHub/rPackageTutorial' 350 | ✓ Creating '.github/' 351 | ✓ Adding '^\\.github$' to '.Rbuildignore' 352 | ✓ Adding '*.html' to '.github/.gitignore' 353 | ✓ Creating '.github/workflows/' 354 | ✓ Writing '.github/workflows/lint.yaml' 355 | 356 | 老样子,提交并推送代码到 GitHub 357 | 上([`454c7f6`](https://github.com/swsoyee/rPackageTutorial/commit/454c7f6bf604d6ecb84c04848b1c923750193cd2))。 358 | 在这之后的每次当你提交的代码通过检查的时候,就会有一个绿色的小勾表示通过,是不是稍微显得专业一点了呢? 359 | 360 | ![3.7.1 多出了一个小~~狗狗~~勾勾来显示我们的代码完全没问题,已通过检查。](man/figures/3.7.1.png) 361 | 362 | ### 3.8 用 `{testthat}` 来测试代码 363 | 364 | 在编写一个具有可靠性的包的时候,特别是当函数变得复杂的时候,测试是必不可少的。 我们可以用 `{usethis}` 来快速设定编写测试所需要的 365 | `{testthat}` 的环境。 366 | 367 | ``` r 368 | # 设定环境 369 | usethis::use_testthat() 370 | ``` 371 | 372 | 弹出运行结果: 373 | 374 | ✓ Adding 'testthat' to Suggests field in DESCRIPTION 375 | ✓ Setting Config/testthat/edition field in DESCRIPTION to '3' 376 | ✓ Creating 'tests/testthat/' 377 | ✓ Writing 'tests/testthat.R' 378 | ● Call `use_test()` to initialize a basic test file and open it for editing. 379 | 380 | ``` r 381 | # 安装 {testthat} 382 | install.packages("testthat") 383 | 384 | # 快速创建测试文件 385 | > usethis::use_test() 386 | ``` 387 | 388 | 弹出运行结果: 389 | 390 | ✓ Writing 'tests/testthat/test-add.R' 391 | ● Modify 'tests/testthat/test-add.R' 392 | 393 | ``` r 394 | # 在自动生成的测试文件中包含了下述代码 395 | test_that("multiplication works", { 396 | expect_equal(2 * 2, 4) 397 | }) 398 | 399 | # 将其修改成有意义的测试 400 | test_that("add() function return the sum of two number", { 401 | expect_equal(add(1, 2), 3) 402 | }) 403 | ``` 404 | 405 | 在写完测试后,可以点击编写面板的右上角 `Run Tests` 进行单文件测试,也可以选择 `Addins > Report test 406 | coverage for a package` 进行整个包的函数测试覆盖率的测算。 407 | 408 | ![3.8.1 运行测试的方法多种多样,最简单的单文件测试是点击编辑界面右上角的 `Run 409 | Tests`。](man/figures/3.8.1.png) 410 | 411 | 当然还有其他办法来达成同样的目的,这只是其中一种方法而已。 但如果他人对你的代码进行修改或者实现了功能时,通过什么办法确保对方修改的代码不会造成 412 | Bug 呢? 同样是通过 GitHub Action 来进行实现,稍后将会讲解到。回到 GitHub Desktop 413 | 提交我们本次的修改([`2655b2a`](https://github.com/swsoyee/rPackageTutorial/commit/2655b2a9b936148a33ae50c30d7e8c6bb7ea7095))。 414 | 415 | ### 3.9 编写包的说明 416 | 417 | 在别的教程中,一般都会把包的说明放到前面,说明这也是很重要的一步。 在这里,为了把说明部分都汇总到一起,就将其放到了偏后的部分。 418 | 419 | 打开根目录下的 `DESCRIPTION` 文件,对包说明进行一些修改,主要是 Title、Authors 和 Description 420 | 部分,别的都可以原封不动: 421 | 422 | Package: rPackageTutorial 423 | Title: Create R Package In A Professional Way Tutorial 424 | Version: 0.0.0.9000 425 | Authors@R: 426 | person(given = "Wei", 427 | family = "Su", 428 | role = c("aut", "cre"), 429 | email = "swsoyee@gmail.com") 430 | Description: Show an example of how to create a r package in a professional way. 431 | License: `use_mit_license()`, `use_gpl3_license()` or friends to 432 | pick a license 433 | Encoding: UTF-8 434 | LazyData: true 435 | Roxygen: list(markdown = TRUE) 436 | RoxygenNote: 7.1.1 437 | Suggests: 438 | testthat (>= 3.0.0) 439 | Config/testthat/edition: 3 440 | 441 | 通过 `{usethis}` 来修改 License 和 Version 信息: 442 | 443 | ``` r 444 | # 如果没有依赖到别的具有不同版权的第三方包的话,一般选择最为广泛使用的 MIT 即可 445 | usethis::use_mit_license() 446 | ``` 447 | 448 | 结果显示: 449 | 450 | ✓ Setting active project to '/Users/suwei/Documents/GitHub/rPackageTutorial' 451 | ✓ Setting License field in DESCRIPTION to 'MIT + file LICENSE' 452 | ✓ Writing 'LICENSE' 453 | ✓ Writing 'LICENSE.md' 454 | ✓ Adding '^LICENSE\\.md$' to '.Rbuildignore' 455 | 456 | ``` r 457 | # 升级版本号 458 | usethis::use_version() 459 | ``` 460 | 461 | There are uncommitted changes and you're about to bump version 462 | Do you want to proceed anyway? 463 | 464 | 1: Negative 465 | 2: No 466 | 3: Yeah 467 | 468 | Selection: 3 469 | Current version is 0.0.0.9000. 470 | Which part to increment? (0 to exit) 471 | 472 | 1: major --> 1.0.0 473 | 2: minor --> 0.1.0 474 | 3: patch --> 0.0.1 475 | 4: dev --> 0.0.0.9001 476 | 477 | Selection: 2 478 | ✓ Setting Version field in DESCRIPTION to '0.1.0' 479 | There is 1 uncommitted file: 480 | * 'DESCRIPTION' 481 | Is it ok to commit it? 482 | 483 | 1: No way 484 | 2: I agree 485 | 3: Absolutely not 486 | 487 | Selection: 3 488 | 489 | 最后,提交本次变更([`7052d68`](https://github.com/swsoyee/rPackageTutorial/commit/7052d684f41e6cf36a6c9f1d9527743456f6ecaa))。 490 | 只有在完成 `DESCRIPTION` 的合理编写后,运行所编写的 R 包的检查时,才能够无错误通过。 491 | 492 | ``` r 493 | # 轻量版检查 494 | devtools::check() 495 | ``` 496 | 497 | 省略中途的信息: 498 | 499 | ── R CMD check results ───────────────────────────── rPackageTutorial 0.1.0 ──── 500 | Duration: 36.8s 501 | 502 | 0 errors ✓ | 0 warnings ✓ | 0 notes ✓ 503 | 504 | R CMD check succeeded 505 | 506 | 一切安好。做到这个地步,你的包已经逐渐流露出专业的味道了。 507 | 508 | ### 3.10 用 `{pkgdown}` 制作包的说明书 509 | 510 | 在很多较为专业的包中,经常能看到会有一个包的专门的网站(在线版)说明书以供用户查阅。 就算我们只创建了一个 1 + 1 = 2 511 | 的函数,该有的逼格可必须得有。让我们同样使用 512 | [`{pkgdown}`](https://pkgdown.r-lib.org/) 来完成创建。 513 | 514 | ``` r 515 | # 安装 {pkgdown} 516 | install.packages("pkgdown") 517 | 518 | # 初始化你的用户手册网站 519 | usethis::use_pkgdown() 520 | ``` 521 | 522 | 结果显示: 523 | 524 | ✓ Adding '^_pkgdown\\.yml$', '^docs$' to '.Rbuildignore' 525 | ✓ Adding '^pkgdown$' to '.Rbuildignore' 526 | ✓ Adding 'docs' to '.gitignore' 527 | ● Record your site's url in the pkgdown config file (optional, but recommended) 528 | ● Modify '_pkgdown.yml' 529 | 530 | ``` r 531 | # 让我们来试一下看创建我们的网站 532 | pkgdown::build_site() 533 | ``` 534 | 535 | ![3.10.1 嗯,有内味儿了。](man/figures/3.10.1.png) 536 | 537 | 要对网站进行定制化,只需要给 `_pkgdown.yml` 中添加配置即可。 具体可以参考文档说明,因此在这里也不赘述了。 538 | 还是别忘了提交本次变更([`18b5d45`](https://github.com/swsoyee/rPackageTutorial/commit/18b5d4539bc3ce738532633c65901aa1f0b49957))。 539 | 540 | ### 3.11 用 Github Action 自动检查 541 | 542 | 当包越来越复杂,代码和文档都大量增加的时候,特别是如果还是想搞开源,期待他人能更好地参与到由你主导的 R 543 | 包开发时,就需要设定一些自动检查的流程,从而减少一些不必要的“摩擦”。 544 | 同时,如果每次小更新都要执行一大堆的文档网站更新,运行测试等等操作的话实在是太烦了,作为一个程序员,就好好利用自动化的力量吧。 545 | 546 | ``` r 547 | # 如无特殊需求,或者新手入门下述命令直接无脑设置即可 548 | usethis::use_tidy_github_actions() 549 | ``` 550 | 551 | 显示结果: 552 | 553 | ✓ Adding 'covr' to Suggests field in DESCRIPTION 554 | ✓ Writing 'codecov.yml' 555 | ✓ Adding '^codecov\\.yml$' to '.Rbuildignore' 556 | ✓ Adding Codecov test coverage badge to 'README.Rmd' 557 | ● Re-knit 'README.Rmd' 558 | ✓ Writing '.github/workflows/R-CMD-check.yaml' 559 | ✓ Adding R-CMD-check badge to 'README.Rmd' 560 | ● Re-knit 'README.Rmd' 561 | ✓ Writing '.github/workflows/pr-commands.yaml' 562 | ✓ Writing '.github/workflows/pkgdown.yaml' 563 | ✓ Writing '.github/workflows/test-coverage.yaml' 564 | 565 | 很好,直接返回 GitHub Desktop 保存提交。 嗯?是否无法提交了?弹出错误提示: 566 | 567 | Commit failed - exit code 1 received, with output: 'README.md is out of date; please re-knit README.Rmd 568 | use 'git commit --no-verify' to override this check' 569 | 570 | 还记得上文提到的备注吗? 571 | 572 | > 注:由于设定,每一次编写后必须要点击 `Knit` 进行更新才能提交本次变更。 573 | 574 | 所以在此,由于引入的设置命令中对 `README.Rmd` 进行了修改(加了 2 个徽章),因此首先需要更新我们的 `README.md` 575 | 才行。 打开 `README.Rmd` 点击编辑面板上方的 `Knit` 进行同步更新。或者使用下述命令更新也可。 576 | 577 | ``` r 578 | devtools::build_rmd() 579 | ``` 580 | 581 | 提交本次变更推送到 GitHub 582 | 中([`e78b035`](https://github.com/swsoyee/rPackageTutorial/commit/e78b03590faf9ed4281a7506f35bc73e85d48ce7))。 583 | 点开检查的标记,发现多出了非常多的流程。 584 | 585 | ![3.11.1 运行中是黄色小圈圈,通过是绿色的勾,有错误是红色的叉。](man/figures/3.11.1.png) 586 | 587 | 包括 `{lintr}` 的代码规范性检查、 `test-coverage` 的测试覆盖度计算、 `{pkgdown}` 588 | 的帮助文档网站生成、包的构建结果检查 `R-CMD-check` 589 | 等等一些列的操作都会在合适的时候自动执行。 其结果会反映到 README 590 | 和网站中,是不是整个就显得专业起来了呢? 591 | 592 | 不过等下,我们还需要在 GitHub 上完成一点小设置: 593 | 594 | 首先在 GitHub 上设置激活我们的帮助手册托管页面。 来到项目的 `Setting`,向下滚动到 `GitHub Pages` 一项。 595 | 596 | ![3.11.2 来到设定页面。并且滚动到 `GitHub Pages` 一项。](man/figures/3.11.2.png) 597 | 598 | 按图中设置分支 `Branch:gh-pages / root` 后按保存即可。 599 | 600 | ![3.11.3 按图中设置即可。](man/figures/3.11.3.png) 601 | 602 | 保存成功后会自动返回页面最上方,还是向下滚动到刚才的 `GitHub Pages`,能看到多了一行字,没几十秒就能看到我们的网址被激活了: 603 | 604 | Your site is ready to be published at https://swsoyee.github.io/rPackageTutorial/. 605 | 606 | 然后让我们回到 GitHub 仓库的首页,你可能会看到一个 [`codecov | 607 | unknown`](https://codecov.io/gh/swsoyee/rPackageTutorial?branch=main) 608 | 的小徽章。 但不用着急,只需要后续再提交内容的话,unknown 就会自动更新为测试覆盖度了。 609 | 610 | ### 3.12 丰富文档 611 | 612 | 当我们完成上面步骤的设置后,整体环境也搭建的差不多了,剩下就是丰富我们的各种文档,让专业程度更上一层楼。 613 | 614 | ``` r 615 | # 增加 NEWS 页面,用于记录每一次升级所做出的变更 616 | usethis::use_news_md() 617 | ``` 618 | 619 | 显示结果 620 | 621 | ✓ Writing 'NEWS.md' 622 | ● Modify 'NEWS.md' 623 | There is 1 uncommitted file: 624 | * 'NEWS.md' 625 | Is it ok to commit it? 626 | 627 | 1: Yeah 628 | 2: No 629 | 3: Negative 630 | 631 | Selection: 2 632 | 633 | 由于默认的提交信息没有遵循我们的规范,所以我选择了不自动提交而手动修改提交信息后用 GitHub Desktop 634 | 自己提交[`06b3cd5`](https://github.com/swsoyee/rPackageTutorial/commit/06b3cd50ff9ee969b17ebdc6aa761dbe6f0110b8)。 635 | 636 | ``` r 637 | # 添加 Code of Conduct 638 | usethis::use_code_of_conduct() 639 | ``` 640 | 641 | 根据弹出的提示,把自动生成的内容添加到 `README.Rmd` 中并且重新生成 `README.md` 642 | 后提交本次变更[`c5834f3`](https://github.com/swsoyee/rPackageTutorial/commit/c5834f3ba6c6b5eb88e104166026433a37c0d8bc)。 643 | 644 | 接着手动在 `DESCRIPTION` 中最后的位置添加项目地址和 Bug 报告链接: 645 | 646 | URL: https://github.com/swsoyee/rPackageTutorial 647 | BugReports: https://github.com/swsoyee/rPackageTutorial/issues 648 | 649 | > 注:在这里同样可以通过 `usethis::use_github_links()` 来达成相同目的,但是首先要进行 PAT 的设置。 650 | > 有兴趣的可以自己设置一下,这里就采取最简单易懂的复制粘贴形式来达成相同目的了。 651 | 652 | 提交本次变更并且推送后[`8166dfa`](https://github.com/swsoyee/rPackageTutorial/commit/8166dface73993a7fe6be0c7e6d67bf3512eba68),等通过 653 | GitHub Action 调用 `{pkgdown}` 自动执行完操作后,一个页面信息更丰富的专业包说明网站就被自动更新了。 654 | 655 | ![3.12.1 整体框架搭建完成。](man/figures/3.12.1.png) 656 | 657 | ## 4\. 结语 658 | 659 | 在洋洋洒洒地完成了上述所有步骤后,一个能“唬人”的专业 R 包开发环境就建立完毕了。 接下来只需要继续的在 `R` 660 | 文件夹中添加自己编写的函数,和 `tests/testthat` 661 | 中持续编写追加你的内容,每次更新一点内容就即使进行提交,以记录你的每一步变更(推荐函数和测试同时提交,见 662 | **TDD原则**)。 本地编写时候,时常用 `devtools::check()` 进行检查,确保每一次变更都不会造成你的包出现问题。 663 | 664 | 即使是个人开发,对于大范围的改动也尽量通过使用 `Pull Request` 的方式进行,而不是直接推送到 `master` 或者 `main` 665 | 的主分支中,方便搞砸时候的回退操作等等。 只有构建了一个较为专业的环境,用户在搜索的时候才会对你的包略有信赖,从而获取用户。 666 | 而也只有遵循了开源社区的规范时,才更方便他人来对你的包进行改进,降低了阅读源码的难度。 667 | 668 | 除了以上列举的步骤外,其实还有很多能用的东西,单就 `{usethis}` 一个包中能用的函数就好几十个,如果写起来真是没完没了。 669 | 如果感兴趣的话可以去查看 **Hadley Wickham** 编写的 [R 670 | Packages](https://r-pkgs.org/)和各个工具包的说明文档来了解更多详细内容和具体的说明。 671 | -------------------------------------------------------------------------------- /_pkgdown.yml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/swsoyee/rPackageTutorial/cf55a9a2c3f5b34d9005fe5e49aa88b9098bbcf3/_pkgdown.yml -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | comment: false 2 | 3 | coverage: 4 | status: 5 | project: 6 | default: 7 | target: auto 8 | threshold: 1% 9 | informational: true 10 | patch: 11 | default: 12 | target: auto 13 | threshold: 1% 14 | informational: true 15 | -------------------------------------------------------------------------------- /man/add.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/add.R 3 | \name{add} 4 | \alias{add} 5 | \title{Returns the sum of two numbers} 6 | \usage{ 7 | add(a, b) 8 | } 9 | \arguments{ 10 | \item{a}{Number one.} 11 | 12 | \item{b}{Number two.} 13 | } 14 | \value{ 15 | Sum of two number. 16 | } 17 | \description{ 18 | Returns the sum of two numbers 19 | } 20 | \examples{ 21 | library(rPackageTutorial) 22 | add(1, 2) 23 | } 24 | -------------------------------------------------------------------------------- /man/figures/3.10.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/swsoyee/rPackageTutorial/cf55a9a2c3f5b34d9005fe5e49aa88b9098bbcf3/man/figures/3.10.1.png -------------------------------------------------------------------------------- /man/figures/3.11.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/swsoyee/rPackageTutorial/cf55a9a2c3f5b34d9005fe5e49aa88b9098bbcf3/man/figures/3.11.1.png -------------------------------------------------------------------------------- /man/figures/3.11.2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/swsoyee/rPackageTutorial/cf55a9a2c3f5b34d9005fe5e49aa88b9098bbcf3/man/figures/3.11.2.png -------------------------------------------------------------------------------- /man/figures/3.11.3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/swsoyee/rPackageTutorial/cf55a9a2c3f5b34d9005fe5e49aa88b9098bbcf3/man/figures/3.11.3.png -------------------------------------------------------------------------------- /man/figures/3.12.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/swsoyee/rPackageTutorial/cf55a9a2c3f5b34d9005fe5e49aa88b9098bbcf3/man/figures/3.12.1.png -------------------------------------------------------------------------------- /man/figures/3.2.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/swsoyee/rPackageTutorial/cf55a9a2c3f5b34d9005fe5e49aa88b9098bbcf3/man/figures/3.2.1.png -------------------------------------------------------------------------------- /man/figures/3.2.2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/swsoyee/rPackageTutorial/cf55a9a2c3f5b34d9005fe5e49aa88b9098bbcf3/man/figures/3.2.2.png -------------------------------------------------------------------------------- /man/figures/3.2.3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/swsoyee/rPackageTutorial/cf55a9a2c3f5b34d9005fe5e49aa88b9098bbcf3/man/figures/3.2.3.png -------------------------------------------------------------------------------- /man/figures/3.2.4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/swsoyee/rPackageTutorial/cf55a9a2c3f5b34d9005fe5e49aa88b9098bbcf3/man/figures/3.2.4.png -------------------------------------------------------------------------------- /man/figures/3.3.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/swsoyee/rPackageTutorial/cf55a9a2c3f5b34d9005fe5e49aa88b9098bbcf3/man/figures/3.3.1.png -------------------------------------------------------------------------------- /man/figures/3.4.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/swsoyee/rPackageTutorial/cf55a9a2c3f5b34d9005fe5e49aa88b9098bbcf3/man/figures/3.4.1.png -------------------------------------------------------------------------------- /man/figures/3.5.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/swsoyee/rPackageTutorial/cf55a9a2c3f5b34d9005fe5e49aa88b9098bbcf3/man/figures/3.5.1.png -------------------------------------------------------------------------------- /man/figures/3.5.2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/swsoyee/rPackageTutorial/cf55a9a2c3f5b34d9005fe5e49aa88b9098bbcf3/man/figures/3.5.2.png -------------------------------------------------------------------------------- /man/figures/3.7.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/swsoyee/rPackageTutorial/cf55a9a2c3f5b34d9005fe5e49aa88b9098bbcf3/man/figures/3.7.1.png -------------------------------------------------------------------------------- /man/figures/3.8.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/swsoyee/rPackageTutorial/cf55a9a2c3f5b34d9005fe5e49aa88b9098bbcf3/man/figures/3.8.1.png -------------------------------------------------------------------------------- /rPackageTutorial.Rproj: -------------------------------------------------------------------------------- 1 | Version: 1.0 2 | 3 | RestoreWorkspace: No 4 | SaveWorkspace: No 5 | AlwaysSaveHistory: Default 6 | 7 | EnableCodeIndexing: Yes 8 | UseSpacesForTab: Yes 9 | NumSpacesForTab: 2 10 | Encoding: UTF-8 11 | 12 | RnwWeave: Sweave 13 | LaTeX: pdfLaTeX 14 | 15 | AutoAppendNewline: Yes 16 | StripTrailingWhitespace: Yes 17 | LineEndingConversion: Posix 18 | 19 | BuildType: Package 20 | PackageUseDevtools: Yes 21 | PackageInstallArgs: --no-multiarch --with-keep.source 22 | PackageRoxygenize: rd,collate,namespace 23 | -------------------------------------------------------------------------------- /renv.lock: -------------------------------------------------------------------------------- 1 | { 2 | "R": { 3 | "Version": "4.0.3", 4 | "Repositories": [ 5 | { 6 | "Name": "CRAN", 7 | "URL": "https://cran.rstudio.com" 8 | } 9 | ] 10 | }, 11 | "Packages": { 12 | "R6": { 13 | "Package": "R6", 14 | "Version": "2.5.0", 15 | "Source": "Repository", 16 | "Repository": "CRAN", 17 | "Hash": "b203113193e70978a696b2809525649d" 18 | }, 19 | "askpass": { 20 | "Package": "askpass", 21 | "Version": "1.1", 22 | "Source": "Repository", 23 | "Repository": "CRAN", 24 | "Hash": "e8a22846fff485f0be3770c2da758713" 25 | }, 26 | "assertthat": { 27 | "Package": "assertthat", 28 | "Version": "0.2.1", 29 | "Source": "Repository", 30 | "Repository": "CRAN", 31 | "Hash": "50c838a310445e954bc13f26f26a6ecf" 32 | }, 33 | "base64enc": { 34 | "Package": "base64enc", 35 | "Version": "0.1-3", 36 | "Source": "Repository", 37 | "Repository": "CRAN", 38 | "Hash": "543776ae6848fde2f48ff3816d0628bc" 39 | }, 40 | "brio": { 41 | "Package": "brio", 42 | "Version": "1.1.0", 43 | "Source": "Repository", 44 | "Repository": "CRAN", 45 | "Hash": "570a24963009b9cce0869a0463c83580" 46 | }, 47 | "callr": { 48 | "Package": "callr", 49 | "Version": "3.5.1", 50 | "Source": "Repository", 51 | "Repository": "CRAN", 52 | "Hash": "b7d7f1e926dfcd57c74ce93f5c048e80" 53 | }, 54 | "cli": { 55 | "Package": "cli", 56 | "Version": "2.2.0", 57 | "Source": "Repository", 58 | "Repository": "CRAN", 59 | "Hash": "3ef298932294b775fa0a3eeaa3a645b0" 60 | }, 61 | "covr": { 62 | "Package": "covr", 63 | "Version": "3.5.1", 64 | "Source": "Repository", 65 | "Repository": "CRAN", 66 | "Hash": "6d80a9fc3c0c8473153b54fa54719dfd" 67 | }, 68 | "cpp11": { 69 | "Package": "cpp11", 70 | "Version": "0.2.6", 71 | "Source": "Repository", 72 | "Repository": "CRAN", 73 | "Hash": "f08909ebdad90b19d8d3930da4220564" 74 | }, 75 | "crayon": { 76 | "Package": "crayon", 77 | "Version": "1.3.4", 78 | "Source": "Repository", 79 | "Repository": "CRAN", 80 | "Hash": "0d57bc8e27b7ba9e45dba825ebc0de6b" 81 | }, 82 | "curl": { 83 | "Package": "curl", 84 | "Version": "4.3", 85 | "Source": "Repository", 86 | "Repository": "CRAN", 87 | "Hash": "2b7d10581cc730804e9ed178c8374bd6" 88 | }, 89 | "desc": { 90 | "Package": "desc", 91 | "Version": "1.2.0", 92 | "Source": "Repository", 93 | "Repository": "CRAN", 94 | "Hash": "6c8fe8fa26a23b79949375d372c7b395" 95 | }, 96 | "diffobj": { 97 | "Package": "diffobj", 98 | "Version": "0.3.2", 99 | "Source": "Repository", 100 | "Repository": "CRAN", 101 | "Hash": "16533929cf545f3c9b796780cccf5eff" 102 | }, 103 | "digest": { 104 | "Package": "digest", 105 | "Version": "0.6.27", 106 | "Source": "Repository", 107 | "Repository": "CRAN", 108 | "Hash": "a0cbe758a531d054b537d16dff4d58a1" 109 | }, 110 | "downlit": { 111 | "Package": "downlit", 112 | "Version": "0.2.1", 113 | "Source": "Repository", 114 | "Repository": "CRAN", 115 | "Hash": "f24f1e44320a978c03050b8403a83793" 116 | }, 117 | "ellipsis": { 118 | "Package": "ellipsis", 119 | "Version": "0.3.1", 120 | "Source": "Repository", 121 | "Repository": "CRAN", 122 | "Hash": "fd2844b3a43ae2d27e70ece2df1b4e2a" 123 | }, 124 | "evaluate": { 125 | "Package": "evaluate", 126 | "Version": "0.14", 127 | "Source": "Repository", 128 | "Repository": "CRAN", 129 | "Hash": "ec8ca05cffcc70569eaaad8469d2a3a7" 130 | }, 131 | "fansi": { 132 | "Package": "fansi", 133 | "Version": "0.4.1", 134 | "Source": "Repository", 135 | "Repository": "CRAN", 136 | "Hash": "7fce217eaaf8016e72065e85c73027b5" 137 | }, 138 | "fs": { 139 | "Package": "fs", 140 | "Version": "1.5.0", 141 | "Source": "Repository", 142 | "Repository": "CRAN", 143 | "Hash": "44594a07a42e5f91fac9f93fda6d0109" 144 | }, 145 | "glue": { 146 | "Package": "glue", 147 | "Version": "1.4.2", 148 | "Source": "Repository", 149 | "Repository": "CRAN", 150 | "Hash": "6efd734b14c6471cfe443345f3e35e29" 151 | }, 152 | "highr": { 153 | "Package": "highr", 154 | "Version": "0.8", 155 | "Source": "Repository", 156 | "Repository": "CRAN", 157 | "Hash": "4dc5bb88961e347a0f4d8aad597cbfac" 158 | }, 159 | "htmltools": { 160 | "Package": "htmltools", 161 | "Version": "0.5.1.1", 162 | "Source": "Repository", 163 | "Repository": "CRAN", 164 | "Hash": "af2c2531e55df5cf230c4b5444fc973c" 165 | }, 166 | "httr": { 167 | "Package": "httr", 168 | "Version": "1.4.2", 169 | "Source": "Repository", 170 | "Repository": "CRAN", 171 | "Hash": "a525aba14184fec243f9eaec62fbed43" 172 | }, 173 | "jsonlite": { 174 | "Package": "jsonlite", 175 | "Version": "1.7.2", 176 | "Source": "GitHub", 177 | "RemoteType": "github", 178 | "RemoteHost": "api.github.com", 179 | "RemoteRepo": "jsonlite", 180 | "RemoteUsername": "jeroen", 181 | "RemoteRef": "HEAD", 182 | "RemoteSha": "88c3fe640fa41230e2ff66de0c1185d79af59070", 183 | "Hash": "f3fc979654687a96c39b3a012f83f5f7" 184 | }, 185 | "knitr": { 186 | "Package": "knitr", 187 | "Version": "1.30", 188 | "Source": "Repository", 189 | "Repository": "CRAN", 190 | "Hash": "eed7ee0d02eee88d53881cdc92457c62" 191 | }, 192 | "lazyeval": { 193 | "Package": "lazyeval", 194 | "Version": "0.2.2", 195 | "Source": "Repository", 196 | "Repository": "CRAN", 197 | "Hash": "d908914ae53b04d4c0c0fd72ecc35370" 198 | }, 199 | "lifecycle": { 200 | "Package": "lifecycle", 201 | "Version": "0.2.0", 202 | "Source": "Repository", 203 | "Repository": "CRAN", 204 | "Hash": "361811f31f71f8a617a9a68bf63f1f42" 205 | }, 206 | "magrittr": { 207 | "Package": "magrittr", 208 | "Version": "2.0.1", 209 | "Source": "Repository", 210 | "Repository": "CRAN", 211 | "Hash": "41287f1ac7d28a92f0a286ed507928d3" 212 | }, 213 | "markdown": { 214 | "Package": "markdown", 215 | "Version": "1.1", 216 | "Source": "Repository", 217 | "Repository": "CRAN", 218 | "Hash": "61e4a10781dd00d7d81dd06ca9b94e95" 219 | }, 220 | "memoise": { 221 | "Package": "memoise", 222 | "Version": "1.1.0", 223 | "Source": "Repository", 224 | "Repository": "CRAN", 225 | "Hash": "58baa74e4603fcfb9a94401c58c8f9b1" 226 | }, 227 | "mime": { 228 | "Package": "mime", 229 | "Version": "0.9", 230 | "Source": "Repository", 231 | "Repository": "CRAN", 232 | "Hash": "e87a35ec73b157552814869f45a63aa3" 233 | }, 234 | "openssl": { 235 | "Package": "openssl", 236 | "Version": "1.4.3", 237 | "Source": "Repository", 238 | "Repository": "CRAN", 239 | "Hash": "a399e4773075fc2375b71f45fca186c4" 240 | }, 241 | "pillar": { 242 | "Package": "pillar", 243 | "Version": "1.4.7", 244 | "Source": "Repository", 245 | "Repository": "CRAN", 246 | "Hash": "3b3dd89b2ee115a8b54e93a34cd546b4" 247 | }, 248 | "pkgbuild": { 249 | "Package": "pkgbuild", 250 | "Version": "1.2.0", 251 | "Source": "Repository", 252 | "Repository": "CRAN", 253 | "Hash": "725fcc30222d4d11ec68efb8ff11a9af" 254 | }, 255 | "pkgconfig": { 256 | "Package": "pkgconfig", 257 | "Version": "2.0.3", 258 | "Source": "Repository", 259 | "Repository": "CRAN", 260 | "Hash": "01f28d4278f15c76cddbea05899c5d6f" 261 | }, 262 | "pkgdown": { 263 | "Package": "pkgdown", 264 | "Version": "1.6.1", 265 | "Source": "Repository", 266 | "Repository": "CRAN", 267 | "Hash": "8896076540d9e9b556a2ec658c81f916" 268 | }, 269 | "pkgload": { 270 | "Package": "pkgload", 271 | "Version": "1.1.0", 272 | "Source": "Repository", 273 | "Repository": "CRAN", 274 | "Hash": "b6b150cd4709e0c0c9b5d51ac4376282" 275 | }, 276 | "praise": { 277 | "Package": "praise", 278 | "Version": "1.0.0", 279 | "Source": "Repository", 280 | "Repository": "CRAN", 281 | "Hash": "a555924add98c99d2f411e37e7d25e9f" 282 | }, 283 | "prettyunits": { 284 | "Package": "prettyunits", 285 | "Version": "1.1.1", 286 | "Source": "Repository", 287 | "Repository": "CRAN", 288 | "Hash": "95ef9167b75dde9d2ccc3c7528393e7e" 289 | }, 290 | "processx": { 291 | "Package": "processx", 292 | "Version": "3.4.5", 293 | "Source": "Repository", 294 | "Repository": "CRAN", 295 | "Hash": "22aab6098cb14edd0a5973a8438b569b" 296 | }, 297 | "ps": { 298 | "Package": "ps", 299 | "Version": "1.5.0", 300 | "Source": "Repository", 301 | "Repository": "CRAN", 302 | "Hash": "ebaed51a03411fd5cfc1e12d9079b353" 303 | }, 304 | "purrr": { 305 | "Package": "purrr", 306 | "Version": "0.3.4", 307 | "Source": "Repository", 308 | "Repository": "CRAN", 309 | "Hash": "97def703420c8ab10d8f0e6c72101e02" 310 | }, 311 | "ragg": { 312 | "Package": "ragg", 313 | "Version": "0.4.1", 314 | "Source": "Repository", 315 | "Repository": "CRAN", 316 | "Hash": "70bd921f54c3763f3dbec15106118828" 317 | }, 318 | "rematch2": { 319 | "Package": "rematch2", 320 | "Version": "2.1.2", 321 | "Source": "Repository", 322 | "Repository": "CRAN", 323 | "Hash": "76c9e04c712a05848ae7a23d2f170a40" 324 | }, 325 | "renv": { 326 | "Package": "renv", 327 | "Version": "0.12.5", 328 | "Source": "Repository", 329 | "Repository": "CRAN", 330 | "Hash": "5c0cdb37f063c58cdab3c7e9fbb8bd2c" 331 | }, 332 | "rex": { 333 | "Package": "rex", 334 | "Version": "1.2.0", 335 | "Source": "Repository", 336 | "Repository": "CRAN", 337 | "Hash": "093584b944440c5cd07a696b3c8e0e4c" 338 | }, 339 | "rlang": { 340 | "Package": "rlang", 341 | "Version": "0.4.10", 342 | "Source": "Repository", 343 | "Repository": "CRAN", 344 | "Hash": "599df23c40a4fce9c7b4764f28c37857" 345 | }, 346 | "rmarkdown": { 347 | "Package": "rmarkdown", 348 | "Version": "2.6", 349 | "Source": "Repository", 350 | "Repository": "CRAN", 351 | "Hash": "bc4bac38960b446c183957bfd563e763" 352 | }, 353 | "rprojroot": { 354 | "Package": "rprojroot", 355 | "Version": "2.0.2", 356 | "Source": "Repository", 357 | "Repository": "CRAN", 358 | "Hash": "249d8cd1e74a8f6a26194a91b47f21d1" 359 | }, 360 | "rstudioapi": { 361 | "Package": "rstudioapi", 362 | "Version": "0.13", 363 | "Source": "Repository", 364 | "Repository": "CRAN", 365 | "Hash": "06c85365a03fdaf699966cc1d3cf53ea" 366 | }, 367 | "stringi": { 368 | "Package": "stringi", 369 | "Version": "1.5.3", 370 | "Source": "Repository", 371 | "Repository": "CRAN", 372 | "Hash": "a063ebea753c92910a4cca7b18bc1f05" 373 | }, 374 | "stringr": { 375 | "Package": "stringr", 376 | "Version": "1.4.0", 377 | "Source": "Repository", 378 | "Repository": "CRAN", 379 | "Hash": "0759e6b6c0957edb1311028a49a35e76" 380 | }, 381 | "sys": { 382 | "Package": "sys", 383 | "Version": "3.4", 384 | "Source": "Repository", 385 | "Repository": "CRAN", 386 | "Hash": "b227d13e29222b4574486cfcbde077fa" 387 | }, 388 | "systemfonts": { 389 | "Package": "systemfonts", 390 | "Version": "1.0.1", 391 | "Source": "Repository", 392 | "Repository": "CRAN", 393 | "Hash": "6e899d7c097698d50ec87b1d8e65f246" 394 | }, 395 | "testthat": { 396 | "Package": "testthat", 397 | "Version": "3.0.1", 398 | "Source": "Repository", 399 | "Repository": "CRAN", 400 | "Hash": "17826764cb92d8b5aae6619896e5a161" 401 | }, 402 | "textshaping": { 403 | "Package": "textshaping", 404 | "Version": "0.3.0", 405 | "Source": "Repository", 406 | "Repository": "CRAN", 407 | "Hash": "08517f55a30f309465c722ebd83f7e10" 408 | }, 409 | "tibble": { 410 | "Package": "tibble", 411 | "Version": "3.0.4", 412 | "Source": "Repository", 413 | "Repository": "CRAN", 414 | "Hash": "71dffd8544691c520dd8e41ed2d7e070" 415 | }, 416 | "tinytex": { 417 | "Package": "tinytex", 418 | "Version": "0.29", 419 | "Source": "Repository", 420 | "Repository": "CRAN", 421 | "Hash": "f0b0ba735febac9a8344253ae6fa93f5" 422 | }, 423 | "utf8": { 424 | "Package": "utf8", 425 | "Version": "1.1.4", 426 | "Source": "Repository", 427 | "Repository": "CRAN", 428 | "Hash": "4a5081acfb7b81a572e4384a7aaf2af1" 429 | }, 430 | "vctrs": { 431 | "Package": "vctrs", 432 | "Version": "0.3.6", 433 | "Source": "Repository", 434 | "Repository": "CRAN", 435 | "Hash": "5cf1957f93076c19fdc81d01409d240b" 436 | }, 437 | "waldo": { 438 | "Package": "waldo", 439 | "Version": "0.2.3", 440 | "Source": "Repository", 441 | "Repository": "CRAN", 442 | "Hash": "181d1a31b1ba2009ef20926f2ee0570c" 443 | }, 444 | "whisker": { 445 | "Package": "whisker", 446 | "Version": "0.4", 447 | "Source": "Repository", 448 | "Repository": "CRAN", 449 | "Hash": "ca970b96d894e90397ed20637a0c1bbe" 450 | }, 451 | "withr": { 452 | "Package": "withr", 453 | "Version": "2.3.0", 454 | "Source": "Repository", 455 | "Repository": "CRAN", 456 | "Hash": "7307d79f58d1885b38c4f4f1a8cb19dd" 457 | }, 458 | "xfun": { 459 | "Package": "xfun", 460 | "Version": "0.20", 461 | "Source": "Repository", 462 | "Repository": "CRAN", 463 | "Hash": "d7222684dc02327871e3b1da0aba7089" 464 | }, 465 | "xml2": { 466 | "Package": "xml2", 467 | "Version": "1.3.2", 468 | "Source": "Repository", 469 | "Repository": "CRAN", 470 | "Hash": "d4d71a75dd3ea9eb5fa28cc21f9585e2" 471 | }, 472 | "yaml": { 473 | "Package": "yaml", 474 | "Version": "2.2.1", 475 | "Source": "Repository", 476 | "Repository": "CRAN", 477 | "Hash": "2826c5d9efb0a88f657c7a679c7106db" 478 | } 479 | } 480 | } 481 | -------------------------------------------------------------------------------- /renv/.gitignore: -------------------------------------------------------------------------------- 1 | library/ 2 | lock/ 3 | python/ 4 | staging/ 5 | -------------------------------------------------------------------------------- /renv/activate.R: -------------------------------------------------------------------------------- 1 | 2 | local({ 3 | 4 | # the requested version of renv 5 | version <- "0.12.5" 6 | 7 | # the project directory 8 | project <- getwd() 9 | 10 | # avoid recursion 11 | if (!is.na(Sys.getenv("RENV_R_INITIALIZING", unset = NA))) 12 | return(invisible(TRUE)) 13 | 14 | # signal that we're loading renv during R startup 15 | Sys.setenv("RENV_R_INITIALIZING" = "true") 16 | on.exit(Sys.unsetenv("RENV_R_INITIALIZING"), add = TRUE) 17 | 18 | # signal that we've consented to use renv 19 | options(renv.consent = TRUE) 20 | 21 | # load the 'utils' package eagerly -- this ensures that renv shims, which 22 | # mask 'utils' packages, will come first on the search path 23 | library(utils, lib.loc = .Library) 24 | 25 | # check to see if renv has already been loaded 26 | if ("renv" %in% loadedNamespaces()) { 27 | 28 | # if renv has already been loaded, and it's the requested version of renv, 29 | # nothing to do 30 | spec <- .getNamespaceInfo(.getNamespace("renv"), "spec") 31 | if (identical(spec[["version"]], version)) 32 | return(invisible(TRUE)) 33 | 34 | # otherwise, unload and attempt to load the correct version of renv 35 | unloadNamespace("renv") 36 | 37 | } 38 | 39 | # load bootstrap tools 40 | bootstrap <- function(version, library) { 41 | 42 | # attempt to download renv 43 | tarball <- tryCatch(renv_bootstrap_download(version), error = identity) 44 | if (inherits(tarball, "error")) 45 | stop("failed to download renv ", version) 46 | 47 | # now attempt to install 48 | status <- tryCatch(renv_bootstrap_install(version, tarball, library), error = identity) 49 | if (inherits(status, "error")) 50 | stop("failed to install renv ", version) 51 | 52 | } 53 | 54 | renv_bootstrap_tests_running <- function() { 55 | getOption("renv.tests.running", default = FALSE) 56 | } 57 | 58 | renv_bootstrap_repos <- function() { 59 | 60 | # check for repos override 61 | repos <- Sys.getenv("RENV_CONFIG_REPOS_OVERRIDE", unset = NA) 62 | if (!is.na(repos)) 63 | return(repos) 64 | 65 | # if we're testing, re-use the test repositories 66 | if (renv_bootstrap_tests_running()) 67 | return(getOption("renv.tests.repos")) 68 | 69 | # retrieve current repos 70 | repos <- getOption("repos") 71 | 72 | # ensure @CRAN@ entries are resolved 73 | repos[repos == "@CRAN@"] <- "https://cloud.r-project.org" 74 | 75 | # add in renv.bootstrap.repos if set 76 | default <- c(CRAN = "https://cloud.r-project.org") 77 | extra <- getOption("renv.bootstrap.repos", default = default) 78 | repos <- c(repos, extra) 79 | 80 | # remove duplicates that might've snuck in 81 | dupes <- duplicated(repos) | duplicated(names(repos)) 82 | repos[!dupes] 83 | 84 | } 85 | 86 | renv_bootstrap_download <- function(version) { 87 | 88 | # if the renv version number has 4 components, assume it must 89 | # be retrieved via github 90 | nv <- numeric_version(version) 91 | components <- unclass(nv)[[1]] 92 | 93 | methods <- if (length(components) == 4L) { 94 | list( 95 | renv_bootstrap_download_github 96 | ) 97 | } else { 98 | list( 99 | renv_bootstrap_download_cran_latest, 100 | renv_bootstrap_download_cran_archive 101 | ) 102 | } 103 | 104 | for (method in methods) { 105 | path <- tryCatch(method(version), error = identity) 106 | if (is.character(path) && file.exists(path)) 107 | return(path) 108 | } 109 | 110 | stop("failed to download renv ", version) 111 | 112 | } 113 | 114 | renv_bootstrap_download_impl <- function(url, destfile) { 115 | 116 | mode <- "wb" 117 | 118 | # https://bugs.r-project.org/bugzilla/show_bug.cgi?id=17715 119 | fixup <- 120 | Sys.info()[["sysname"]] == "Windows" && 121 | substring(url, 1L, 5L) == "file:" 122 | 123 | if (fixup) 124 | mode <- "w+b" 125 | 126 | utils::download.file( 127 | url = url, 128 | destfile = destfile, 129 | mode = mode, 130 | quiet = TRUE 131 | ) 132 | 133 | } 134 | 135 | renv_bootstrap_download_cran_latest <- function(version) { 136 | 137 | repos <- renv_bootstrap_download_cran_latest_find(version) 138 | 139 | message("* Downloading renv ", version, " from CRAN ... ", appendLF = FALSE) 140 | 141 | info <- tryCatch( 142 | utils::download.packages( 143 | pkgs = "renv", 144 | repos = repos, 145 | destdir = tempdir(), 146 | quiet = TRUE 147 | ), 148 | condition = identity 149 | ) 150 | 151 | if (inherits(info, "condition")) { 152 | message("FAILED") 153 | return(FALSE) 154 | } 155 | 156 | message("OK") 157 | info[1, 2] 158 | 159 | } 160 | 161 | renv_bootstrap_download_cran_latest_find <- function(version) { 162 | 163 | all <- renv_bootstrap_repos() 164 | 165 | for (repos in all) { 166 | 167 | db <- tryCatch( 168 | as.data.frame( 169 | x = utils::available.packages(repos = repos), 170 | stringsAsFactors = FALSE 171 | ), 172 | error = identity 173 | ) 174 | 175 | if (inherits(db, "error")) 176 | next 177 | 178 | entry <- db[db$Package %in% "renv" & db$Version %in% version, ] 179 | if (nrow(entry) == 0) 180 | next 181 | 182 | return(repos) 183 | 184 | } 185 | 186 | fmt <- "renv %s is not available from your declared package repositories" 187 | stop(sprintf(fmt, version)) 188 | 189 | } 190 | 191 | renv_bootstrap_download_cran_archive <- function(version) { 192 | 193 | name <- sprintf("renv_%s.tar.gz", version) 194 | repos <- renv_bootstrap_repos() 195 | urls <- file.path(repos, "src/contrib/Archive/renv", name) 196 | destfile <- file.path(tempdir(), name) 197 | 198 | message("* Downloading renv ", version, " from CRAN archive ... ", appendLF = FALSE) 199 | 200 | for (url in urls) { 201 | 202 | status <- tryCatch( 203 | renv_bootstrap_download_impl(url, destfile), 204 | condition = identity 205 | ) 206 | 207 | if (identical(status, 0L)) { 208 | message("OK") 209 | return(destfile) 210 | } 211 | 212 | } 213 | 214 | message("FAILED") 215 | return(FALSE) 216 | 217 | } 218 | 219 | renv_bootstrap_download_github <- function(version) { 220 | 221 | enabled <- Sys.getenv("RENV_BOOTSTRAP_FROM_GITHUB", unset = "TRUE") 222 | if (!identical(enabled, "TRUE")) 223 | return(FALSE) 224 | 225 | # prepare download options 226 | pat <- Sys.getenv("GITHUB_PAT") 227 | if (nzchar(Sys.which("curl")) && nzchar(pat)) { 228 | fmt <- "--location --fail --header \"Authorization: token %s\"" 229 | extra <- sprintf(fmt, pat) 230 | saved <- options("download.file.method", "download.file.extra") 231 | options(download.file.method = "curl", download.file.extra = extra) 232 | on.exit(do.call(base::options, saved), add = TRUE) 233 | } else if (nzchar(Sys.which("wget")) && nzchar(pat)) { 234 | fmt <- "--header=\"Authorization: token %s\"" 235 | extra <- sprintf(fmt, pat) 236 | saved <- options("download.file.method", "download.file.extra") 237 | options(download.file.method = "wget", download.file.extra = extra) 238 | on.exit(do.call(base::options, saved), add = TRUE) 239 | } 240 | 241 | message("* Downloading renv ", version, " from GitHub ... ", appendLF = FALSE) 242 | 243 | url <- file.path("https://api.github.com/repos/rstudio/renv/tarball", version) 244 | name <- sprintf("renv_%s.tar.gz", version) 245 | destfile <- file.path(tempdir(), name) 246 | 247 | status <- tryCatch( 248 | renv_bootstrap_download_impl(url, destfile), 249 | condition = identity 250 | ) 251 | 252 | if (!identical(status, 0L)) { 253 | message("FAILED") 254 | return(FALSE) 255 | } 256 | 257 | message("OK") 258 | return(destfile) 259 | 260 | } 261 | 262 | renv_bootstrap_install <- function(version, tarball, library) { 263 | 264 | # attempt to install it into project library 265 | message("* Installing renv ", version, " ... ", appendLF = FALSE) 266 | dir.create(library, showWarnings = FALSE, recursive = TRUE) 267 | 268 | # invoke using system2 so we can capture and report output 269 | bin <- R.home("bin") 270 | exe <- if (Sys.info()[["sysname"]] == "Windows") "R.exe" else "R" 271 | r <- file.path(bin, exe) 272 | args <- c("--vanilla", "CMD", "INSTALL", "-l", shQuote(library), shQuote(tarball)) 273 | output <- system2(r, args, stdout = TRUE, stderr = TRUE) 274 | message("Done!") 275 | 276 | # check for successful install 277 | status <- attr(output, "status") 278 | if (is.numeric(status) && !identical(status, 0L)) { 279 | header <- "Error installing renv:" 280 | lines <- paste(rep.int("=", nchar(header)), collapse = "") 281 | text <- c(header, lines, output) 282 | writeLines(text, con = stderr()) 283 | } 284 | 285 | status 286 | 287 | } 288 | 289 | renv_bootstrap_prefix <- function() { 290 | 291 | # construct version prefix 292 | version <- paste(R.version$major, R.version$minor, sep = ".") 293 | prefix <- paste("R", numeric_version(version)[1, 1:2], sep = "-") 294 | 295 | # include SVN revision for development versions of R 296 | # (to avoid sharing platform-specific artefacts with released versions of R) 297 | devel <- 298 | identical(R.version[["status"]], "Under development (unstable)") || 299 | identical(R.version[["nickname"]], "Unsuffered Consequences") 300 | 301 | if (devel) 302 | prefix <- paste(prefix, R.version[["svn rev"]], sep = "-r") 303 | 304 | # build list of path components 305 | components <- c(prefix, R.version$platform) 306 | 307 | # include prefix if provided by user 308 | prefix <- Sys.getenv("RENV_PATHS_PREFIX") 309 | if (nzchar(prefix)) 310 | components <- c(prefix, components) 311 | 312 | # build prefix 313 | paste(components, collapse = "/") 314 | 315 | } 316 | 317 | renv_bootstrap_library_root_name <- function(project) { 318 | 319 | # use project name as-is if requested 320 | asis <- Sys.getenv("RENV_PATHS_LIBRARY_ROOT_ASIS", unset = "FALSE") 321 | if (asis) 322 | return(basename(project)) 323 | 324 | # otherwise, disambiguate based on project's path 325 | id <- substring(renv_bootstrap_hash_text(project), 1L, 8L) 326 | paste(basename(project), id, sep = "-") 327 | 328 | } 329 | 330 | renv_bootstrap_library_root <- function(project) { 331 | 332 | path <- Sys.getenv("RENV_PATHS_LIBRARY", unset = NA) 333 | if (!is.na(path)) 334 | return(path) 335 | 336 | path <- Sys.getenv("RENV_PATHS_LIBRARY_ROOT", unset = NA) 337 | if (!is.na(path)) { 338 | name <- renv_bootstrap_library_root_name(project) 339 | return(file.path(path, name)) 340 | } 341 | 342 | file.path(project, "renv/library") 343 | 344 | } 345 | 346 | renv_bootstrap_validate_version <- function(version) { 347 | 348 | loadedversion <- utils::packageDescription("renv", fields = "Version") 349 | if (version == loadedversion) 350 | return(TRUE) 351 | 352 | # assume four-component versions are from GitHub; three-component 353 | # versions are from CRAN 354 | components <- strsplit(loadedversion, "[.-]")[[1]] 355 | remote <- if (length(components) == 4L) 356 | paste("rstudio/renv", loadedversion, sep = "@") 357 | else 358 | paste("renv", loadedversion, sep = "@") 359 | 360 | fmt <- paste( 361 | "renv %1$s was loaded from project library, but this project is configured to use renv %2$s.", 362 | "Use `renv::record(\"%3$s\")` to record renv %1$s in the lockfile.", 363 | "Use `renv::restore(packages = \"renv\")` to install renv %2$s into the project library.", 364 | sep = "\n" 365 | ) 366 | 367 | msg <- sprintf(fmt, loadedversion, version, remote) 368 | warning(msg, call. = FALSE) 369 | 370 | FALSE 371 | 372 | } 373 | 374 | renv_bootstrap_hash_text <- function(text) { 375 | 376 | hashfile <- tempfile("renv-hash-") 377 | on.exit(unlink(hashfile), add = TRUE) 378 | 379 | writeLines(text, con = hashfile) 380 | tools::md5sum(hashfile) 381 | 382 | } 383 | 384 | renv_bootstrap_load <- function(project, libpath, version) { 385 | 386 | # try to load renv from the project library 387 | if (!requireNamespace("renv", lib.loc = libpath, quietly = TRUE)) 388 | return(FALSE) 389 | 390 | # warn if the version of renv loaded does not match 391 | renv_bootstrap_validate_version(version) 392 | 393 | # load the project 394 | renv::load(project) 395 | 396 | TRUE 397 | 398 | } 399 | 400 | # construct path to library root 401 | root <- renv_bootstrap_library_root(project) 402 | 403 | # construct library prefix for platform 404 | prefix <- renv_bootstrap_prefix() 405 | 406 | # construct full libpath 407 | libpath <- file.path(root, prefix) 408 | 409 | # attempt to load 410 | if (renv_bootstrap_load(project, libpath, version)) 411 | return(TRUE) 412 | 413 | # load failed; inform user we're about to bootstrap 414 | prefix <- paste("# Bootstrapping renv", version) 415 | postfix <- paste(rep.int("-", 77L - nchar(prefix)), collapse = "") 416 | header <- paste(prefix, postfix) 417 | message(header) 418 | 419 | # perform bootstrap 420 | bootstrap(version, libpath) 421 | 422 | # exit early if we're just testing bootstrap 423 | if (!is.na(Sys.getenv("RENV_BOOTSTRAP_INSTALL_ONLY", unset = NA))) 424 | return(TRUE) 425 | 426 | # try again to load 427 | if (requireNamespace("renv", lib.loc = libpath, quietly = TRUE)) { 428 | message("* Successfully installed and loaded renv ", version, ".") 429 | return(renv::load()) 430 | } 431 | 432 | # failed to download or load renv; warn the user 433 | msg <- c( 434 | "Failed to find an renv installation: the project will not be loaded.", 435 | "Use `renv::activate()` to re-initialize the project." 436 | ) 437 | 438 | warning(paste(msg, collapse = "\n"), call. = FALSE) 439 | 440 | }) 441 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /tests/testthat.R: -------------------------------------------------------------------------------- 1 | library(testthat) 2 | library(rPackageTutorial) 3 | 4 | test_check("rPackageTutorial") 5 | -------------------------------------------------------------------------------- /tests/testthat/test-add.R: -------------------------------------------------------------------------------- 1 | test_that("add() function return the sum of two number", { 2 | expect_equal(add(1, 2), 3) 3 | }) 4 | --------------------------------------------------------------------------------