Templates
Templates let users scaffold new projects with cargo bp new or merge config files into existing projects with cargo bp add <pack> -t <name>.
Structure
A template lives in a subdirectory under templates/:
templates/
└── default/
├── bp-template.toml
├── _Cargo.toml
└── src/
└── main.rs
Note: Template
Cargo.tomlfiles must be named_Cargo.toml.cargo packagetreats any subdirectory containing aCargo.tomlas a separate crate and excludes it. The template engine maps_Cargo.tomlback toCargo.tomlin the output.
Register templates in your Cargo.toml:
[package.metadata.battery.templates]
default = { path = "templates/default", description = "A basic starting point" }
subcmds = { path = "templates/subcmds", description = "Multi-command CLI" }
If you have multiple templates, users choose with --template:
cargo bp new my-pack --template subcmds
Placeholders
The bp-template.toml configures template variables using MiniJinja syntax:
[placeholders.description]
type = "string"
prompt = "What does this project do?"
default = "A new project"
Placeholder names must use snake_case (my_value, not my-value) because MiniJinja treats - as minus.
Types
# String (default)
[placeholders.description]
type = "string"
prompt = "Project description"
default = "A new project"
# Bool — yes/no prompt, defaults to false
[placeholders.benchmarks]
type = "bool"
prompt = "Include benchmarks?"
# Select — arrow-key selection, requires explicit default
[placeholders.ci_platform]
type = "select"
prompt = "CI platform"
options = ["github", "none"]
default = "github"
Bool values work naturally in templates: {% if benchmarks %}. On the command line, bare -d benchmarks implies =true.
Built-in variables
These are always available (no declaration needed):
{{ project_name }}— the project name from--name{{ crate_name }}— derived fromproject_namewith-replaced by_
Built-in functions
{{ pin_github_action("actions/checkout", "v6") }}— resolves a GitHub Action tag to a SHA-pinned reference at generation time{{ rust_stable_version() }}— returns the current stable Rust version
Category-linked placeholders
A select placeholder can derive its options from a category instead of a hardcoded list:
[placeholders.allocator]
type = "select"
prompt = "Global allocator"
options.category = "allocator"
default = "jemalloc"
If the user already made a selection in the cargo bp add picker, this placeholder is pre-filled.
Managed dependencies
Use bp-managed = true in your template’s _Cargo.toml instead of hardcoding versions:
[dependencies]
clap.bp-managed = true
[build-dependencies]
cli-battery-pack.bp-managed = true
When someone generates a project, cargo bp resolves actual versions from your battery pack’s spec. You never need to update template files when you bump dependency versions.
You can override features or add keys alongside bp-managed:
# Managed version, explicit features:
clap = { bp-managed = true, features = ["derive", "env"] }
# Managed version with optional:
serde = { bp-managed = true, optional = true }
Merge-friendly templates
Templates applied to existing projects with cargo bp add <pack> -t <name> handle file conflicts by type:
Cargo.toml— dependencies merged (versions upgraded if behind, features unioned)- Other
.toml— new sections/keys added, existing ones left alone .yml/.yaml— top-level keys merged;jobs,on,permissionsdeep-merged- Everything else — user prompted to skip or overwrite
Tips for merge-friendly templates:
- Keep template
Cargo.tomlminimal — only what the template needs - Use
bp-managed = trueso versions stay current - Use unique filenames for workflows (e.g.,
typos.ymlnotci.yml)
Hints
For steps that can’t be automated, declare hints:
[[hints]]
message = "Add `mod errors;` to your lib.rs or main.rs"
[[hints]]
message = "Run `cargo install cargo-fuzz` if you haven't already"
Hints are printed after the merge summary (only for cargo bp add -t, not cargo bp new).
Including files from outside the template
[[files]]
src = "LICENSE-MIT" # relative to crate root
dest = "LICENSE-MIT" # relative to generated project
Validating templates
cargo bp validate generates each template into a temp directory, runs cargo check and cargo test, and reports failures. Add this to your CI:
#![allow(unused)]
fn main() {
#[cfg(test)]
mod tests {
#[test]
fn validate() {
::battery_pack::testing::validate(env!("CARGO_MANIFEST_DIR")).unwrap();
}
}
}
Placeholders should have default values so validation can generate templates non-interactively.