use fmt;
use strings;
use bufio;
use os;
use io;
use strconv;
// A node in the file system tree is either a dir or a file
type inode = (*dir | *file);
type dir = struct {
name: str,
parent: (*dir | void),
children: []inode,
sz: size,
};
type file = struct {
name: str,
sz: size,
};
fn inode_free(root: inode) void = {
match (root) {
case let f: *file =>
free(f.name);
free(f);
case let d: *dir =>
for (let i = 0z; i < len(d.children); i += 1)
inode_free(d.children[i]);
free(d.children);
free(d.name);
free(d);
};
};
fn inode_print(root: inode, indent: size) void = {
for (let i = 0z; i < indent; i += 1)
fmt::print(" ")!;
match (root) {
case let f: *file =>
fmt::printfln("- {} ({})", f.name, f.sz)!;
case let d: *dir =>
fmt::printfln("{} ({} entr{})", d.name, len(d.children), if
(len(d.children) == 1) "y" else "ies")!;
for (let i = 0z; i < len(d.children); i += 1)
inode_print(d.children[i], indent + 1);
};
};
// Do we have an entry with the given name?
fn dir_find(d: *dir, name: str) (void | *dir) = {
for (let i = 0z; i < len(d.children); i += 1) {
match (d.children[i]) {
case let d0: *dir => if (d0.name == name) return d0;
case => void;
};
};
return void;
};
fn dir_size(d: *dir, tally: *size) size = {
let total = 0z;
for (let i = 0z; i < len(d.children); i += 1) match (d.children[i]) {
case let f: *file =>
total += f.sz;
case let d0: *dir =>
total += dir_size(d0, tally);
};
if (total <= 100000)
*tally += total;
d.sz = total;
return total;
};
// Find a candidate for deletion of at least threshold in size
//
// Returns void if none is found, otherwise the smallest candidate identified
// (recursively).
fn find_delcandidates(d: *dir, threshold: size, candidates: *[]*dir) void = {
for (let i = 0z; i < len(d.children); i += 1) match (d.children[i]) {
case let d0: *dir =>
if (d0.sz >= threshold) {
append(candidates, d0);
};
find_delcandidates(d0, threshold, candidates);
case => void;
};
if (d.sz >= threshold) {
append(candidates, d);
};
};
export fn main() void = {
let root: *dir = alloc(dir {
name = strings::dup("/"),
parent = void,
children = [],
sz = 0,
});
defer inode_free(root);
let cwd = root;
for (true) match (bufio::scanline(os::stdin)) {
case let buf: []u8 =>
if (buf[0] == '$') {
// running command
let cmd = strings::fromutf8(buf[2..4])!;
switch (cmd) {
case "cd" =>
// get dir to go to
let to = strings::fromutf8(buf[5..])!;
if (to == "/" && root == cwd) {
void;
} else if (to == "..") {
assert(cwd != root);
cwd = cwd.parent as *dir;
} else {
// check if we already know the
// subfolder
let subfolder = match (dir_find(cwd, to)) {
case let d: *dir=> yield d;
case void =>
let subfolder = alloc(dir {
name = strings::dup(to),
parent = cwd,
children = [],
sz = 0,
});
append(cwd.children, subfolder);
yield subfolder;
};
cwd = subfolder;
};
case "ls" => void;
};
} else {
// processing ls output
let line = strings::fromutf8(buf)!;
let (what, name) = strings::cut(line, " ");
if (what == "dir") {
if (dir_find(cwd, name) is void) {
let subfolder = alloc(dir {
name = strings::dup(name),
parent = cwd,
children = [],
sz = 0,
});
append(cwd.children, subfolder);
};
} else {
let sz = strconv::stoz(what)!;
let file = alloc(file {
name = strings::dup(name),
sz = sz,
});
append(cwd.children, file);
};
};
case io::EOF =>
break;
case let err: io::error =>
fmt::fatalf("error: {}", io::strerror(err));
};
inode_print(root, 0);
let tally = 0z;
let rootsz = dir_size(root, &tally);
fmt::printfln("total size of /: {}", rootsz)!;
fmt::printfln("tally = {}", tally)!;
// find smallest candidate that could be deleted to make room
let unused = 70000000z - rootsz;
let threshold = 30000000 - unused;
fmt::printfln("unused space = {} (missing {})", unused, threshold)!;
let candidates: []*dir = [];
defer free(candidates);
find_delcandidates(root, threshold, &candidates);
fmt::printfln("found {} deletion candidates", len(candidates))!;
let min = candidates[0];
for (let i = 1z; i < len(candidates); i += 1) {
let d = candidates[i];
if (d.sz < min.sz)
min = d;
fmt::printfln("'{}' ({})", d.name, d.sz)!;
};
fmt::printfln("let's delete {} ({})", min.name, min.sz)!;
};