Files
LLS/README.md
T
2026-10-29 01:02:06 -03:00

9.5 KiB

LLS

Interactive C++17 / SQLite task + account tracker. Type commands at the > prompt.

Build

Requires CMake >= 3.14 and a C++17 compiler. SQLite is taken from the system if installed; otherwise CMake downloads the amalgamation (needs network once).

# optional, system SQLite:
#   Debian/Ubuntu: sudo apt install libsqlite3-dev
#   Fedora:        sudo dnf install sqlite-devel
#   Arch:          sudo pacman -S sqlite

cmake -S . -B build
cmake --build build
./build/lls [database-file]      # default: tasks.db

This builds three things: the prompt (lls), the C API as a shared library (liblls_api.so / lls_api.dll, header include/lls_api.h) and a C example (lls_example).

Commands

acc add <name>              tsk add <name> [acc] [status]
acc del <name>              tsk del <name>
acc rename <name> <new>     tsk rename <name> <new>
acc wait <name> <time>      tsk reacc <name> <new|->
acc unwait <name>           tsk restat <name> <new>
acc ls                      tsk notes <name>      (opens your editor)
                            tsk ls

disp tsk (hide|show)
disp tsk level (hide|show) (prompt|limit-reset|testing|wait|uploading|frozen|done|all)
disp acc (hide|show)
disp acc level (hide|show) (busy|taken|free|all)
disp                        (show current settings)

db save | db backup [file] | db restore <file>
editor [command | default]  (show / set the editor used by tsk notes)
help | quit

Names containing spaces go in double quotes: tsk add "Breeze A" Acrux testing.

Statuses and times

prompt (free to accept prompts), limit-reset, testing, wait <time>, uploading, frozen, done. limit-reset is normally computed: a wait task shows as Limit reset once its time has passed (it can also be set by hand; limit reset with a space works too). Old databases that say available are converted to prompt on load.

Times may be 24-hour or 12-hour: wait 16:30, wait 4:30pm, wait 4 pm, wait 12:05am. Without am/pm the time is read as 24-hour (6:40 = 06:40). A time that has already passed today is stored as tomorrow.

Prompt keys

Up/Down history (saved between sessions), Left/Right/Home/End editing, Ctrl-Left/Right word jump, Tab completion (commands, task and account names, statuses, levels, editors, file paths; press Tab twice-ish to list options), Ctrl-A/E, Ctrl-U/K/W, Ctrl-L clear, Ctrl-C cancel line, Ctrl-D quit. Pressing Enter on an empty line redraws the panels (waits expire with time).

Editor

tsk notes <name> opens the notes in an external editor. Use editor nvim (or vim, vi, nano, code --wait, ...) to choose one; editor default goes back to $VISUAL / $EDITOR / nano / vim / vi. The choice is stored in ~/.config/task_tracker/config (history is in the same folder).

