190 lines
9.5 KiB
Markdown
190 lines
9.5 KiB
Markdown
# 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)
|