Skip to content

Commit f21fd67

Browse files
feat(init): Default-driven template + --force / --name flags (#72)
Closes #36. `init_manifest` previously hardcoded TOML strings for the octad defaults. Any change to `OctadConfig::default()` silently diverged from what `init` wrote. Replace the hardcoded body with `render_manifest_template(database, name)`, which builds the TOML from `ProjectConfig::default()`, `OctadConfig::default()` and `SidecarConfig::default()`. The rendered output now tracks code. Add two flags to `Commands::Init`: - `--name <NAME>` — set `[project].name` directly so a new repo doesn't need a post-init edit. - `--force` — overwrite an existing `verisimiser.toml` instead of bailing. Default behaviour (no flag) still refuses to clobber. Three unit tests in `manifest::init_template_tests`: - `template_round_trips_through_toml` — rendered template parses as `Manifest` and every octad field equals `OctadConfig::default()`. - `template_uses_explicit_name_when_provided` — passing `Some("name")` produces `[project].name = "name"`. - `template_falls_back_to_default_name` — passing `None` uses the default project name. `cargo clippy --all-targets -- -D warnings` clean; 32 unit tests pass. Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
1 parent 6824a42 commit f21fd67

2 files changed

Lines changed: 98 additions & 24 deletions

File tree

src/main.rs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,13 @@ enum Commands {
3434
/// Database backend: postgresql, sqlite, or mongodb.
3535
#[arg(short, long, default_value = "postgresql")]
3636
database: String,
37+
/// Project name to set under [project].name. Defaults to the value
38+
/// from `ProjectConfig::default()` if not provided.
39+
#[arg(short, long)]
40+
name: Option<String>,
41+
/// Overwrite an existing verisimiser.toml instead of erroring.
42+
#[arg(short, long)]
43+
force: bool,
3744
},
3845
/// Parse the target database schema and generate sidecar overlay + interceptors.
3946
Generate {
@@ -86,7 +93,11 @@ enum Commands {
8693
fn main() -> Result<()> {
8794
let cli = Cli::parse();
8895
match cli.command {
89-
Commands::Init { database } => manifest::init_manifest(&database),
96+
Commands::Init {
97+
database,
98+
name,
99+
force,
100+
} => manifest::init_manifest(&database, name.as_deref(), force),
90101

91102
Commands::Generate { manifest, output } => {
92103
let m = manifest::load_manifest(&manifest)?;

src/manifest/mod.rs

Lines changed: 86 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -359,48 +359,111 @@ pub fn load_manifest(path: &str) -> Result<Manifest> {
359359

360360
/// Generate a new `verisimiser.toml` manifest file with the Phase 1 schema.
361361
///
362-
/// The `database` parameter sets the backend type (postgresql, sqlite, mongodb).
363-
/// Fails if the file already exists to prevent accidental overwrites.
364-
pub fn init_manifest(database: &str) -> Result<()> {
362+
/// The `database` parameter sets the backend type. Field defaults are pulled
363+
/// from `OctadConfig::default()`, `SidecarConfig::default()` and friends so
364+
/// the emitted template tracks code without drift.
365+
///
366+
/// - `name`: project name; defaults to `ProjectConfig::default().name` if `None`.
367+
/// - `force`: if `true`, overwrite an existing file. Otherwise, error.
368+
pub fn init_manifest(database: &str, name: Option<&str>, force: bool) -> Result<()> {
365369
let path = "verisimiser.toml";
366-
if std::path::Path::new(path).exists() {
367-
anyhow::bail!("{} already exists — remove it first to reinitialise", path);
370+
if std::path::Path::new(path).exists() && !force {
371+
anyhow::bail!(
372+
"{} already exists — pass --force to overwrite or remove it first",
373+
path
374+
);
368375
}
369376

370-
// Simulation is unimplemented across all backends; placeholder "false".
371-
let enable_simulation = "false";
377+
let template = render_manifest_template(database, name);
378+
std::fs::write(path, template)?;
379+
println!("Created {} for {} backend", path, database);
380+
Ok(())
381+
}
372382

373-
let template = format!(
383+
/// Render the manifest template, pulling defaults from the Default impls.
384+
/// Public to the crate so tests can assert the rendered TOML round-trips.
385+
pub(crate) fn render_manifest_template(database: &str, name: Option<&str>) -> String {
386+
let project = ProjectConfig::default();
387+
let octad = OctadConfig::default();
388+
let sidecar = SidecarConfig::default();
389+
let project_name = name.unwrap_or(&project.name);
390+
format!(
374391
r#"# SPDX-License-Identifier: PMPL-1.0-or-later
375392
# VeriSimiser manifest — augment {database} with VeriSimDB octad capabilities
376393
377394
[project]
378-
name = "my-augmented-db"
379-
version = "0.1.0"
395+
name = "{project_name}"
396+
version = "{project_version}"
380397
# description = "My database augmented with VeriSimDB octad dimensions"
381398
382399
[database]
383400
backend = "{database}"
384-
connection-string-env = "DATABASE_URL"
401+
connection-string-env = "{conn_env}"
385402
# schema-source = "schema.sql"
386403
387404
[octad]
388-
enable-provenance = true
389-
enable-lineage = true
390-
enable-temporal = true
391-
enable-access-control = true
392-
enable-constraints = true
405+
enable-provenance = {enable_provenance}
406+
enable-lineage = {enable_lineage}
407+
enable-temporal = {enable_temporal}
408+
enable-access-control = {enable_access_control}
409+
enable-constraints = {enable_constraints}
393410
enable-simulation = {enable_simulation}
394411
395412
[sidecar]
396-
storage = "sqlite"
397-
path = ".verisim/sidecar.db"
398-
"#
399-
);
413+
storage = "{sidecar_storage}"
414+
path = "{sidecar_path}"
415+
"#,
416+
project_version = project.version,
417+
conn_env = default_connection_env(),
418+
enable_provenance = octad.enable_provenance,
419+
enable_lineage = octad.enable_lineage,
420+
enable_temporal = octad.enable_temporal,
421+
enable_access_control = octad.enable_access_control,
422+
enable_constraints = octad.enable_constraints,
423+
enable_simulation = octad.enable_simulation,
424+
sidecar_storage = sidecar.storage,
425+
sidecar_path = sidecar.path,
426+
)
427+
}
400428

401-
std::fs::write(path, template)?;
402-
println!("Created {} for {} backend", path, database);
403-
Ok(())
429+
#[cfg(test)]
430+
mod init_template_tests {
431+
use super::{render_manifest_template, Manifest, OctadConfig};
432+
433+
#[test]
434+
fn template_round_trips_through_toml() {
435+
let rendered = render_manifest_template("postgresql", None);
436+
let m: Manifest =
437+
toml::from_str(&rendered).expect("rendered template must parse as Manifest");
438+
let defaults = OctadConfig::default();
439+
// Every octad field equals its Default::default() — no drift.
440+
assert_eq!(m.octad.enable_provenance, defaults.enable_provenance);
441+
assert_eq!(m.octad.enable_lineage, defaults.enable_lineage);
442+
assert_eq!(m.octad.enable_temporal, defaults.enable_temporal);
443+
assert_eq!(
444+
m.octad.enable_access_control,
445+
defaults.enable_access_control
446+
);
447+
assert_eq!(m.octad.enable_constraints, defaults.enable_constraints);
448+
assert_eq!(m.octad.enable_simulation, defaults.enable_simulation);
449+
assert_eq!(m.database.backend, "postgresql");
450+
}
451+
452+
#[test]
453+
fn template_uses_explicit_name_when_provided() {
454+
let rendered = render_manifest_template("sqlite", Some("acme-warehouse"));
455+
let m: Manifest = toml::from_str(&rendered).expect("template parses");
456+
assert_eq!(m.project.name, "acme-warehouse");
457+
assert_eq!(m.database.backend, "sqlite");
458+
}
459+
460+
#[test]
461+
fn template_falls_back_to_default_name() {
462+
let rendered = render_manifest_template("mongodb", None);
463+
let m: Manifest = toml::from_str(&rendered).expect("template parses");
464+
let default_name = super::ProjectConfig::default().name;
465+
assert_eq!(m.project.name, default_name);
466+
}
404467
}
405468

406469
/// Print a human-readable status summary of a loaded manifest.

0 commit comments

Comments
 (0)