How it works

  • Data lives in an in-memory SQLite database. db save writes it to the file, db backup writes a copy, db restore loads a file. On quit you are asked to save if there are unsaved changes (y save, n discard, c cancel the exit); any other answer asks again, so a mistyped key never loses data.
  • The stored status is not always what is shown: a wait task displays as Wait HH:MM until its account's wait ends, then as Limit reset. Tasks are sorted by computed status (Limit reset, Testing, Prompt, Wait, Uploading, Frozen, Done); Wait and Limit reset tasks then sort by time (earliest first: when the wait ends / ended), then account A-Z (unassigned last), then name. The panels are cleared and redrawn after changes.
  • Account panel: the RESET column is when the account's wait ends (16:30, tomorrow 06:40, or a full date); - if it has none. One row per account, judged by its tasks: Busy if any is Testing, Limit reset or Wait; else Taken if any is Prompt; else Free (only Done, Frozen or Uploading tasks, or no tasks). The TASKS column lists the tasks at that level, so a Free account shows its Uploading, Frozen and Done tasks (- if it has none). Rows are sorted most free first: Free, Taken, Busy; within Taken and Busy fewer tasks first, ties by name (see levelOf in Display.cpp and StatusUtil::isBusy / isTaken to change the rules).
  • Waits: an account hits its limit until a time (acc wait <name> <time>). A task that is set to wait is waiting for that: tsk restat <task> wait <time> sets its account's wait to the time and marks the task Wait (several waiting tasks share the account's time), and tsk restat <task> wait with no time marks it Wait while the account is already waiting (otherwise a time is required). Tasks without an account keep their own wait time.
  • When the time passes the account stops waiting by itself, and every task that was Wait now shows Limit reset: the conversation can be continued. Those tasks stay Limit reset until you set them to something else. acc unwait <name> ends the wait early, with the same result.
  • Prompt and Testing tasks are not waiting: they show as they are, with (account-waiting) added while their account waits, e.g. Testing (account-waiting). The account row shows when the wait ends in the RESET column, and stays Busy while it waits; if none of its tasks is set to Wait (e.g. you deleted them) its TASKS column lists (wait).
  • Old databases with per-task waits are converted on load.
  • Deleting an account leaves its tasks unassigned (-).
  • Names are case-insensitive; the original spelling is kept.
  • Notes live in the database (SQLite TEXT, binary-safe, SQLite's default limit is ~1 GB). The task table shows - when a task has no notes, otherwise the number of lines (e.g. 3 lines); open them with tsk notes <name>.

C API and Python

include/lls_api.h is a plain C API over the same database and the same rules as the prompt (it is built on the same Tracker class), so a program written against it behaves exactly like typing the commands. Build it with the project (liblls_api.so), link against it from C/C++, or load it from Python.

#include "lls_api.h"

lls_db* db;
if (lls_open("tasks.db", &db) != LLS_OK) { /* lls_last_error(NULL) */ }

lls_account_add(db, "Acrux");
lls_task_add(db, "Breeze A", "Acrux", "testing");
lls_task_add(db, "Breeze B", "Acrux", "wait 4:30pm"); /* Acrux hit its limit; Breeze B waits for it */

lls_task_list* tasks;
lls_tasks(db, &tasks);                           /* display order */
for (int i = 0; i < lls_task_list_count(tasks); ++i) {
    const lls_task_info* t = lls_task_list_get(tasks, i);
    printf("%s %s\n", t->name, t->status_label);  /* "Breeze A Testing (account-waiting)" ... */
}
lls_task_list_free(tasks);

lls_save(db);                                    /* nothing is saved automatically */
lls_close(db);
  • Every call that can fail returns LLS_OK or an error code (LLS_E_INVALID, LLS_E_NOT_FOUND, LLS_E_EXISTS, LLS_E_DATABASE) with the message in lls_last_error(db). No C++ exception crosses the API.
  • After a change, lls_last_messages(db) holds what the prompt would have printed ("Account 'A' is back in use: ..."). Queries do not reset it.
  • Statuses are the text you would type: "prompt", "testing", "wait 4:30pm", "wait" (only while the account is waiting)... A task set to wait waits for its account; when the time passes the account stops waiting by itself and those tasks show Limit reset. Prompt / Testing tasks keep their status, with (account-waiting) in status_label (and account_waiting = 1) while the account waits.
  • lls_tasks / lls_accounts return lists in the order of the panels, with the computed status (status, status_label, until) and the stored one underneath (stored_status). Strings in a list are valid until lls_task_list_free.
  • lls_set_clock(db, unix_seconds) pretends it is another time, handy for tests and simulations; 0 goes back to the real clock.
  • A lls_db must not be shared between threads without your own locking.

python/lls.py is a ctypes binding (no compiler needed):

import lls                       # python/ is on sys.path; LLS_LIBRARY=build/liblls_api.so

with lls.Database("tasks.db") as db:
    db.add_account("Acrux")
    db.add_task("Breeze A", account="Acrux", status="testing")
    db.add_task("Breeze B", account="Acrux", status="wait 4:30pm")   # sets Acrux's wait
    for task in db.tasks():
        print(task.name, task.account, task.status_label, task.until)
    for account in db.accounts():
        print(account.name, account.level.name, account.tasks)
    print(db.messages)
    db.save()

Errors are exceptions (lls.NotFound, lls.AlreadyExists, lls.InvalidArgument, lls.DatabaseError, all lls.LLSError). The library is found through the LLS_LIBRARY environment variable, ../build next to the module, or the current directory's build. Run the tests with

LLS_LIBRARY=build/liblls_api.so python3 python/test_lls.py -v

Layout

include/    headers (declarations only, no inline definitions); lls_api.h is the public C header
src/        implementations
examples/   example.c - the C API from C
python/     lls.py (ctypes binding) and test_lls.py

Core (static library, shared by the prompt and the C API):
  Database    SQLite storage            Tracker   all the rules; the only thing that changes data
  Status      statuses, times           Views     computed task / account views and their order
Prompt only:  Repl, LineEditor, Completer, Display, Table, Config, ExternalEditor...
C API:        capi.cpp (wraps Tracker + Views)