About the abusethis package

History

In the old days people doing casual work tended to not write packages, and instead developed their own ways of working - often using source to read a file of function definitions into their working environment. To structure this a bit more, maybe they put functions into separate files and then wrote a “master” file that called source on everything else they needed. Make one tiny edit, then source everything.

The alternative, putting your code into packages, meant you could have a well-structured folder of code and documentation, including dependency specification, compiled languages, vignettes and all that goodness. But the downside was you had to build and install the package after every change. Users eventually developed ways to create things more like Python modules that could be attached and detached.

Devtools

The devtools package gave us an easier way to work with the package structure without needing to build and reinstall on every change. A simple load_all call would detach the old package, recompile any compiled code that needed going, and attach the new code. It took care of all the details and worked pretty well.

I started using it and developed a discipline of putting all my code into a package structure. This meant sometimes I was working on multiple packages at once - for example I might be implementing a new statistical method and have a dataset for it. That method would have its own package, and the dataset would have another package for functions for loading and cleaning the data. That meant I would end up with a general package for a statistical method that could be shared, and a package for the specific project for manipulating the data and producing the project outputs.

The devtools package also supplied a stack of use_ functions which added functionality to your package. At first you could specify the package folder, so I could do use_package("AA", "sp") and use_package("BB", "gam") to add specific requirements to packages AA and BB respectively.

Splitters

Then devtools split out the use_ functions into the usethis package. And at some point the ability to specify a package folder went. I think at some point it was just a matter of changing the working directory with setwd before running them, so the above operations would have been:

> setwd("A")
> use_package("sp")
> setwd("../B")
> use_package("gam")
> setwd("..")

That’s quite clunky, and its easy to forget to go back to your original working directory. Luckily the withr package has some functions that do things within setups that are guaranteed to be reset after the code completes, and with_dir will help with the above, making it:

> with_dir("./A", use_package("sp"))
> with_dir("./B", use_package("gam"))

But if this used to work, it doesn’t now. Currently, usethis keeps a global idea of the current “project” in an environment. Use of the word “project” here, which is not an R concept, appears to relate to tighter integration with particular IDEs that support it.

Again, there’s a withr function that lets you apply code within the context of a new “project”, resetting the current project back afterwards. For package development this is sufficient. So we now have:

> with_project("./A", use_package("sp"))
> with_project("./B", use_package("gam"))

Easier with A-B-Usage

Can we make this any easier? That’s what the abusethis package is all about. It lets you use the use_ functions on any number of packages.

First we need the devtools package and this package:

> library(devtools)
#> Loading required package: usethis
> library(abusethis)

Next we’ll make a couple of skeletal packages called A and B in R’s temporary directory location. They could be anywhere in your filesystem, with any names. The extra options to create_package stop R setting the new package as the current project, and also to allow a single-character package name (which is valid in R, but CRAN doesn’t like it):

> A = file.path(tempdir(), "A")
> message("Package A is at ",A)
#> Package A is at /tmp/RtmpU1d09D/A
> B = file.path(tempdir(), "B")
> message("Package B is at ",B)
#> Package B is at /tmp/RtmpU1d09D/B
> create_package(A, open=FALSE, check=FALSE)
#> ✔ Creating '/tmp/RtmpU1d09D/A/'.
#> ✔ Setting active project to "/tmp/RtmpU1d09D/A".
#> ✔ Creating 'R/'.
#> ✔ Writing 'DESCRIPTION'.
#> Package: A
#> Title: What the Package Does (One Line, Title Case)
#> Version: 0.0.0.9000
#> Authors@R (parsed):
#>     * First Last <[email protected]> [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.3.2
#> ✔ Writing 'NAMESPACE'.
#> ✔ Setting active project to "<no active project>".
> create_package(B, open=FALSE, check=FALSE)
#> ✔ Creating '/tmp/RtmpU1d09D/B/'.
#> ✔ Setting active project to "/tmp/RtmpU1d09D/B".
#> ✔ Creating 'R/'.
#> ✔ Writing 'DESCRIPTION'.
#> Package: B
#> Title: What the Package Does (One Line, Title Case)
#> Version: 0.0.0.9000
#> Authors@R (parsed):
#>     * First Last <[email protected]> [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.3.2
#> ✔ Writing 'NAMESPACE'.
#> ✔ Setting active project to "<no active project>".

Now we have two packages that we’d like to use usethis on. We can use attach_use to create a set of wrapped use_ functions from the usethis package and attach them to the search path. We do this once for package A and once for package B. See how they are added to the search path:

> print(search())
#>  [1] ".GlobalEnv"        "package:abusethis" "package:devtools" 
#>  [4] "package:usethis"   "package:rmarkdown" "package:stats"    
#>  [7] "package:graphics"  "package:grDevices" "package:utils"    
#> [10] "package:datasets"  "package:methods"   "Autoloads"        
#> [13] "package:base"
> attach_use(A)
#> attaching A_use
#> [1] "A_use"
> attach_use(B)
#> attaching B_use
#> [1] "B_use"
> print(search())
#>  [1] ".GlobalEnv"        "B_use"             "A_use"            
#>  [4] "package:abusethis" "package:devtools"  "package:usethis"  
#>  [7] "package:rmarkdown" "package:stats"     "package:graphics" 
#> [10] "package:grDevices" "package:utils"     "package:datasets" 
#> [13] "package:methods"   "Autoloads"         "package:base"

