Developer’s Guide to qrtdown
Overview
qrtdown is a modular Quarto-based documentation system for R packages. This guide explains the internal architecture for developers who want to understand, maintain, or extend the package.
Total codebase: ~4,100 lines across 11 R source files.
File Architecture
R/
├── build.R (745 lines) # Main orchestration & CLI
├── config.R (426 lines) # Configuration system
├── utils.R (207 lines) # Core utilities
├── build-home.R (244 lines) # Homepage generation
├── build-reference.R (374 lines) # Function reference pages
├── build-articles.R (258 lines) # Article processing
├── build-news.R (111 lines) # Changelog handling
├── build-citation.R (94 lines) # Citation page
├── build-llm.R (323 lines) # LLM-optimized docs
├── rd-to-qmd.R (432 lines) # Rd → Quarto conversion
└── usethis.R (928 lines) # Setup & maintenance functions
Core Build Pipeline
The main entry point is build_site(). Here’s the complete execution flow:
build_site()
│
├──► find_package_root() # Locate DESCRIPTION
├──► pkg_meta() # Extract package metadata
├──► get_config() # Merge user + default config
│ │
│ ├──► read_config() # Read _qrtdown.yml
│ └──► default_config() # Generate defaults
│ ├──► has_articles_dir()
│ ├──► has_citation_file()
│ ├──► get_main_article()
│ └──► get_articles_menu()
│
├──► init_site() # Setup site structure
│ ├──► write_quarto_yml()
│ ├──► update_gitignore()
│ └──► update_rbuildignore()
│
├──► build_home() # README → index.qmd (with include)
├──► build_reference() # man/*.Rd → _qrtdown/reference/*.qmd
├──► build_articles() # vignettes/articles/ → _qrtdown/articles.qmd
├──► build_news() # NEWS.md handling
├──► build_citation() # CITATION → _qrtdown/citation.qmd
│
└──► run_quarto(["render"]) # Execute Quarto
│
└──► [auto-retry on cache failure]
Module Dependencies
┌─────────────┐
│ build.R │ ← Main entry point
│ build_site │
└──────┬──────┘
│
┌───────────────────┼───────────────────┐
│ │ │
▼ ▼ ▼
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ config.R │ │ utils.R │ │ usethis.R │
│ get_config │◄───│ pkg_meta │───►│use_qrtdown │
│ defaults │ │ run_quarto │ │deploy_site │
└──────┬──────┘ └──────┬──────┘ └─────────────┘
│ │
│ ┌─────────────┼─────────────┐
│ │ │ │
▼ ▼ ▼ ▼
┌───────────────┐ ┌───────────────┐ ┌───────────────┐
│ build-home.R │ │build-reference│ │build-articles │
│ README → │ │ man/*.Rd │ │ vignettes/ │
│ index.qmd │ │ → │ │ articles/ │
└───────────────┘ │ reference/ │ └───────────────┘
└───────┬───────┘
│
▼
┌───────────────┐
│ rd-to-qmd.R │
│ Rd parsing │
│ & conversion │
└───────────────┘
Key Subsystems
1. Configuration System (config.R)
The configuration system uses a merge strategy: user settings in _qrtdown.yml override intelligent defaults generated from package metadata.
User Config (_qrtdown.yml)
│
▼
┌─────────┐
│ merge() │ ◄── Default Config (from pkg_meta)
└────┬────┘
│
▼
Merged Config
│
├──► generate_quarto_yml() → _quarto.yml
└──► Used by all build_*() functions
Critical functions:
| Function | Purpose |
|---|---|
get_config() |
Main entry: returns merged config |
default_config() |
Generates defaults from DESCRIPTION |
generate_quarto_yml() |
Creates Quarto-compatible YAML |
get_articles_menu() |
Dynamically builds Articles dropdown |
What to watch for:
- Navbar is dynamically populated based on detected content
- Articles, Citation, and Changelog items are injected even if not in user config
- YAML boolean handling: Quarto 1.2+ requires
"true"/"false"strings
2. Rd Conversion System (rd-to-qmd.R)
Converts roxygen2 .Rd files to Quarto markdown. Uses two parsing strategies:
.Rd File
│
▼
┌─────────────────┐
│ rd_to_md_via_ │ ← Primary strategy
│ html() │
└────────┬────────┘
│
┌──────────────┴──────────────┐
│ │
▼ ▼
┌───────────────┐ ┌───────────────┐
│ tools::Rd2HTML│ │ Direct Rd │
│ ↓ │ │ Parsing │
│ HTML output │ │ (fallback) │
│ ↓ │ └───────────────┘
│ extract_html_ │
│ section() │
│ ↓ │
│ html_to_ │
│ markdown() │
└───────────────┘
│
▼
Quarto Markdown
Rd Markup Conversion Table:
| Rd Markup | Markdown Output |
|---|---|
\code{x} |
`x` |
\emph{x} |
*x* |
\strong{x} |
**x** |
\link[pkg]{fn} |
[fn]() |
\url{...} |
<...> |
\dontrun{...} |
# Not run:\n... |
\donttest{...} |
(unwrapped) |
What to watch for:
- HTML parsing is more robust but may fail on unusual Rd
- Direct Rd parsing is the fallback
- HTML entity unescaping is critical (
<→<) - Zero-width spaces must be removed
3. Reference Builder (build-reference.R)
Generates individual function pages and an index.
man/*.Rd files
│
▼
┌─────────────────────────────────────────────────┐
│ FOR EACH .Rd file: │
│ ├──► parse_rd_file() │
│ │ └──► rd_to_md_via_html() │
│ ├──► find_function_source() → source link │
│ └──► build_function_page() → reference/fn.qmd│
└─────────────────────────────────────────────────┘
│
▼
build_reference_index()
│
├──► Custom grouping via match_pattern()
│ ├── starts_with("prefix")
│ ├── ends_with("suffix")
│ ├── contains("text")
│ ├── matches("regex")
│ └── exact function name
│
└──► _qrtdown/reference.qmd (index page)
Output structure:
_qrtdown/
├── reference.qmd # Index with all functions
└── reference/
├── build_site.qmd
├── use_qrtdown.qmd
├── deploy_site.qmd
└── ... (one file per function)
Source link generation:
The find_function_source() function locates function definitions and generates repository-appropriate URLs:
- GitHub:
https://github.com/user/repo/blob/HEAD/R/file.R#L42 - GitLab:
https://gitlab.com/user/repo/-/blob/main/R/file.R#L42 - Codeberg:
https://codeberg.org/user/repo/src/branch/main/R/file.R#L42
4. Article System (build-articles.R)
Articles live in vignettes/articles/ (NOT vignettes/).
vignettes/articles/
├── getting-started.qmd ← "Get started" tab (special)
├── tutorial.qmd
└── advanced.qmd
Main article detection priority:
{packagename}.qmdgetting-started.qmdindex.qmd- First article found
The main article appears as a “Get started” navbar tab; others go in the “Articles” dropdown.
What to watch for:
- Users often put articles in
vignettes/instead ofvignettes/articles/ - The distinction prevents R CMD check warnings
- Underscore-prefixed files (
_draft.qmd) are excluded
5. usethis Functions (usethis.R)
User-facing setup and maintenance functions.
Version Bump Workflow (use_qrtdown_version()):
use_qrtdown_version("patch")
│
├──► Update DESCRIPTION (via desc package)
├──► Add heading to NEWS.md
├──► Update navbar title in _qrtdown.yml
├──► Regenerate _quarto.yml
└──► Clear .quarto cache ← CRITICAL!
Why cache clearing matters: Quarto caches site chrome (navbar, footer). Without clearing, version changes don’t appear until a full rebuild.
Deployment Workflow (deploy_site()):
deploy_site()
│
├──► git subtree push (preferred)
│ │
│ ▼
│ pages branch updated
│
└──► [on failure] git subtree split + force push
Error Handling & Edge Cases
Quarto Cache Issues
Symptom: “No such file or directory” during rename operations
Cause: Quarto’s internal cache out of sync
Solution implemented in build_site():
# Auto-retry logic
if (render_fails && "rename" in error) {
unlink(".quarto", recursive = TRUE)
retry_render()
}Articles Not Appearing
Symptom: Articles dropdown missing despite having .qmd files
Cause: Articles in vignettes/ instead of vignettes/articles/
Detection: qrtdown_sitrep() checks for this condition
Testing Strategy
tests/testthat/
├── test-build-home.R # Home page generation
├── test-build-articles.R # Article processing
├── test-config.R # Configuration merging
├── test-quarto-yml.R # YAML generation
├── test-utils.R # Utility functions
├── test-usethis.R # Setup functions
└── helper.R # Test fixtures
Test helpers in helper.R:
| Helper | Purpose |
|---|---|
create_test_package() |
Minimal package with DESCRIPTION |
create_test_package_with_articles() |
Add articles dir |
create_test_package_with_citation() |
Add CITATION file |
cleanup_test_package() |
Teardown |
Running tests:
devtools::test()Adding New Features
Adding a New Builder
- Create
R/build-{feature}.R - Export main function:
build_{feature}(pkg, quiet = FALSE) - Add call to
build_site()inbuild.R - Update default navbar in
config.Rif needed - Add tests in
tests/testthat/test-build-{feature}.R
Adding Configuration Options
- Add default in
default_config()(config.R) - Document in
_qrtdown.ymlexamples - Update
generate_quarto_yml()if Quarto-facing - Test config merging behavior
Adding a usethis Function
- Add function in
usethis.R - Follow naming:
use_qrtdown_{feature}() - Use
clipackage for user messages - Export in NAMESPACE via roxygen2
- Add template files to
inst/templates/if needed
Diagnostic Tools
qrtdown_sitrep()
Comprehensive diagnostic that checks:
- Package installation and version mismatches
- Quarto installation(s) and versions
- Configuration files existence and validity
- Package content (man files, articles, citations)
- Navbar configuration (auto vs manual)
- CLI installation status
- Build settings (freeze, cache)
- Deployment setup (CI workflows)
quarto_info()
Returns detailed Quarto installation info:
quarto_info()
#> $path
#> [1] "/usr/local/bin/quarto"
#> $version
#> [1] "1.4.550"Package Dependencies
Critical imports:
| Package | Used For |
|---|---|
cli |
User-facing messages |
desc |
DESCRIPTION file handling |
fs |
File system operations |
yaml |
YAML read/write |
tools |
Rd parsing (parse_Rd, Rd2HTML) |
glue |
String interpolation |
purrr |
Functional programming |
Optional (Suggests):
| Package | Used For |
|---|---|
quarto |
preview_site() R interface |
docopt |
CLI argument parsing |
usethis |
Some setup utilities |
Common Maintenance Tasks
Updating Quarto Compatibility
If Quarto changes output format:
- Check
rd_to_md_via_html()HTML extraction patterns - Update
generate_quarto_yml()for new YAML syntax - Test with multiple Quarto versions
Adding New Rd Tag Support
- Add pattern in
clean_rd_text()orhtml_to_markdown() - Handle in
extract_rd_section() - Test with an Rd file using that tag
Updating CI Templates
Templates are in inst/templates/: - github-action.yml - GitHub Actions workflow - woodpecker.yml - Woodpecker CI workflow
Update the R/Quarto installation steps as needed.
Architectural Decisions
| Decision | Rationale |
|---|---|
Generated files in _qrtdown/ |
Keeps package root clean, clear separation |
index.qmd uses include directive |
README.qmd stays untouched, sidebar added |
Articles in vignettes/articles/ |
Prevents R CMD check warnings |
| HTML intermediate for Rd parsing | More robust than direct Rd parsing |
| Git subtree for deployment | Simple, works with any Git host |
| Individual function pages | Better SEO, cross-linking support |
| Config merging (not replacement) | Sensible defaults with customization |
| Cache clearing on version bump | Prevents stale navbar issues |
Debugging Tips
- Build failures: Run
qrtdown_sitrep()first - Cache issues:
clean_site()then rebuild - Rd conversion: Test with
rd_to_qmd(path, output)directly - Config issues: Compare
get_config()output with expectations - Quarto issues: Check
quarto_info()for path conflicts
Quick Reference: Function Locations
| Function | File | Line (approx) |
|---|---|---|
build_site() |
build.R | 1-150 |
init_site() |
build.R | 150-250 |
qrtdown_sitrep() |
build.R | 400-700 |
get_config() |
config.R | 200-250 |
default_config() |
config.R | 50-150 |
generate_quarto_yml() |
config.R | 250-400 |
build_home() |
build-home.R | 1-100 |
build_reference() |
build-reference.R | 1-150 |
rd_to_md_via_html() |
rd-to-qmd.R | 200-350 |
use_qrtdown_version() |
usethis.R | 600-700 |
deploy_site() |
usethis.R | 750-900 |