:: lib::std::containers::collections
:: lib::std::cryptography::sha2
:: lib::std::encoding::hex
:: lib::std::io::buffered
const usize READ_CAPACITY = 32768;
export (collections::Array<model::Entry>, error) scan(stringv rootPath) {
entries := collections::Array<model::Entry>::init();
scanError := collect(rootPath, "", &entries);
return (entries, scanError);
entries.sortBy(model::entry_less);
&collections::Array<model::Entry> entries
string directoryPath = none;
if relativePath.len() == 0 {
directoryPath, copyError = model::copy_text(rootPath);
directoryPath, joinError = path::join(rootPath, relativePath);
directory, openError := file::Directory::open(directoryPath.view());
fmt::eprint("workspace_guard: cannot open directory: ");
fmt::eprintln(directoryPath.view());
entry, present, readError := directory.next();
if utf8::validate(entry.name()) != none {
fmt::eprintln("workspace_guard: non-UTF-8 path is unsupported");
return model::GuardError.UnsupportedEntry;
childRelative, relativeError :=
model::child_path(relativePath, entry.name());
if relativeError != none {
if !model::valid_relative_path(childRelative.view()) {
fmt::eprint("workspace_guard: non-portable path: ");
fmt::eprintln(childRelative.view());
return model::GuardError.UnsupportedEntry;
if entry.kind() == file::EntryType.Directory {
recurseError := collect(rootPath, childRelative.view(), entries);
if recurseError != none {
if entry.kind() != file::EntryType.File {
fmt::eprint("workspace_guard: unsupported entry: ");
fmt::eprintln(childRelative.view());
return model::GuardError.UnsupportedEntry;
actualPath, joinError := path::join(rootPath, childRelative.view());
digest, size, hashError := hash_file(actualPath.view());
fmt::eprint("workspace_guard: cannot hash file: ");
fmt::eprintln(childRelative.view());
pushError := entries->push(
model::Entry::init(childRelative, digest, size)
return directory.close();
(string, u64, error) hash_file(stringv filePath) {
input, openError := file::File::open(filePath, file::OpenOptions::readOnly());
return (none, none, openError);
hasher := sha2::Sha256::init();
adapter := input.reader();
buffered::BufferedReader::withCapacity(adapter, READ_CAPACITY);
return (none, none, readerError);
count, readError := reader.read(chunk[:]);
const u64 NEXT = total + count.cast<u64>;
return (none, none, AllocationError.OutOfMemory);
result = hasher.update(chunk[:count]);
return (none, none, result);
return (none, none, readError);
closeError := input.close();
return (none, none, closeError);
digest, digestError := hasher.finalize();
return (none, none, digestError);
encoded, encodeError := hex::encode_bytes(digest.bytes());
return (encoded, total, encodeError);