forked from hadley/r-pkgs
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Whole-game.Rmd
773 lines (551 loc) · 35.6 KB
/
Whole-game.Rmd
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
# The Whole Game {#whole-game}
```{r, include = FALSE}
source("common.R")
# Do you get this error?
# "there is no package called 'foofactors'"
# fix = manually delete Whole-game_cache
knitr::opts_chunk$set(cache = TRUE)
## do you want to see how this sausage is made?
debug <- FALSE
## do we build the toy package? if so, where?
## * NA --> no, don't build it
## * "tmp_user" --> build in ~/tmp/foofactors, so it's easy to access after
## * "tmp_session" --> build in session temp dir, it's disposable
where <- "tmp_session"
```
*Spoiler alert!*
This chapter runs through the development of a small toy package. It's meant to paint the Big Picture and suggest a workflow, before we descend into the detailed treatment of the key components of an R package.
To keep the pace brisk, we exploit the modern conveniences in the devtools package and the RStudio IDE. In later chapters, we are more explicit about what those helpers are doing for us.
*Work in progress! Adapting from material originally developed as part of STAT 545 and as a possible vignette for devtools.*
## Load devtools and friends
Load the devtools package, which is the public face of a set of packages that support various aspects of package development.
```{r}
library(devtools)
```
For presentation purposes only, we use [fs](https://fs.r-lib.org), for filesystem work, and the [tidyverse](https://tidyverse.tidyverse.org), for light data wrangling.
```{r, R.options = list(tidyverse.quiet = TRUE)}
library(tidyverse)
library(fs)
```
## Toy package: foofactors
We use various functions from devtools to build a small toy package from scratch, with features commonly seen in released packages:
* Functions to address a specific need, such as helpers to work with factors.
* Access to established workflows for installation, getting help, and checking basic quality.
* Version control and an open development process.
- This is completely optional in your work, but recommended. You'll see how Git and GitHub helps us expose all the intermediate stages of our package.
* Documentation for individual functions via [roxygen2](https://CRAN.R-project.org/package=roxygen2).
* Unit testing with [testthat](http://testthat.r-lib.org).
* Documentation for the package as a whole via an executable `README.Rmd`.
We call the package **foofactors** and it will have a couple functions for handling factors. Please note that these functions are super simple and definitely not the point! For a proper package for factor handling, please see [forcats](https://cran.r-project.org/package=forcats).
The foofactors package itself is not our goal here. It is a device for demonstrating a typical workflow for package development with devtools.
## Peek at the finished product
The foofactors package is tracked during its development with the Git version control system. This is purely optional and you can certainly follow along without implementing this. A nice side benefit is that we eventually connect it to a remote repository on GitHub, which means you can see the glorious result we are working towards by visiting foofactors on GitHub: <https://github.com/jennybc/foofactors>. By inspecting the [commit history](https://github.com/jennybc/foofactors/commits/master) and especially the diffs, you can see exactly what changes at each step of the process laid out below.
FIXME: I think these diffs are extremely useful and would like to surface them better here.
## `create_package()`
Call `create_package()` to initialize a new package in a directory on your computer (and create the directory, if necessary).
Make a deliberate choice about where to create this package on your computer. It should probably be somewhere within your home directory, alongside your other R projects. It should not be nested inside another RStudio Project, R package, or Git repo. Nor should it be in an R package library, which holds packages that have already been built and installed. The conversion of the source package we are creating here into an installed package is part of what devtools facilitates. Don't try to do devtools' job for it!
Substitute your chosen path into a `create_package()` call like this:
```{r create-package-fake, eval = FALSE}
create_package("~/path/to/foofactors")
```
We have to work in a temp directory, because this book is built non-interactively, in the cloud. Behind the scenes, we're executing our own `create_package()` command, but don't be surprised if our output differs a bit from yours.
```{r configure, include = FALSE}
where <- match.arg(where, choices = c(NA, "tmp_user", "tmp_session"))
create <- !is.na(where)
where <- switch(
where,
tmp_user = fs::path_home("tmp"),
tmp_session = fs::path_temp(),
NULL
)
foopath <- path(where, "foofactors")
if (!is.null(where)) {
if (requireNamespace("foofactors", quietly = TRUE)) {
remove.packages("foofactors")
}
if (fs::dir_exists(foopath)) {
fs::dir_delete(foopath)
}
fs::dir_create(where)
}
```
```{r create-package, eval = create, echo = debug, R.options = list(usethis.description = NULL)}
create_package(foopath, open = FALSE, rstudio = TRUE)
proj_set(foopath)
```
```{r set-proj-and-wd, include = debug, eval = create}
# being kind to the vignette developer
(owd <- getwd())
if (is.null(getOption("knitr.in.progress"))) {
setwd(foopath)
Sys.setenv(TESTTHAT = "true")
}
getwd()
## I normally am not this masochistic, but there is little choice
knitr::opts_knit$set(root.dir = foopath)
```
```{r sitrep, include = debug, eval = create}
## can't be in chunk above, because knitr
proj_sitrep()
```
If you're working in RStudio, you should find yourself in a new instance of RStudio, opened into your new foofactors package (and Project). If you somehow need to do this manually, navigate to the directory and double click on `foofactors.Rproj`. RStudio has special handling for packages and you should now see a *Build* tab in the same pane as *Environment* and *History*.
FIXME: good place for a screenshot
What's in this new directory that is also an R package and, probably, an RStudio Project? Here's a listing (locally, you can consult your *Files* pane):
```{r init-show-files, echo = FALSE, eval = create}
dir_info(all = TRUE) %>%
select(path, type)
```
:::rstudio-tip
In the file browser, go to *More > Show Hidden Files* to toggle the visibility of hidden files (a.k.a. ["dotfiles"](https://en.wikipedia.org/wiki/Hidden_file_and_hidden_directory#Unix_and_Unix-like_environments)). A select few are visible all the time, but sometimes you want to see them all.
:::
* `.Rbuildignore` lists files that we need to have around but that should not be included when building the R package from source.
* `.Rproj.user`, if you have it, is a directory used internally by RStudio.
* `.gitignore` anticipates Git usage and ignores some standard, behind-the-scenes files created by R and RStudio. Even if you do not plan to use Git, this is harmless.
* `DESCRIPTION` provides [metadata about your package](#description). We edit this shortly.
* [`NAMESPACE`](#namespace) declares the functions your package exports for external use and the external functions your package imports from other packages. At the moment, it holds temporary-yet-functional placeholder content.
* The `R/` directory is the ["business end" of your package](#r). It will soon contain `.R` files with function definitions.
* `foofactors.Rproj` is the file that makes this directory an RStudio Project. Even if you don't use RStudio, this file is harmless. Or you can suppress its creation with `create_package(..., rstudio = FALSE)`.
## `use_git()`
:::tip
The use of Git or another version control system is optional, but a recommended practice in the long-term. We explain its importance in FUTURE LINK (FIXME: link to right place when exists).
:::
The foofactors directory is an R source package and an RStudio Project. Now we make it also a Git repository, with `use_git()`.
```{r use-git, eval = create, chunk_envvar = c("TESTTHAT" = "true")}
use_git()
```
In an interactive session, you will be asked if you want to commit some files here and you should probably accept the offer. Behind the scenes, we'll cause the same to happen for us.
```{r git2r-begin, echo = debug, eval = create}
suppressPackageStartupMessages(library(git2r))
repo <- repository(proj_get())
paths <- unlist(status(repo), use.names = FALSE)
add(repo, paths)
commit(repo, "Initial commit")
```
What's new? Only the creation of a `.git` directory, which is hidden in most contexts, including the RStudio file browser. Its existence is evidence that we have indeed initialized a Git repo here.
```{r post-git-file-list, echo = FALSE, eval = create}
dir_info(all = TRUE, regexp = "^[.]git$") %>%
select(path, type)
```
If you're using RStudio, it probably requested permission to relaunch itself in this Project. You can do so manually by quitting and relaunching by double clicking on `foofactors.Rproj`. Now, in addition to package development support, you have access to a basic Git client in the *Git* tab of the *Environment/History/Build* pane.
FIXME: good place for a screenshot
Click on History (the clock icon) and, if you consented, you will see an initial commit made via `use_git()`:
```{r inspect-first-commit, echo = FALSE, eval = create}
commits(repo)[[1]]
```
:::rstudio-tip
RStudio can initialize a Git repository, in any Project, even if it's not an R package, as long you've set up RStudio + Git integration. Do *Tools > Version Control > Project Setup*. Then choose *Version control system: Git* and *initialize a new git repository for this project*.
:::
## Write the first function
It is not too hard to find a puzzling operation involving factors. Let's see what happens when we catenate two factors.
```{r}
(a <- factor(c("character", "hits", "your", "eyeballs")))
(b <- factor(c("but", "integer", "where it", "counts")))
c(a, b)
```
Huh? Many people do not expect the result of catenating two factors to be an integer vector consisting of the numbers 1, 2, 3, and 4. What if we coerce each factor to character, catenate, then re-convert to factor?
```{r}
factor(c(as.character(a), as.character(b)))
```
That seems to produce a result that makes more sense. Let's drop that logic into the body of a function called `fbind()`:
```{r fbind-fodder, asis = TRUE, echo = FALSE, comment = NA}
fbind_fodder <- c(
"fbind <- function(a, b) {",
" factor(c(as.character(a), as.character(b)))",
"}"
)
cat(fbind_fodder, sep = "\n")
```
This book does not teach you how to write functions in R. To learn more about that take a look at the [Functions chapter](https://r4ds.had.co.nz/functions.html) of R for Data Science and the [Functions chapter](https://adv-r.hadley.nz/functions.html) of Advanced R.
## `use_r()`
Where shall we define `fbind()`? Save it in a `.R` file, in the `R/` subdirectory of your package. A reasonable starting position is to make a new `.R` file for each function in your package and name the file after the function. As you add more functions, you'll want to relax this and begin to group related functions together. We'll save the definition of `fbind()` in the file `R/fbind.R`.
The helper `use_r()` creates and/or opens a script below `R/`. It really shines in a more mature package, when navigating between `.R` files and the associated tests. But, even here, it's useful to keep yourself from getting too carried away while working in `Untitled4`.
```{r init-fbind, eval = create, chunk_envvar = c("TESTTHAT" = "true")}
use_r("fbind")
```
```{r write-fbind, include = FALSE, eval = create}
writeLines(fbind_fodder, path("R", "fbind.R"))
```
Put the definition of `fbind()` **and only the definition of `fbind()`** in `R/fbind.R` and save it. The file `R/fbind.R` should NOT contain any of the other top-level code we have recently executed, such as the definitions of factors `a` and `b`, `library(devtools)` or `use_git()`. This foreshadows an adjustment you'll need to make as you transition from writing R scripts to R packages. Packages and scripts use different mechanisms to declare their dependency on other packages and to store example or test code. We explore this further in chapter \@ref(r).
## `load_all()`
How do we test drive `fbind()`? If this were a regular R script, we might use RStudio to send the function definition to the R Console and define `fbind()` in the global workspace. Or maybe we'd call `source("R/fbind.R")`. For package development, however, devtools offers a more robust approach.
Call `load_all()` to make `fbind()` available for experimentation.
```{r load-all, eval = create}
load_all()
```
Now call `fbind(a, b)` to see how it works.
```{r, eval = create}
fbind(a, b)
```
Note that `load_all()` has made the `fbind()` function available, although it does not exist in the global workspace.
```{r, eval = create}
exists("fbind", where = ".GlobalEnv", inherits = FALSE)
```
`load_all()` simulates the process of building, installing, and attaching the foofactors package. As your package accumulates more functions, some exported, some not, some of which call each other, some of which call functions from packages you depend on, `load_all()` gives you a much more accurate sense of how the package is developing than test driving functions defined in the global workspace. Also `load_all()` allows much faster iteration than actually building, installing, and attaching the package.
Review so far:
* We wrote our first function, `fbind()`, to catenate two factors.
* We used `load_all()` to quickly make this function available for interactive use, as if we'd built and installed foofactors and attached it via `library(foofactors)`.
:::rstudio-tip
RStudio exposes `load_all()` in the *Build* menu, in the *Build* pane via *More > Load All*, and in keyboard shortcuts Ctrl + Shift + L (Windows & Linux) or Cmd + Shift + L (macOS).
:::
### Commit `fbind()`
If you're using Git, use your preferred method to commit the new `R/fbind.R` file. We do so behind the scenes here and here's the associated diff.
```{r fbind-commit, echo = debug, comment = NA, include = FALSE, eval = create}
add(repo, path = path("R", "fbind.R"))
commit(repo, message = "Add fbind()")
## tags might be useful for making stable links to the package at specific
## evolutionary stages
## possible convention: tag name = chunk label
#tag_name <- knitr::opts_current$get("label")
#tag(repo, tag_name, "initial creation of fbind()")
#tag(repo, "fbind-init", "initial creation of fbind()")
#sha <- (commits(repo)[[1]])@sha
```
```{r add-fbind-diff, echo = FALSE, eval = create, asis = TRUE}
commits(repo)[[1]]
tree_1 <- tree(commits(repo)[[2]])
tree_2 <- tree(commits(repo)[[1]])
cat(diff(tree_1, tree_2, as_char = TRUE))
```
From this point on, we commit after each step. Remember [these commits](https://github.com/jennybc/foofactors/commits/master) are available in the public repository.
## `check()`
We have empirical evidence that `fbind()` works. But how can we be sure that all the moving parts of the foofactors package still work? This may seem silly to check, after such a small addition, but it's good to establish the habit of checking this often.
`R CMD check`, executed in the shell, is the gold standard for checking that an R package is in full working order. `check()` is a convenient way to run this without leaving your R session.
Note that `check()` produces rather voluminous output, optimized for interactive consumption. We intercept that here and just reveal a summary. Your local `check()` output will be different.
```{r first-check-fake, eval = FALSE}
check()
```
```{r first-check, eval = create, warning = TRUE, echo = FALSE}
shhh_check(error_on = "never")
```
**Read the output of the check!** Deal with problems early and often. It's just like incremental development of `.R` and `.Rmd` files. The longer you go between full checks that everything works, the harder it becomes to pinpoint and solve your problems.
At this point, we expect 2 warnings (and 0 errors, 0 notes):
* `Non-standard license specification`
* `Undocumented code objects: 'fbind'`
We'll address both soon.
:::rstudio-tip
RStudio exposes `check()` in the *Build* menu, in the *Build* pane via *Check*, and in keyboard shortcuts Ctrl + Shift + E (Windows & Linux) or Cmd + Shift + E (macOS).
:::
## Edit `DESCRIPTION`
Before we tackle the warnings about the license and documentation, let's work on the boilerplate content in `DESCRIPTION`. The `DESCRIPTION` file provides metadata about your package and is covered fully in chapter \@ref(description).
Make these edits:
* Make yourself the author.
* Write some descriptive text in the `Title` and `Description` fields.
:::rstudio-tip
Use Ctrl + `.` in RStudio and start typing "DESCRIPTION" to activate a helper that makes it easy to open a file for editing. In addition to a filename, your hint can be a function name. This is very handy once a package has lots of functions and files below `R/`.
:::
When you're done, `DESCRIPTION` should look similar to this:
```{r description-fodder, asis = TRUE, echo = FALSE, comment = NA, eval = create}
DESCRIPTION_fodder <- c(
'Package: foofactors',
'Title: Make Factors Less Aggravating',
'Version: 0.0.0.9000',
'Authors@R:',
' person("Jane", "Doe", email = "[email protected]", role = c("aut", "cre"))',
'Description: Factors have driven people to extreme measures, like ordering',
' custom conference ribbons and laptop stickers to express how HELLNO we',
' feel about stringsAsFactors. And yet, sometimes you need them. Can they',
' be made less maddening? Let\'s find out.',
'License: What license it uses',
'Encoding: UTF-8',
'LazyData: true'
)
writeLines(DESCRIPTION_fodder, "DESCRIPTION")
cat(DESCRIPTION_fodder, sep = "\n")
```
```{r commit-description, echo = debug, comment = NA, eval = create}
add(repo, path = "DESCRIPTION")
commit(repo, message = "Edit DESCRIPTION")
```
## `use_mit_license()`
> [Pick a License, Any License. -- Jeff Atwood](http://blog.codinghorror.com/pick-a-license-any-license/)
For foofactors, we use the MIT license. This requires specification in `DESCRIPTION` and an additional file called `LICENSE`, naming the copyright holder and year. We'll use the helper `use_mit_license()`. Substitute your name here.
```{r use-mit-license, eval = create}
use_mit_license("Jane Doe")
```
Open the newly created `LICENSE` file and confirm it has the current year and your name.
```{r license-fodder, asis = TRUE, echo = FALSE, comment = NA, eval = create}
cat(readLines("LICENSE"), sep = "\n")
```
Like other license helpers, `use_mit_license()` also puts a copy of the full license in `LICENSE.md` and adds this file to `.Rbuildignore`. It's considered a best practice to include a full license in your package's source, such as on GitHub, but CRAN disallows the inclusion of this file in a package tarball.
```{r commit-license, echo = debug, comment = NA, eval = create}
add(repo, path = "LICENSE")
commit(repo, message = "Add LICENSE")
```
## `document()`
Wouldn't it be nice to get help on `fbind()`, just like we do with other R functions? This requires that your package have a special R documentation file, `man/fbind.Rd`, written in an R-specific markup language that is sort of like LaTeX. Luckily we don't necessarily have to author that directly.
We write a specially formatted comment right above `fbind()`, in its source file, and then let a package called [roxygen2](https://CRAN.R-project.org/package=roxygen2) handle the creation of `man/fbind.Rd`. The motivation and mechanics of roxygen2 are covered in chapter \@ref(man).
If you use RStudio, open `R/fbind.R` in the source editor and put the cursor somewhere in the `fbind()` function definition. Now do *Code > Insert roxygen skeleton*. A very special comment should appear above your function, in which each line begins with `#'`. RStudio only inserts a barebones template, so you will need to edit it to look something like that below.
If you don't use RStudio, create the comment yourself. Regardless, you should modify it to look something like this:
```{r fbind-roxygen-header, asis = TRUE, echo = FALSE, comment = NA, eval = create}
fbind_roxygen_header <- c(
"#' Bind two factors",
"#'",
"#' Create a new factor from two existing factors, where the new factor's levels",
"#' are the union of the levels of the input factors.",
"#'",
"#' @param a factor",
"#' @param b factor",
"#'",
"#' @return factor",
"#' @export",
"#' @examples",
"#' fbind(iris$Species[c(1, 51, 101)], PlantGrowth$group[c(1, 11, 21)])"
)
fbind_safe <- readLines(path("R", "fbind.R"))
writeLines(
c(fbind_roxygen_header, paste(fbind_safe, collapse = "\n")),
path("R", "fbind.R")
)
cat(fbind_roxygen_header, sep = "\n")
```
FIXME: mention how RStudio helps you execute examples here?
```{r commit-fbind-roxygen-header, echo = debug, comment = NA, eval = create}
add(repo, path = file.path("R", "fbind.R"))
commit(repo, message = "Add roxygen header to document fbind()")
```
But we're not done yet! We still need to trigger the conversion of this new roxygen comment into `man/fbind.Rd` with `document()`:
```{r document-fbind, eval = create}
document()
```
:::rstudio-tip
RStudio exposes `document()` in the *Build* menu, in the *Build* pane via *More > Document*, and in keyboard shortcuts Ctrl + Shift + D (Windows & Linux) or Cmd + Shift + D (macOS).
:::
You should now be able to preview your help file like so:
```{r eval = FALSE}
?fbind
```
You'll see a message like "Rendering development documentation for 'fbind'", which reminds that you are basically previewing draft documentation. That is, this documentation is present in your package's source, but is not yet present in an installed package. In fact, we haven't installed foofactors yet, but we will soon.
Note also that your package's documentation won't be properly wired up until it has been formally built and installed. This polishes off niceties like the links between help files and the creation of a package index.
### `NAMESPACE` changes
In addition to converting `fbind()`'s special comment into `man/fbind.Rd`, the call to `document()` updates the `NAMESPACE` file, based on `@export` directives found in roxygen comments. Open `NAMESPACE` for inspection. The contents should be:
```{r asis = TRUE, echo = FALSE, comment = NA, eval = create}
cat(readLines("NAMESPACE"), sep = "\n")
```
It no longer has the placeholder content that says "export everything". Instead, there is now an explicit directive to export the `fbind()` function.
The export directive in `NAMESPACE` is what makes `fbind()` available to a user after attaching foofactors via `library(foofactors)`. Just as it is entirely possible to author `.Rd` files "by hand", you can manage `NAMESPACE` explicitly yourself. But we choose to delegate this to devtools (and roxygen2).
```{r commit-namespace, echo = debug, comment = NA, eval = create}
add(repo, path = c("DESCRIPTION", "NAMESPACE", path("man", "fbind.Rd")))
commit(repo, message = "Run document()")
```
## `check()` again
foofactors should pass `R CMD check` cleanly now and forever more: 0 errors, 0 warnings, 0 notes.
```{r first-clean-check-fake, eval = FALSE}
check()
```
```{r first-clean-check, eval = create, warning = TRUE, echo = FALSE}
shhh_check(error_on = "never")
```
## `install()`
Since we have a minimum viable product now, let's install the foofactors package into your library via `install()`:
```{r first-install-fake, eval = FALSE}
install()
```
```{r first-install, eval = create, echo = FALSE, asis = TRUE, comment = NA}
cat(pretty_install(), sep = "\n")
```
:::rstudio-tip
RStudio exposes similar functionality in the *Build* menu and in the *Build* pane via *Install and Restart*.
:::
Now we can attach and use foofactors like any other package. Let's revisit our small example from the top. This is a good time to restart your R session and ensure you have a clean workspace.
```{r, eval = create}
library(foofactors)
a <- factor(c("character", "hits", "your", "eyeballs"))
b <- factor(c("but", "integer", "where it", "counts"))
fbind(a, b)
```
Success!
## `use_testthat()`
We've tested `fbind()` informally, in a single example. We can formalize and expand this with some unit tests. This means we express a few concrete expectations about the correct `fbind()` result for various inputs.
First, we declare our intent to write unit tests and to use the testthat package for this, via `use_testthat()`:
```{r use-testthat, eval = create}
use_testthat()
```
This initializes the unit testing machinery for your package. It adds `Suggests: testthat` to `DESCRIPTION`, creates the directory `tests/testthat/`, and adds the script `test/testthat.R`.
```{r commit-testthat-init, echo = debug, comment = NA, eval = create}
paths <- unlist(status(repo))
add(repo, path = paths)
commit(repo, message = "Add testing infrastructure")
```
However, it's still up to YOU to write the actual tests!
The helper `use_test()` opens and/or creates a test file. You can provide the file's basename or, if you are editing the relevant source file in RStudio, it will be automatically generated. Since this book is built non-interactively, we must provide the basename explicitly:
```{r test-fbind, eval = create}
use_test("fbind")
```
This creates the file `tests/testthat/test-fbind.R`. Put this content in it:
```{r test-fbind-fodder, asis = TRUE, echo = FALSE, comment = NA, eval = create}
test_fodder <- c(
'context("Binding factors")',
'',
'test_that("fbind() binds factor (or character)", {',
' x <- c("a", "b")',
' x_fact <- factor(x)',
' y <- c("c", "d")',
' z <- factor(c("a", "b", "c", "d"))',
'',
' expect_identical(fbind(x, y), z)',
' expect_identical(fbind(x_fact, y), z)',
'})'
)
test_path <- path("tests", "testthat", "test-fbind.R")
writeLines(test_fodder, test_path)
cat(test_fodder, sep = "\n")
```
This tests that `fbind()` gives an expected result when combining two factors and a character vector and a factor.
```{r commit-fbind-test, echo = debug, comment = NA, eval = FALSE}
paths <- unlist(status(repo))
add(repo, path = paths)
commit(repo, message = "Test fbind()")
```
Run this test interactively, as you will when you write your own. Note you'll have to attach testthat via `library(testthat)` in your R session first and you'll probably want to `load_all()`.
Going forward, your tests will mostly run *en masse* and at arms's length via `test()`:
FIXME: work on the aesthetics of this output.
```{r test, eval = create, message = FALSE}
test()
```
:::rstudio-tip
RStudio exposes `test()` in the *Build* menu, in the *Build* pane via *More > Test package*, and in keyboard shortcuts Ctrl + Shift + T (Windows & Linux) or Cmd + Shift + T (macOS).
:::
Your tests are also run whenever you `check()` the package. In this way, you basically augment the standard checks with some of your own, that are specific to your package. It is a good idea to use the [covr package](https://covr.r-lib.org) to track what proportion of your package's source code is exercised by the tests. More details can be found in chapter \@ref(tests).
## `use_package()`
You will inevitably want to use a function from another package in your own package. Just as we needed to **export** `fbind()`, we need to **import** functions from the namespace of other packages. If you plan to submit a package to CRAN, note that this even applies to functions in packages that you think of as "always available", such as `stats::median()` or `utils::head()`.
We're going to add another function to foofactors that produces a sorted frequency table for a factor. We'll borrow some smarts from the forcats package, specifically the function `forcats::fct_count()`.
First, declare your general intent to use some functions from the forcats namespace with `use_package()`:
```{r use-forcats, eval = create}
use_package("forcats")
```
This adds the forcats package to the "Imports" section of `DESCRIPTION`. And that is all.
```{r commit-forcats-imports, echo = debug, comment = NA, eval = create}
paths <- unlist(status(repo))
add(repo, path = paths)
commit(repo, message = "Import forcats")
```
Now we add a second function to foofactors: imagine we want a frequency table for a factor, as a regular data frame with nice variable names, versus as an object of class `table` or something with odd names. Let's also sort the rows so that the most prevalent level is at the top.
Initiate a new `.R` file below `R/` with `use_r()`:
```{r init-fcount, eval = create, chunk_envvar = c("TESTTHAT" = "true")}
use_r("fcount")
```
Put this content in the file `R/fcount.R`:
```{r fcount-fodder, asis = TRUE, echo = FALSE, comment = NA}
fcount_fodder <- c(
"#' Make a sorted frequency table for a factor",
"#'",
"#' @param x factor",
"#'",
"#' @return A tibble",
"#' @export",
"#' @examples",
"#' fcount(iris$Species)",
"fcount <- function(x) {",
" forcats::fct_count(x, sort = TRUE)",
"}"
)
cat(fcount_fodder, sep = "\n")
```
```{r write-fcount, include = FALSE, eval = create}
writeLines(fcount_fodder, path("R", "fcount.R"))
```
Notice how we preface the call to a forcats functions with `forcats::`. This specifies that we want to call the `fct_count()` function from the forcats namespace. There is more than one way to call functions in other packages and the one we espouse here is explained fully in chapter \@ref(namespace).
```{r commit-fcount, echo = debug, comment = NA, eval = create}
paths <- unlist(status(repo))
add(repo, path = paths)
commit(repo, message = "Add fcount()")
```
Try out the new `fcount()` function by simulating package installation via `load_all()`:
```{r fcount-test-drive, eval = create}
load_all()
fcount(iris$Species)
```
Generate the associated help file via `document()`.
```{r document-fcount, eval = create}
document()
```
```{r commit-fcount-rd, echo = debug, comment = NA, eval = create}
paths <- unlist(status(repo))
add(repo, path = paths)
commit(repo, message = "Document fcount()")
```
## `use_github()`
You've seen us making commits during the development process for foofactors. You can see an indicative history at <https://github.com/jennybc/foofactors>. Our use of version control and the decision to expose the development process means you can inspect the state of the foofactors source at each developmental stage. By looking at so-called diffs, you can see exactly how each devtools helper function modifies the source files that constitute the foofactors package.
How would you connect your local foofactors package and Git repository to a companion repository on GitHub?
1. [`use_github()`](https://usethis.r-lib.org/reference/use_github.html) is a helper that we recommend for the long-term. We won't demonstrate it here because it requires some nontrivial setup on your end. We also don't want to tear down and rebuild the public foofactors package every time we build this book.
1. Set up the GitHub repo first! It sounds counterintuitive, but the easiest way to get your work onto GitHub is to initiate there, then use RStudio to start working in a synced local copy. This approach is described in Happy Git's workflows [New project, GitHub first](https://happygitwithr.com/new-github-first.html) and [Existing project, GitHub first](https://happygitwithr.com/existing-github-first.html).
1. Command line Git can always be used to add a remote repository *post hoc*. This is described in the Happy Git workflow [Existing project, GitHub last](https://happygitwithr.com/existing-github-last.html).
Any of these approaches will connect your local foofactors project to a GitHub repo, public or private, which you can push to or pull from using the Git client built into RStudio.
## `use_readme_rmd()`
Now that your package is on GitHub, the `README.md` file matters. It is the package's home page and welcome mat, at least until you decide to give it a website (see [pkgdown](https://pkgdown.r-lib.org)), add a vignette (see chapter \@ref(vignettes)), or submit it to CRAN (see chapter \@ref(release)).
The `use_readme_rmd()` function initializes a basic, executable `README.Rmd` ready for you to edit:
```{r use-readme-rmd, eval = create, chunk_envvar = c("TESTTHAT" = "true")}
use_readme_rmd()
```
In addition to creating `README.Rmd`, this adds some lines to `.Rbuildignore`, and creates a Git pre-commit hook to help you keep `README.Rmd` and `README.md` in sync.
`README.Rmd` already has sections that:
* Prompt you to describe the purpose of the package.
* Provide code to install your package.
* Prompt you to show a bit of usage.
How to populate this skeleton? Copy stuff liberally from `DESCRIPTION` and any formal and informal tests or examples you have. Anything is better than nothing. Otherwise ... do you expect people to install your package and comb through individual help files to figure out how to use it? They probably won't.
We like to write the `README` in R Markdown, so it can feature actual usage. It will load the currently installed version of your package, so this is a good time to do "Install and Restart" in RStudio. Or do this in R Console:
```{r pre-readme-install-fake, eval = FALSE}
install()
```
```{r pre-readme-install, eval = create, echo = FALSE, asis = TRUE, comment = NA}
cat(pretty_install(), sep = "\n")
```
If RStudio has not already done so, open `README.Rmd` for editing. Make sure it shows some usage of `fbind()` and/or `fcount()`, for example.
```{r commit-readme-rmd, echo = debug, comment = NA, eval = create}
paths <- unlist(status(repo))
add(repo, path = paths)
commit(repo, message = "Set up README.Rmd")
```
The `README.Rmd` we use is here: [README.Rmd](https://raw.githubusercontent.com/jennybc/foofactors/master/README.Rmd) and these are the contents:
FIXME: update this link after merge into r-pkgs.
```{r copy-readme-rmd, include = debug, eval = create}
file_copy(
path(owd, "fixtures", "foofactors-README.Rmd"),
"README.Rmd",
overwrite = TRUE
)
```
```{r cat-readme-rmd, asis = TRUE, echo = FALSE, comment = NA, eval = create}
cat(readLines("README.Rmd"), sep = "\n")
```
Don't forget to render it to make `README.md`! The pre-commit hook should remind you if you try to commit `README.Rmd` but not `README.md` and also when `README.md` appears to be out-of-date.
```{r eval = FALSE}
rmarkdown::render("README.Rmd") ## or use "Knit HTML" in RStudio
```
```{r render-readme-rmd, include = debug, eval = create}
callr::r(
function(.input) rmarkdown::render(input = .input, quiet = TRUE),
args = list(.input = "README.Rmd")
)
```
You can see the rendered `README.md` simply by [visiting foofactors on GitHub](https://github.com/jennybc/foofactors#readme).
Finally, don't forget to do one last commit. And push, if you're using GitHub.
```{r commit-rendered-readme, echo = debug, comment = NA, eval = create}
paths <- unlist(status(repo))
add(repo, path = paths)
commit(repo, message = "Write README.Rmd and render")
```
```{r final-push, include = debug, eval = FALSE}
push(repo, credentials = cred_user_pass("EMAIL", Sys.getenv("GITHUB_PAT")))
## if tags become useful, here's how to push one
## push(repo, "origin", "refs/tags/fbind-init",
## credentials = cred_user_pass("EMAIL", Sys.getenv("GITHUB_PAT")))
## not clear if git2r has easy way to push all tags
## https://github.com/ropensci/git2r/issues/265
```
## The End: `check()` and `install()`
Let's run `check()` again to make sure all is still well.
```{r final-check-fake, eval = FALSE}
check()
```
```{r final-check, eval = create, warning = TRUE, echo = FALSE}
shhh_check(error_on = "never")
```
foofactors should have no errors, warnings or notes. This would be a good time to re-build and install if properly. And celebrate!
```{r final-install-fake, eval = FALSE}
install()
```
```{r final-install, eval = create, echo = FALSE, asis = TRUE, comment = NA}
cat(pretty_install(), sep = "\n")
```
Feel free to visit the [foofactors package](https://github.com/jennybc/foofactors) on GitHub, which is exactly as developed here. The commit history reflects each individual step, so use the diffs to see the addition and modification of files, as the package evolved. The rest of this book goes in greater detail for each step you've seen here and much more.