- R 100%
| man | bump dev version | |
| R | bump dev version | |
| tests | update tests and test data to better target new doc range args | |
| .gitignore | initialise package repo | |
| .Rbuildignore | add readme | |
| DESCRIPTION | bump dev version | |
| LICENSE.md | initialise package repo | |
| NAMESPACE | export the catchup function | |
| README.md | bump dev version | |
| README.Rmd | bump dev version | |
catchup
R CMD CHECK result Project Status: WIP – Initial development is in progress, but there has not yet been a stable, usable release suitable for the public. CRAN status Codeberg badge
The {catchup} package provides a way to "catch up" your R environment
to a specified point in a computational notebook containing R chunks. It
currently works with both Quarto and R
Markdown notebooks.
Note: This package is early in its development, and hasn’t been extensively tested in the wild. So far, I’ve been using it regularly and it’s working as intended for my own workflow and needs, but I’m sure that other people’s mileage will vary. I’d appreciate it if you try the package and let me know if you run into any problems.
Contents
Package goals
The package was born of my own frustration as someone who frequently works with Quarto notebooks interactively over multiple sessions. Typically, I’ll open a Quarto notebook with lots of different sections and want to jump to a given point in the notebook and pick up where I left off, catching my R session’s state up to reflect that position. Manually going through each chunk and sending it to the console is a pain, especially when some chunks are meant to be evaluated and others aren’t.
If using an IDE developed by the same company behind Quarto, you’ll have
the convenient option of placing your cursor somewhere in the document
and then hitting a button called "Run all chunks above" or similar,
which is smart enough to ignore chunks with the eval option set to
false. I wanted to reproduce this behaviour in the form of an R
package so that the user isn’t dependent on any particular development
environment to get the benefits.
My goal was to do this using only base R, so that the package is lightweight and dependency-free. To this end, it’s all just plain-text parsing and regular expressions under the hood.
Installation
You can install the latest version of {catchup} using your preferred
method of installing R packages from git repo URLs, e.g.:
# Using the `pak` package:
pak::pkg_install("git::https://codeberg.org/pjphd/catchup.git")
# Using the `remotes` package:
remotes::install_git(url = "https://codeberg.org/pjphd/catchup.git")
Usage
library(catchup)
The whole functionality of the package is in the catchup() function.
You call this function and pass it the path to a Quarto or RMarkdown
file, and it will find the R chunks and run them in the R session from
which it is called.
For example:
qmd_test_file <- testthat::test_path("test_data", "quarto_test.qmd")
catchup(doc_path = qmd_test_file)
#>
#> Evaluating R expressions from quarto_test.qmd :
#> [catchup]: for_example <- "I'm a variable"
#> [catchup]: i_should_exist <- 0
#> [catchup]: i_should_exist <- i_should_exist + 1
#> [catchup]: i_should_exist <- 5
#>
#> {catchup} found and evaluated 4 R chunks. The environment is now caught up to the specified point in the notebook.
The test file we just used to "catch up" our session creates a variable
called i_should_exist and modifies its value a couple of times.
Because we just did the equivalent of "run all chunks" in the file, it’s
now in our environment:
i_should_exist
#> [1] 5
By default, catchup will parse the whole file and won’t evaluate any
chunks where the eval option is set to FALSE.
You can customise the behaviour a bit via some optional arguments:
- Defining the document range (the lines in the document that
catchupwill consider when looking for code chunks to execute):from_label/to_label: If you provide a character string corresponding to a Quarto/R Markdown chunk label,catchupwill use it to define either the start (from_label) or end (to_label) of the document range. E.g. to run all chunks in a Quarto notebook upto and including the chunk with option#| label: "read-in-data", use argumentto_label = "read-in-data".from_line/to_line: If you specify a line number to either of these arguments,catchupwill use those line numbers to define the document range. E.g. to run all chunks up to and including line 99, use argumentto_line = 99.- You can mix and match
from_labelwithto_lineand vice versa, but if you provide bothfrom_labelandfrom_lineor bothto_labelandto_line, the_labelargument will be used to define the range and the_lineargument will be ignored.
force_eval: If you wantcatchupto ignore theevalchunk option and execute all R chunks it encounters, set this argument toTRUE.
More examples:
catchup(qmd_test_file, to_line = 33)
#>
#> Evaluating R expressions from quarto_test.qmd :
#> [catchup]: for_example <- "I'm a variable"
#> [catchup]: i_should_exist <- 0
#>
#> {catchup} found and evaluated 2 R chunks. The environment is now caught up to the specified point in the notebook.
Because we only caught up to line 33, rather than the whole file, the
variable i_should_exist won’t be incremented as highly now.
i_should_exist
#> [1] 0
The test file we read in also creates a variable called
i_should_not_exist, but it only appears in chunks with the eval
option set to FALSE. As a result, it’s not in our environment at this
point:
try(
print(i_should_not_exist)
)
#> Error in eval(expr, envir) : object 'i_should_not_exist' not found
We can force those chunks to be evaluated anyway:
catchup(qmd_test_file, force_eval = TRUE)
#>
#> Evaluating R expressions from quarto_test.qmd :
#> [catchup]: for_example <- "I'm a variable"
#> [catchup]: i_should_exist <- 0
#> [catchup]: i_should_not_exist <- 0
#> [catchup]: i_should_exist <- i_should_exist + 1
#> [catchup]: i_should_not_exist <- i_should_not_exist + 1
#> [catchup]: i_should_exist <- 5
#>
#> {catchup} found and evaluated 6 R chunks. The environment is now caught up to the specified point in the notebook.
i_should_not_exist
#> [1] 1
A note of caution
Normally, it’s bad
practice to modify a
user’s environment from within a package. However, this package is
developed for the very specific use case that you want to evaluate
embedded code in order to modify your environment. It does this by
parsing the text as R expressions and feeding them to R’s
source().
The code is actually executed, as it would be if you sourced in an R
script.
It’s therefore particularly important that you verify that the code in
the notebook you use to catch up is safe and that you want it
executed in your environment. If you’re not sure, it would be better to
go through and run the chunks manually instead of using {catchup}.
Controlling the environment in which chunks are run
By default, the code in the notebook’s R chunks will be evaluated in the
context from which the catchup() function is called (which will
normally be the global environment). However, the catchup() function
does allow you to specify (via the exec_env argument) an environment
that will be passed to source(), and the expressions will be evaluated
there.
This is useful, for example, for
testing the package -
{testthat} tests are each run in their
own temporary environment, but if catchup evaluates the code chunks in
the global environment, testthat can’t access the results when
cleaning up after itself and subsequent tests will be compromised. By
setting exec_env to the value of environment() inside the test
functions, it ensures that the global environment is never changed, and
the objects created as a result of catchup() are in a place that
{testthat} can clean up.
Get help
For more on using the package, see the documentation:
?catchup
For any other questions, contact me directly or raise an issue.
Package checks
Note that the following checks are run in R on my local machine when the README is built and their results embedded here via rmarkdown/knitr.
Cloud computing has real environmental costs and using CI for simple checks like these on small packages is a waste of resources in my opinion. Instead, I’m just rebuilding the README before each push to keep the results of the checks up-to-date.
Code coverage:
covr::package_coverage()
#> catchup Coverage: 75.36%
#> R/helpers.R: 64.91%
#> R/catchup.R: 82.72%
R CMD CHECK results:
devtools::check(
env_vars = c("NOT_CRAN" = "true", "_R_CHECK_SYSTEM_CLOCK_" = 0),
quiet = TRUE
)
#> i Loading catchup
#> ── R CMD check results ───────────────────────────────── catchup 0.0.1.9001 ────
#> Duration: 8.7s
#>
#> 0 errors ✔ | 0 warnings ✔ | 0 notes ✔
These checks were run in the following environment:
cbind("Session information" = sessioninfo::platform_info()[c(
"version", "system", "os", "date"
)])
#> Session information
#> version "R version 4.4.3 (2025年02月28日)"
#> system "x86_64, linux-gnu"
#> os "Fedora Linux 41 (Container Image)"
#> date "2025年09月26日"