From 3e2f36bc25e5a393ebbb02a30f9ecc44abee21a6 Mon Sep 17 00:00:00 2001
From: Ghislain Durif <gd.dev@libertymail.net>
Date: Tue, 28 Feb 2023 12:49:38 +0100
Subject: [PATCH] presentation

---
 R_pkg_dev_helper.qmd | 399 +++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 399 insertions(+)
 create mode 100644 R_pkg_dev_helper.qmd

diff --git a/R_pkg_dev_helper.qmd b/R_pkg_dev_helper.qmd
new file mode 100644
index 0000000..f283250
--- /dev/null
+++ b/R_pkg_dev_helper.qmd
@@ -0,0 +1,399 @@
+---
+title: "R helper packages to develop R packages"
+author: "Ghislain Durif (<https://gdurif.perso.math.cnrs.fr/>)"
+institute: "LBMC -- CNRS"
+date: "2023/02/28"
+toc: true
+format:
+  html:
+    toc: true
+    execute:
+      echo: true
+      warning: false
+---
+
+## R package ?
+
+```
+my_package
+├── .Rbuildignore
+├── _pkgdown.yml
+├── DESCRIPTION
+├── LICENSE.md
+├── LICENSE.note
+├── NAMESPACE
+├── README.md
+├── README.Rmd
+├── man
+│   └── my_function.Rd
+├── R
+│   └── my_function.R
+├── tests
+│   ├── testthat
+│   │   └── test-my_function.R
+│   └── testthat.R
+└── vignettes
+    └── getting-started-with-my-function.Rmd
+```
+
+## [`devtools`](https://devtools.r-lib.org/)
+
+### Tools to Make Developing R Packages Easier • devtools
+
+```{r, eval=FALSE}
+# load local package without installing it
+devtools::load_all()
+# Build the documentation (man pages)
+devtools::document()
+# Run the unit tests
+devtools::test()
+# Run coverage test
+devtools::test_coverage()
+# Build package source
+devtools::build()
+# Run package check
+devtools::check()
+```
+
+#### Additional linls
+
+- [Package dev cheatsheet](https://devtools.r-lib.org/#cheatsheet)
+
+## [`usethis`](https://usethis.r-lib.org/)
+
+### Automate Package and Project Setup
+
+```{r, eval=FALSE}
+# Create a new package -------------------------------------------------
+path <- file.path(tempdir(), "mypkg")
+usethis::create_package(path)
+#> ✔ Creating '/tmp/Rtmp4VMzwK/mypkg/'
+#> ✔ Setting active project to '/private/tmp/Rtmp4VMzwK/mypkg'
+#> ✔ Creating 'R/'
+#> ✔ Writing 'DESCRIPTION'
+#> Package: mypkg
+#> Title: What the Package Does (One Line, Title Case)
+#> Version: 0.0.0.9000
+#> Authors@R (parsed):
+#>     * First Last <first.last@example.com> [aut, cre] (YOUR-ORCID-ID)
+#> Description: What the package does (one paragraph).
+#> License: `use_mit_license()`, `use_gpl3_license()` or friends to pick a
+#>     license
+#> Encoding: UTF-8
+#> Roxygen: list(markdown = TRUE)
+#> RoxygenNote: 7.2.0
+#> ✔ Writing 'NAMESPACE'
+#> ✔ Setting active project to '<no active project>'
+# only needed since this session isn't interactive
+usethis::proj_activate(path)
+#> ✔ Setting active project to '/private/tmp/Rtmp4VMzwK/mypkg'
+#> ✔ Changing working directory to '/tmp/Rtmp4VMzwK/mypkg/'
+
+# Modify the description ----------------------------------------------
+usethis::use_mit_license("My Name")
+#> ✔ Setting License field in DESCRIPTION to 'AGPL (>= 3)'
+#> ✔ Writing 'LICENSE.md'
+#> ✔ Adding '^LICENSE\\.md$' to '.Rbuildignore'
+
+usethis::use_package("ggplot2", "Suggests")
+#> ✔ Adding 'ggplot2' to Suggests field in DESCRIPTION
+#> • Use `requireNamespace("ggplot2", quietly = TRUE)` to test if package is installed
+#> • Then directly refer to functions with `ggplot2::fun()`
+
+# Set up other files -------------------------------------------------
+usethis::use_readme_md()
+#> ✔ Writing 'README.md'
+#> • Update 'README.md' to include installation instructions.
+
+usethis::use_news_md()
+#> ✔ Writing 'NEWS.md'
+
+usethis::use_test("my-test")
+#> ✔ Adding 'testthat' to Suggests field in DESCRIPTION
+#> ✔ Setting Config/testthat/edition field in DESCRIPTION to '3'
+#> ✔ Creating 'tests/testthat/'
+#> ✔ Writing 'tests/testthat.R'
+#> ✔ Writing 'tests/testthat/test-my-test.R'
+#> • Edit 'tests/testthat/test-my-test.R'
+
+x <- 1
+y <- 2
+usethis::use_data(x, y)
+#> ✔ Adding 'R' to Depends field in DESCRIPTION
+#> ✔ Creating 'data/'
+#> ✔ Setting LazyData to 'true' in 'DESCRIPTION'
+#> ✔ Saving 'x', 'y' to 'data/x.rda', 'data/y.rda'
+#> • Document your data (see 'https://r-pkgs.org/data.html')
+
+# Use git ------------------------------------------------------------
+usethis::use_git()
+#> ✔ Initialising Git repo
+#> ✔ Adding '.Rproj.user', '.Rhistory', '.Rdata', '.httr-oauth', '.DS_Store' to '.gitignore'
+```
+
+#### Other
+
+- `usethis::use_devtools()`
+- `usethis::use_build_ignore("<filename>")` to add files to `.Rbuildignore`
+- `usethis::use_git_ignore("<filename>")` to add files to `.gitignore`
+- `usethis::use_github_action()` or `usethis::use_gitlab_ci()` to setup a CI (c.f. `gitlabr` package [below](#and-more))
+
+#### Additional links:
+
+- [References](https://usethis.r-lib.org/reference/index.html)
+
+## [`fusen`](https://thinkr-open.github.io/fusen/)
+
+### Build a Package from Rmarkdown Files
+
+> "`{fusen}` inflates a Rmarkdown file to magically create a package."
+
+![](img/fusen_inflate_functions.png)
+
+[Credit](https://thinkr-open.github.io/fusen/reference/figures/fusen_inflate_functions.png)
+
+```{r fusen, eval=FALSE}
+path <- file.path(getwd(), "examples", "my.fusen.pkg")
+fusen::create_fusen(path, template = "full", open = FALSE)
+#> ── Creating new directory: /path/to/examples/my.fusen.pkg ──────────────────────────────────────────────────────────────────────────────────────────────────
+#> ✔ Creating '/path/to/examples/my.fusen.pkg/'
+#> ✔ Setting active project to '/path/to/examples/my.fusen.pkg'
+#> ✔ Creating 'R/'
+#> ✔ Writing a sentinel file '.here'
+#> • Build robust paths within your project via `here::here()`
+#> • Learn more at <https://here.r-lib.org>
+#> ✔ Setting active project to '<no active project>'
+#> ✔ New directory created: /path/to/examples/my.fusen.pkg
+#> ── Adding dev/flat_full.Rmd ───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
+#> File .here already exists in /path/to/examples/my.fusen.pkg
+#> ✔ Added /path/to/examples/my.fusen.pkg/dev/flat_full.Rmd, /path/to/examples/my.fusen.pkg/dev/0-dev_history.Rmd
+fusen::add_flat_template(path, flat_name = "package")
+#> '0-dev_history.Rmd' already exists. It was not overwritten. Set `add_flat_template(overwrite = TRUE)` if you want to do so.
+#> File .here already exists in /path/to/examples/my.fusen.pkg
+#> • Modify '/path/to/examples/my.fusen.pkg/dev/flat_package.Rmd'
+```
+
+#### Package development
+
+- [`dev/(0-)dev_history.Rmd`](./examples/my.fusen.pkg/dev/dev_history.Rmd): recipe
+- [`dev/flat_<flat_name1>.Rmd`](./examples/my.fusen.pkg/dev/flat_package.Rmd), `dev/flat_<flat_name2>.Rmd`, etc.: package content
+
+
+#### Additional links
+
+- [FAQ](https://thinkr-open.github.io/fusen/articles/tips-and-tricks.html)
+
+## [`roxygen2`](https://roxygen2.r-lib.org/)
+
+### In-Line Documentation for R • roxygen2
+
+File `R/add.R`:
+
+```{r, eval=FALSE}
+#' Add together two numbers
+#'
+#' @param x A number.
+#' @param y A number.
+#' @return A number.
+#' @examples
+#' add(1, 1)
+#' add(10, 1)
+add <- function(x, y) {
+  x + y
+}
+```
+
+Automatic generation of `man/add.Rd` with `devtools::document()`
+```Rd
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in ./<text>
+\name{add}
+\alias{add}
+\title{Add together two numbers}
+\usage{
+add(x, y)
+}
+\arguments{
+\item{x}{A number.}
+
+\item{y}{A number.}
+}
+\value{
+A number.
+}
+\description{
+Add together two numbers
+}
+\examples{
+add(1, 1)
+add(10, 1)
+}
+```
+
+#### Additional link
+
+- [Getting started](https://roxygen2.r-lib.org/articles/roxygen2.html)
+
+## [`testthat`](https://testthat.r-lib.org/)
+
+### Unit Testing for R
+
+```{r, eval=FALSE}
+# run once to configure the package meta-data
+usethis::use_testthat()
+# to create a test file "tests/testthat/test-my_function.R"
+usethis::use_test("my_function")
+#> ✔ Setting active project to '/home/drg/work/dev/R/funStatTest'
+#> ✔ Writing 'tests/testthat/test-my_function.R'
+#> • Modify 'tests/testthat/test-my_function.R'
+```
+
+File `tests/testthat/test-my_function.R`:
+```R
+test_that("multiplication works", {
+  expect_equal(2 * 2, 4)
+})
+```
+
+Automatically run during package check (e.g. with `devtools::check()`) or specifically with `devtools::check()`.
+
+## [`checkmate`](https://mllg.github.io/checkmate/)
+
+
+### Fast and versatile argument checks for R
+
+- `assert_xxx()` fails if assertion not met
+- `test_xxx()` returns a the check result as a logical (`TRUE`/`FALSE`) value
+- `expect_xxx()` are designed to be used in `testthat` unit tests.
+
+```{r, error=TRUE}
+checkmate::assert_choice("my-choice", choices = c("choice_1", "choice_2"))
+```
+
+#### Example ([source](https://mllg.github.io/checkmate/articles/checkmate.html))
+
+Standard input check:
+```{r, eval=FALSE}
+fact <- function(n, method = "stirling") {
+  if (length(n) != 1)
+    stop("Argument 'n' must have length 1")
+  if (!is.numeric(n))
+    stop("Argument 'n' must be numeric")
+  if (is.na(n))
+    stop("Argument 'n' may not be NA")
+  if (is.double(n)) {
+    if (is.nan(n))
+      stop("Argument 'n' may not be NaN")
+    if (is.infinite(n))
+      stop("Argument 'n' must be finite")
+    if (abs(n - round(n, 0)) > sqrt(.Machine$double.eps))
+      stop("Argument 'n' must be an integerish value")
+    n <- as.integer(n)
+  }
+  if (n < 0)
+    stop("Argument 'n' must be >= 0")
+  if (length(method) != 1)
+    stop("Argument 'method' must have length 1")
+  if (!is.character(method) || !method %in% c("stirling", "factorial"))
+    stop("Argument 'method' must be either 'stirling' or 'factorial'")
+
+  if (method == "factorial")
+    factorial(n)
+  else
+    sqrt(2 * pi * n) * (n / exp(1))^n
+}
+```
+
+`checkmate`-based input check:
+```{r, eval=FALSE}
+fact <- function(n, method = "stirling") {
+  assertCount(n)
+  assertChoice(method, c("stirling", "factorial"))
+
+  if (method == "factorial")
+    factorial(n)
+  else
+    sqrt(2 * pi * n) * (n / exp(1))^n
+}
+```
+
+#### Advanced checks
+
+```{r, error=TRUE}
+x <- runif(100)
+y <- rnorm(100)
+# expect 100 numerical values with NA and between 0 and 1
+checkmate::qassert(x, "N100[0,1]")
+checkmate::qassert(y, "N100[0,1]")
+```
+
+#### Additional links
+
+- [Getting started](https://mllg.github.io/checkmate/articles/checkmate.html)
+- [References](https://mllg.github.io/checkmate/reference/)
+
+## [`pkgdown`](https://pkgdown.r-lib.org/)
+
+### Build websites for R packages
+
+```{r, eval=FALSE}
+# Run once to configure package to use pkgdown
+usethis::use_pkgdown()
+```
+
+Config file: `_pkgdown.yml` (see the https://pkgdown.r-lib.org/articles/customise.html)
+
+```{r, eval=FALSE}
+# Run to build the website
+pkgdown::build_site()
+```
+
+- `README.md` -> home page `index.html`
+- vignettes -> "article" pages
+- `man/*.Rd` -> "reference" pages
+- `NEWS.md` -> "News" pages
+- `DESCRIPTION` metadata file -> home page side bar with links and description
+
+#### Additional links
+
+- [Getting started](https://pkgdown.r-lib.org/articles/pkgdown.html)
+
+## And more
+
+### [`covr`](https://covr.r-lib.org/): Test Coverage for Packages
+
+```{r, eval=FALSE}
+usethis::use_coverage()
+covr::package_coverage()
+```
+
+### [`gitlabr`](https://cran.r-project.org/package=gitlabr): Access to the 'Gitlab' API
+
+Setup a Gitlab CI to check the package, check the test coverage and deploy a `pkgdown` website as Gitlab pages:
+
+```{r, eval=FALSE}
+gitlabr::use_gitlab_ci(
+    image = "rocker/verse:latest",
+    type = "check-coverage-pkgdown"
+)
+```
+
+### [`lintr`](https://lintr.r-lib.org/): A Linter for R Code
+
+```{r, eval=FALSE}
+lintr::use_lintr(type = "tidyverse")
+lintr::lint_package()
+```
+
+### [`withr`](https://withr.r-lib.org/): Run Code With Temporarily Modified Global State
+
+```{r, eval=FALSE}
+getwd()
+#> [1] "/home/runner/work/withr/withr/docs/reference"
+
+with_dir(tempdir(), {
+    # do some stuff
+    getwd()
+})
+#> [1] "/tmp/RtmpR75In3"
+```
-- 
GitLab