Looking at the first four functions in these environments you can see how they are prefixed versions of the corresponding usethis functions:

> ls("A_use")[1:4]
#> [1] "A_use_addin"          "A_use_agpl3_license"  "A_use_agpl_license"  
#> [4] "A_use_apache_license"
> ls("B_use")[1:4]
#> [1] "B_use_addin"          "B_use_agpl3_license"  "B_use_agpl_license"  
#> [4] "B_use_apache_license"

Then if I want to set package A to have the MIT license, and package B to have the GPL3 licence, I do:

> A_use_mit_license()
#> ✔ Setting active project to "/tmp/RtmpU1d09D/A".
#> ✔ Adding "MIT + file LICENSE" to 'License'.
#> ✔ Writing 'LICENSE'.
#> ✔ Writing 'LICENSE.md'.
#> ✔ Adding "^LICENSE\\.md$" to '.Rbuildignore'.
#> ✔ Setting active project to "<no active project>".
> B_use_gpl3_license()
#> ✔ Setting active project to "/tmp/RtmpU1d09D/B".
#> ✔ Adding "GPL (>= 3)" to 'License'.
#> ✔ Writing 'LICENSE.md'.
#> ✔ Adding "^LICENSE\\.md$" to '.Rbuildignore'.
#> ✔ Setting active project to "<no active project>".

Arguments are the same as the corresponding usethis functions. So if package A uses the grid package, and package B uses the stats package, we do this:

> A_use_package("grid")
#> ✔ Setting active project to "/tmp/RtmpU1d09D/A".
#> ✔ Adding grid to 'Imports' field in DESCRIPTION.
#> ☐ Refer to functions with `grid::fun()`.
#> ✔ Setting active project to "<no active project>".
> B_use_package("stats")
#> ✔ Setting active project to "/tmp/RtmpU1d09D/B".
#> ✔ Adding stats to 'Imports' field in DESCRIPTION.
#> ☐ Refer to functions with `stats::fun()`.
#> ✔ Setting active project to "<no active project>".

More Details

By default abusethis uses the basename of the package folder to create the environment and the functions. But what if I have two packages with the same name? In that case you can use the prefix argument to create wrapped functions with different names.

For example, suppose you have two versions of a package, one for development and one for production? Let’s set that up.

> devdir = file.path(tempdir(), "dev")
> prodir = file.path(tempdir(), "pro")
> dir.create(devdir)
> dir.create(prodir)
> create_package(file.path(devdir,"codez"), open=FALSE)
#> ✔ Creating '/tmp/RtmpU1d09D/dev/codez/'.
#> ✔ Setting active project to "/tmp/RtmpU1d09D/dev/codez".
#> ✔ Creating 'R/'.
#> ✔ Writing 'DESCRIPTION'.
#> Package: codez
#> Title: What the Package Does (One Line, Title Case)
#> Version: 0.0.0.9000
#> Authors@R (parsed):
#>     * First Last <[email protected]> [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.3.2
#> ✔ Writing 'NAMESPACE'.
#> ✔ Setting active project to "<no active project>".
> create_package(file.path(prodir,"codez"), open=FALSE)
#> ✔ Creating '/tmp/RtmpU1d09D/pro/codez/'.
#> ✔ Setting active project to "/tmp/RtmpU1d09D/pro/codez".
#> ✔ Creating 'R/'.
#> ✔ Writing 'DESCRIPTION'.
#> Package: codez
#> Title: What the Package Does (One Line, Title Case)
#> Version: 0.0.0.9000
#> Authors@R (parsed):
#>     * First Last <[email protected]> [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.3.2
#> ✔ Writing 'NAMESPACE'.
#> ✔ Setting active project to "<no active project>".

Now I can attach all the usethis functions with a development or production prefix:

> attach_use(file.path(devdir,"codez"), prefix="dev_")
#> attaching dev_use
#> [1] "dev_use"
> attach_use(file.path(prodir,"codez"), prefix="pro_")
#> attaching pro_use
#> [1] "pro_use"

and do stuff like this:

> dev_use_mit_license()
#> ✔ Setting active project to "/tmp/RtmpU1d09D/dev/codez".
#> ✔ Adding "MIT + file LICENSE" to 'License'.
#> ✔ Writing 'LICENSE'.
#> ✔ Writing 'LICENSE.md'.
#> ✔ Adding "^LICENSE\\.md$" to '.Rbuildignore'.
#> ✔ Setting active project to "<no active project>".
> pro_use_gpl3_license()
#> ✔ Setting active project to "/tmp/RtmpU1d09D/pro/codez".
#> ✔ Adding "GPL (>= 3)" to 'License'.
#> ✔ Writing 'LICENSE.md'.
#> ✔ Adding "^LICENSE\\.md$" to '.Rbuildignore'.
#> ✔ Setting active project to "<no active project>".

Summary

I wrote this because its useful. If you find it useful, use it. If you don’t, then don’t. If you’ve got an easier way to do this, use that. If you tell me about it and I like it I’ll use it too.