Simple Menu for user config at startup etc

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]);
+}