Skip to content

Presets and markers

A preset is a whole project template. A marker is a comment pair inside a generated file marking the region the scaffolder owns. Together they are what makes rtb regenerate project able to refresh a tool without destroying what you added to it.

Bundled presets

Two ship in the binary. --preset selects one; an unknown name is a hard error, never a silent fall back to the default.

Preset Default Depends on Command shape
minimal yes clap, linkme only A local Command trait with name / about / clap / run, dispatched from a linkme slice
cli no the rust-tool-base umbrella crate, plus tokio, miette, async-trait, clap, linkme, serde, schemars The framework Command trait — spec() and async run(App) — with arguments in a #[derive(Parser)] struct re-parsed through rtb::cli::parse_passthrough

Both render the same five files: Cargo.toml, .gitignore, README.md, src/main.rs, src/commands/mod.rs. generate project also writes .rtb/manifest.yaml, for six files in total.

Why minimal depends on nothing from the framework

minimal deliberately pulls in only clap and linkme, so cargo check on a freshly scaffolded tool works without reaching the network for the framework crates. That makes it the preset to reach for when you want a working binary immediately, or when you are demonstrating the scaffolder somewhere hermetic. Switch to cli when you want the built-in command suite.

What cli gives you that minimal does not

The cli preset's main.rs calls rtb_cli::Application::builder(), so the tool inherits version, doctor, update, docs, mcp, config, credentials and telemetry — see built-in commands. It also installs a typed AppConfig through rtb::config::Config, which is what makes config schema and config validate meaningful in a scaffolded tool.

The framework version pin is rendered, not hardcoded

The cli preset's Cargo.toml pins rtb = { package = "rust-tool-base", version = "{{ rtb_version }}" }, where the value is the umbrella version the scaffolding rtb binary was built from. A template must never hardcode that pin; a guard test in the workspace fails the build if one does.

preset_version

The manifest records a preset_version — an internal schema counter bumped whenever the rendered shape changes in a way the regenerator has to notice. Despite the name of the constant behind it (MINIMAL_PRESET_VERSION) there is one counter, written for both presets. It is at 5:

Version Change
1 Single-file clap binary with --version.
2 commands/ module with linkme-based dispatch, so generate command no longer edits main.rs.
3 AppConfig stub with settings markers, so generate setting has somewhere to write.
4 --version gained help text and stopped being global, so it no longer appeared blank on every subcommand.
5 The cli preset's main.rs moved from VersionInfo::from_env() to version_info!().

Version 5 matters beyond tidiness: a tool scaffolded at version 4 reports the framework's version rather than its own, and rtb update refuses to swap a binary whose reported version does not match the release tag. Regenerating moves it onto the macro.

Marker regions

Marker pair File Preset Owns
// rtb:commands-begin / -end src/commands/mod.rs both pub mod <command>; lines, kept sorted and deduplicated
// rtb:flags-begin / -end src/commands/<cmd>.rs minimal .arg(clap::Arg::new(…)) builder calls
// rtb:args-begin / -end src/commands/<cmd>.rs cli #[derive(Parser)] struct fields
// rtb:settings-begin / -end src/main.rs both AppConfig fields

Regeneration splices the interior of each region from the file on disk into the freshly rendered template. Everything outside the markers is taken from the new template. Leading whitespace before a marker is tolerated, which is what lets the settings markers sit indented inside a struct body.

Removing or reordering a marker line breaks the splice for that region: the fresh template's empty region wins and your additions are gone. If you need to move code out of a marked region, move it to a file the manifest does not own.

Templates are embedded, and escaped on the way out

Templates live under crates/rtb-cli-bin/templates/ and are compiled into the binary with rust-embed, so scaffolding works with no network access. They render through minijinja, and directories starting with _ — currently just _partials/ — are internal rather than selectable presets.

Four context-aware filters are registered on the template environment and applied at non-code render sites: escape_yaml, escape_toml, escape_markdown, escape_shell_arg. Each is the identity function on the safe character class, never panics, and cannot fail. They are defence in depth — the primary defence is input validation, which rejects the dangerous character classes before a value reaches a template at all.