Andy Little
/
userMenu
Simple Menu for user config at startup etc
Diff: userMenu.cpp
- Revision:
- 0:694155408133
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/userMenu.cpp Sun Mar 29 16:00:36 2020 +0000 @@ -0,0 +1,168 @@ + + +// +/// Simple commandline menu system. +/// Originally from https://code.google.com/archive/p/ardu-imu/downloads + + +#include <ctype.h> +#include <string.h> + +#include <iostream> + +#include "userMenu.h" + +// statics +char Menu::_inbuf[commandlineLenMax]; +Menu::arg Menu::_argv[argsLenMax+ 1]; + +// constructor +Menu::Menu(const char*prompt, const Menu::command *commands, uint8_t entries, preprompt ppfunc) : + _prompt(prompt), + _commands(commands), + _entries(entries), + _ppfunc(ppfunc) +{ +} +namespace { + // could be a eeprom option + // If terminal at PC end already echoes then don't echo here + static constexpr bool serial_echo = false; +} + +// run the menu +void +Menu::run(void) +{ + for (;;) { + if(_ppfunc != nullptr){ + // run the included pre-prompt function + if ( !_ppfunc()){ + return; + } + } + // Serial.print(_prompt); + // Serial.print(']'); + std::cout << _prompt << ']'; + for (uint8_t len = 0,done = false; done == false;) { + // int c = -1;// = Serial.read(); + int c = std::cin.get(); + switch(c) { + case -1: + //eof + break; + case '\r': + // carriage return -> process command + _inbuf[len] = '\0'; + if ( serial_echo){ + // Serial.write('\r'); + // Serial.write('\n'); + std::cout << '\r'; + } + std::cout << '\n'; + done = true; + break; + case '\b': + // backspace + if (len > 0) { + len--; + if ( serial_echo){ + // Serial.write('\b'); + std::cout << '\b'; + } + // Serial.write(' '); + // Serial.write('\b'); + std::cout << " \b"; + } + break; + default: + // printable character + if (isprint(c) && (len < (commandlineLenMax - 1))) { + _inbuf[len++] = c; + if(serial_echo){ + // Serial.write((char)c); + std::cout << static_cast<char>(c); + } + } + break; + } + } + // split the input line into tokens + uint8_t argc = 0; + char *s = nullptr; + _argv[argc++].str = strtok_r(_inbuf, " ", &s); + // XXX should an empty line by itself back out of the current menu? + for ( ;argc <= argsLenMax; ++argc) { + _argv[argc].str = strtok_r(nullptr, " ", &s); + if (_argv[argc].str != nullptr){ + _argv[argc].i = atol(_argv[argc].str); + _argv[argc].f = atof(_argv[argc].str); // calls strtod, > 700B ! + }else{ + break; + } + } + + if (_argv[0].str == nullptr) { + continue; + } + + // populate arguments that have not been specified with "" and 0 + // this is safer than NULL in the case where commands may look + // without testing argc + for(int i = argc; i <= argsLenMax;++i){ + _argv[i].str = nullptr; + _argv[i].i = 0; + _argv[i].f = 0; + } + + bool cmd_found = false; + // look for a command matching the first word (note that it may be empty) + int i = 0; + for (; i < _entries; ++i) { + if (!strcmp(_argv[0].str, _commands[i].command)) { + int8_t ret = _call(i, argc); + cmd_found=true; + if (-2 == ret){ + return; + } + break; + } + } + + // implicit commands + if (i == _entries) { + if (!strcmp(_argv[0].str, "?") || (!strcmp(_argv[0].str, "help"))) { + _help(); + cmd_found=true; + } else if (!strcmp(_argv[0].str, "exit")) { + return; + } + } + + if (cmd_found == false){ + //Serial.println("Invalid command, type 'help'"); + std::cout << "Invalid command, type 'help'\n" ; + } + } +} + +// display the list of commands in response to the 'help' command +void +Menu::_help(void) +{ + // Serial.println("Commands:"); + std::cout << "Commands:\n"; + for (int i = 0; i < _entries; i++){ + std::cout << " " << _commands[i].command << '\n'; + // Serial.print(" "); + // Serial.print(_commands[i].command); + // Serial.print('\n'); + } +} + +// run the n'th command in the menu +int8_t +Menu::_call(uint8_t n, uint8_t argc) +{ + return _commands[n].func(argc, &_argv[0]); +}