diff options
| author | Julian Hurst <julian.hurst@digdash.com> | 2025-01-15 18:08:44 +0100 |
|---|---|---|
| committer | Julian Hurst <julian.hurst@digdash.com> | 2025-01-15 18:31:21 +0100 |
| commit | 0490236116c4fcf80563251e4175dbcc78e05787 (patch) | |
| tree | 4c702a8c0de8840984c3dd969da4827e3135476b | |
| parent | 4129e3dba2cc3953af6b97fc1c99a1516b9214cd (diff) | |
| download | hatask-0490236116c4fcf80563251e4175dbcc78e05787.tar.gz | |
Add get command to get misc properties from tasks
Unknown entries in the task metadata are now added to a misc map and can be
gotten via the get command.
| -rw-r--r-- | cmd.ha | 39 | ||||
| -rw-r--r-- | hatask.ha | 8 | ||||
| -rw-r--r-- | map/map.ha | 117 |
3 files changed, 163 insertions, 1 deletions
@@ -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"); @@ -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); +}; |
