summaryrefslogtreecommitdiff
path: root/resource.ha
diff options
context:
space:
mode:
Diffstat (limited to 'resource.ha')
-rw-r--r--resource.ha191
1 files changed, 191 insertions, 0 deletions
diff --git a/resource.ha b/resource.ha
new file mode 100644
index 0000000..6b393f2
--- /dev/null
+++ b/resource.ha
@@ -0,0 +1,191 @@
+use bufio;
+use dejson;
+use encoding::json;
+use errors;
+use fs;
+use io;
+use os;
+use path;
+use strings;
+use trace;
+
+def MISSINGNO = "minecraft:missingno";
+def BUILTIN_MISSING = "minecraft:builtin/missing";
+
+type Pack = struct {
+ fs: *fs::fs,
+ kind: str,
+};
+
+fn resource_splitpath(path: str) (str, str, str, str, str) = {
+ const (base, path) = strings::cut(path, "/");
+ const (ns, path) = strings::cut(path, "/");
+ const (kind, path) = strings::cut(path, "/");
+ const bnameindex = match (strings::rindex(path, '/')) {
+ case let index: size =>
+ yield index + 1;
+ case void =>
+ yield 0z;
+ };
+ const extindex = match (strings::rindex(
+ strings::sub(path, bnameindex, strings::end), '.')) {
+ case let index: size =>
+ yield bnameindex + index;
+ case void =>
+ yield len(path);
+ };
+ const name = strings::sub(path, 0, extindex);
+ const ext = strings::sub(path, extindex, strings::end);
+ return (base, ns, kind, name, ext);
+};
+
+fn resource_open(
+ pack: *Pack,
+ kind: str,
+ ident: str,
+ ext: str,
+ tr: *trace::tracer,
+) (io::handle | trace::failed) = {
+ const (ns, name) = ident_split(ident);
+ const fname = strings::concat(name, ext);
+ defer free(fname);
+ let path = path::init()!;
+ if (path::set(&path, pack.kind, ns, kind, fname) is path::too_long) {
+ return trace::error(tr, "Path too long");
+ };
+ const path = path::string(&path);
+
+ match (fs::open(pack.fs, path, fs::flag::RDONLY)) {
+ case let f: io::handle =>
+ return f;
+ case let err: fs::error =>
+ return trace::error(tr, "open: {}", fs::strerror(err));
+ };
+};
+
+fn resource_load_json(
+ pack: *Pack,
+ kind: str,
+ ident: str,
+ ext: str,
+ tr: *trace::tracer,
+) (json::value | trace::failed) = {
+ const f = resource_open(pack, kind, ident, ext, tr)?;
+
+ let rbuf: [os::BUFSZ]u8 = [0...];
+ let buf = bufio::init(f, rbuf, []);
+
+ const json = match (json::load(&buf)) {
+ case let json: json::value =>
+ yield json;
+ case let err: json::error =>
+ io::close(f): void;
+ return trace::error(tr, "Invalid JSON: {}",
+ json::strerror(err));
+ };
+
+ match (io::close(f)) {
+ case void => void;
+ case let err: io::error =>
+ json::finish(json);
+ return trace::error(tr, "close: {}", io::strerror(err));
+ };
+
+ return json;
+};
+
+fn resource_search(
+ pack: *Pack,
+ kind: str,
+ exts: str...
+) [](str, str) = {
+ const tr = trace::ctx(&trace::root, "resource search");
+
+ let out: [](str, str) = [];
+ let path = path::init()!;
+
+ const it = match (fs::iter(pack.fs, pack.kind)) {
+ case let x: *fs::iterator =>
+ yield x;
+ case let err: fs::error =>
+ trace::error(&tr, "{}: fs::iter: {}",
+ pack.kind, fs::strerror(err)): void;
+ return out;
+ };
+ defer fs::finish(it);
+ for (true) match (fs::next(it)) {
+ case let ent: fs::dirent =>
+ if (!fs::isdir(ent.ftype)) continue;
+ if (strings::hasprefix(ent.name, ".")) continue;
+ if (path::set(&path, pack.kind, ent.name, kind)
+ is path::too_long) {
+
+ trace::error(&tr, "{}/{}/{}: Path too long",
+ pack.kind, ent.name, kind): void;
+ continue;
+ };
+ resource_search_ns(pack, &out, path::string(&path), &tr, exts...);
+ case done => break;
+ };
+
+ return out;
+};
+
+fn resource_search_ns(
+ pack: *Pack,
+ out: *[](str, str),
+ parent: str,
+ tr: *trace::tracer,
+ exts: str...
+) void = {
+ let path = path::init()!;
+
+ const it = match (fs::iter(pack.fs, parent)) {
+ case let x: *fs::iterator =>
+ yield x;
+ case errors::noentry =>
+ return;
+ case let err: fs::error =>
+ trace::error(tr, "{}: fs::iter: {}",
+ parent, fs::strerror(err)): void;
+ return;
+ };
+ defer fs::finish(it);
+ for (true) match (fs::next(it)) {
+ case let ent: fs::dirent =>
+ if (strings::hasprefix(ent.name, ".")) continue;
+ if (path::set(&path, parent, ent.name) is path::too_long) {
+ trace::error(tr, "{}/{}: Path too long",
+ parent, ent.name): void;
+ continue;
+ };
+ const path = path::string(&path);
+ if (fs::isdir(ent.ftype)) {
+ resource_search_ns(pack, out, path, tr, exts...);
+ } else if (fs::isfile(ent.ftype)) {
+ const (_, ns, _, name, ext) =
+ resource_splitpath(path);
+ for (let i = 0z; i < len(exts); i += 1) {
+ if (ext == exts[i]) {
+ const ident = ident_make(ns, name);
+ // yes, this borrow from input is
+ // intentional. needs to be documented
+ // later.
+ append(out, (ident, exts[i]));
+ break;
+ };
+ };
+ };
+ case done => break;
+ };
+};
+
+fn wassert_fields(de: *dejson::deser, names: str...) (void | dejson::error) = {
+ dejson::object(de)?;
+ match (dejson::assert_fields(de, names...)) {
+ case let err: dejson::error =>
+ defer free(err);
+ trace::warn(&trace::root, "{}", err);
+ case void => void;
+ };
+};