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>
-
- */
-
- #pragma once
-
- #include <Arduino.h>
-
- #include "terminal_parsing.h"
-
- #include <unordered_map>
- #include <functional>
- #include <vector>
-
- namespace terminal {
-
- struct Terminal;
-
- // We need to be able to pass arbitrary Args structure into the command function
- // Like Embedis implementation, we only pass things that we actually use instead of complete obj instance
- struct CommandContext {
- std::vector<String> argv;
- size_t argc;
- Print& output;
- };
-
- struct Terminal {
-
- enum class Result {
- Error, // Genric error condition
- Command, // We successfully parsed the line and executed the callback specified via addCommand
- CommandNotFound, // ... similar to the above, but command was never added via addCommand
- BufferOverflow, // Command line processing failed, no \r\n / \n before buffer was filled
- Pending, // We got something in the buffer, but can't yet do anything with it
- NoInput // We got nothing in the buffer and stream read() returns -1
- };
-
- using CommandFunc = void(*)(const CommandContext&);
- using ProcessFunc = bool(*)(Result);
-
- // stream - see `stream` description below
- // buffer_size - set internal limit for the total command line length
- Terminal(Stream& stream, size_t buffer_size = 128) :
- stream(stream),
- buffer_size(buffer_size)
- {
- buffer.reserve(buffer_size);
- }
-
- static void addCommand(const String& name, CommandFunc func);
- static size_t commandsSize();
- static std::vector<String> commandNames();
-
- // Try to process a single line (until either `\r\n` or just `\n`)
- Result processLine();
-
- // Calls processLine() repeatedly.
- // Blocks until the stream no longer has any data available.
- // `process_f` will return each individual processLine() Result,
- // and we can either stop (false) or continue (true) the function.
- void process(ProcessFunc = defaultProcessFunc);
-
- private:
-
- static bool defaultProcessFunc(Result);
-
- // general input / output stream:
- // - stream.read() should return user iput
- // - stream.write() can be called from the command callback
- // - stream.write() can be called by us to show error messages
- Stream& stream;
-
- // buffer for the input stream, fixed in size
- std::vector<char> buffer;
- const size_t buffer_size;
-
- // TODO: every command is shared, instance should probably also have an
- // option to add 'private' commands list?
- // Note: we can save ~2.5KB by using std::vector<std::pair<String, CommandFunc>>
- // https://github.com/xoseperez/espurna/pull/2247#issuecomment-633689741
- static std::unordered_map<String, CommandFunc,
- parsing::LowercaseFnv1Hash<String>,
- parsing::LowercaseEquals<String>> commands;
-
- };
- }
|