#![allow(dead_code, clippy::unwrap_used, clippy::expect_used, clippy::panic)]
use std::path::{Path, PathBuf};
use ggen_engine::sync::{sync, SyncOptions};
use tempfile::TempDir;
pub(crate) fn copy_tree(src: &Path, dst: &Path) {
std::fs::create_dir_all(dst).expect("mkdir");
for entry in std::fs::read_dir(src).expect("read_dir") {
let entry = entry.expect("entry");
let from = entry.path();
let to = dst.join(entry.file_name());
if from.is_dir() {
copy_tree(&from, &to);
} else {
std::fs::copy(&from, &to).expect("copy");
}
}
}
pub(crate) fn scaffold_pack(pack_src_dir: &Path) -> (TempDir, PathBuf) {
let dir = TempDir::new().expect("tempdir");
let pack_name = pack_src_dir
.file_name()
.and_then(|n| n.to_str())
.expect("pack dir must have a valid utf8 name");
copy_tree(pack_src_dir, &dir.path().join(pack_name));
let project = dir.path().join("consumer");
std::fs::create_dir_all(project.join("templates")).expect("mkdir templates");
std::fs::write(project.join("ontology.ttl"), "").expect("write ontology.ttl");
std::fs::write(
project.join("ggen.toml"),
format!(
"[project]\nname = \"consumer\"\n\n\
[ontology]\nsource = \"ontology.ttl\"\n\n\
[packs]\n{pack_name} = {{ path = \"../{pack_name}\" }}\n\n\
[templates]\ndir = \"templates\"\n"
),
)
.expect("write ggen.toml");
(dir, project)
}
pub(crate) fn read(project: &Path, rel: &str) -> String {
std::fs::read_to_string(project.join(rel)).unwrap_or_else(|e| panic!("read {rel}: {e}"))
}
pub(crate) fn read_json(project: &Path, rel: &str) -> serde_json::Value {
let raw = read(project, rel);
serde_json::from_str(&raw).unwrap_or_else(|e| panic!("parse {rel} as JSON: {e}\n{raw}"))
}
pub(crate) fn scaffold_multi_pack(pack_names: &[&str]) -> (TempDir, PathBuf) {
use std::fmt::Write as _;
let dir = TempDir::new().expect("tempdir");
let packs_root = Path::new(env!("CARGO_MANIFEST_DIR")).join("../../packs");
for name in pack_names {
copy_tree(&packs_root.join(name), &dir.path().join(name));
}
let project = dir.path().join("consumer");
std::fs::create_dir_all(project.join("templates")).expect("mkdir templates");
std::fs::write(project.join("ontology.ttl"), "").expect("write ontology.ttl");
let packs_table = pack_names.iter().fold(String::new(), |mut table, name| {
let _ = writeln!(table, "{name} = {{ path = \"../{name}\" }}");
table
});
std::fs::write(
project.join("ggen.toml"),
format!(
"[project]\nname = \"consumer\"\n\n\
[ontology]\nsource = \"ontology.ttl\"\n\n\
[packs]\n{packs_table}\n\
[templates]\ndir = \"templates\"\n"
),
)
.expect("write ggen.toml");
(dir, project)
}
pub(crate) fn scaffold_pack_with_ontology(
pack_src_dir: &Path, ontology: &str,
) -> (TempDir, PathBuf) {
let (dir, project) = scaffold_pack(pack_src_dir);
std::fs::write(project.join("ontology.ttl"), ontology).expect("write ontology.ttl");
(dir, project)
}
pub(crate) fn count_md_files(dir: &Path) -> usize {
std::fs::read_dir(dir)
.unwrap_or_else(|e| panic!("read_dir {}: {e}", dir.display()))
.filter_map(std::result::Result::ok)
.filter(|e| e.path().extension().is_some_and(|x| x == "md"))
.count()
}
pub(crate) fn write_pack(root: &Path, name: &str, class_name: &str, to_path: &str, body: &str) {
let pack_dir = root.join(name);
std::fs::create_dir_all(pack_dir.join("templates")).expect("mkdir pack templates");
std::fs::write(
pack_dir.join("pack.toml"),
format!(
"[pack]\nname = \"{name}\"\nversion = \"0.1.0\"\ndescription = \"test pack {name}\"\n"
),
)
.expect("write pack.toml");
std::fs::write(
pack_dir.join("ontology.ttl"),
format!(
"@prefix dom: <http://example.com/ontology#> .\ndom:{class_name} a dom:DomainClass .\n"
),
)
.expect("write ontology.ttl");
let tmpl_name = format!("{}.rs.tmpl", class_name.to_lowercase());
std::fs::write(
pack_dir.join("templates").join(tmpl_name),
format!("---\nto: {to_path}\n---\n{body}\n"),
)
.expect("write template");
}
pub(crate) fn write_single_pack_project(
root: &Path, project_name: &str, pack_name: &str,
) -> PathBuf {
let project = root.join(project_name);
std::fs::create_dir_all(project.join("templates")).expect("mkdir templates");
std::fs::write(project.join("ontology.ttl"), "").expect("write ontology.ttl");
std::fs::write(
project.join("ggen.toml"),
format!(
"[project]\nname = \"{project_name}\"\n\n\
[ontology]\nsource = \"ontology.ttl\"\n\n\
[packs]\npack-a = {{ path = \"../{pack_name}\" }}\n\n\
[templates]\ndir = \"templates\"\n"
),
)
.expect("write ggen.toml");
project
}
pub(crate) fn write_two_pack_project(root: &Path, pack_a: &str, pack_b: &str) -> PathBuf {
let project = root.join("two-pack-project");
std::fs::create_dir_all(project.join("templates")).expect("mkdir templates");
std::fs::write(project.join("ontology.ttl"), "").expect("write ontology.ttl");
std::fs::write(
project.join("ggen.toml"),
format!(
"[project]\nname = \"two-pack-project\"\n\n\
[ontology]\nsource = \"ontology.ttl\"\n\n\
[packs]\npack-a = {{ path = \"../{pack_a}\" }}\npack-b = {{ path = \"../{pack_b}\" }}\n\n\
[templates]\ndir = \"templates\"\n"
),
)
.expect("write ggen.toml");
project
}
pub(crate) fn assert_idempotent(project: &Path) -> String {
let lock_before =
std::fs::read_to_string(project.join("ggen.lock")).expect("ggen.lock must exist");
let second = sync(
project,
SyncOptions {
consumer_mode: Default::default(),
dry_run: false,
..Default::default()
},
)
.expect("second sync must succeed");
assert!(
second.written.is_empty(),
"second sync wrote (not idempotent): {:?}",
second.written
);
let lock_after =
std::fs::read_to_string(project.join("ggen.lock")).expect("ggen.lock must exist");
assert_eq!(
lock_after, lock_before,
"ggen.lock must be byte-identical across identical runs"
);
lock_after
}
pub(crate) fn assert_gate_refuses(project: &Path, sabotage_ttl: &str, gate_name_fragment: &str) {
let lock_path = project.join("ggen.lock");
let lock_before = std::fs::read_to_string(&lock_path).ok();
std::fs::write(project.join("ontology.ttl"), sabotage_ttl).expect("write sabotage ontology");
let err = sync(
project,
SyncOptions {
consumer_mode: Default::default(),
dry_run: false,
..Default::default()
},
)
.expect_err("sabotage ontology must be refused by the gate");
let msg = format!("{err}");
assert!(
msg.contains(gate_name_fragment),
"refusal must cite gate {gate_name_fragment}: {msg}"
);
let lock_after = std::fs::read_to_string(&lock_path).ok();
assert_eq!(
lock_after, lock_before,
"gate refusal must leave ggen.lock exactly as it was before the sabotage attempt \
(no partial state) — absent stays absent, a prior legitimate lock stays unchanged"
);
}