:: lib::std::containers::collections
:: lib::std::process::command;
export error snapshot(stringv rootPath, stringv manifestPath) {
entries, scanError := scanner::scan(rootPath);
writeError := manifest::write(manifestPath, &entries);
fmt::print("snapshot: ");
fmt::print(entries.len());
export error verify(stringv rootPath, stringv manifestPath, bool quiet = false) {
expected, manifestError := manifest::read(manifestPath);
if manifestError != none {
actual, scanError := scanner::scan(rootPath);
usize differences = compare(&expected, &actual, false);
fmt::eprint("workspace_guard: ");
fmt::eprint(differences);
fmt::eprintln(" workspace differences");
return model::GuardError.Mismatch;
fmt::print("verified: ");
fmt::print(actual.len());
export error diff(stringv oldManifest, stringv newManifest) {
oldEntries, oldError := manifest::read(oldManifest);
newEntries, newError := manifest::read(newManifest);
usize differences = compare(&oldEntries, &newEntries, true);
fmt::println(" differences");
&const collections::Array<model::Entry> expected,
&const collections::Array<model::Entry> actual,
expectedValues := expected->view();
actualValues := actual->view();
while expectedIndex < expectedValues.len() or
actualIndex < actualValues.len() {
if expectedIndex == expectedValues.len() {
report("added", actualValues[actualIndex].path(), standardOutput);
if actualIndex == actualValues.len() {
report("removed", expectedValues[expectedIndex].path(), standardOutput);
expectedPath := expectedValues[expectedIndex].path();
actualPath := actualValues[actualIndex].path();
if model::text_less(expectedPath, actualPath) {
report("removed", expectedPath, standardOutput);
if model::text_less(actualPath, expectedPath) {
report("added", actualPath, standardOutput);
&expectedValues[expectedIndex],
&actualValues[actualIndex]
report("modified", expectedPath, standardOutput);
void report(stringv kind, stringv path, bool standardOutput) {
verifyError := verify(rootPath, manifestPath, true);
return (none, verifyError);
if arguments.len() == 0 {
return (none, model::GuardError.Usage);
executable, copyError := model::copy_text(arguments[0].view());
return (none, copyError);
childCommand := command::Command::init(executable);
loop usize index = 1; index < arguments.len(); index++ {
argument, argumentError := model::copy_text(arguments[index].view());
if argumentError != none {
return (none, argumentError);
if childCommand.arg(argument) != none {
return (none, AllocationError.OutOfMemory);
child, spawnError := childCommand.spawn();
return (none, spawnError);
status, waitError := child.wait();
return (none, waitError);
return (none, model::GuardError.ProcessFailure);
return (status.detail(), none);