Public Access
Shell: only its own replies, Apps by their names, and rm as Unix has it (#67)
The Shell shows the replies to its own commands and nothing else. The console knows who each line is printed for (Console::As, Console::origin): a command run from the Shell prints as the Shell's, and what answers it later from another task carries that along (ls, tasks, du, cp, update check, sd list, screenshot, gemini get). Ctrl+b shows everything instead. This replaces the ten-second window, which was a guess. An App's name with a capital opens it (Notes, Irc, Wifi, Gnss, Gemini, Lora, Storage, Shell, System, Settings), from the Shell and from the consoles. rm needs -r for a folder, here and over the consoles. In the Shell a file, or a folder with something in it, is asked about unless -f; an empty folder with -r goes without a word. The Shell now hands its line to the main loop to run: run from inside the key handler, rm on a folder overflowed the loop's stack and crashed the device. `info` says which App is in front. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EhqxQ49eCju4CzKYNjZzwT
This commit is contained in:
@@ -260,6 +260,19 @@ void test_the_help_panel_scrolls_within_its_rows() {
|
||||
TEST_ASSERT_EQUAL(0, m.rows().size());
|
||||
}
|
||||
|
||||
// Issue #67: an App is opened by its name with a capital letter.
|
||||
void test_an_app_command_is_its_id_with_a_capital() {
|
||||
TEST_ASSERT_EQUAL_STRING("Notes", AppManager::commandFor("notes").c_str());
|
||||
TEST_ASSERT_EQUAL_STRING("Wifi", AppManager::commandFor("wifi-tools").c_str());
|
||||
TEST_ASSERT_EQUAL_STRING("Irc", AppManager::commandFor("irc").c_str());
|
||||
Fixture f;
|
||||
TEST_ASSERT_NOT_NULL(f.manager.byCommand("Notes"));
|
||||
TEST_ASSERT_EQUAL_STRING("notes", f.manager.byCommand("Notes")->id);
|
||||
TEST_ASSERT_NULL(f.manager.byCommand("notes")); // small letters are commands, not Apps
|
||||
TEST_ASSERT_NULL(f.manager.byCommand("Demo")); // hidden: not in the Launcher, not here
|
||||
TEST_ASSERT_NULL(f.manager.byCommand("Nope"));
|
||||
}
|
||||
|
||||
int main() {
|
||||
UNITY_BEGIN();
|
||||
RUN_TEST(test_launcher_is_in_foreground_after_begin);
|
||||
@@ -283,5 +296,6 @@ int main() {
|
||||
RUN_TEST(test_open_help_takes_every_key_and_any_but_the_arrows_closes_it);
|
||||
RUN_TEST(test_help_works_in_a_modal_app_and_closes_when_the_app_changes);
|
||||
RUN_TEST(test_the_help_panel_scrolls_within_its_rows);
|
||||
RUN_TEST(test_an_app_command_is_its_id_with_a_capital);
|
||||
return UNITY_END();
|
||||
}
|
||||
|
||||
@@ -104,6 +104,30 @@ void test_looks_like_text() {
|
||||
TEST_ASSERT_TRUE(looksLikeText(nullptr, 0)); // an empty file reads as text
|
||||
}
|
||||
|
||||
void test_rm_takes_its_switches_like_unix() {
|
||||
auto a = parseRm("/notes/a.txt");
|
||||
TEST_ASSERT_FALSE(a.recursive);
|
||||
TEST_ASSERT_FALSE(a.force);
|
||||
TEST_ASSERT_EQUAL_STRING("/notes/a.txt", a.path.c_str());
|
||||
a = parseRm("-r /gemini/saved");
|
||||
TEST_ASSERT_TRUE(a.recursive);
|
||||
TEST_ASSERT_FALSE(a.force);
|
||||
TEST_ASSERT_EQUAL_STRING("/gemini/saved", a.path.c_str());
|
||||
for (const char* both : {"-rf /x", "-fr /x", "-r -f /x", "-f -r /x", "-R -f /x"}) {
|
||||
a = parseRm(both);
|
||||
TEST_ASSERT_TRUE(a.recursive);
|
||||
TEST_ASSERT_TRUE(a.force);
|
||||
TEST_ASSERT_EQUAL_STRING("/x", a.path.c_str());
|
||||
}
|
||||
a = parseRm("-f /a file with spaces.txt ");
|
||||
TEST_ASSERT_TRUE(a.force);
|
||||
TEST_ASSERT_EQUAL_STRING("/a file with spaces.txt", a.path.c_str());
|
||||
a = parseRm("-x /odd"); // not a switch of rm: taken as the name
|
||||
TEST_ASSERT_FALSE(a.force);
|
||||
TEST_ASSERT_EQUAL_STRING("-x /odd", a.path.c_str());
|
||||
TEST_ASSERT_EQUAL_STRING("", parseRm("-rf").path.c_str());
|
||||
}
|
||||
|
||||
int main() {
|
||||
UNITY_BEGIN();
|
||||
RUN_TEST(test_path_parts);
|
||||
@@ -114,5 +138,6 @@ int main() {
|
||||
RUN_TEST(test_copy_names);
|
||||
RUN_TEST(test_kinds);
|
||||
RUN_TEST(test_looks_like_text);
|
||||
RUN_TEST(test_rm_takes_its_switches_like_unix);
|
||||
return UNITY_END();
|
||||
}
|
||||
|
||||
@@ -10,13 +10,13 @@ using namespace roro;
|
||||
void setUp() {}
|
||||
void tearDown() {}
|
||||
|
||||
static void feed(ShellLog& log, const std::string& text, uint32_t now) { log.feed(text.data(), text.size(), now); }
|
||||
static void feed(ShellLog& log, const std::string& text) { log.feed(text.data(), text.size()); }
|
||||
|
||||
void test_lines_arrive_in_pieces() {
|
||||
ShellLog log;
|
||||
feed(log, "firmware: roro", 0);
|
||||
feed(log, "firmware: roro");
|
||||
TEST_ASSERT_EQUAL(0, log.lines().size());
|
||||
feed(log, "9stack\r\nuptime: 1s\n", 0);
|
||||
feed(log, "9stack\r\nuptime: 1s\n");
|
||||
TEST_ASSERT_EQUAL(2, log.lines().size());
|
||||
TEST_ASSERT_EQUAL_STRING("firmware: roro9stack", log.lines()[0].c_str());
|
||||
TEST_ASSERT_EQUAL_STRING("uptime: 1s", log.lines()[1].c_str());
|
||||
@@ -24,38 +24,14 @@ void test_lines_arrive_in_pieces() {
|
||||
|
||||
void test_the_heap_line_is_never_kept() {
|
||||
ShellLog log;
|
||||
feed(log, "status: heap 105000 min 90000\nirc: connected\n", 0);
|
||||
feed(log, "status: heap 105000 min 90000\nirc: connected\n");
|
||||
TEST_ASSERT_EQUAL(1, log.lines().size());
|
||||
TEST_ASSERT_EQUAL_STRING("irc: connected", log.lines()[0].c_str());
|
||||
}
|
||||
|
||||
// Q206: with background lines hidden, only what's printed in the ten seconds after a command.
|
||||
void test_mine_only_keeps_what_follows_a_command() {
|
||||
ShellLog log;
|
||||
log.showBackground(false);
|
||||
feed(log, "irc: someone joined\n", 1000); // no command yet
|
||||
TEST_ASSERT_EQUAL(0, log.lines().size());
|
||||
log.commandRun(5000);
|
||||
feed(log, "ls: end of /\n", 5200); // the reply, a moment later, from another task
|
||||
feed(log, "tasks: ...\n", 14999);
|
||||
feed(log, "irc: someone left\n", 15000); // ten seconds on: background again
|
||||
TEST_ASSERT_EQUAL(2, log.lines().size());
|
||||
log.showBackground(true);
|
||||
feed(log, "irc: back\n", 99000);
|
||||
TEST_ASSERT_EQUAL(3, log.lines().size());
|
||||
}
|
||||
|
||||
void test_mine_only_holds_across_the_clock_wrap() {
|
||||
ShellLog log;
|
||||
log.showBackground(false);
|
||||
log.commandRun(0xFFFFFF00u);
|
||||
feed(log, "reply\n", 0x00000100u); // 512 ms later, past the wrap
|
||||
TEST_ASSERT_EQUAL(1, log.lines().size());
|
||||
}
|
||||
|
||||
void test_the_oldest_lines_go_past_4_kb() {
|
||||
ShellLog log;
|
||||
for (int i = 0; i < 200; i++) feed(log, std::string(49, 'x') + "\n", 0); // 10 KB in all
|
||||
for (int i = 0; i < 200; i++) feed(log, std::string(49, 'x') + "\n"); // 10 KB in all
|
||||
size_t bytes = 0;
|
||||
for (auto& l : log.lines()) bytes += l.size() + 1;
|
||||
TEST_ASSERT_TRUE(bytes <= ShellLog::kMaxBytes);
|
||||
@@ -74,11 +50,12 @@ static const char* kHelp =
|
||||
"lora probe | lora status | lora rx on|off the LoRa radio\n"
|
||||
"log level <0-5> ESP-IDF log level\n"
|
||||
"sd card | sd list | cat <path> | log <text> | burst\n"
|
||||
"get <path> | put <path> <size> <sha256> | screenshot (Debug Console only) binary\n";
|
||||
"get <path> | put <path> <size> <sha256> | screenshot (Debug Console only) binary\n"
|
||||
"Irc | Notes | Storage open that App\n";
|
||||
|
||||
void test_command_words_come_from_the_help_text() {
|
||||
auto words = commandWords(kHelp);
|
||||
const char* expected[] = {"info", "ls", "du", "mkdir", "rm", "lora", "log", "sd", "cat", "burst", "get", "put", "screenshot"};
|
||||
const char* expected[] = {"info", "ls", "du", "mkdir", "rm", "lora", "log", "sd", "cat", "burst", "get", "put", "screenshot", "Irc", "Notes", "Storage"};
|
||||
TEST_ASSERT_EQUAL(sizeof expected / sizeof expected[0], words.size());
|
||||
for (size_t i = 0; i < words.size(); i++) TEST_ASSERT_EQUAL_STRING(expected[i], words[i].c_str());
|
||||
}
|
||||
@@ -96,14 +73,14 @@ void test_tab_completes_the_first_word() {
|
||||
TEST_ASSERT_EQUAL_STRING("zz", completeCommand("zz", words, matches).c_str()); // nothing: as it was
|
||||
TEST_ASSERT_EQUAL_STRING("ls /no", completeCommand("ls /no", words, matches).c_str()); // past the first word
|
||||
TEST_ASSERT_EQUAL_STRING("", completeCommand("", words, matches).c_str());
|
||||
TEST_ASSERT_EQUAL_STRING("Notes ", completeCommand("N", words, matches).c_str()); // an App: a capital
|
||||
TEST_ASSERT_EQUAL_STRING("n", completeCommand("n", words, matches).c_str()); // and no command starts with n here
|
||||
}
|
||||
|
||||
int main(int, char**) {
|
||||
UNITY_BEGIN();
|
||||
RUN_TEST(test_lines_arrive_in_pieces);
|
||||
RUN_TEST(test_the_heap_line_is_never_kept);
|
||||
RUN_TEST(test_mine_only_keeps_what_follows_a_command);
|
||||
RUN_TEST(test_mine_only_holds_across_the_clock_wrap);
|
||||
RUN_TEST(test_the_oldest_lines_go_past_4_kb);
|
||||
RUN_TEST(test_command_words_come_from_the_help_text);
|
||||
RUN_TEST(test_tab_completes_the_first_word);
|
||||
|
||||
Reference in New Issue
Block a user