Terminal: change command-line parser (#2247)
Change the underlying command line handling:
- switch to a custom parser, inspired by redis / sds
- update terminalRegisterCommand signature, pass only bare minimum
- clean-up `help` & `commands`. update settings `set`, `get` and `del`
- allow our custom test suite to run command-line tests
- clean-up Stream IO to allow us to print large things into debug stream (for example, `eeprom.dump`)
- send parsing errors to the debug log
As a proof of concept, introduce `TERMINAL_MQTT_SUPPORT` and `TERMINAL_WEB_API_SUPPORT`
- MQTT subscribes to the `<root>/cmd/set` and sends response to the `<root>/cmd`. We can't output too much, as we don't have any large-send API.
- Web API listens to the `/api/cmd?apikey=...&line=...` (or PUT, params inside the body). This one is intended as a possible replacement of the `API_SUPPORT`. Internals introduce a 'task' around the AsyncWebServerRequest object that will simulate what WiFiClient does and push data into it continuously, switching between CONT and SYS.
Both are experimental. We only accept a single command and not every command is updated to use Print `ctx.output` object. We are also somewhat limited by the Print / Stream overall, perhaps I am overestimating the usefulness of Arduino compatibility to such an extent :)
Web API handler can also sometimes show only part of the result, whenever the command tries to yield() by itself waiting for something. Perhaps we would need to create a custom request handler for that specific use-case. 4 years ago |
|
- /*
-
- Part of the TERMINAL MODULE
-
- Copyright (C) 2020 by Maxim Prokhorov <prokhorov dot max at outlook dot com>
-
- Heavily inspired by the Embedis design:
- - https://github.com/thingSoC/embedis
-
- */
-
- #include <Arduino.h>
-
- #include "terminal_commands.h"
-
- #include <memory>
-
- namespace terminal {
-
- std::unordered_map<String, Terminal::CommandFunc,
- parsing::LowercaseFnv1Hash<String>,
- parsing::LowercaseEquals<String>> Terminal::commands;
-
- void Terminal::addCommand(const String& name, CommandFunc func) {
- if (!func) return;
- commands.emplace(std::make_pair(name, func));
- }
-
- size_t Terminal::commandsSize() {
- return commands.size();
- }
-
- std::vector<String> Terminal::commandNames() {
- std::vector<String> out;
- out.reserve(commands.size());
- for (auto& command : commands) {
- out.push_back(command.first);
- }
- return out;
- }
-
- Terminal::Result Terminal::processLine() {
-
- // Arduino stream API returns either `char` >= 0 or -1 on error
- int c = -1;
- while ((c = stream.read()) >= 0) {
- if (buffer.size() >= (buffer_size - 1)) {
- buffer.clear();
- return Result::BufferOverflow;
- }
- buffer.push_back(c);
- if (c == '\n') {
- // in case we see \r\n, offset minus one and overwrite \r
- auto end = buffer.end() - 1;
- if (*(end - 1) == '\r') {
- --end;
- }
- *end = '\0';
-
- // parser should pick out at least one arg (command)
- auto cmdline = parsing::parse_commandline(buffer.data());
- buffer.clear();
- if (cmdline.argc >= 1) {
- auto command = commands.find(cmdline.argv[0]);
- if (command == commands.end()) return Result::CommandNotFound;
- (*command).second(CommandContext{std::move(cmdline.argv), cmdline.argc, stream});
- return Result::Command;
- }
- }
- }
-
- // we need to notify about the fixable things
- if (buffer.size() && (c < 0)) {
- return Result::Pending;
- } else if (!buffer.size() && (c < 0)) {
- return Result::NoInput;
- // ... and some unexpected conditions
- } else {
- return Result::Error;
- }
-
- }
-
- bool Terminal::defaultProcessFunc(Result result) {
- return (result != Result::Error) && (result != Result::NoInput);
- }
-
- void Terminal::process(ProcessFunc func) {
- while (func(processLine())) {
- }
- }
-
- } // ns terminal
|