use fmt; use strings; use getopt; use os; use io; use path; use os::exec; use strconv; use ascii; use format::tsv; type error = !(!str | io::error | path::error | exec::error | strconv::error); type func = fn(cfg: config, tasks: []task, args: arguments) (void | task | error); type command = struct { names: []str, func: *func, }; type arguments = *getopt::command; const PADDING: size = 30z; const commands: [_]command = [ command { names = ["f", "filter"], func = &filter, }, command { names = ["s", "show"], func = &show, }, command { names = ["a", "add"], func = &add, }, command { names = ["w", "write"], func = &write, }, command { names = ["d", "done"], func = &do, }, command { names = ["t", "tsv"], func = &tsv, }, ]; fn execcommand(cfg: config, name: str, tasks: []task, args: arguments) (void | error) = { for (const c .. commands) { for (const n .. c.names) { if (n == name) { c.func(cfg, tasks, args)?; }; }; }; }; fn add(cfg: config, tasks: []task, a: arguments) (void | task | error) = { const args = a.args; if (len(args) != 1z) { getopt::printhelp(os::stderr, "write", a.help)?; return; }; let buf = path::init(cfg.tasksdir, args[0])?; const c = exec::cmd("vim", path::string(&buf))?; exec::exec(&c); }; fn write(cfg: config, tasks: []task, a: arguments) (void | task | error) = { const args = a.args; if (len(args) != 1z) { getopt::printhelp(os::stderr, "write", a.help)?; return; }; const id = strconv::stoz(args[0])?; const t = if (len(tasks) > id) { yield tasks[id]; } else { return "No such task"; }; const c = exec::cmd("vim", t.path)?; exec::exec(&c); }; fn show(cfg: config, tasks: []task, a: arguments) (void | task | error) = { const args = a.args; const id = strconv::stoz(args[0])?; const t = if (len(tasks) > id) { yield tasks[id]; } else { return "No such task"; }; fmt::println(t.content)!; }; fn printtasktsv(cfg: config, t: task, id: size) (void | error) = { const sid = strings::dup(strconv::ztos(id)); defer free(sid); const spriority = strconv::ztos(t.priority); const buf = path::init(t.path)?; const stags = strings::join(",", t.tags...); defer free(stags); const p = path::trimprefix(&buf, cfg.tasksdir)?; tsv::writerecord(os::stdout, [sid, t.name, spriority, stags, p])!; }; fn printtask(cfg: config, t: task, id: size) (void | error) = { let name = strings::dup(t.name); defer free(name); if (len(t.name) > PADDING - 4) { name = strings::concat(strings::sub(t.name, 0z, PADDING - 4), "..."); }; const pad = PADDING - len(name) + len(strconv::utos(t.priority)); const namepad = 10 - len(strconv::ztos(id)) + len(name); const buf = path::init(t.path)?; const stags = strings::join(",", t.tags...); defer free(stags); const tagspad = 15 - len(strconv::utos(t.priority)) + len(stags); const p = path::trimprefix(&buf, cfg.tasksdir)?; const pathpad = 20 - len(stags) + len(p); fmt::printfln("{}{%}{%}{%}{%}", id, name, &fmt::mods { pad = ' ', width = namepad, ... }, t.priority, &fmt::mods { pad = ' ', width = pad, ... }, stags, &fmt::mods { pad = ' ', width = tagspad, ... }, p, &fmt::mods { pad = ' ', width = pathpad, ... }, )!; }; fn do(cfg: config, tasks: []task, a: arguments) (void | task | error) = { if (len(a.args) != 1z) { getopt::printhelp(os::stderr, "done", a.help)?; os::exit(os::status::FAILURE); }; const id = strconv::stoz(a.args[0])?; if (id >= len(tasks)) { return "No such task"; }; const t = tasks[id]; os::remove(t.path)?; fmt::printfln("Task {}: \"{}\" done (deleted)", id, t.name)!; }; fn filter(cfg: config, tasks: []task, a: arguments) (void | task | error) = { const headpad = PADDING - len("name") + len("priority"); const namepad = 10 - len("id") + len("name"); const tagspad = 15 - len("priority") + len("tags"); const pathpad = 20 - len("priority") + len("path"); fmt::printfln("id{%}{%}{%}{%}", "name", &fmt::mods { pad = ' ', width = namepad, ... },"priority", &fmt::mods { pad = ' ', width = headpad, ... },"tags", &fmt::mods { pad = ' ', width = tagspad, ... },"path", &fmt::mods { pad = ' ', width = pathpad, ... }, )!; const args = a.args; for (let i = 0z; i < len(tasks); i += 1) { const t = tasks[i]; if (len(args) == 0z) { printtask(cfg, t, i)?; }; for (const s .. args) { const lname = ascii::strlower(t.name); defer free(lname); const ls = ascii::strlower(s); defer free(ls); if (strings::contains(lname, ls)) { printtask(cfg, t, i)?; }; }; }; }; fn tsv(cfg: config, tasks: []task, a: arguments) (void | task | error) = { tsv::writerecord(os::stdout, ["id" ,"name", "priority", "tags", "path"])!; for (let i = 0z; i < len(tasks); i += 1) { const t = tasks[i]; printtasktsv(cfg, t, i)?; }; }; fn listall(cfg: config, tasks: []task) void = { const headpad = PADDING - len("name") + len("priority"); const namepad = 10 - len("id") + len("name"); const tagspad = 15 - len("priority") + len("tags"); const pathpad = 20 - len("tags") + len("path"); fmt::printfln("id{%}{%}{%}{%}", "name", &fmt::mods { pad = ' ', width = namepad, ... },"priority", &fmt::mods { pad = ' ', width = headpad, ... },"tags", &fmt::mods { pad = ' ', width = tagspad, ... },"path", &fmt::mods { pad = ' ', width = pathpad, ... }, )!; for (let i = 0z; i < len(tasks); i += 1) { printtask(cfg, tasks[i], i)!; }; }; fn strerror(e: error) str = { match (e) { case let e: !str => return e; case let e: io::error => return io::strerror(e); case let e: path::error => return path::strerror(e); case let e: exec::error => return exec::strerror(e); case let e: strconv::error => return strconv::strerror(e); }; };