Task sorting
- Time: Wait tasks sort by when the wait ends, earliest first. Limit reset tasks sort by when their wait ended, so the one that has been ready longest comes first. The time is kept when a wait expires, which is what makes the second sort possible. - Order: I put time before account for these two statuses, so the full order is status, then time, then account, then name. Other statuses still go status, account, name. If you'd rather have account before time, swap the two checks in taskLess in Display.cpp. - Manual limit reset: a task you set to limit-reset by hand has no wait time, so it sorts first among limit resets. Account status: an account takes the most taken level among its tasks. - Busy: it has a Testing, Limit reset or Wait task. The TASKS column lists those tasks. - Taken: it has a Prompt task and nothing Busy. The TASKS column lists the Prompt tasks. - Free: it only has Done, Frozen or Uploading tasks, or no tasks. The TASKS column shows Changes to be committed: modified: README.md modified: include/Display.h modified: include/Status.h modified: src/Completer.cpp modified: src/Display.cpp modified: src/Repl.cpp modified: src/Status.cpp
This commit is contained in:
1 parent
04fa2b1b20
commit
6cb5a8611f
7 files changed
+107
-36
No files matched your search
@@ -29,7 +29,7 @@ installed; otherwise CMake downloads the amalgamation (needs network once).
|
||||
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|free|all)
|
||||
disp acc level (hide|show) (busy|taken|free|all)
|
||||
disp (show current settings)
|
||||
|
||||
db save | db backup [file] | db restore <file>
|
||||
@@ -73,11 +73,14 @@ goes back to `$VISUAL` / `$EDITOR` / nano / vim / vi. The choice is stored in
|
||||
* The stored status is never shown directly: a `wait` task displays as
|
||||
`Wait HH:MM` until that time passes, then as `Limit reset`. Tasks are sorted
|
||||
by computed status (Limit reset, Testing, Prompt, Wait, Uploading, Frozen,
|
||||
Done), then account A-Z (unassigned last), then wait time, then name. The panels are cleared and redrawn after changes.
|
||||
* Account panel: one row per account, sorted from most free to least free.
|
||||
`Busy` while any of its tasks is Testing or Wait (those tasks are listed),
|
||||
otherwise `Free`; fewer busy tasks sorts first, ties by name
|
||||
(see `Display::renderAccounts` / `StatusUtil::isBusy` to change the rules).
|
||||
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: 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. Rows are sorted most free first: Free, Taken, Busy,
|
||||
fewer tasks first, ties by name (see `levelOf` in `Display.cpp` and
|
||||
`StatusUtil::isBusy` / `isTaken` to change the rules).
|
||||
* 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>`.
|
||||
|
||||
+7
-1
@@ -7,7 +7,13 @@
|
||||
#include "Database.h"
|
||||
#include "Status.h"
|
||||
|
||||
enum class AccountLevel { Busy, Free };
|
||||
// How taken an account is, judged by its tasks:
|
||||
// Free - only Done, Frozen or Uploading tasks, or no tasks
|
||||
// Taken - has a Prompt task (and nothing Busy)
|
||||
// Busy - has a Testing, Limit reset or Wait task
|
||||
enum class AccountLevel { Busy, Taken, Free };
|
||||
|
||||
std::string accountLevelName(AccountLevel level); // "busy", "taken", "free"
|
||||
|
||||
struct DisplaySettings {
|
||||
bool showTasks = true; // auto-print the task table after each change
|
||||
|
||||
+3
-2
@@ -11,7 +11,7 @@ enum class StatusKind { Prompt, LimitReset, Testing, Wait, Uploading, Frozen, Do
|
||||
|
||||
struct Status {
|
||||
StatusKind kind = StatusKind::Prompt;
|
||||
std::time_t waitUntil = 0; // only meaningful when kind == Wait
|
||||
std::time_t waitUntil = 0; // when the wait ends; set for Wait, and kept on the computed LimitReset
|
||||
};
|
||||
|
||||
namespace StatusUtil {
|
||||
@@ -36,6 +36,7 @@ std::vector<StatusKind> allKinds();
|
||||
// old name for "prompt".
|
||||
std::optional<StatusKind> kindFromName(const std::string& name);
|
||||
int rank(StatusKind kind); // task sort priority, lowest first
|
||||
bool isBusy(StatusKind kind); // does this status hold the account?
|
||||
bool isBusy(StatusKind kind); // keeps its account Busy: Testing, Limit reset, Wait
|
||||
bool isTaken(StatusKind kind); // keeps its account Taken: Prompt
|
||||
|
||||
} // namespace StatusUtil
|
||||
+1
-1
@@ -101,7 +101,7 @@ std::vector<std::string> Completer::valuesFor(ArgKind kind, const std::string& c
|
||||
values.push_back("all");
|
||||
break;
|
||||
case ArgKind::AccountLevel:
|
||||
values = {"busy", "free", "all"};
|
||||
values = {"busy", "taken", "free", "all"};
|
||||
break;
|
||||
case ArgKind::File:
|
||||
values = fileNames(current);
|
||||
|
||||
+73
-20
@@ -1,6 +1,7 @@
|
||||
#include "Display.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <optional>
|
||||
#include <vector>
|
||||
|
||||
@@ -30,14 +31,19 @@ std::vector<TaskView> loadViews(const Database& db, std::time_t now) {
|
||||
return views;
|
||||
}
|
||||
|
||||
// Task order: status priority (see StatusUtil::rank), then account A-Z
|
||||
// (tasks without an account last), then earliest wait time, then task name.
|
||||
// Task order: status priority (see StatusUtil::rank); then, for Wait and Limit
|
||||
// reset, time (earliest first: when the wait ends / ended); then account A-Z
|
||||
// (tasks without an account last); then task name.
|
||||
bool taskLess(const TaskView& a, const TaskView& b) {
|
||||
const int rankA = StatusUtil::rank(a.status.kind);
|
||||
const int rankB = StatusUtil::rank(b.status.kind);
|
||||
if (rankA != rankB) {
|
||||
return rankA < rankB;
|
||||
}
|
||||
const StatusKind kind = a.status.kind; // same kind as b: ranks are unique per kind
|
||||
if ((kind == StatusKind::Wait || kind == StatusKind::LimitReset) && a.status.waitUntil != b.status.waitUntil) {
|
||||
return a.status.waitUntil < b.status.waitUntil;
|
||||
}
|
||||
if (a.account.empty() != b.account.empty()) {
|
||||
return !a.account.empty();
|
||||
}
|
||||
@@ -46,12 +52,29 @@ bool taskLess(const TaskView& a, const TaskView& b) {
|
||||
if (accountA != accountB) {
|
||||
return accountA < accountB;
|
||||
}
|
||||
if (a.status.kind == StatusKind::Wait && a.status.waitUntil != b.status.waitUntil) {
|
||||
return a.status.waitUntil < b.status.waitUntil;
|
||||
}
|
||||
return StringUtil::lower(a.name) < StringUtil::lower(b.name);
|
||||
}
|
||||
|
||||
AccountLevel levelOf(StatusKind kind) {
|
||||
if (StatusUtil::isBusy(kind)) {
|
||||
return AccountLevel::Busy;
|
||||
}
|
||||
if (StatusUtil::isTaken(kind)) {
|
||||
return AccountLevel::Taken;
|
||||
}
|
||||
return AccountLevel::Free;
|
||||
}
|
||||
|
||||
// Free first, then Taken, then Busy.
|
||||
int levelRank(AccountLevel level) {
|
||||
switch (level) {
|
||||
case AccountLevel::Free: return 0;
|
||||
case AccountLevel::Taken: return 1;
|
||||
case AccountLevel::Busy: return 2;
|
||||
}
|
||||
return 2;
|
||||
}
|
||||
|
||||
// "-" when there are no notes, otherwise how many lines they have.
|
||||
std::string notesSummary(const std::string& notes) {
|
||||
const std::size_t lines = StringUtil::lineCount(notes);
|
||||
@@ -90,44 +113,74 @@ std::string Display::renderTasks(std::time_t now) const {
|
||||
return table.render();
|
||||
}
|
||||
|
||||
std::string accountLevelName(AccountLevel level) {
|
||||
switch (level) {
|
||||
case AccountLevel::Busy: return "busy";
|
||||
case AccountLevel::Taken: return "taken";
|
||||
case AccountLevel::Free: return "free";
|
||||
}
|
||||
return "free";
|
||||
}
|
||||
|
||||
std::string Display::renderAccounts(std::time_t now) const {
|
||||
std::vector<TaskView> views = loadViews(db_, now);
|
||||
std::sort(views.begin(), views.end(), taskLess);
|
||||
|
||||
// One row per account. It is Busy while any of its tasks is Testing or
|
||||
// Wait (those tasks are listed); otherwise it is Free.
|
||||
// One row per account. Its level is the most taken of its tasks
|
||||
// (Busy > Taken > Free); the TASKS column lists the tasks at that level.
|
||||
struct AccountView {
|
||||
std::string name;
|
||||
std::vector<std::string> busyTasks;
|
||||
AccountLevel level;
|
||||
std::vector<std::string> tasks;
|
||||
};
|
||||
|
||||
std::vector<AccountView> accounts;
|
||||
for (const std::string& account : db_.listAccounts()) {
|
||||
std::vector<std::string> busy;
|
||||
std::vector<std::string> taken;
|
||||
for (const TaskView& view : views) {
|
||||
if (view.account != account) {
|
||||
continue;
|
||||
}
|
||||
const AccountLevel level = levelOf(view.status.kind);
|
||||
if (level == AccountLevel::Busy) {
|
||||
busy.push_back(view.name);
|
||||
} else if (level == AccountLevel::Taken) {
|
||||
taken.push_back(view.name);
|
||||
}
|
||||
}
|
||||
|
||||
AccountView entry;
|
||||
entry.name = account;
|
||||
for (const TaskView& view : views) {
|
||||
if (view.account == account && StatusUtil::isBusy(view.status.kind)) {
|
||||
entry.busyTasks.push_back(view.name);
|
||||
}
|
||||
if (!busy.empty()) {
|
||||
entry.level = AccountLevel::Busy;
|
||||
entry.tasks = busy;
|
||||
} else if (!taken.empty()) {
|
||||
entry.level = AccountLevel::Taken;
|
||||
entry.tasks = taken;
|
||||
} else {
|
||||
entry.level = AccountLevel::Free;
|
||||
}
|
||||
accounts.push_back(entry);
|
||||
}
|
||||
|
||||
// Most free first: fewer busy tasks first, then by name (listAccounts is
|
||||
// already alphabetical and stable_sort keeps that order for ties).
|
||||
// Most free first: Free, then Taken, then Busy; within a level fewer tasks
|
||||
// first. listAccounts is alphabetical and stable_sort keeps that for ties.
|
||||
std::stable_sort(accounts.begin(), accounts.end(), [](const AccountView& a, const AccountView& b) {
|
||||
return a.busyTasks.size() < b.busyTasks.size();
|
||||
if (a.level != b.level) {
|
||||
return levelRank(a.level) < levelRank(b.level);
|
||||
}
|
||||
return a.tasks.size() < b.tasks.size();
|
||||
});
|
||||
|
||||
TextTable table({"ACCOUNT", "STATUS", "TASKS"});
|
||||
for (const AccountView& entry : accounts) {
|
||||
const AccountLevel level = entry.busyTasks.empty() ? AccountLevel::Free : AccountLevel::Busy;
|
||||
if (settings_.hiddenAccountLevels.count(level) > 0) {
|
||||
if (settings_.hiddenAccountLevels.count(entry.level) > 0) {
|
||||
continue;
|
||||
}
|
||||
table.addRow({entry.name,
|
||||
level == AccountLevel::Busy ? "Busy" : "Free",
|
||||
entry.busyTasks.empty() ? "-" : StringUtil::join(entry.busyTasks, 0, ", ")});
|
||||
std::string status = accountLevelName(entry.level);
|
||||
status[0] = static_cast<char>(std::toupper(static_cast<unsigned char>(status[0])));
|
||||
table.addRow({entry.name, status, entry.tasks.empty() ? "-" : StringUtil::join(entry.tasks, 0, ", ")});
|
||||
}
|
||||
return table.render();
|
||||
}
|
||||
+6
-4
@@ -85,7 +85,7 @@ void Repl::registerCommands() {
|
||||
{ArgKind::Visibility, ArgKind::TaskLevel}, &Repl::cmdDispTskLevel);
|
||||
add("disp acc", "disp acc (hide|show)", "auto-print the account table after changes", 1, 1,
|
||||
{ArgKind::Visibility}, &Repl::cmdDispAcc);
|
||||
add("disp acc level", "disp acc level (hide|show) (busy|free|all)",
|
||||
add("disp acc level", "disp acc level (hide|show) (busy|taken|free|all)",
|
||||
"hide or show account rows by status", 2, 2,
|
||||
{ArgKind::Visibility, ArgKind::AccountLevel}, &Repl::cmdDispAccLevel);
|
||||
|
||||
@@ -421,7 +421,7 @@ bool Repl::cmdDisp(const Args&) {
|
||||
std::cout << " none";
|
||||
}
|
||||
for (AccountLevel level : settings.hiddenAccountLevels) {
|
||||
std::cout << (level == AccountLevel::Busy ? " busy" : " free");
|
||||
std::cout << " " << accountLevelName(level);
|
||||
}
|
||||
std::cout << "\n";
|
||||
return false;
|
||||
@@ -471,13 +471,15 @@ bool Repl::cmdDispAccLevel(const Args& args) {
|
||||
std::set<AccountLevel>& hidden = display_.settings().hiddenAccountLevels;
|
||||
std::vector<AccountLevel> targets;
|
||||
if (level == "all") {
|
||||
targets = {AccountLevel::Busy, AccountLevel::Free};
|
||||
targets = {AccountLevel::Busy, AccountLevel::Taken, AccountLevel::Free};
|
||||
} else if (level == "busy") {
|
||||
targets = {AccountLevel::Busy};
|
||||
} else if (level == "taken") {
|
||||
targets = {AccountLevel::Taken};
|
||||
} else if (level == "free") {
|
||||
targets = {AccountLevel::Free};
|
||||
} else {
|
||||
throw std::runtime_error("Unknown level '" + args[1] + "'. Use: busy, free, all");
|
||||
throw std::runtime_error("Unknown level '" + args[1] + "'. Use: busy, taken, free, all");
|
||||
}
|
||||
|
||||
for (AccountLevel target : targets) {
|
||||
|
||||
+8
-2
@@ -153,7 +153,11 @@ int rank(StatusKind kind) {
|
||||
}
|
||||
|
||||
bool isBusy(StatusKind kind) {
|
||||
return kind == StatusKind::Testing || kind == StatusKind::Wait;
|
||||
return kind == StatusKind::Testing || kind == StatusKind::LimitReset || kind == StatusKind::Wait;
|
||||
}
|
||||
|
||||
bool isTaken(StatusKind kind) {
|
||||
return kind == StatusKind::Prompt;
|
||||
}
|
||||
|
||||
std::optional<Status> parse(const std::string& text, std::time_t now) {
|
||||
@@ -243,7 +247,9 @@ std::optional<Status> deserialize(const std::string& text) {
|
||||
|
||||
Status effective(const Status& status, std::time_t now) {
|
||||
if (status.kind == StatusKind::Wait && now >= status.waitUntil) {
|
||||
return make(StatusKind::LimitReset);
|
||||
Status expired = make(StatusKind::LimitReset);
|
||||
expired.waitUntil = status.waitUntil; // keep it: Limit reset tasks sort by when the wait ended
|
||||
return expired;
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user