--- title: "Getting started with prefab" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{Getting started with prefab} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r, include = FALSE} knitr::opts_chunk$set(collapse = TRUE, comment = "#>") local({ hook_source <- knitr::knit_hooks$get("source") knitr::knit_hooks$set(source = function(x, options) { if (!is.null(options$display_code)) { x <- options$display_code } hook_source(x, options) }) }) ``` ## Basics The goal of prefab is to make it easier for you to set up files and directories you need for a given project. You can think of the project scaffolding--like README files and directory structures--as a *theme* you want to apply to a new or existing project directory. ### Viewing and applying themes For example, `r_analysis()` is a prefab theme shipped with this package to provide scaffolding for a simple R data analysis project. To understand what it does, simply call the theme to show the outline of steps: ```{r call_theme} library(prefab) r_analysis() ``` Nothing happens to the file system until you pass the theme to `use_theme()` or `create_project()`. Above, the `r_analysis()` theme indicated that it contains five steps: two create directories, and three write files. File steps also contain a merge strategy shown in `()` for handling pre-existing destination files. Pass the theme to `create_project()` to create a new project folder with that scaffolding. ```{r create_example, display_code = 'create_project(tempfile("my-analysis-project"), r_analysis())'} withr::with_tempdir({ app <- cli::start_app(output = "stdout", .auto_close = FALSE) create_project(tempfile("my-analysis-project"), r_analysis()) cli::stop_app(app) }) ``` That created a directory called `~/my-analysis-project` and added two new subdirectories and three new files. Then (assuming you are using Positron or Rstudio) it opened the project in a new session. Instead of creating a new project directory, you can also apply a theme to your current project with `use_theme()`: ```{r use_example, display_code = "use_theme(r_analysis())"} withr::with_tempdir({ file.create(".here") app <- cli::start_app(output = "stdout", .auto_close = FALSE) use_theme(r_analysis()) cli::stop_app(app) }) ``` ### Composing themes with `+` and arguments A prefab theme is a function that returns an ordered list of steps that modify files and paths, or run other functions Because themes are just functions, they can have arguments and you can compose them with `+`, concatenating their steps. ```{r compose, display_code = "use_theme(r_analysis(data_dirs = FALSE) + claude_r_analysis())"} withr::with_tempdir({ file.create(".here") app <- cli::start_app(output = "stdout", .auto_close = FALSE) use_theme(r_analysis() + claude_r_analysis(settings_json = FALSE)) cli::stop_app(app) }) ``` Steps run left to right. ## Building your own theme There are three ways to build your own theme: **From a directory of files.** Arrange template files in a folder and `theme_from_dir()` turns them into a theme. For example, put your desired template files in the folder `~/my-project-structure`: ```{r theme-from-dir-setup, echo = FALSE} # Create a small directory of template files theme_dir <- file.path(tempdir(), "my-project-structure") fs::dir_create(file.path(theme_dir, "R")) fs::dir_create(file.path(theme_dir, "data")) writeLines(c("*.csv", ".Rhistory"), file.path(theme_dir, ".gitignore")) writeLines("# My Project\n", file.path(theme_dir, "README.md")) writeLines("# analysis code here", file.path(theme_dir, "R", "analysis.R")) ``` ``` r ├── data ├── R │ └── analysis.R │ .gitignore └── README.md ``` Use `theme_from_dir()` to turn them into a theme, and then apply them to a new or existing project: ```{r theme-from-dir-apply, display_code = 'my_theme <- theme_from_dir("~/my-project-structure")\nuse_theme(my_theme)'} my_theme <- theme_from_dir(theme_dir) withr::with_tempdir({ file.create(".here") app <- cli::start_app(output = "stdout", .auto_close = FALSE) use_theme(my_theme) cli::stop_app(app) }) ``` An optional `_prefab.yml` sidecar controls per-file merge strategies and template data. **By composing existing themes.** Combine and extend built-in or custom themes with `+`: ``` r my_theme <- theme_from_dir("~/my-extras") + claude_r_analysis() ``` Themes are combined left to right. **From steps.** Build themes programmatically with `step_file()`, `step_text()`, and `step_run()`: ``` r my_theme <- function() { new_theme( step_file("~/my_themes/header.R", "R/header.R"), step_text(c("*.csv", "*.rds"), ".gitignore", strategy = "union"), step_run(fs::dir_create, "tables", .label = "fs::dir_create('tables')") ) } ``` ## Writing themes from steps A prefab theme is a function that returns an ordered list of steps that modify files and paths, or run other functions. The three step types are: - `step_file(source, dest)` -- deploy a file - `step_text(content, dest)` -- deploy inline text - `step_run(fn, ...)` -- execute a function for its side effects ```{r custom, eval = FALSE} my_analysis_theme <- function() { ignore_lines <- c(".Rproj.user", ".Rhistory", ".RData") new_theme( step_file("~/my-templates/main.R", "main.R", strategy = "skip"), step_text(ignore_lines, ".gitignore", strategy = "union"), step_run(fs::dir_create, "data", .label = "fs::dir_create") ) } ``` Because themes are just functions, parameters give you conditional behavior for free. `NULL` arguments to `new_theme()` are silently dropped, so `if (cond) step(...)` works naturally. ```{r custom_conditional, eval = FALSE} my_analysis_theme <- function(use_data_dir = TRUE, extra_ignores = character(0)) { ignore_lines <- c(".Rproj.user", ".Rhistory", ".RData", extra_ignores) new_theme( step_file("~/my-templates/main.R", "main.R", strategy = "skip"), step_text(ignore_lines, ".gitignore", strategy = "union"), if (use_data_dir) step_run(fs::dir_create, "data", .label = "fs::dir_create") ) } ``` ### Source helpers `from_dir()` and `from_package()` create step-builders that resolve source paths relative to a directory or an installed R package: ```{r source-helpers, eval = FALSE} # Resolve from a local directory from_templates <- from_dir("~/my-templates") from_templates("header.md", "README.md", strategy = "skip") # Resolve from an installed package's inst/ directory from_my_package <- from_package("my_package") from_my_package("r_analysis/main.R", "main.R", strategy = "skip") ``` `from_dir()` resolves its path to absolute at creation time, so later working directory changes do not affect it. `from_package()` works with both installed packages and `devtools::load_all()`. ### Merge strategies Every file step declares a **strategy** for handling pre-existing destination files. Strategies are what make `use_theme()` safe to re-run. | Strategy | Behavior | Idempotent | Typical use | |---|---|---|---| | `"overwrite"` | Replace the file entirely | Yes | Managed config files | | `"skip"` | Do nothing if file exists | Yes | Starter files users will edit | | `"union"` | Append lines not already present | Yes | `.gitignore`, `.Rbuildignore` | | `"append"` | Append all content unconditionally | No | Rare; prefer `"union"` | | `"merge_json"` | Recursively merge JSON objects | Yes | `.claude/settings.json` | Guidelines for choosing: - Files the user should never edit: `"overwrite"`. - Files the user will customize after first deploy: `"skip"`. - Line-based config where entries accumulate: `"union"`. - Structured JSON config: `"merge_json"` (objects merge key-by-key, arrays are union-merged, scalar collisions preserve the destination value). - Avoid `"append"` unless you want duplicate content on re-runs. ### Template rendering File steps support `{{var}}` interpolation when `data` is non-NULL: ```{r template, eval = FALSE} from_templates <- from_dir("~/my-templates") new_theme( # data = list() enables rendering with auto-discovered variables only from_templates("README.md", "README.md", strategy = "skip", data = list()), # Explicit variables supplement or override auto-context from_templates("CITATION.md", "CITATION.md", data = list(org_name = "Acme Corp")) ) ``` Auto-discovered variables (built once per `use_theme()` call): | Variable | Source | |---|---| | `project_dir` | Name of the project root directory | | `package_name` | `Package` field from DESCRIPTION, or `project_dir` | | `year` | Current year | | `date` | Current date (`YYYY-MM-DD`) | A template file like: ``` # {{project_dir}} Created {{date}} by {{org_name}}. ``` is rendered by merging explicit `data` on top of auto-context. Explicit values win on collision. If a variable is referenced but not available, rendering fails with an informative error. `step_text()` does not support `data` -- inline content can interpolate R variables directly. ## Sharing and re-using custom themes **Source a file.** Save theme functions in an external script and source them. This can of course be any external script, or you can put themes in `~/.prefab-themes.R` which is sourced by the function `load_themes()`: ``` r load_themes() use_theme(my_analysis_theme()) ``` **Ship in a package.** This package ships with several basic themes, and you can add themes (which are just functions) to your own package. Using a package is best sharing themes across an organization. When writing a theme function for your own package, place template files in `inst/` and use `from_package()`. ```r my_theme <- function() { from_my_package <- from_package("my_package") new_theme( from_my_package("main.R", "main.R", strategy = "skip") from_my_package("README.md", "README.md", strategy = "skip") ) } ``` ## Inspecting themes with `theme_code()` `theme_code()` prints R code that reproduces a theme -- useful for understanding built-in themes or as a starting point for customization: ```{r theme-code} theme_code(claude_r_analysis()) ``` Copy the output, edit it into your own theme function, and swap out or add steps. The code is also returned invisibly as a string.