abusethis
packageIn 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.
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.
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:
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:
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:
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:
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>".
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>".
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.