Layer 3: Templates as pure data, weather template, .anvilignore refresh system

Templates are now composed declaratively via template.toml -- no Rust code
changes needed to add new templates. The weather station is the first
composed template, demonstrating the full pattern.

Template engine:
- Composed templates declare base, required libraries, and per-board pins
- Overlay mechanism replaces base files (app, sketch, tests) cleanly
- Generic orchestration: extract base, apply overlay, install libs, assign pins
- Template name tracked in .anvil.toml for refresh awareness

Weather template (--template weather):
- WeatherApp with 2-second polling, C/F conversion, serial output
- TMP36 driver: TempSensor interface, Tmp36 impl, Tmp36Mock, Tmp36Sim
- Managed example tests in test_weather.cpp (unit + system)
- Minimal student starters in test_unit.cpp and test_system.cpp
- Per-board pin defaults (A0 for uno, A0 for mega, A0 for nano)

.anvilignore system:
- Glob pattern matching (*, ?) with comments and backslash normalization
- Default patterns protect student tests, app code, sketch, config
- anvil refresh --force respects .anvilignore
- anvil refresh --force --file <path> overrides ignore for one file
- anvil refresh --ignore/--unignore manages patterns from CLI
- Missing managed files always recreated even without --force
- .anvilignore itself is in NEVER_REFRESH (cannot be overwritten)

Refresh rewrite:
- Discovers all template-produced files dynamically (no hardcoded list)
- Extracts fresh template + libraries into temp dir for byte comparison
- Config template field drives which files are managed
- Separated missing-file creation from changed-file updates

428 tests passing on Windows MSVC, 0 warnings.
This commit is contained in:
Eric Ratliff
2026-02-21 20:52:48 -06:00
parent 0abe907811
commit ca855dd3af
17 changed files with 5190 additions and 236 deletions

View File

@@ -81,9 +81,21 @@ enum Commands {
/// Path to project directory (defaults to current directory)
dir: Option<String>,
/// Overwrite scripts even if they have been modified
/// Overwrite managed files even if they have been modified
#[arg(long)]
force: bool,
/// Override .anvilignore for a specific file (use with --force)
#[arg(long, value_name = "PATH")]
file: Option<String>,
/// Add a pattern to .anvilignore (protect a file from refresh)
#[arg(long, value_name = "PATTERN", conflicts_with_all = ["unignore", "force"])]
ignore: Option<String>,
/// Remove a pattern from .anvilignore (allow refresh to update it)
#[arg(long, value_name = "PATTERN", conflicts_with_all = ["ignore", "force"])]
unignore: Option<String>,
},
/// Manage board profiles in .anvil.toml
@@ -264,11 +276,24 @@ fn main() -> Result<()> {
commands::devices::scan_devices()
}
}
Commands::Refresh { dir, force } => {
commands::refresh::run_refresh(
dir.as_deref(),
force,
)
Commands::Refresh { dir, force, file, ignore, unignore } => {
if let Some(pattern) = ignore {
commands::refresh::add_ignore(
dir.as_deref(),
&pattern,
)
} else if let Some(pattern) = unignore {
commands::refresh::remove_ignore(
dir.as_deref(),
&pattern,
)
} else {
commands::refresh::run_refresh(
dir.as_deref(),
force,
file.as_deref(),
)
}
}
Commands::Board { name, add, remove, default, listall, id, baud, dir } => {
if listall {