This was not refactoring. This was a physical examination.
The story began simply: I looked at the drwateR-related R packages, and Codex looked at me looking at them. There was a brief silence, the kind doctors produce after opening a medical chart and removing their glasses.
The packages were not useless. Quite the opposite: they did plenty of useful work. They also carried the unmistakable “make it run first” style of a long-lived research codebase. README files had become family albums, version numbers lived in several corners, releases depended on memory, portability occasionally depended on prayer, and an old tarball could still impersonate a new package at exactly the wrong moment.
So we did not spray perfume on the spaghetti. We took it apart, labelled the pieces, tested them, documented the decisions, and sent the result toward DRAN. The project Git entry point is git.drwater.net, the package repositories live under the DRWATER Git organisation, and the package repository is DRAN.
First: count the mountains
This pass covered ten core packages. dateR1 is no longer maintained, and customsetup has retired, so neither was invited into the clinic. The ten packages form a slightly untidy but productive team:
| Package | Main role | Repository | SVG logo |
|---|---|---|---|
| drwateR | Ecosystem entry point, shared tools, DRAN update checks | Git | logo.svg |
| dfeR | Data formats and data processing | Git | logo.svg |
| dateR | Date and time utilities | Git | logo.svg |
| langeR | Language and internationalisation helpers | Git | logo.svg |
| uniteR | Units, dimensions, and consistent representation | Git | logo.svg |
| cctdb | Database access and data interfaces | Git | logo.svg |
| cctda | Algae and aquatic-environment analysis | Git | logo.svg |
| dwfun | General functions and analysis helpers | Git | logo.svg |
| rmdify | R Markdown, Quarto, and complex markup | Git | logo.svg |
| figeR | Figures, graphics, and figure workflows | Git | logo.svg |
Ten logos, ten assigned desks
These SVG logos are served directly from each package’s main branch. They are displayed here as well, so “the package has a logo” does not remain a sentence trapped in DESCRIPTION prose.
Second: translate “it runs” into “someone else can take over”
Each package now has <pkg>_version(), <pkg>_info(), and <pkg>_lifecycle(). These functions do not cure every disease, but they answer three questions that are often asked and rarely answered confidently: who are you, which version are you, and are you still alive?
The lifecycle is declared in DESCRIPTION, README files are bilingual, and NEWS entries explain what changed instead of merely saying “update”. A package should not have to infer its own health from the maintainer’s facial expression.
Third: make three operating systems stop pretending to be one
The audit checked paths, temporary directories, executable discovery, file viewers, and system2() calls on macOS, Linux, and Windows. The conclusions were refreshingly unglamorous:
- Use
file.path()instead of hand-built slash mazes. - Use
Sys.which()orR.home("bin")to find commands instead of assuming one absolute path. - Do not treat
.exeas a universal suffix, or/bin/shas a universal personality. -
system2()is fine, but command names and arguments must not quietly belong to one machine.
Windows users should not be expelled from DRWATER because /usr/bin is missing, and Linux users should not lose the right to open a file just because Finder is elsewhere.
rmdify: the regular expression was sent for tea
rmdify and its clab parser were the most realistic stress test. A marker may cross lines and paragraphs, contain nested references and equations, include inline R, and surround a figure chunk. A single regular expression facing that document often behaves like a cat seeing a vacuum cleaner: freeze first, then escape in the least useful direction.
The parser now tracks bracket depth and fenced code with a state machine. It supports both the legacy attribute form and options clab="...", and it handles content across paragraphs. Inline 2 is processed, while fenced R/Quarto chunks are preserved rather than executed behind the user’s back. Figure references use an explicit Quarto label:
::: {.cell}
```{.r .cell-code}
ggsavep("../figures/demo.pdf", loadit = TRUE)
```
:::The rule belongs on an office wall: a document transformer may read code, but should not execute it merely because it found a code fence.
Fourth: make releases less dependent on memory
The core packages now follow a Makefile workflow that bumps versions, runs roxygenisation, generates NEWS, checks, commits to main, pushes the commit, creates and pushes a tag, and builds DRAN. The DRAN entry point is dran.drwater.net, so installation is consistent:
install.packages("drwateR", repos = "https://dran.drwater.net")We also caught a classic failure mode: if an old tarball remained in the root directory, the build could successfully copy the wrong version. The workflow now removes stale tarballs and matches the build output against the exact version in DESCRIPTION. Before the terminal says success, it should at least be clear that today’s package succeeded, not yesterday’s ghost.
Finally: the spaghetti has addresses now
All ten core packages received version updates, checks, commits, pushes, and tags, and were rebuilt into the local DRAN repository. drwateR also provides an update check:
drwateR::check_drwateR_updates()It will not upgrade packages at midnight or turn a laptop into a release server. It simply says, “There is a newer version; you decide.” That is a healthy boundary.
Historical examples and older documentation still deserve follow-up, and the full examples gate should be restored incrementally. We have not declared technical debt extinct. We have merely given it a ledger.
The answer to “How do we release this package?” used to be “find the person who remembers.” Now it is: open Git, inspect main, read NEWS, run the Makefile, and check DRAN.
The spaghetti is still there, but it now has a version number, tests, a logo, an address, and an escape route. For software, that is not a miracle. It is the beginning of decent hygiene.