aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--cmd.ha39
-rw-r--r--hatask.ha8
-rw-r--r--map/map.ha117
3 files changed, 163 insertions, 1 deletions
diff --git a/cmd.ha b/cmd.ha
index 88579a6..717db2f 100644
--- a/cmd.ha
+++ b/cmd.ha
@@ -8,6 +8,7 @@ use os::exec;
use strconv;
use ascii;
use format::tsv;
+use map;
type error = !(!str | io::error | path::error | exec::error | strconv::error);
@@ -47,6 +48,10 @@ const commands: [_]command = [
names = ["t", "tsv"],
func = &tsv,
},
+ command {
+ names = ["g", "get"],
+ func = &getmisc,
+ },
];
fn execcommand(cfg: config, name: str, tasks: []task, args: arguments) (void | error) = {
@@ -223,6 +228,40 @@ fn tsv(cfg: config, tasks: []task, a: arguments) (void | task | error) = {
};
};
+fn getmisc(cfg: config, tasks: []task, a: arguments) (void | task | error) = {
+ const args = a.args;
+ if (len(args) == 0) {
+ for (let task .. tasks) {
+ const it = map::newiterator(task.misc);
+ for (let misc => map::next(&it)) {
+ fmt::printfln("{}: {}", misc.key, misc.val)!;
+ };
+ };
+ } else if (len(args) == 1) {
+ const id = strconv::stoz(a.args[0])?;
+ if (id >= len(tasks)) {
+ return "No such task";
+ };
+ const task = tasks[id];
+ const it = map::newiterator(task.misc);
+ for (let misc => map::next(&it)) {
+ fmt::printfln("{}: {}", misc.key, misc.val)!;
+ };
+ } else {
+ const id = strconv::stoz(args[0])?;
+ if (id >= len(tasks)) {
+ return "No such task";
+ };
+ const task = tasks[id];
+ match (map::get(task.misc, args[1])) {
+ case void =>
+ return "No such misc property";
+ case let val: str =>
+ fmt::printfln("{}: {}", args[1], val)!;
+ };
+ };
+};
+
fn listall(cfg: config, tasks: []task) void = {
const headpad = PADDING - len("name") + len("priority");
const namepad = 10 - len("id") + len("name");
diff --git a/hatask.ha b/hatask.ha
index 747e2ef..35f3f9b 100644
--- a/hatask.ha
+++ b/hatask.ha
@@ -15,6 +15,7 @@ use sort;
use fnmatch;
use errors;
use time;
+use map;
type task = struct {
name: str,
@@ -22,6 +23,7 @@ type task = struct {
context: (str | void),
tags: []str,
priority: uint,
+ misc: map::map,
content: str,
lmd: time::instant,
};
@@ -84,6 +86,7 @@ fn readtask(taskpath: str) (task | rtaskerror) = {
context = void,
tags = [],
lmd = stat.mtime,
+ misc = map::newmap(),
...
};
for (let entry: ini::entry => ini::next(&isc)?) {
@@ -95,7 +98,7 @@ fn readtask(taskpath: str) (task | rtaskerror) = {
case "tags" =>
t.tags = strings::dupall(strings::split(entry.2, ","));
case =>
- void;
+ map::put(&t.misc, entry.1, entry.2);
};
};
if (t.name == "") {
@@ -114,6 +117,7 @@ fn freetask(t: *task) void = {
free(t.context as str);
};
strings::freeall(t.tags);
+ map::finishmap(&t.misc);
};
fn finishall(tasks: []task) void = {
@@ -201,6 +205,8 @@ export fn main() void = {
("d", ["delete a task", "id"]: []getopt::help),
("tsv", ["print tsv of tasks"]: []getopt::help),
("t", ["print tsv of tasks"]: []getopt::help),
+ ("get", ["get a misc property of a task", "[id]", "[key]"]: []getopt::help),
+ ("g", ["get a misc property of a task", "[id]", "[key]"]: []getopt::help),
);
defer getopt::finish(&cmd);
diff --git a/map/map.ha b/map/map.ha
new file mode 100644
index 0000000..e266032
--- /dev/null
+++ b/map/map.ha
@@ -0,0 +1,117 @@
+use hash::fnv;
+use strings;
+
+def BUCKETSZ: size = 32z;
+
+export type map = struct {
+ buckets: [BUCKETSZ][]entry,
+};
+
+export type entry = struct {
+ hash: size,
+ key: str,
+ val: str,
+};
+
+export type iterator = struct {
+ i: size,
+ j: size,
+ m: map,
+};
+
+export fn newmap() map = {
+ return map {
+ ...
+ };
+};
+
+export fn newiterator(m: map) iterator = {
+ return iterator {
+ i = 0z,
+ j = 0z,
+ m = m,
+ };
+};
+
+export fn put(m: *map, k: str, v: str) void = {
+ const s = fnv::string(k);
+ const bucket = &m.buckets[s%len(m.buckets)];
+ for (let e &.. bucket) {
+ if (e.hash == s) {
+ e.val = v;
+ return;
+ };
+ };
+ append(bucket, entry {
+ hash = s,
+ key = strings::dup(k),
+ val = strings::dup(v),
+ })!;
+};
+
+export fn get(m: map, k: str) (str | void) = {
+ const s = fnv::string(k);
+ const bucket = m.buckets[s%len(m.buckets)];
+ for (let e .. bucket) {
+ if (e.hash == s) {
+ return e.val;
+ };
+ };
+ return;
+};
+
+export fn next(it: *iterator) (entry | done) = {
+ if (len(it.m.buckets) == 0 || len(it.m.buckets) <= it.i) {
+ return done;
+ };
+ for (true) {
+ if (len(it.m.buckets) <= it.i) {
+ return done;
+ };
+ const b = it.m.buckets[it.i];
+ if (len(b) == 0 || len(b) <= it.j) {
+ it.i += 1;
+ it.j = 0;
+ } else {
+ const e = b[it.j];
+ it.j += 1;
+ return e;
+ };
+ };
+};
+
+export fn finishmap(m: *map) void = {
+ for (let e &.. m.buckets) {
+ finishentries(e);
+ };
+};
+
+fn finishentries(e: *[]entry) void = {
+ for (let i = 0z; i < len(e); i += 1) {
+ free(e[i].key);
+ free(e[i].val);
+ };
+};
+
+@test fn testmap() void = {
+ const m = newmap();
+ defer finishmap(&m);
+ put(&m, "test", "value");
+ const v = get(m, "test");
+ assert(v is str);
+ assert(v as str == "value");
+};
+
+@test fn testit() void = {
+ const m = newmap();
+ defer finishmap(&m);
+ put(&m, "test", "value");
+ let it = newiterator(m);
+ let count = 0;
+ for (let e => next(&it)) {
+ count += 1;
+ assert(e.key == "test");
+ assert(e.val == "value");
+ };
+ assert(count == 1);
+};