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 savewrites it to the file,db backupwrites a copy,db restoreloads a file. Onquityou are asked to save if there are unsaved changes (ysave,ndiscard,ccancel the exit); any other answer asks again, so a mistyped key never loses data. - The stored status is never shown directly: a
waittask displays asWait HH:MMuntil that time passes, then asLimit 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:Busyif any is Testing, Limit reset or Wait; elseTakenif any is Prompt; elseFree(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 (seelevelOfinDisplay.cppandStatusUtil::isBusy/isTakento change the rules). - Waits belong to the account:
acc wait <name> <time>(ortsk restat <task> wait <time>, which sets its account's wait). Until the time the account's Prompt and Testing tasks showWait HH:MM; after it they showLimit reset, until you runacc unwait <name>. Uploading, Frozen and Done tasks are not affected, and the tasks keep their own stored status underneath. A wait that no task shows (no tasks, or only Done / Frozen / Uploading ones) is listed in the account row as(wait)or(limit reset)and keeps the account Busy. Tasks without an account keep their own wait. - A task you set explicitly to
testingorprompt(tsk restat <task> testing,tsk add <task> <acc> testing) shows that status, even while its account is waiting: that task ignores the wait, and the account's other tasks keep following it.tsk restat <task> wait(no time) makes it follow the wait again, and a newacc waitoracc unwaitapplies to every task again. Adding a task without a status leaves it following the account. - Auto unwait: once an account's limit has reset (its tasks show
Limit reset), setting a task on it explicitly totestingorpromptclears that finished wait, because the account is in use again, and its tasks go back to their own status. A wait that is still running is never cleared automatically (useacc unwait). - A time is only optional while the account is already waiting: then
tsk restat <task> wait(ortsk add <task> <acc> wait) just makes the task follow that wait. Otherwise (no account, or its wait is over) a time is required. 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 withtsk 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_account_set_wait(db, "Acrux", "4:30pm"); /* the account hit its limit */
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 Wait 16:30" */
}
lls_task_list_free(tasks);
lls_save(db); /* nothing is saved automatically */
lls_close(db);
- Every call that can fail returns
LLS_OKor an error code (LLS_E_INVALID,LLS_E_NOT_FOUND,LLS_E_EXISTS,LLS_E_DATABASE) with the message inlls_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)... Waits belong to the account; a task set to"testing"/"prompt"while it waits ignores the wait (overrides_waitis 1) and"wait"makes it follow the account again. lls_tasks/lls_accountsreturn 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 untillls_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_dbmust 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.set_account_wait("Acrux", "4:30pm")
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)