1
0
Fork
You've already forked catchup
0
An R package for catching the R environment up to a point in a computational notebook.
  • R 100%
2025年09月26日 18:20:11 -03:00
man bump dev version 2025年09月26日 18:20:11 -03:00
R bump dev version 2025年09月26日 18:20:11 -03:00
tests update tests and test data to better target new doc range args 2025年09月26日 18:18:23 -03:00
.gitignore initialise package repo 2024年11月15日 11:54:03 -03:00
.Rbuildignore add readme 2024年11月20日 14:59:26 -03:00
DESCRIPTION bump dev version 2025年09月26日 18:20:11 -03:00
LICENSE.md initialise package repo 2024年11月15日 11:54:03 -03:00
NAMESPACE export the catchup function 2024年11月20日 13:43:07 -03:00
README.md bump dev version 2025年09月26日 18:20:11 -03:00
README.Rmd bump dev version 2025年09月26日 18:20:11 -03:00

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

  1. Package goals
  2. Installation
  3. Usage
  4. A note of caution
  5. Get help
  6. Package checks

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 catchup will 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, catchup will 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 argument to_label = "read-in-data".
    • from_line/to_line: If you specify a line number to either of these arguments, catchup will use those line numbers to define the document range. E.g. to run all chunks up to and including line 99, use argument to_line = 99.
    • You can mix and match from_label with to_line and vice versa, but if you provide both from_label and from_line or both to_label and to_line, the _label argument will be used to define the range and the _line argument will be ignored.
  • force_eval: If you want catchup to ignore the eval chunk option and execute all R chunks it encounters, set this argument to TRUE.

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日"