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
+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