Repository: jbtronics/ESP32Console Branch: master Commit: ceb313606151 Files: 40 Total size: 187.1 KB Directory structure: gitextract_h38c8r86/ ├── .github/ │ └── workflows/ │ ├── arduino-ci.yaml │ └── arduino-lint.yaml ├── LICENSE ├── README.md ├── commands.md ├── examples/ │ ├── argparser/ │ │ └── argparser.ino │ ├── basic/ │ │ └── basic.ino │ ├── gpio/ │ │ └── gpio.ino │ ├── network/ │ │ └── network.ino │ ├── other_uart_channel/ │ │ └── other_uart_channel.ino │ └── vfs/ │ └── vfs.ino ├── library.json ├── library.properties └── src/ ├── ESP32Console/ │ ├── Commands/ │ │ ├── CoreCommands.cpp │ │ ├── CoreCommands.h │ │ ├── GPIOCommands.cpp │ │ ├── GPIOCommands.h │ │ ├── NetworkCommands.cpp │ │ ├── NetworkCommands.h │ │ ├── SystemCommands.cpp │ │ ├── SystemCommands.h │ │ ├── VFSCommands.cpp │ │ └── VFSCommands.h │ ├── Console.cpp │ ├── Console.h │ ├── ConsoleCommand.h │ ├── ConsoleCommandBase.h │ ├── ConsoleCommandD.cpp │ ├── ConsoleCommandD.h │ ├── Helpers/ │ │ ├── InputParser.cpp │ │ ├── InputParser.h │ │ ├── PWDHelpers.cpp │ │ └── PWDHelpers.h │ ├── OptionsConsoleCommand.cpp │ └── OptionsConsoleCommand.h ├── ESP32Console.h ├── cxxopts/ │ └── cxxopts.hpp └── kilo/ ├── CREDITS ├── kilo.cpp └── kilo.h ================================================ FILE CONTENTS ================================================ ================================================ FILE: .github/workflows/arduino-ci.yaml ================================================ name: Arduino Library CI on: [pull_request, push, repository_dispatch] jobs: build: runs-on: ubuntu-latest steps: - uses: actions/setup-python@v5 with: python-version: '3.x' - uses: actions/checkout@v4 - uses: actions/checkout@v4 with: repository: adafruit/ci-arduino path: ci - name: pre-install run: bash ci/actions_install.sh - name: test platforms run: python3 ci/build_platform.py --no_warn esp32 #- name: clang # run: python3 ci/run-clang-format.py -e "ci/*" -e "bin/*" -r . - name: doxygen env: GH_REPO_TOKEN: ${{ secrets.GITHUB_TOKEN }} PRETTYNAME : "ESP32Console Library" run: bash ci/doxy_gen_and_deploy.sh ================================================ FILE: .github/workflows/arduino-lint.yaml ================================================ on: [push, pull_request] jobs: lint: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: arduino/arduino-lint-action@v1 with: library-manager: update compliance: strict ================================================ FILE: LICENSE ================================================ MIT License Copyright (c) 2022 Jan Böhmer Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ # ESP32Console [![PlatformIO Registry](https://badges.registry.platformio.org/packages/jbtronics/library/ESP32Console.svg)](https://registry.platformio.org/libraries/jbtronics/ESP32Console) Arduino library to add a serial console to your ESP32 console. It includes some useful commands and allows to easily add your own custom commands. This easily allows you to control and configure your control via serial console in a comfortable and easy way. This library encapsulates the Console component included in the ESP-IDF and allows to use it in an easy "Arduino-way". ![Screenshot of console output](extras/screenshot.png) ## Features * Simple to use * Navigatable history, autocompletion with tab for commands (when using an ANSI terminal) * Persistent history if wished (history gets saved across reboots) * Many useful commands included like showing infos about system, memory, network and more (see [commands.md](commands.md) for more info) * Console works in its own asynchronous task, so you can use your arduino loop() function as you like * Support for environment variables and variable interpolation in commands * Easy to implement own commands * Easy to use argument parsing using cxxopts (see `argparser` example) * Customizable prompt * Ships a simple file editor to modify and create files locally on system if wanted ## Usage ### Installation This library is available via Arduino Library Manager and [PlatformIO registry](https://registry.platformio.org/libraries/jbtronics/ESP32Console). So just install it via your preferred IDE's library manager and you are ready to start. ### Basic To use this library you have to do an `#include ` to import all needed files into your project. This library uses namespaces, so you have to do an `using namespace ESP32Console` to not having to prefix all classes (see example below). You instantiate an `Console` object and initialize it with `Console.begin(BAUD)`. You can specifiy the baud rate and rx/tx pins similar to `Serial.begin`. Please note that you can use EITHER `Serial` OR `Console`. If you try to start ESP32Console after Serial was inited you will get an error. Using `Console.registerCommand()` you can register your own custom commands. You have to pass a command object (see example below), which contains the name of the command, a little help text and the function which should be executed, when this command is executed. There are different types of commands: * `ConsoleCommand`: The "default" console command, which takes a pointer to a function which is executed on call (similar to arduinos `attachInterrupt` function). The function receives an `int argc` which contains the number of arguments and `char** argv` which contains the arguments itself. The function MUST return an integer. Return 0 if everything was successfull, return something else (e.g. 1) if an error happened. This command type has the lowest memory usage. * `ConsoleCommandD`: Similar to `ConsoleCommand` but allows to pass an `std::function` object as handler. This allows you to use lambda-functions and `std::bind` * `OptionsConsoleCommand`: Allows you to define and parse command options and arguments in an easy way. Uses [cxxopts](https://github.com/jarro2783/cxxopts) for arguments parsing. Every command of this type has an `--help` and `--version` option. See `examples/argparser` for usage. ### Included commands ESP32Console includes many useful commands, which can be registered using their respective `registerXXX()` functions. See [commands.md](commands.md) for a detailed list of the commands. ### Environment variables The ESP32Console supports environment variables and string interpolation in the console. You can use `env` command to list all existing environment variables and `declare [VAR] [VALUE]` to change one. In the console prompt `$ENV` and `${ENV}` will get replaced by the value of the defined env value. With that you can for example define a variable with `declare HOST www.github.com` and access it in other commands: `ping $HOST`. You can change and predefine env variables from your code. See `examples/gpio` for more info. ### Computer side You can use almost any terminal software on PC for connecting with ESP32Console. You can use a simple terminal like the one included in Arduino but it is highly recommended to use a VT100 compatible terminal (e.g. PuTTY on windows). This kind of terminal is needed for more complex functions like auto-complete with TAB, history scrolling, colors and more. If you use a VT100 compatible terminal you can use the keybinds, when in prompt: * `Ctrl + L`: Clear screen * `Ctrl + A`: Jump cursor to begin of line * `Ctrl + E`: Jump cursor to end of line * `Ctrl + U`: Delete whole line * `Ctrl + K`: Delete from current position to end of line * `Ctrl + W`: Delete previous word * `Ctrl + T`: Swap current character with previous one ## Examples A simple usage can be seen here (see `examples/simple.ino`): More advanced usages can be found in `examples/` folder. ``` #include #include "ESP32Console.h" using namespace ESP32Console; Console console; constexpr int LED = 2; int led(int argc, char **argv) { //Ensure that we have an argument to parse if (argc != 2) { printf("You have to give 'on' or 'off' as a argument (e.g. 'led on')\n"); //Return EXIT_FAILURE if something did not worked. return EXIT_FAILURE; } //Take the first argument... auto arg = String(argv[1]); //and use it to decide what to do with the LED if (arg == "on") { digitalWrite(LED, HIGH); printf("LED is now on\n"); } else if(arg == "off") { digitalWrite(LED, LOW); printf("LED is now off\n"); } else { printf("Unknown argument!\n"); return EXIT_FAILURE; } //Return EXIT_SUCCESS if everything worked as intended. return EXIT_SUCCESS; } void setup() { pinMode(LED, OUTPUT); //You can change the console prompt before calling begin(). By default it is "ESP32>" console.setPrompt("MyConsole> "); //You can change the baud rate and pin numbers similar to Serial.begin() here. console.begin(115200); //Register builtin commands like 'reboot', 'sysinfo', or 'meminfo' console.registerSystemCommands(); //Register our own command //First argument is the name with which the command can be executed, second argument is the function to execute and third one is the description shown in help command. console.registerCommand(ConsoleCommand("led", &led, "Turn the LED on or off")); //With ConsoleCommandD you can use lambda functions (and anything else that can be cast to std::function). This needs a bit more memory and CPU time than the normal ConsoleCommand. console.registerCommand(ConsoleCommandD("test", [](int argc, char **argv) -> int { printf("Lambda function test\n"); return EXIT_SUCCESS; }, "Just a test command!")); //When console is in use, we can not use Serial.print but you can use printf to output text printf("\n\nWelcome to ESP32Console example. Try out typing 'led off' and 'led on' (without quotes) or see 'help' for all commands."); } void loop() { //Console works async in its own task, so you can do whatever you want in your loop() function. } ``` ## Credits * This library utilizes the console component of ESP-IDF written by Espressif at core. * Argument parsing is done by [cxxopts](https://github.com/jarro2783/cxxopts). * As editor a modified version of [kilo](https://github.com/antirez/kilo) is used. ## LICENSE ESP32Console is licensed under MIT LICENSE. See [LICENSE file](LICENSE) for more info. kilo shipped with ESP32Console is licensed under BSD-2 clause license. See the respective file for more info. ## TODO * Add more useful commands * Easy integration of colors and console styles * Support of command batch files * Add support for ESP8266 (this should be possible in theory as the old RTOS-SDK already ships the console parts) * Check if more complex terminal stuff, like pipes, output redirection and similar is possible (difficult due only having one global stdout) ================================================ FILE: commands.md ================================================ # Included command reference ## Core commands (automatically loaded, when begin() is called) * `help`: Show a list of all possible commands with descriptions * `clear`: Clear terminal screen using ANSI commands (only working when using an ANSI compatible terminal) * `echo`: Echo the parameter strings * `history`: Show the recent command history * `multiline_mode`: Switch multiline mode on or off. When it is on, lines get break into a second line, if it gets too long. * `env`: List all environment variables * `declare`: Change the value of an environment variable (syntax: `declare VARIABLE Value` or `declare VARIABLE "Long Value"`) ## System commands (loaded by calling registerSystemCommands()) * `sysinfo`: Prints info about the chip model, revision, ESP-IDF version, EFuse MAC, flash and PSRAM * `restart`: Restarts the system * `meminfo`: Show informations about used and free Heap * `date`: Shows and set the current system time. Change time with `date -s "2022-07-13 22:47:00"`. Timezone can be set by changing the `TZ` env variable (e.g. `declare TZ CET-1`). ## Network commands (loaded by calling registerNetworkCommands()) * `ipconfig`: Shows information about WiFi connection * `ping [HOST]`: Pings a hostname. You can change the number of pings done with `-n`parameter. Use `-n 0` for infinite pinging. You can stop the ping by `Strg + C` or `Strg + D`. ## VFS commands (loaded by calling registerVFSCommands()) This functions allows you to navigate through and edit files in ESP-IDFs Virtual Filesystem. Things likes SPIFF, SDCards, some hardware and more are getting mounted into VFS with different prefixes. The following commands allow an unified access on it. * `pwd`: Show the current working directory (the directory we are currently in) * `cd [PATH]`: Change the current directory * `ls [PATH]`: List the contents of the current or given directory * `cat [FILES...]`: Show the content of the given files * `rm [FILE]`: Delete the given file * `rmdir [DIR]`: Delete the given director * `mv [ORIGIN] [TARGET]`: Moves/Rename the file to new name/posiion * `cp [ORIGIN] [TARGET]`: Copies the contents of origin to target file * `edit [FILE]`: Opens a file editor with a visual editor. Use `Strg + S` to save, `Strg + Q` to quit and `Strg + F` to search in file. ## GPIO commands (loaded by calling registerGPIOCommands()) The commands allow you to read and change the states of the ESPs GPIO pins. They are similar to the arduino included functions: * `pinMode [PIN] [MODE]`: Change the pinMode of an GPIO * `digitalRead [PIN]`: Reads the state of an digital GPIO * `digitalWrite [PIN] [LEVEL]`: Changes the state of an digital GPIO * `analogRead [PIN]`: Reads the voltage applied to an analog GPIO in millivolts ================================================ FILE: examples/argparser/argparser.ino ================================================ /** * This example demonstrates the usage of OptionsConsoleCommand, which allows you to easily build commands with complex options and argument parsing. */ #include #include "ESP32Console.h" using namespace ESP32Console; Console console; void setup() { console.begin(115200); // Define a new command. Every command has an --option argument which shows the possible arguments and (if defined) a --version option which shows the version of this command. OptionsConsoleCommand test( "test", // The command name [](int argc, char **argv, ParseResult result, Options options) -> int { // The function which is called when command is called. You get the parsed arguments and the options object printf("Lambda function test\n"); // Check if argument was passed, then print it: if (result.count("i")) { int i = result["i"].as(); printf("Selected mode: %d\n", i); } return EXIT_SUCCESS; }, "Just a test command!", "v1.0.0"); //You can define a version number which is shown when the command is called with --version //Customize the options of the console object. See https://github.com/jarro2783/cxxopts for explaination test.options.add_options()("i,integer", "Int param", cxxopts::value()); //Register it like any other command console.registerCommand(test); } void loop() { // Console works async in its own task, so you can do whatever you want in your loop() function. } ================================================ FILE: examples/basic/basic.ino ================================================ #include #include "ESP32Console.h" using namespace ESP32Console; Console console; constexpr int LED = 2; int led(int argc, char **argv) { //Ensure that we have an argument to parse if (argc != 2) { printf("You have to give 'on' or 'off' as a argument (e.g. 'led on')\n"); //Return EXIT_FAILURE if something did not worked. return EXIT_FAILURE; } //Take the first argument... auto arg = String(argv[1]); //and use it to decide what to do with the LED if (arg == "on") { digitalWrite(LED, HIGH); printf("LED is now on\n"); } else if(arg == "off") { digitalWrite(LED, LOW); printf("LED is now off\n"); } else { printf("Unknown argument!\n"); return EXIT_FAILURE; } //Return EXIT_SUCCESS if everything worked as intended. return EXIT_SUCCESS; } void setup() { pinMode(LED, OUTPUT); //You can change the console prompt before calling begin(). By default it is "ESP32>" console.setPrompt("MyConsole> "); //You can change the baud rate and pin numbers similar to Serial.begin() here. console.begin(115200); //Register builtin commands like 'reboot', 'version', or 'meminfo' console.registerSystemCommands(); //Register our own command //First argument is the name with which the command can be executed, second argument is the function to execute and third one is the description shown in help command. console.registerCommand(ConsoleCommand("led", &led, "Turn the LED on or off")); //With ConsoleCommandD you can use lambda functions (and anything else that can be cast to std::function). This needs a bit more memory and CPU time than the normal ConsoleCommand. console.registerCommand(ConsoleCommandD("test", [](int argc, char **argv) -> int { printf("Lambda function test\n"); return EXIT_SUCCESS; }, "Just a test command!")); //When console is in use, we can not use Serial.print but you can use printf to output text printf("\n\nWelcome to ESP32Console example. Try out typing 'led off' and 'led on' (without quotes) or see 'help' for all commands."); } void loop() { //Console works async in its own task, so you can do whatever you want in your loop() function. } ================================================ FILE: examples/gpio/gpio.ino ================================================ /** * This example demonstrates the usage of the internal GPIO functions and the env variables interpolation */ #include #include "ESP32Console.h" #include "ESP32Console/Helpers/PWDHelpers.h" using namespace ESP32Console; Console console; void setup() { console.begin(115200); console.registerSystemCommands(); /** * Set environment variables, so users can access pin numbers easily in console. * Users can do commands like `digitalWrite $LED HIGH` which will then be expanded to `digitalWrite 10 HIGH`. * Users can change these env variables or create new ones dynamically in the console by `declare LED 10`. */ setenv("LED", "10", 1); setenv("BUTTON", "4", 1); /** * Users can list all defined env variables and their values by `env` command */ //Register GPIO commands console.registerGPIOCommands(); } void loop() { } ================================================ FILE: examples/network/network.ino ================================================ /** * This example demonstrates the usage of the builtin network functions */ #include #include "ESP32Console.h" #include #include "ESP32Console/Helpers/PWDHelpers.h" using namespace ESP32Console; Console console; const char *ssid = "yourSSID"; const char *password = "yourWLAN"; void setup() { //Connect to WiFi WiFi.begin(ssid, password); console.begin(115200); console.registerSystemCommands(); //Register network commands console.registerNetworkCommands(); } void loop() { } ================================================ FILE: examples/other_uart_channel/other_uart_channel.ino ================================================ /** * This example demonstrates the usage of ESP32Console on a seperate UART channel, using its own Pins, which allows you to use * the console besides normal Serial.* commands */ #include #include "ESP32Console.h" using namespace ESP32Console; Console console; void setup() { //Initialize Serial port on UART0 (the onboard USB Serial Converter) Serial.begin(115200); //Enable ESP32Console on Pin 12 & 14 on UART1 console.begin(115200, 12, 14, 1); console.registerSystemCommands(); console.registerNetworkCommands(); } void loop() { //We can use Serial on UART0 as usual, while ESP32Console is available on the Pins above. Serial.println("Test"); delay(1000); } ================================================ FILE: examples/vfs/vfs.ino ================================================ /** * This example demonstrates the usage of the builtin VFS commands and persistent history */ #include #include "ESP32Console.h" #include #include "SPIFFS.h" #include "ESP32Console/Helpers/PWDHelpers.h" using namespace ESP32Console; Console console; void setup() { //Initalize SPIFFS and mount it on /spiffs SPIFFS.begin(true, "/spiffs"); //Modify prompt to show current file path (%pwd% get replaced by the filepath) console.setPrompt("ESP32 %pwd%> "); //Set HOME env for easier navigating (type cd to jump to home) setenv("HOME", "/spiffs", 1); //Set PWD to env console_chdir("/spiffs"); console.begin(115200); //Enable the saving of our command history to SPIFFS. You will be able to see it, when you type ls in your console. console.enablePersistentHistory("/spiffs/.history.txt"); console.registerSystemCommands(); //Register the VFS specific commands console.registerVFSCommands(); } void loop() { } ================================================ FILE: library.json ================================================ { "name": "ESP32Console", "version": "1.3.0", "description": "Extensible UART console for ESP32 with useful included commands. This library encapsules the console component of ESP-IDF and make them easy to use in an Arduino environment.", "keywords": "esp32, console, ping, ifconfig, sysinfo, editor, file", "repository": { "type": "git", "url": "https://github.com/jbtronics/ESP32Console" }, "authors": [ { "name": "Jan Böhmer", "email": "mail@jan-boehmer.de", "url": "https://github.com/jbtronics", "maintainer": true } ], "license": "MIT", "homepage": "https://github.com/jbtronics/ESP32Console", "dependencies": { }, "exclude": [ ".github", "extras" ], "frameworks": "arduino", "platforms": "espressif32" } ================================================ FILE: library.properties ================================================ name=ESP32Console version=1.3.0 author=Jan Böhmer maintainer=Jan Böhmer sentence=Extensible UART console for ESP32 with useful included commands. paragraph=This library encapsules the console component of ESP-IDF and make them easy to use in an Arduino environment category=Communication url=https://github.com/jbtronics/ESP32Console architectures=esp32 ================================================ FILE: src/ESP32Console/Commands/CoreCommands.cpp ================================================ #include "./CoreCommands.h" #include "linenoise/linenoise.h" #include "Arduino.h" #include "soc/soc_caps.h" //#include "argparse/argparse.hpp" static int clear(int argc, char **argv) { // If we are on a dumb erminal clearing does not work if (linenoiseProbe()) { printf("\nYour terminal does not support escape sequences. Clearing screen does not work!\n"); return EXIT_FAILURE; } linenoiseClearScreen(); return EXIT_SUCCESS; } static int echo(int argc, char **argv) { for (int n = 1; n 1) { if (strcasecmp(argv[1], "-c")) { // When -c option was detected clear history. linenoiseHistorySetMaxLen(0); printf("History cleared!\n"); linenoiseHistorySetMaxLen(10); return EXIT_SUCCESS; } else { printf("Invalid argument. Use -c to clear history.\n"); return EXIT_FAILURE; } } else*/ { // Without arguments we just output the history // We use the ESP-IDF VFS to directly output the file to an UART. UART channel 0 has the path /dev/uart/0 and so on. char path[12] = {0}; snprintf(path, 12, "/dev/uart/%d", history_channel); // If we found the correct one, let linoise save (output) them. linenoiseHistorySave(path); return EXIT_SUCCESS; } return EXIT_FAILURE; } extern char **environ; static int env(int argc, char **argv) { char **s = environ; for (; *s; s++) { printf("%s\n", *s); } return EXIT_SUCCESS; } static int declare(int argc, char **argv) { if (argc != 3) { fprintf(stderr, "Syntax: declare VAR short OR declare VARIABLE \"Long Value\"\n"); return EXIT_FAILURE; } setenv(argv[1], argv[2], 1); return EXIT_SUCCESS; } namespace ESP32Console::Commands { const ConsoleCommand getClearCommand() { return ConsoleCommand("clear", &clear, "Clears the screen using ANSI codes"); } const ConsoleCommand getEchoCommand() { return ConsoleCommand("echo", &echo, "Echos the text supplied as argument"); } const ConsoleCommand getSetMultilineCommand() { return ConsoleCommand("multiline_mode", &set_multiline_mode, "Sets the multiline mode of the console"); } const ConsoleCommand getHistoryCommand(int uart_channel) { history_channel = uart_channel; return ConsoleCommand("history", &history, "Shows and clear command history (using -c parameter)"); } const ConsoleCommand getEnvCommand() { return ConsoleCommand("env", &env, "List all environment variables."); } const ConsoleCommand getDeclareCommand() { return ConsoleCommand("declare", &declare, "Change enviroment variables"); } } ================================================ FILE: src/ESP32Console/Commands/CoreCommands.h ================================================ #pragma once #include "ESP32Console/ConsoleCommand.h" namespace ESP32Console::Commands { const ConsoleCommand getClearCommand(); const ConsoleCommand getEchoCommand(); const ConsoleCommand getSetMultilineCommand(); const ConsoleCommand getHistoryCommand(int uart_channel=0); const ConsoleCommand getEnvCommand(); const ConsoleCommand getDeclareCommand(); } ================================================ FILE: src/ESP32Console/Commands/GPIOCommands.cpp ================================================ #include "./GPIOCommands.h" #include "Arduino.h" #include #include static int _pinmode(int argc, char **argv) { if (argc != 3) { printf("You have to pass a pin number and mode. Syntax: pinMode [GPIO] [MODE]\n"); return 1; } char *pin_str = argv[1]; String mode_str = String(argv[2]); unsigned long pin = 0; try { pin = std::stoul(pin_str); } catch (std::invalid_argument ex) { fprintf(stderr, "Invalid argument for pin: %s\n", ex.what()); return 1; } if (pin > 255 || !digitalPinIsValid(pin)) { fprintf(stderr, "%d is not a GPIO pin\n", pin); return 1; } int mode = INPUT; if (mode_str.equalsIgnoreCase("INPUT")) { mode = INPUT; } else if (mode_str.equalsIgnoreCase("OUTPUT")) { mode = OUTPUT; } else if (mode_str.equalsIgnoreCase("INPUT_PULLUP")) { mode = INPUT_PULLUP; } else if (mode_str.equalsIgnoreCase("INPUT_PULLDOWN")) { mode = INPUT_PULLDOWN; } else if (mode_str.equalsIgnoreCase("OUTPUT_OPEN_DRAIN")) { mode = OUTPUT_OPEN_DRAIN; } else { fprintf(stderr, "Invalid mode: Allowed modes are INPUT, OUTPUT, INPUT_PULLUP, INPUT_PULLDOWN, OUTPUT_OPEN_DRAIN\n"); } pinMode(pin, mode); printf("Mode set successful.\n"); return 0; } static int _digitalWrite(int argc, char** argv) { if (argc != 3) { printf("You have to pass an pin number and level. Syntax: digitalWrite [GPIO] [Level]\n"); return 1; } char *pin_str = argv[1]; String mode_str = String(argv[2]); unsigned long pin = 0; try { pin = std::stoul(pin_str); } catch (std::invalid_argument ex) { fprintf(stderr, "Invalid argument for pin: %s\n", ex.what()); return 1; } if (pin > 255 || !digitalPinCanOutput(pin)) { fprintf(stderr, "%d is not a GPIO pin\n", pin); return 1; } int mode = LOW; if (mode_str.equalsIgnoreCase("HIGH") || mode_str.equalsIgnoreCase("1")) { mode = HIGH; } else if (mode_str.equalsIgnoreCase("LOW") || mode_str.equalsIgnoreCase("0")) { mode = LOW; } else { fprintf(stderr, "Invalid mode: Allowed levels are HIGH, LOW, 0 and 1\n"); } pinMode(pin, mode); printf("Output set successful.\n"); return 0; } static int _digitalRead(int argc, char** argv) { if (argc != 2) { printf("You have to pass an pin number to read\n"); return 1; } char *pin_str = argv[1]; unsigned long pin = 0; try { pin = std::stoul(pin_str); } catch (std::invalid_argument ex) { fprintf(stderr, "Invalid argument for pin: %s\n", ex.what()); return 1; } if (pin > 255 || !digitalPinCanOutput(pin)) { fprintf(stderr, "%d is not a GPIO pin\n", pin); return 1; } auto level = digitalRead(pin); if(level == HIGH) { printf("HIGH\n"); } else if(level == LOW) { printf("LOW\n"); } else { fprintf(stderr, "Unknown state (%u) of pin %u!\n", level, pin); return 1; } return 0; } static int _analogRead(int argc, char** argv) { if (argc != 2) { printf("You have to pass an pin number to read\n"); return 1; } char *pin_str = argv[1]; unsigned long pin = 0; try { pin = std::stoul(pin_str); } catch (std::invalid_argument ex) { fprintf(stderr, "Invalid argument for pin: %s\n", ex.what()); return 1; } if (pin > 255 || digitalPinToAnalogChannel(pin) == -1) { fprintf(stderr, "%d is not a ADC pin\n", pin); return 1; } auto value = analogReadMilliVolts(pin); printf("%u mV\n", value); return 0; } namespace ESP32Console::Commands { const ConsoleCommand getPinModeCommand() { return ConsoleCommand("pinMode", &_pinmode, "Changes the pinmode of an GPIO pin (similar to Arduino function)"); } const ConsoleCommand getDigitalWriteCommand() { return ConsoleCommand("digitalWrite", &_digitalWrite, "Writes the state of an ouput pin (similar to Arduino function)"); } const ConsoleCommand getDigitalReadCommand() { return ConsoleCommand("digitalRead", &_digitalRead, "Reads the state of an input pin (similar to Arduino function)"); } const ConsoleCommand getAnalogReadCommand() { return ConsoleCommand("analogRead", &_analogRead, "Show the voltage at an analog pin in millivollts."); } } ================================================ FILE: src/ESP32Console/Commands/GPIOCommands.h ================================================ #pragma once #include "ESP32Console/ConsoleCommand.h" namespace ESP32Console::Commands { const ConsoleCommand getPinModeCommand(); const ConsoleCommand getDigitalWriteCommand(); const ConsoleCommand getDigitalReadCommand(); const ConsoleCommand getAnalogReadCommand(); } ================================================ FILE: src/ESP32Console/Commands/NetworkCommands.cpp ================================================ #include "./NetworkCommands.h" #include #include #include #include #include "lwip/inet.h" #include "lwip/netdb.h" #include "lwip/sockets.h" #include #include "WiFi.h" static const char *wlstatus2string(wl_status_t status) { switch (status) { case WL_NO_SHIELD: return "Not initialized"; case WL_CONNECT_FAILED: return "Connection failed"; case WL_CONNECTED: return "Connected"; case WL_CONNECTION_LOST: return "Connection lost"; case WL_DISCONNECTED: return "Disconnected"; case WL_IDLE_STATUS: return "Idle status"; case WL_NO_SSID_AVAIL: return "No SSID available"; case WL_SCAN_COMPLETED: return "Scan completed"; default: return "Unknown"; } } const char* wlmode2string(wifi_mode_t mode) { switch(mode) { case WIFI_MODE_NULL: return "Not initialized"; case WIFI_MODE_AP: return "Accesspoint"; case WIFI_MODE_STA: return "Station"; case WIFI_MODE_APSTA: return "Station + Accesspoint"; default: return "Unknown"; } } static void on_ping_success(esp_ping_handle_t hdl, void *args) { uint8_t ttl; uint16_t seqno; uint32_t elapsed_time, recv_len; ip_addr_t target_addr; esp_ping_get_profile(hdl, ESP_PING_PROF_SEQNO, &seqno, sizeof(seqno)); esp_ping_get_profile(hdl, ESP_PING_PROF_TTL, &ttl, sizeof(ttl)); esp_ping_get_profile(hdl, ESP_PING_PROF_IPADDR, &target_addr, sizeof(target_addr)); esp_ping_get_profile(hdl, ESP_PING_PROF_SIZE, &recv_len, sizeof(recv_len)); esp_ping_get_profile(hdl, ESP_PING_PROF_TIMEGAP, &elapsed_time, sizeof(elapsed_time)); printf("%u bytes from %s icmp_seq=%d ttl=%d time=%u ms\n", recv_len, inet_ntoa(target_addr.u_addr.ip4), seqno, ttl, elapsed_time); } static void on_ping_timeout(esp_ping_handle_t hdl, void *args) { uint16_t seqno; ip_addr_t target_addr; esp_ping_get_profile(hdl, ESP_PING_PROF_SEQNO, &seqno, sizeof(seqno)); esp_ping_get_profile(hdl, ESP_PING_PROF_IPADDR, &target_addr, sizeof(target_addr)); printf("From %s icmp_seq=%u timeout\n", inet_ntoa(target_addr.u_addr.ip4), seqno); } static void on_ping_end(esp_ping_handle_t hdl, void *args) { } static int ping(int argc, char **argv) { //By default do 5 pings int number_of_pings = 5; int opt; while ((opt = getopt(argc, argv, "n:")) != -1) { switch(opt) { case 'n': number_of_pings = atoi(optarg); break; case '?': printf("Unknown option: %c\n", optopt); break; case ':': printf("Missing arg for %c\n", optopt); break; default: fprintf(stderr, "Usage: ping -n 5 [HOSTNAME]\n"); fprintf(stderr, "-n: The number of pings. 0 means infinite. Can be aborted with Ctrl+D or Ctrl+C."); return 1; } } int argind = optind; //Get hostname if (argind >= argc) { fprintf(stderr, "You need to pass an hostname!\n"); return EXIT_FAILURE; } char* hostname = argv[argind]; /* convert hostname to IP address */ ip_addr_t target_addr; struct addrinfo hint; struct addrinfo *res = NULL; memset(&hint, 0, sizeof(hint)); memset(&target_addr, 0, sizeof(target_addr)); auto result = getaddrinfo(hostname, NULL, &hint, &res); if (result) { fprintf(stderr, "Could not resolve hostname! (getaddrinfo returned %d)\n", result); return 1; } struct in_addr addr4 = ((struct sockaddr_in *) (res->ai_addr))->sin_addr; inet_addr_to_ip4addr(ip_2_ip4(&target_addr), &addr4); freeaddrinfo(res); //Configure ping session esp_ping_config_t ping_config = ESP_PING_DEFAULT_CONFIG(); ping_config.task_stack_size = 4096; ping_config.target_addr = target_addr; // target IP address ping_config.count = number_of_pings; // 0 means infinite ping /* set callback functions */ esp_ping_callbacks_t cbs; cbs.on_ping_success = on_ping_success; cbs.on_ping_timeout = on_ping_timeout; cbs.on_ping_end = on_ping_end; //Pass a variable as pointer so the sub tasks can decrease it //cbs.cb_args = &number_of_pings_remaining; esp_ping_handle_t ping; esp_ping_new_session(&ping_config, &cbs, &ping); esp_ping_start(ping); char c = 0; uint16_t seqno; esp_ping_get_profile(ping, ESP_PING_PROF_SEQNO, &seqno, sizeof(seqno)); //Make stdin input non blocking so we can query for input AND check ping seqno int flags = fcntl(fileno(stdin), F_GETFL, 0); fcntl(fileno(stdin), F_SETFL, flags | O_NONBLOCK); //Wait for Ctrl+D or Ctr+C or that our task finishes //The async tasks decrease number_of_pings, so wait for it to get to 0 while((number_of_pings == 0 || seqno < number_of_pings) && c != 4 && c != 3) { esp_ping_get_profile(ping, ESP_PING_PROF_SEQNO, &seqno, sizeof(seqno)); c = getc(stdin); delay(50); } //Reset flags, so we dont end up destroying our terminal env later, when linenoise takes over again fcntl(fileno(stdin), F_SETFL, flags); esp_ping_stop(ping); //Print total statistics uint32_t transmitted; uint32_t received; uint32_t total_time_ms; esp_ping_get_profile(ping, ESP_PING_PROF_REQUEST, &transmitted, sizeof(transmitted)); esp_ping_get_profile(ping, ESP_PING_PROF_REPLY, &received, sizeof(received)); esp_ping_get_profile(ping, ESP_PING_PROF_DURATION, &total_time_ms, sizeof(total_time_ms)); printf("%u packets transmitted, %u received, time %u ms\n", transmitted, received, total_time_ms); esp_ping_delete_session(ping); return EXIT_SUCCESS; } static void ipconfig_wlan() { printf("==== WLAN ====\n"); auto status = WiFi.status(); printf("Mode: %s\n", wlmode2string(WiFi.getMode())); printf("Status: %s\n", wlstatus2string(status)); if (status == WL_NO_SHIELD) { return; } printf("\n"); printf("SSID: %s\n", WiFi.SSID().c_str()); printf("BSSID: %s\n", WiFi.BSSIDstr().c_str()); printf("Channel: %d\n", WiFi.channel()); printf("\n"); printf("IP: %s\n", WiFi.localIP().toString().c_str()); printf("Subnet Mask: %s (/%d)\n", WiFi.subnetMask().toString().c_str(), WiFi.subnetCIDR()); printf("Gateway: %s\n", WiFi.gatewayIP().toString().c_str()); #if !defined(ESP_ARDUINO_VERSION_MAJOR) || ESP_ARDUINO_VERSION_MAJOR < 3 printf("IPv6: %s\n", WiFi.localIPv6().toString().c_str()); #endif printf("\n"); printf("Hostname: %s\n", WiFi.getHostname()); printf("DNS1: %s\n", WiFi.dnsIP(0).toString().c_str()); printf("DNS2: %s\n", WiFi.dnsIP(0).toString().c_str()); } static int ipconfig(int argc, char **argv) { ipconfig_wlan(); return EXIT_SUCCESS; } namespace ESP32Console::Commands { const ConsoleCommand getPingCommand() { return ConsoleCommand("ping", &ping, "Ping host"); } const ConsoleCommand getIpconfigCommand() { return ConsoleCommand("ipconfig", &ipconfig, "Show IP and connection informations"); } } ================================================ FILE: src/ESP32Console/Commands/NetworkCommands.h ================================================ #pragma once #include "ESP32Console/ConsoleCommand.h" namespace ESP32Console::Commands { const ConsoleCommand getPingCommand(); const ConsoleCommand getIpconfigCommand(); } ================================================ FILE: src/ESP32Console/Commands/SystemCommands.cpp ================================================ #include "./SystemCommands.h" #include "ESP32Console.h" #include #include #include #include #if __has_include() #include #endif #if __has_include() #include #endif #include // For XTSTR macros (Xtensa-specific) #if __has_include() #include #else // Provide a fallback stringification macro for non-Xtensa architectures (e.g. RISC-V) #ifndef XTSTR #define _XTSTR(x) #x #define XTSTR(x) _XTSTR(x) #endif #endif static String mac2String(uint64_t mac) { byte *ar = (byte *)&mac; String s; for (byte i = 0; i < 6; ++i) { char buf[3]; sprintf(buf, "%02X", ar[i]); // J-M-L: slight modification, added the 0 in the format for padding s += buf; if (i < 5) s += ':'; } return s; } static const char *getFlashModeStr() { switch (ESP.getFlashChipMode()) { case FM_DIO: return "DIO"; case FM_DOUT: return "DOUT"; case FM_FAST_READ: return "FAST READ"; case FM_QIO: return "QIO"; case FM_QOUT: return "QOUT"; case FM_SLOW_READ: return "SLOW READ"; case FM_UNKNOWN: default: return "UNKNOWN"; } } static const char *getResetReasonStr() { switch (esp_reset_reason()) { case ESP_RST_BROWNOUT: return "Brownout reset (software or hardware)"; case ESP_RST_DEEPSLEEP: return "Reset after exiting deep sleep mode"; case ESP_RST_EXT: return "Reset by external pin (not applicable for ESP32)"; case ESP_RST_INT_WDT: return "Reset (software or hardware) due to interrupt watchdog"; case ESP_RST_PANIC: return "Software reset due to exception/panic"; case ESP_RST_POWERON: return "Reset due to power-on event"; case ESP_RST_SDIO: return "Reset over SDIO"; case ESP_RST_SW: return "Software reset via esp_restart"; case ESP_RST_TASK_WDT: return "Reset due to task watchdog"; case ESP_RST_WDT: return "ESP_RST_WDT"; case ESP_RST_UNKNOWN: default: return "Unknown"; } } static int sysInfo(int argc, char **argv) { esp_chip_info_t info; esp_chip_info(&info); printf("ESP32Console version: %s\n", ESP32CONSOLE_VERSION); #if defined(ARDUINO_ESP32_GIT_DESC) printf("Arduino Core version: %s (%x)\n", XTSTR(ARDUINO_ESP32_GIT_DESC), ARDUINO_ESP32_GIT_VER); #endif printf("ESP-IDF Version: %s\n", ESP.getSdkVersion()); printf("\n"); printf("Chip info:\n"); printf("\tModel: %s\n", ESP.getChipModel()); printf("\tRevison number: %d\n", ESP.getChipRevision()); printf("\tCores: %d\n", ESP.getChipCores()); printf("\tClock: %d MHz\n", ESP.getCpuFreqMHz()); printf("\tFeatures:%s%s%s%s%s\r\n", info.features & CHIP_FEATURE_WIFI_BGN ? " 802.11bgn " : "", info.features & CHIP_FEATURE_BLE ? " BLE " : "", info.features & CHIP_FEATURE_BT ? " BT " : "", info.features & CHIP_FEATURE_EMB_FLASH ? " Embedded-Flash " : " External-Flash ", info.features & CHIP_FEATURE_EMB_PSRAM ? " Embedded-PSRAM" : ""); printf("EFuse MAC: %s\n", mac2String(ESP.getEfuseMac()).c_str()); printf("Flash size: %d MB (mode: %s, speed: %d MHz)\n", ESP.getFlashChipSize() / (1024 * 1024), getFlashModeStr(), ESP.getFlashChipSpeed() / (1024 * 1024)); printf("PSRAM size: %d MB\n", ESP.getPsramSize() / (1024 * 1024)); printf("Sketch size: %d KB\n", ESP.getSketchSize() / (1024)); printf("Sketch MD5: %s\n", ESP.getSketchMD5().c_str()); #ifndef CONFIG_APP_REPRODUCIBLE_BUILD printf("Compilation datetime: " __DATE__ " " __TIME__ "\n"); #endif printf("\nReset reason: %s\n", getResetReasonStr()); printf("\n"); printf("CPU temperature: %.01f °C\n", temperatureRead()); return EXIT_SUCCESS; } static int restart(int argc, char **argv) { printf("Restarting..."); ESP.restart(); return EXIT_SUCCESS; } static int meminfo(int argc, char **argv) { uint32_t free = ESP.getFreeHeap() / 1024; uint32_t total = ESP.getHeapSize() / 1024; uint32_t used = total - free; uint32_t min = ESP.getMinFreeHeap() / 1024; printf("Heap: %u KB free, %u KB used, (%u KB total)\n", free, used, total); printf("Minimum free heap size during uptime was: %u KB\n", min); return EXIT_SUCCESS; } static int date(int argc, char **argv) { bool set_time = false; char *target = nullptr; int c; opterr = 0; // Set timezone from env variable tzset(); while ((c = getopt(argc, argv, "s")) != -1) switch (c) { case 's': set_time = true; break; case '?': printf("Unknown option: %c\n", optopt); return 1; case ':': printf("Missing arg for %c\n", optopt); return 1; } if (optind < argc) { target = argv[optind]; } if (set_time) { if (!target) { fprintf(stderr, "Set option requires an datetime as argument in format '%Y-%m-%d %H:%M:%S' (e.g. 'date -s \"2022-07-13 22:47:00\"'\n"); return 1; } tm t; if (!strptime(target, "%Y-%m-%d %H:%M:%S", &t)) { fprintf(stderr, "Set option requires an datetime as argument in format '%Y-%m-%d %H:%M:%S' (e.g. 'date -s \"2022-07-13 22:47:00\"'\n"); return 1; } timeval tv = { .tv_sec = mktime(&t), .tv_usec = 0}; if (settimeofday(&tv, nullptr)) { fprintf(stderr, "Could not set system time: %s", strerror(errno)); return 1; } time_t tmp = time(nullptr); constexpr int buffer_size = 100; char buffer[buffer_size]; strftime(buffer, buffer_size, "%a %b %e %H:%M:%S %Z %Y", localtime(&tmp)); printf("Time set: %s\n", buffer); return 0; } // If no target was supplied put a default one (similar to coreutils date) if (!target) { target = (char*) "+%a %b %e %H:%M:%S %Z %Y"; } // Ensure the format string is correct if (target[0] != '+') { fprintf(stderr, "Format string must start with an +!\n"); return 1; } // Ignore + by moving pointer one step forward target++; constexpr int buffer_size = 100; char buffer[buffer_size]; time_t t = time(nullptr); strftime(buffer, buffer_size, target, localtime(&t)); printf("%s\n", buffer); return 0; return EXIT_SUCCESS; } namespace ESP32Console::Commands { const ConsoleCommand getRestartCommand() { return ConsoleCommand("restart", &restart, "Restart / Reboot the system"); } const ConsoleCommand getSysInfoCommand() { return ConsoleCommand("sysinfo", &sysInfo, "Shows informations about the system like chip model and ESP-IDF version"); } const ConsoleCommand getMemInfoCommand() { return ConsoleCommand("meminfo", &meminfo, "Shows information about heap usage"); } const ConsoleCommand getDateCommand() { return ConsoleCommand("date", &date, "Shows and modify the system time"); } } ================================================ FILE: src/ESP32Console/Commands/SystemCommands.h ================================================ #pragma once #include "ESP32Console/ConsoleCommand.h" namespace ESP32Console::Commands { const ConsoleCommand getSysInfoCommand(); const ConsoleCommand getRestartCommand(); const ConsoleCommand getMemInfoCommand(); const ConsoleCommand getDateCommand(); }; ================================================ FILE: src/ESP32Console/Commands/VFSCommands.cpp ================================================ #include "./VFSCommands.h" #include #include #include #include #include #include #include #include "ESP32Console/Helpers/PWDHelpers.h" #include "kilo/kilo.h" char *canonicalize_file_name(const char *path); int cat(int argc, char **argv) { if (argc == 1) { fprintf(stderr, "You have to pass at least one file path!\n"); return EXIT_SUCCESS; } for (int n = 1; n < argc; n++) { char filename[PATH_MAX]; // We have manually do resolving of . and .., as VFS does not do it ESP32Console::console_realpath(argv[n], filename); FILE *file = fopen(filename, "r"); if (file == nullptr) { fprintf(stderr, "%s %s : %s\n", argv[0], filename, strerror(errno)); return errno; } int chr; while ((chr = getc(file)) != EOF) fprintf(stdout, "%c", chr); fclose(file); } return EXIT_SUCCESS; } int pwd(int argc, char **argv) { printf("%s\n", ESP32Console::console_getpwd()); return EXIT_SUCCESS; } int cd(int argc, char **argv) { const char *path; if (argc != 2) { path = getenv("HOME"); if (!path) { fprintf(stderr, "No HOME env variable set!\n"); return EXIT_FAILURE; } } else { path = argv[1]; } // Check if target path is a file char resolved[PATH_MAX]; ESP32Console::console_realpath(path, resolved); FILE *file = fopen(resolved, "r"); // If we can open it, then we can not chdir into it. if (file) { fclose(file); fprintf(stderr, "Can not cd into a file!\n"); return 1; } if (ESP32Console::console_chdir(path)) { fprintf(stderr, "Error: %s\n", strerror(errno)); return 1; } const char *pwd = ESP32Console::console_getpwd(); // Check if the new PWD exists, and show a warning if not DIR *dir = opendir(pwd); if (dir) { closedir(dir); } else if (ENOENT == errno) { fprintf(stderr, "Choosen directory maybe does not exists!\n"); } return EXIT_SUCCESS; } int ls(int argc, char **argv) { char *inpath; if (argc == 1) { inpath = (char *)"."; } else if (argc == 2) { inpath = argv[1]; } else { printf("You can pass only one filename!\n"); return 1; } char path[PATH_MAX]; ESP32Console::console_realpath(inpath, path); DIR *dir = opendir(path); if (!dir) { fprintf(stderr, "Could not open filepath: %s\n", strerror(errno)); return 1; } struct dirent *d; while ((d = readdir(dir)) != NULL) { printf("%s\n", d->d_name); } closedir(dir); return EXIT_SUCCESS; } int mv(int argc, char **argv) { if (argc != 3) { fprintf(stderr, "Syntax is mv [ORIGIN] [TARGET]\n"); return 1; } char old_name[PATH_MAX], new_name[PATH_MAX]; // Resolve arguments to full path ESP32Console::console_realpath(argv[1], old_name); ESP32Console::console_realpath(argv[2], new_name); // Do rename if (rename(old_name, new_name)) { printf("Error moving: %s\n", strerror(errno)); return EXIT_FAILURE; } return EXIT_SUCCESS; } int cp(int argc, char **argv) { //TODO: Shows weird error message if (argc != 3) { fprintf(stderr, "Syntax is cp [ORIGIN] [TARGET]\n"); return 1; } char old_name[PATH_MAX], new_name[PATH_MAX]; // Resolve arguments to full path ESP32Console::console_realpath(argv[1], old_name); ESP32Console::console_realpath(argv[2], new_name); // Do copy FILE *origin = fopen(old_name, "r"); if (!origin) { fprintf(stderr, "Error opening origin file: %s\n", strerror(errno)); return 1; } FILE *target = fopen(new_name, "w"); if (!target) { fclose(origin); fprintf(stderr, "Error opening target file: %s\n", strerror(errno)); return 1; } int buffer; // Clear existing errors auto error = errno; while ((buffer = getc(origin)) != EOF) { if(fputc(buffer, target) == EOF) { fprintf(stderr, "Error writing: %s\n", strerror(errno)); fclose(origin); fclose(target); return 1; } } error = errno; if (error && !feof(origin)) { fprintf(stderr, "Error copying: %s\n", strerror(error)); fclose(origin); fclose(target); return 1; } fclose(origin); fclose(target); return EXIT_SUCCESS; } int rm(int argc, char **argv) { if (argc != 2) { fprintf(stderr, "You have to pass exactly one file. Syntax rm [FIILE]\n"); return EXIT_SUCCESS; } char filename[PATH_MAX]; ESP32Console::console_realpath(argv[1], filename); if(unlink(filename)) { fprintf(stderr, "Error deleting %s: %s\n", filename, strerror(errno)); } return EXIT_SUCCESS; } int rmdir(int argc, char **argv) { if (argc != 2) { fprintf(stderr, "You have to pass exactly one file. Syntax rmdir [FIILE]\n"); return EXIT_SUCCESS; } char filename[PATH_MAX]; ESP32Console::console_realpath(argv[1], filename); if(rmdir(filename)) { fprintf(stderr, "Error deleting %s: %s\n", filename, strerror(errno)); } return EXIT_SUCCESS; } namespace ESP32Console::Commands { const ConsoleCommand getCatCommand() { return ConsoleCommand("cat", &cat, "Show the content of one or more files."); } const ConsoleCommand getPWDCommand() { return ConsoleCommand("pwd", &pwd, "Show the current working dir"); } const ConsoleCommand getCDCommand() { return ConsoleCommand("cd", &cd, "Change the working directory"); } const ConsoleCommand getLsCommand() { return ConsoleCommand("ls", &ls, "List the contents of the given path"); } const ConsoleCommand getMvCommand() { return ConsoleCommand("mv", &mv, "Move the given file to another place or name"); } const ConsoleCommand getCPCommand() { return ConsoleCommand("cp", &cp, "Copy the given file to another place or name"); } const ConsoleCommand getRMCommand() { return ConsoleCommand("rm", &rm, "Permanenty deletes the given file."); } const ConsoleCommand getRMDirCommand() { return ConsoleCommand("rmdir", &rmdir, "Permanenty deletes the given folder. Folder must be empty!"); } const ConsoleCommand getEditCommand() { return ConsoleCommand("edit", &ESP32Console::Kilo::kilo, "Edit files"); } } ================================================ FILE: src/ESP32Console/Commands/VFSCommands.h ================================================ #pragma once #include "ESP32Console/ConsoleCommand.h" namespace ESP32Console::Commands { const ConsoleCommand getCatCommand(); const ConsoleCommand getPWDCommand(); const ConsoleCommand getCDCommand(); const ConsoleCommand getLsCommand(); const ConsoleCommand getMvCommand(); const ConsoleCommand getCPCommand(); const ConsoleCommand getRMCommand(); const ConsoleCommand getRMDirCommand(); const ConsoleCommand getEditCommand(); } ================================================ FILE: src/ESP32Console/Console.cpp ================================================ #include "./Console.h" #include "soc/soc_caps.h" #include "esp_err.h" #include "esp_idf_version.h" #include "ESP32Console/Commands/CoreCommands.h" #include "ESP32Console/Commands/SystemCommands.h" #include "ESP32Console/Commands/NetworkCommands.h" #include "ESP32Console/Commands/VFSCommands.h" #include "ESP32Console/Commands/GPIOCommands.h" #include "driver/uart.h" #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0) #include "driver/uart_vfs.h" #else #include "esp_vfs_dev.h" #endif #include "linenoise/linenoise.h" #include "ESP32Console/Helpers/PWDHelpers.h" #include "ESP32Console/Helpers/InputParser.h" static const char *TAG = "ESP32Console"; using namespace ESP32Console::Commands; namespace ESP32Console { void Console::registerCoreCommands() { registerCommand(getClearCommand()); registerCommand(getHistoryCommand()); registerCommand(getEchoCommand()); registerCommand(getSetMultilineCommand()); registerCommand(getEnvCommand()); registerCommand(getDeclareCommand()); } void Console::registerSystemCommands() { registerCommand(getSysInfoCommand()); registerCommand(getRestartCommand()); registerCommand(getMemInfoCommand()); registerCommand(getDateCommand()); } void ESP32Console::Console::registerNetworkCommands() { registerCommand(getPingCommand()); registerCommand(getIpconfigCommand()); } void Console::registerVFSCommands() { registerCommand(getCatCommand()); registerCommand(getCDCommand()); registerCommand(getPWDCommand()); registerCommand(getLsCommand()); registerCommand(getMvCommand()); registerCommand(getCPCommand()); registerCommand(getRMCommand()); registerCommand(getRMDirCommand()); registerCommand(getEditCommand()); } void Console::registerGPIOCommands() { registerCommand(getPinModeCommand()); registerCommand(getDigitalReadCommand()); registerCommand(getDigitalWriteCommand()); registerCommand(getAnalogReadCommand()); } void Console::beginCommon() { /* Tell linenoise where to get command completions and hints */ linenoiseSetCompletionCallback(&esp_console_get_completion); linenoiseSetHintsCallback((linenoiseHintsCallback *)&esp_console_get_hint); /* Set command history size */ linenoiseHistorySetMaxLen(max_history_len_); /* Set command maximum length */ linenoiseSetMaxLineLen(max_cmdline_len_); // Load history if defined if (history_save_path_) { linenoiseHistoryLoad(history_save_path_); } // Register core commands like echo esp_console_register_help_command(); registerCoreCommands(); } void Console::begin(int baud, int rxPin, int txPin, uart_port_t channel) { log_d("Initialize console"); if (channel >= SOC_UART_NUM) { log_e("Serial number is invalid, please use numers from 0 to %u", SOC_UART_NUM - 1); return; } this->uart_channel_ = channel; //Reinit the UART driver if the channel was already in use if (uart_is_driver_installed(channel)) { uart_driver_delete(channel); } /* Drain stdout before reconfiguring it */ fflush(stdout); fsync(fileno(stdout)); /* Disable buffering on stdin */ setvbuf(stdin, NULL, _IONBF, 0); /* Minicom, screen, idf_monitor send CR when ENTER key is pressed */ #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0) uart_vfs_dev_port_set_rx_line_endings(channel, ESP_LINE_ENDINGS_CR); /* Move the caret to the beginning of the next line on '\n' */ uart_vfs_dev_port_set_tx_line_endings(channel, ESP_LINE_ENDINGS_CRLF); #else esp_vfs_dev_uart_port_set_rx_line_endings(channel, ESP_LINE_ENDINGS_CR); /* Move the caret to the beginning of the next line on '\n' */ esp_vfs_dev_uart_port_set_tx_line_endings(channel, ESP_LINE_ENDINGS_CRLF); #endif /* Configure UART. Note that REF_TICK is used so that the baud rate remains * correct while APB frequency is changing in light sleep mode. */ const uart_config_t uart_config = { .baud_rate = baud, .data_bits = UART_DATA_8_BITS, .parity = UART_PARITY_DISABLE, .stop_bits = UART_STOP_BITS_1, #if SOC_UART_SUPPORT_REF_TICK .source_clk = UART_SCLK_REF_TICK, #elif SOC_UART_SUPPORT_XTAL_CLK .source_clk = UART_SCLK_XTAL, #endif }; ESP_ERROR_CHECK(uart_param_config(channel, &uart_config)); // Set the correct pins for the UART of needed if (rxPin > 0 || txPin > 0) { if (rxPin < 0 || txPin < 0) { log_e("Both rxPin and txPin has to be passed!"); } uart_set_pin(channel, txPin, rxPin, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE); } /* Install UART driver for interrupt-driven reads and writes */ ESP_ERROR_CHECK(uart_driver_install(channel, 256, 0, 0, NULL, 0)); /* Tell VFS to use UART driver */ #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0) uart_vfs_dev_use_driver(channel); #else esp_vfs_dev_uart_use_driver(channel); #endif esp_console_config_t console_config = { .max_cmdline_length = max_cmdline_len_, .max_cmdline_args = max_cmdline_args_, .hint_color = 333333}; ESP_ERROR_CHECK(esp_console_init(&console_config)); beginCommon(); // Start REPL task if (xTaskCreate(&Console::repl_task, "console_repl", 4096, this, 2, &task_) != pdTRUE) { log_e("Could not start REPL task!"); } } static void resetAfterCommands() { //Reset all global states a command could change //Reset getopt parameters optind = 0; } void Console::repl_task(void *args) { Console const &console = *(static_cast(args)); /* Change standard input and output of the task if the requested UART is * NOT the default one. This block will replace stdin, stdout and stderr. * We have to do this in the repl task (not in the begin, as these settings are only valid for the current task) */ if (console.uart_channel_ != CONFIG_ESP_CONSOLE_UART_NUM) { char path[13] = {0}; snprintf(path, 13, "/dev/uart/%d", console.uart_channel_); stdin = fopen(path, "r"); stdout = fopen(path, "w"); stderr = stdout; } setvbuf(stdin, NULL, _IONBF, 0); /* This message shall be printed here and not earlier as the stdout * has just been set above. */ printf("\r\n" "Type 'help' to get the list of commands.\r\n" "Use UP/DOWN arrows to navigate through command history.\r\n" "Press TAB when typing command name to auto-complete.\r\n"); // Probe terminal status int probe_status = linenoiseProbe(); if (probe_status) { linenoiseSetDumbMode(1); } if (linenoiseIsDumbMode()) { printf("\r\n" "Your terminal application does not support escape sequences.\n\n" "Line editing and history features are disabled.\n\n" "On Windows, try using Putty instead.\r\n"); } linenoiseSetMaxLineLen(console.max_cmdline_len_); while (1) { String prompt = console.prompt_; // Insert current PWD into prompt if needed prompt.replace("%pwd%", console_getpwd()); char *line = linenoise(prompt.c_str()); if (line == NULL) { ESP_LOGD(TAG, "empty line"); /* Ignore empty lines */ continue; } log_v("Line received from linenoise: %s\n", line); /* Add the command to the history */ linenoiseHistoryAdd(line); /* Save command history to filesystem */ if (console.history_save_path_) { linenoiseHistorySave(console.history_save_path_); } //Interpolate the input line String interpolated_line = interpolateLine(line); log_v("Interpolated line: %s\n", interpolated_line.c_str()); /* Try to run the command */ int ret; esp_err_t err = esp_console_run(interpolated_line.c_str(), &ret); //Reset global state resetAfterCommands(); if (err == ESP_ERR_NOT_FOUND) { printf("Unrecognized command\n"); } else if (err == ESP_ERR_INVALID_ARG) { // command was empty } else if (err == ESP_OK && ret != ESP_OK) { printf("Command returned non-zero error code: 0x%x (%s)\n", ret, esp_err_to_name(ret)); } else if (err != ESP_OK) { printf("Internal error: %s\n", esp_err_to_name(err)); } /* linenoise allocates line buffer on the heap, so need to free it */ linenoiseFree(line); } ESP_LOGD(TAG, "REPL task ended"); vTaskDelete(NULL); } void Console::end() { } }; ================================================ FILE: src/ESP32Console/Console.h ================================================ #pragma once #if !defined(ESP32) #error This library depends on ESP-IDF and only works on ESP32! #endif #include "esp_console.h" #include "Arduino.h" #include "./ConsoleCommandBase.h" #include "freertos/task.h" #include "linenoise/linenoise.h" #include "driver/uart.h" namespace ESP32Console { class Console { private: const char *prompt_ = "ESP32> "; const uint32_t task_priority_; const BaseType_t task_stack_size_; uint16_t max_history_len_ = 40; const char* history_save_path_ = nullptr; const size_t max_cmdline_len_; const size_t max_cmdline_args_; uart_port_t uart_channel_; TaskHandle_t task_; static void repl_task(void *args); void beginCommon(); public: /** * @brief Create a new ESP32Console with the default parameters */ Console(const uint32_t task_stack_size = 4096, const BaseType_t task_priority = 2, int max_cmdline_len = 256, int max_cmdline_args = 32) : task_priority_(task_priority), task_stack_size_(task_stack_size), max_cmdline_len_(max_cmdline_len), max_cmdline_args_(max_cmdline_args){}; ~Console() { vTaskDelete(task_); end(); } /** * @brief Register the given command, using the raw ESP-IDF structure. * * @param cmd The command that should be registered * @return Return true, if the registration was successfull, false if not. */ bool registerCommand(const esp_console_cmd_t *cmd) { log_v("Registering new command %s", cmd->command); auto code = esp_console_cmd_register(cmd); if (code != ESP_OK) { log_e("Error registering command (Reason %s)", esp_err_to_name(code)); return false; } return true; } /** * @brief Register the given command * * @param cmd The command that should be registered * @return true If the command was registered successful. * @return false If the command was not registered because of an error. */ bool registerCommand(const ConsoleCommandBase &cmd) { auto c = cmd.toCommandStruct(); return registerCommand(&c); } /** * @brief Registers the given command * * @param command The name under which the command can be called (e.g. "ls"). Must not contain spaces. * @param func A pointer to the function which should be run, when this command is called * @param help A text shown in output of "help" command describing this command. When empty it is not shown in help. * @param hint A text describing the usage of the command in help output * @return true If the command was registered successful. * @return false If the command was not registered because of an error. */ bool registerCommand(const char *command, esp_console_cmd_func_t func, const char *help, const char *hint = "") { const esp_console_cmd_t cmd = { .command = command, .help = help, .hint = hint, .func = func, .argtable = nullptr }; return registerCommand(&cmd); }; void registerCoreCommands(); void registerSystemCommands(); void registerNetworkCommands(); void registerVFSCommands(); void registerGPIOCommands(); /** * @brief Set the command prompt. Default is "ESP32>". * * @param prompt */ void setPrompt(const char *prompt) { prompt_ = prompt; }; /** * @brief Set the History Max Length object * * @param max_length */ void setHistoryMaxLength(uint16_t max_length) { max_history_len_ = max_length; linenoiseHistorySetMaxLen(max_length); } /** * @brief Enable saving of command history, which makes history persistent over resets. SPIFF need to be enabled, or you need to pass the filename to use. * * @param history_save_path The file which will be used to save command history. Set to nullptr to disable persistent saving */ void enablePersistentHistory(const char *history_save_path = "/spiffs/.history.txt") { history_save_path_ = history_save_path; }; /** * @brief Starts the console. Similar to the Serial.begin() function * * @param baud The baud rate with which the console should work. Recommended: 115200 * @param rxPin The pin to use for RX * @param txPin The pin to use for TX * @param channel The UART port to use */ void begin(int baud, int rxPin = -1, int txPin = -1, uart_port_t channel = UART_NUM_0); /** * @brief Starts the console. Overload for backward compatibility with uint8_t channel argument. * * @param baud The baud rate with which the console should work. Recommended: 115200 * @param rxPin The pin to use for RX * @param txPin The pin to use for TX * @param channel The number of the UART to use (cast to uart_port_t) */ void begin(int baud, int rxPin, int txPin, uint8_t channel) { begin(baud, rxPin, txPin, static_cast(channel)); } void end(); }; }; ================================================ FILE: src/ESP32Console/ConsoleCommand.h ================================================ #pragma once #include "./ConsoleCommandBase.h" namespace ESP32Console { /** * @brief A class for registering custom console commands via a passed function pointer. * */ class ConsoleCommand : public ConsoleCommandBase { protected: const char *hint_; public: /** * @brief Creates a new ConsoleCommand object with the given parameters. * * @param command The name under which the command will be called (e.g. "ls"). Must not contain spaces. * @param func The pointer to the function which is run if this function is called. Takes two paramaters argc and argv, similar to a classical C program. * @param help The text shown in "help" output for this command. If set to empty string, then the command is not shown in help. * @param hint A hint explaining the parameters in help output */ ConsoleCommand(const char *command, esp_console_cmd_func_t func, const char* help, const char* hint = "") { command_ = command; func_ = func; help_= help; hint_ = hint;}; const esp_console_cmd_t toCommandStruct() const override { const esp_console_cmd_t cmd = { .command = command_, .help = help_, .hint = hint_, .func = func_, .argtable = nullptr }; return cmd; } }; }; ================================================ FILE: src/ESP32Console/ConsoleCommandBase.h ================================================ #pragma once #include "esp_console.h" namespace ESP32Console { class ConsoleCommandBase { protected: const char *command_; const char *help_; esp_console_cmd_func_t func_; public: /** * @brief Get the command name * * @return const char* */ const char* getCommand() { return command_; }; const char* getHelp() { return help_; }; virtual const esp_console_cmd_t toCommandStruct() const = 0; }; }; ================================================ FILE: src/ESP32Console/ConsoleCommandD.cpp ================================================ #include "./ConsoleCommandD.h" #include "Arduino.h" const static char *TAG = "ConsoleCommandD"; namespace ESP32Console { std::unordered_map ConsoleCommandD::registry_ = std::unordered_map(); int ConsoleCommandD::delegateResolver(int argc, char **argv) { // Retrieve ConsoleCommandD from registry auto it = registry_.find(argv[0]); if (it == registry_.end()) { log_e("Could not resolve the delegated function call!"); return 1; } delegateFunc command = it->second; int code = 0; try { return command(argc, argv); } catch (const std::exception &err) { printf("%s", err.what()); printf("\n"); return 1; } } } ================================================ FILE: src/ESP32Console/ConsoleCommandD.h ================================================ #pragma once #include "./ConsoleCommand.h" #include "esp_console.h" #include #include #include #include namespace ESP32Console { using delegateFunc = std::function; /** * @brief A class for registering custom console commands via delegate function element. The difference to ConsoleCommand is that you can pass a std::function object instead of a function pointer directly. * This allows for use of lambda functions. The disadvantage is that we need more heap, as we have to save the delegate function objects in a map. * */ class ConsoleCommandD : public ConsoleCommand { protected: delegateFunc delegateFn_; static int delegateResolver(int argc, char **argv); public: static std::unordered_map registry_; ConsoleCommandD(const char *command, delegateFunc func, const char* help, const char* hint = ""): ConsoleCommand(command, &delegateResolver, help, hint), delegateFn_(func) {}; const esp_console_cmd_t toCommandStruct() const override { const esp_console_cmd_t cmd = { .command = command_, .help = help_, .hint = hint_, .func = func_, .argtable = nullptr }; // When the command gets registered add it to our map, so we can access it later to resolve the delegated function call registry_.insert({std::string(command_), std::move(delegateFn_)}); return cmd; } delegateFunc &getDelegateFunction() { return delegateFn_; } }; } ================================================ FILE: src/ESP32Console/Helpers/InputParser.cpp ================================================ #include "./InputParser.h" #include namespace ESP32Console { String interpolateLine(const char *in_) { String in(in_); String out = in; // Add a space at end of line, this does not change anything for our consoleLine and makes parsing easier in = in + " "; // Interpolate each $ with the env variable if existing. If $ is the first character in a line it is not interpolated int var_index = 1; while ((var_index = in.indexOf("$", var_index + 1)) > 0) { /** * Extract the possible env variable */ int variable_start = var_index + 1; // If the char after $ is a { we look for an closing }. Otherwise we just look for an space char delimiter = ' '; if (in.charAt(variable_start) == '{') { // Our variable starts then at the character after ${ variable_start++; } int variable_end = in.indexOf(delimiter, variable_start + 1); // If delimiter not found look for next possible env variable if (variable_end == -1) { continue; } String env_var = in.substring(variable_start, variable_end); env_var.trim(); // Depending on whether this is an variable string, we have to include the next character String replace_target = in.substring(var_index, delimiter == '}' ? variable_end + 1 : variable_end); // Check if we have an env with this name, then replace it const char *value = getenv(env_var.c_str()); if (value) { out.replace(replace_target.c_str(), value); } } return out.c_str(); } } ================================================ FILE: src/ESP32Console/Helpers/InputParser.h ================================================ #pragma once #include namespace ESP32Console { /** * @brief Interpolate the given line using environment variables. $VAR and ${ENV} are replaced by the representive values of the environment variables. * * @param in * @return String */ String interpolateLine(const char *in); } ================================================ FILE: src/ESP32Console/Helpers/PWDHelpers.cpp ================================================ #include "ESP32Console/Helpers/PWDHelpers.h" #include #include #include namespace ESP32Console { constexpr char *PWD_DEFAULT = (char*) "/"; const char *console_getpwd() { char *pwd = getenv("PWD"); if (pwd) { // If we have defined a PWD value, return it return pwd; } // Otherwise set a default one setenv("PWD", PWD_DEFAULT, 1); return PWD_DEFAULT; } const char *console_realpath(const char *path, char *resolvedPath) { String in = String(path); String pwd = String(console_getpwd()); String result; // If path is not absolute we prepend our pwd if (!in.startsWith("/")) { result = pwd + "/" + in; } else { result = in; } realpath(result.c_str(), resolvedPath); return resolvedPath; } int console_chdir(const char *path) { char buffer[PATH_MAX + 2]; console_realpath(path, buffer); size_t buffer_len = strlen(buffer); //If path does not end with slash, add it. if(buffer[buffer_len - 1] != '/') { buffer[buffer_len] = '/'; buffer[buffer_len + 1] = '\0'; } setenv("PWD", buffer, 1); return 0; } } ================================================ FILE: src/ESP32Console/Helpers/PWDHelpers.h ================================================ #pragma once namespace ESP32Console { /** * @brief Returns the current console process working dir * * @return const char* */ const char *console_getpwd(); /** * @brief Resolves the given path using the console process working dir * * @return const char* */ const char *console_realpath(const char *path, char *resolvedPath); int console_chdir(const char *path); } ================================================ FILE: src/ESP32Console/OptionsConsoleCommand.cpp ================================================ #include "./OptionsConsoleCommand.h" #include "Arduino.h" const static char *TAG = "ArgParseCommand"; namespace ESP32Console { std::unordered_map OptionsConsoleCommand::registry_ = std::unordered_map(); int OptionsConsoleCommand::delegateResolver(int argc, char **argv) { // Retrieve ArgParserCommand from registry auto it = registry_.find(argv[0]); if (it == registry_.end()) { log_e("Could not resolve the delegated function call!"); return 1; } auto command = it->second; int code = 0; try { auto options = command.options; auto result = options.parse(argc, argv); //Print help on --help argument if (result.count("help")) { printf(options.help().c_str()); printf("\n"); return EXIT_SUCCESS; } if (command.getVersion() && result.count("version")) { printf("Version: %s\n", command.getVersion()); return EXIT_SUCCESS; } return command.delegateFn_(argc, argv, result, options); } catch (const std::exception &err) { printf(err.what()); printf("\n"); return EXIT_FAILURE; } } } ================================================ FILE: src/ESP32Console/OptionsConsoleCommand.h ================================================ #pragma once #include "./ConsoleCommandBase.h" #include "esp_console.h" //This define is important, otherwise we get very high memory usage from regex #define CXXOPTS_NO_REGEX 1 #define CXXOPTS_NO_RTTI 1 #include "cxxopts/cxxopts.hpp" #include #include #include namespace ESP32Console { using cxxopts::Options; using cxxopts::ParseResult; using argParseFunc = std::function; class OptionsConsoleCommand : public ConsoleCommandBase { protected: argParseFunc delegateFn_; const char *hint_; const char *version_; static int delegateResolver(int argc, char **argv); public: Options options; static std::unordered_map registry_; OptionsConsoleCommand(const char *command, argParseFunc func, const char *help, const char* version = nullptr, const char *hint = nullptr): options(command, help) { command_ = command; help_ = help; version_ = version; if (hint) { hint_ = hint; } else { hint_ = "Use --help option of command for more info"; } //Add an option options.add_options() ("help", "Show help", cxxopts::value()->default_value("false")) ; if (version_) { options.add_options() ("version", "Show version number of this command", cxxopts::value()->default_value("false")) ; } delegateFn_ = func; func_ = &delegateResolver; } const esp_console_cmd_t toCommandStruct() const override { const esp_console_cmd_t cmd = { .command = command_, .help = help_, .hint = hint_, .func = func_, .argtable = nullptr }; // When the command gets registered add it to our map, so we can access it later to resolve the delegated function call registry_.insert({std::string(command_), std::move(*this)}); return cmd; } argParseFunc &getDelegateFunction() { return delegateFn_; } const char* getVersion() const {return version_;}; }; } ================================================ FILE: src/ESP32Console.h ================================================ #pragma once #if !defined(ESP32) #error This library depends on ESP-IDF and only works on ESP32! #endif #define ESP32CONSOLE_VERSION "1.3.0" #include "ESP32Console/Console.h" #include "ESP32Console/ConsoleCommand.h" #include "ESP32Console/ConsoleCommandD.h" #include "ESP32Console/OptionsConsoleCommand.h" ================================================ FILE: src/cxxopts/cxxopts.hpp ================================================ /* Copyright (c) 2014, 2015, 2016, 2017 Jarryd Beck Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #ifndef CXXOPTS_HPP_INCLUDED #define CXXOPTS_HPP_INCLUDED #include #include #include //#include #include #include #include #include #include #include #include #include #include #include #include #if defined(__GNUC__) && !defined(__clang__) # if (__GNUC__ * 10 + __GNUC_MINOR__) < 49 # define CXXOPTS_NO_REGEX true # endif #endif #ifndef CXXOPTS_NO_REGEX # include #endif // CXXOPTS_NO_REGEX // Nonstandard before C++17, which is coincidentally what we also need for #ifdef __has_include # if __has_include() # include # ifdef __cpp_lib_optional # define CXXOPTS_HAS_OPTIONAL # endif # endif #endif #if __cplusplus >= 201603L #define CXXOPTS_NODISCARD [[nodiscard]] #else #define CXXOPTS_NODISCARD #endif #ifndef CXXOPTS_VECTOR_DELIMITER #define CXXOPTS_VECTOR_DELIMITER ',' #endif #define CXXOPTS__VERSION_MAJOR 3 #define CXXOPTS__VERSION_MINOR 0 #define CXXOPTS__VERSION_PATCH 0 #if (__GNUC__ < 10 || (__GNUC__ == 10 && __GNUC_MINOR__ < 1)) && __GNUC__ >= 6 #define CXXOPTS_NULL_DEREF_IGNORE #endif namespace cxxopts { static constexpr struct { uint8_t major, minor, patch; } version = { CXXOPTS__VERSION_MAJOR, CXXOPTS__VERSION_MINOR, CXXOPTS__VERSION_PATCH }; } // namespace cxxopts //when we ask cxxopts to use Unicode, help strings are processed using ICU, //which results in the correct lengths being computed for strings when they //are formatted for the help output //it is necessary to make sure that can be found by the //compiler, and that icu-uc is linked in to the binary. #ifdef CXXOPTS_USE_UNICODE #include namespace cxxopts { using String = icu::UnicodeString; inline String toLocalString(std::string s) { return icu::UnicodeString::fromUTF8(std::move(s)); } #if defined(__GNUC__) // GNU GCC with -Weffc++ will issue a warning regarding the upcoming class, we want to silence it: // warning: base class 'class std::enable_shared_from_this' has accessible non-virtual destructor #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wnon-virtual-dtor" #pragma GCC diagnostic ignored "-Weffc++" // This will be ignored under other compilers like LLVM clang. #endif class UnicodeStringIterator : public std::iterator { public: UnicodeStringIterator(const icu::UnicodeString* string, int32_t pos) : s(string) , i(pos) { } value_type operator*() const { return s->char32At(i); } bool operator==(const UnicodeStringIterator& rhs) const { return s == rhs.s && i == rhs.i; } bool operator!=(const UnicodeStringIterator& rhs) const { return !(*this == rhs); } UnicodeStringIterator& operator++() { ++i; return *this; } UnicodeStringIterator operator+(int32_t v) { return UnicodeStringIterator(s, i + v); } private: const icu::UnicodeString* s; int32_t i; }; #if defined(__GNUC__) #pragma GCC diagnostic pop #endif inline String& stringAppend(String&s, String a) { return s.append(std::move(a)); } inline String& stringAppend(String& s, size_t n, UChar32 c) { for (size_t i = 0; i != n; ++i) { s.append(c); } return s; } template String& stringAppend(String& s, Iterator begin, Iterator end) { while (begin != end) { s.append(*begin); ++begin; } return s; } inline size_t stringLength(const String& s) { return s.length(); } inline std::string toUTF8String(const String& s) { std::string result; s.toUTF8String(result); return result; } inline bool empty(const String& s) { return s.isEmpty(); } } namespace std { inline cxxopts::UnicodeStringIterator begin(const icu::UnicodeString& s) { return cxxopts::UnicodeStringIterator(&s, 0); } inline cxxopts::UnicodeStringIterator end(const icu::UnicodeString& s) { return cxxopts::UnicodeStringIterator(&s, s.length()); } } //ifdef CXXOPTS_USE_UNICODE #else namespace cxxopts { using String = std::string; template T toLocalString(T&& t) { return std::forward(t); } inline size_t stringLength(const String& s) { return s.length(); } inline String& stringAppend(String&s, const String& a) { return s.append(a); } inline String& stringAppend(String& s, size_t n, char c) { return s.append(n, c); } template String& stringAppend(String& s, Iterator begin, Iterator end) { return s.append(begin, end); } template std::string toUTF8String(T&& t) { return std::forward(t); } inline bool empty(const std::string& s) { return s.empty(); } } // namespace cxxopts //ifdef CXXOPTS_USE_UNICODE #endif namespace cxxopts { namespace { #ifdef _WIN32 const std::string LQUOTE("\'"); const std::string RQUOTE("\'"); #else const std::string LQUOTE("‘"); const std::string RQUOTE("’"); #endif } // namespace #if defined(__GNUC__) // GNU GCC with -Weffc++ will issue a warning regarding the upcoming class, we want to silence it: // warning: base class 'class std::enable_shared_from_this' has accessible non-virtual destructor #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wnon-virtual-dtor" #pragma GCC diagnostic ignored "-Weffc++" // This will be ignored under other compilers like LLVM clang. #endif class Value : public std::enable_shared_from_this { public: virtual ~Value() = default; virtual std::shared_ptr clone() const = 0; virtual void parse(const std::string& text) const = 0; virtual void parse() const = 0; virtual bool has_default() const = 0; virtual bool is_container() const = 0; virtual bool has_implicit() const = 0; virtual std::string get_default_value() const = 0; virtual std::string get_implicit_value() const = 0; virtual std::shared_ptr default_value(const std::string& value) = 0; virtual std::shared_ptr implicit_value(const std::string& value) = 0; virtual std::shared_ptr no_implicit_value() = 0; virtual bool is_boolean() const = 0; }; #if defined(__GNUC__) #pragma GCC diagnostic pop #endif class OptionException : public std::exception { public: explicit OptionException(std::string message) : m_message(std::move(message)) { } CXXOPTS_NODISCARD const char* what() const noexcept override { return m_message.c_str(); } private: std::string m_message; }; class OptionSpecException : public OptionException { public: explicit OptionSpecException(const std::string& message) : OptionException(message) { } }; class OptionParseException : public OptionException { public: explicit OptionParseException(const std::string& message) : OptionException(message) { } }; class option_exists_error : public OptionSpecException { public: explicit option_exists_error(const std::string& option) : OptionSpecException("Option " + LQUOTE + option + RQUOTE + " already exists") { } }; class invalid_option_format_error : public OptionSpecException { public: explicit invalid_option_format_error(const std::string& format) : OptionSpecException("Invalid option format " + LQUOTE + format + RQUOTE) { } }; class option_syntax_exception : public OptionParseException { public: explicit option_syntax_exception(const std::string& text) : OptionParseException("Argument " + LQUOTE + text + RQUOTE + " starts with a - but has incorrect syntax") { } }; class option_not_exists_exception : public OptionParseException { public: explicit option_not_exists_exception(const std::string& option) : OptionParseException("Option " + LQUOTE + option + RQUOTE + " does not exist") { } }; class missing_argument_exception : public OptionParseException { public: explicit missing_argument_exception(const std::string& option) : OptionParseException( "Option " + LQUOTE + option + RQUOTE + " is missing an argument" ) { } }; class option_requires_argument_exception : public OptionParseException { public: explicit option_requires_argument_exception(const std::string& option) : OptionParseException( "Option " + LQUOTE + option + RQUOTE + " requires an argument" ) { } }; class option_not_has_argument_exception : public OptionParseException { public: option_not_has_argument_exception ( const std::string& option, const std::string& arg ) : OptionParseException( "Option " + LQUOTE + option + RQUOTE + " does not take an argument, but argument " + LQUOTE + arg + RQUOTE + " given" ) { } }; class option_not_present_exception : public OptionParseException { public: explicit option_not_present_exception(const std::string& option) : OptionParseException("Option " + LQUOTE + option + RQUOTE + " not present") { } }; class option_has_no_value_exception : public OptionException { public: explicit option_has_no_value_exception(const std::string& option) : OptionException( !option.empty() ? ("Option " + LQUOTE + option + RQUOTE + " has no value") : "Option has no value") { } }; class argument_incorrect_type : public OptionParseException { public: explicit argument_incorrect_type ( const std::string& arg ) : OptionParseException( "Argument " + LQUOTE + arg + RQUOTE + " failed to parse" ) { } }; class option_required_exception : public OptionParseException { public: explicit option_required_exception(const std::string& option) : OptionParseException( "Option " + LQUOTE + option + RQUOTE + " is required but not present" ) { } }; template void throw_or_mimic(const std::string& text) { static_assert(std::is_base_of::value, "throw_or_mimic only works on std::exception and " "deriving classes"); #ifndef CXXOPTS_NO_EXCEPTIONS // If CXXOPTS_NO_EXCEPTIONS is not defined, just throw throw T{text}; #else // Otherwise manually instantiate the exception, print what() to stderr, // and exit T exception{text}; std::cerr << exception.what() << std::endl; std::exit(EXIT_FAILURE); #endif } namespace values { namespace parser_tool { struct IntegerDesc { std::string negative = ""; std::string base = ""; std::string value = ""; }; struct ArguDesc { std::string arg_name = ""; bool grouping = false; bool set_value = false; std::string value = ""; }; #ifdef CXXOPTS_NO_REGEX inline IntegerDesc SplitInteger(const std::string &text) { if (text.empty()) { throw_or_mimic(text); } IntegerDesc desc; const char *pdata = text.c_str(); if (*pdata == '-') { pdata += 1; desc.negative = "-"; } if (strncmp(pdata, "0x", 2) == 0) { pdata += 2; desc.base = "0x"; } if (*pdata != '\0') { desc.value = std::string(pdata); } else { throw_or_mimic(text); } return desc; } inline bool IsTrueText(const std::string &text) { const char *pdata = text.c_str(); if (*pdata == 't' || *pdata == 'T') { pdata += 1; if (strncmp(pdata, "rue\0", 4) == 0) { return true; } } else if (strncmp(pdata, "1\0", 2) == 0) { return true; } return false; } inline bool IsFalseText(const std::string &text) { const char *pdata = text.c_str(); if (*pdata == 'f' || *pdata == 'F') { pdata += 1; if (strncmp(pdata, "alse\0", 5) == 0) { return true; } } else if (strncmp(pdata, "0\0", 2) == 0) { return true; } return false; } inline std::pair SplitSwitchDef(const std::string &text) { std::string short_sw, long_sw; const char *pdata = text.c_str(); if (isalnum(*pdata) && *(pdata + 1) == ',') { short_sw = std::string(1, *pdata); pdata += 2; } while (*pdata == ' ') { pdata += 1; } if (isalnum(*pdata)) { const char *store = pdata; pdata += 1; while (isalnum(*pdata) || *pdata == '-' || *pdata == '_') { pdata += 1; } if (*pdata == '\0') { long_sw = std::string(store, pdata - store); } else { throw_or_mimic(text); } } return std::pair(short_sw, long_sw); } inline ArguDesc ParseArgument(const char *arg, bool &matched) { ArguDesc argu_desc; const char *pdata = arg; matched = false; if (strncmp(pdata, "--", 2) == 0) { pdata += 2; if (isalnum(*pdata)) { argu_desc.arg_name.push_back(*pdata); pdata += 1; while (isalnum(*pdata) || *pdata == '-' || *pdata == '_') { argu_desc.arg_name.push_back(*pdata); pdata += 1; } if (argu_desc.arg_name.length() > 1) { if (*pdata == '=') { argu_desc.set_value = true; pdata += 1; if (*pdata != '\0') { argu_desc.value = std::string(pdata); } matched = true; } else if (*pdata == '\0') { matched = true; } } } } else if (strncmp(pdata, "-", 1) == 0) { pdata += 1; argu_desc.grouping = true; while (isalnum(*pdata)) { argu_desc.arg_name.push_back(*pdata); pdata += 1; } matched = !argu_desc.arg_name.empty() && *pdata == '\0'; } return argu_desc; } #else // CXXOPTS_NO_REGEX namespace { std::basic_regex integer_pattern ("(-)?(0x)?([0-9a-zA-Z]+)|((0x)?0)"); std::basic_regex truthy_pattern ("(t|T)(rue)?|1"); std::basic_regex falsy_pattern ("(f|F)(alse)?|0"); std::basic_regex option_matcher ("--([[:alnum:]][-_[:alnum:]]+)(=(.*))?|-([[:alnum:]]+)"); std::basic_regex option_specifier ("(([[:alnum:]]),)?[ ]*([[:alnum:]][-_[:alnum:]]*)?"); } // namespace inline IntegerDesc SplitInteger(const std::string &text) { std::smatch match; std::regex_match(text, match, integer_pattern); if (match.length() == 0) { throw_or_mimic(text); } IntegerDesc desc; desc.negative = match[1]; desc.base = match[2]; desc.value = match[3]; if (match.length(4) > 0) { desc.base = match[5]; desc.value = "0"; return desc; } return desc; } inline bool IsTrueText(const std::string &text) { std::smatch result; std::regex_match(text, result, truthy_pattern); return !result.empty(); } inline bool IsFalseText(const std::string &text) { std::smatch result; std::regex_match(text, result, falsy_pattern); return !result.empty(); } inline std::pair SplitSwitchDef(const std::string &text) { std::match_results result; std::regex_match(text.c_str(), result, option_specifier); if (result.empty()) { throw_or_mimic(text); } const std::string& short_sw = result[2]; const std::string& long_sw = result[3]; return std::pair(short_sw, long_sw); } inline ArguDesc ParseArgument(const char *arg, bool &matched) { std::match_results result; std::regex_match(arg, result, option_matcher); matched = !result.empty(); ArguDesc argu_desc; if (matched) { argu_desc.arg_name = result[1].str(); argu_desc.set_value = result[2].length() > 0; argu_desc.value = result[3].str(); if (result[4].length() > 0) { argu_desc.grouping = true; argu_desc.arg_name = result[4].str(); } } return argu_desc; } #endif // CXXOPTS_NO_REGEX #undef CXXOPTS_NO_REGEX } namespace detail { template struct SignedCheck; template struct SignedCheck { template void operator()(bool negative, U u, const std::string& text) { if (negative) { if (u > static_cast((std::numeric_limits::min)())) { throw_or_mimic(text); } } else { if (u > static_cast((std::numeric_limits::max)())) { throw_or_mimic(text); } } } }; template struct SignedCheck { template void operator()(bool, U, const std::string&) const {} }; template void check_signed_range(bool negative, U value, const std::string& text) { SignedCheck::is_signed>()(negative, value, text); } } // namespace detail template void checked_negate(R& r, T&& t, const std::string&, std::true_type) { // if we got to here, then `t` is a positive number that fits into // `R`. So to avoid MSVC C4146, we first cast it to `R`. // See https://github.com/jarro2783/cxxopts/issues/62 for more details. r = static_cast(-static_cast(t-1)-1); } template void checked_negate(R&, T&&, const std::string& text, std::false_type) { throw_or_mimic(text); } template void integer_parser(const std::string& text, T& value) { parser_tool::IntegerDesc int_desc = parser_tool::SplitInteger(text); using US = typename std::make_unsigned::type; constexpr bool is_signed = std::numeric_limits::is_signed; const bool negative = int_desc.negative.length() > 0; const uint8_t base = int_desc.base.length() > 0 ? 16 : 10; const std::string & value_match = int_desc.value; US result = 0; for (char ch : value_match) { US digit = 0; if (ch >= '0' && ch <= '9') { digit = static_cast(ch - '0'); } else if (base == 16 && ch >= 'a' && ch <= 'f') { digit = static_cast(ch - 'a' + 10); } else if (base == 16 && ch >= 'A' && ch <= 'F') { digit = static_cast(ch - 'A' + 10); } else { throw_or_mimic(text); } const US next = static_cast(result * base + digit); if (result > next) { throw_or_mimic(text); } result = next; } detail::check_signed_range(negative, result, text); if (negative) { checked_negate(value, result, text, std::integral_constant()); } else { value = static_cast(result); } } template void stringstream_parser(const std::string& text, T& value) { std::stringstream in(text); in >> value; if (!in) { throw_or_mimic(text); } } template ::value>::type* = nullptr > void parse_value(const std::string& text, T& value) { integer_parser(text, value); } inline void parse_value(const std::string& text, bool& value) { if (parser_tool::IsTrueText(text)) { value = true; return; } if (parser_tool::IsFalseText(text)) { value = false; return; } throw_or_mimic(text); } inline void parse_value(const std::string& text, std::string& value) { value = text; } // The fallback parser. It uses the stringstream parser to parse all types // that have not been overloaded explicitly. It has to be placed in the // source code before all other more specialized templates. template ::value>::type* = nullptr > void parse_value(const std::string& text, T& value) { stringstream_parser(text, value); } template void parse_value(const std::string& text, std::vector& value) { if (text.empty()) { T v; parse_value(text, v); value.emplace_back(std::move(v)); return; } std::stringstream in(text); std::string token; while(!in.eof() && std::getline(in, token, CXXOPTS_VECTOR_DELIMITER)) { T v; parse_value(token, v); value.emplace_back(std::move(v)); } } #ifdef CXXOPTS_HAS_OPTIONAL template void parse_value(const std::string& text, std::optional& value) { T result; parse_value(text, result); value = std::move(result); } #endif inline void parse_value(const std::string& text, char& c) { if (text.length() != 1) { throw_or_mimic(text); } c = text[0]; } template struct type_is_container { static constexpr bool value = false; }; template struct type_is_container> { static constexpr bool value = true; }; template class abstract_value : public Value { using Self = abstract_value; public: abstract_value() : m_result(std::make_shared()) , m_store(m_result.get()) { } explicit abstract_value(T* t) : m_store(t) { } ~abstract_value() override = default; abstract_value& operator=(const abstract_value&) = default; abstract_value(const abstract_value& rhs) { if (rhs.m_result) { m_result = std::make_shared(); m_store = m_result.get(); } else { m_store = rhs.m_store; } m_default = rhs.m_default; m_implicit = rhs.m_implicit; m_default_value = rhs.m_default_value; m_implicit_value = rhs.m_implicit_value; } void parse(const std::string& text) const override { parse_value(text, *m_store); } bool is_container() const override { return type_is_container::value; } void parse() const override { parse_value(m_default_value, *m_store); } bool has_default() const override { return m_default; } bool has_implicit() const override { return m_implicit; } std::shared_ptr default_value(const std::string& value) override { m_default = true; m_default_value = value; return shared_from_this(); } std::shared_ptr implicit_value(const std::string& value) override { m_implicit = true; m_implicit_value = value; return shared_from_this(); } std::shared_ptr no_implicit_value() override { m_implicit = false; return shared_from_this(); } std::string get_default_value() const override { return m_default_value; } std::string get_implicit_value() const override { return m_implicit_value; } bool is_boolean() const override { return std::is_same::value; } const T& get() const { if (m_store == nullptr) { return *m_result; } return *m_store; } protected: std::shared_ptr m_result{}; T* m_store{}; bool m_default = false; bool m_implicit = false; std::string m_default_value{}; std::string m_implicit_value{}; }; template class standard_value : public abstract_value { public: using abstract_value::abstract_value; CXXOPTS_NODISCARD std::shared_ptr clone() const override { return std::make_shared>(*this); } }; template <> class standard_value : public abstract_value { public: ~standard_value() override = default; standard_value() { set_default_and_implicit(); } explicit standard_value(bool* b) : abstract_value(b) { set_default_and_implicit(); } std::shared_ptr clone() const override { return std::make_shared>(*this); } private: void set_default_and_implicit() { m_default = true; m_default_value = "false"; m_implicit = true; m_implicit_value = "true"; } }; } // namespace values template std::shared_ptr value() { return std::make_shared>(); } template std::shared_ptr value(T& t) { return std::make_shared>(&t); } class OptionAdder; class OptionDetails { public: OptionDetails ( std::string short_, std::string long_, String desc, std::shared_ptr val ) : m_short(std::move(short_)) , m_long(std::move(long_)) , m_desc(std::move(desc)) , m_value(std::move(val)) , m_count(0) { m_hash = std::hash{}(m_long + m_short); } OptionDetails(const OptionDetails& rhs) : m_desc(rhs.m_desc) , m_value(rhs.m_value->clone()) , m_count(rhs.m_count) { } OptionDetails(OptionDetails&& rhs) = default; CXXOPTS_NODISCARD const String& description() const { return m_desc; } CXXOPTS_NODISCARD const Value& value() const { return *m_value; } CXXOPTS_NODISCARD std::shared_ptr make_storage() const { return m_value->clone(); } CXXOPTS_NODISCARD const std::string& short_name() const { return m_short; } CXXOPTS_NODISCARD const std::string& long_name() const { return m_long; } CXXOPTS_NODISCARD const std::string& essential_name() const { return m_long.empty() ? m_short : m_long; } size_t hash() const { return m_hash; } private: std::string m_short{}; std::string m_long{}; String m_desc{}; std::shared_ptr m_value{}; int m_count; size_t m_hash{}; }; struct HelpOptionDetails { std::string s; std::string l; String desc; bool has_default; std::string default_value; bool has_implicit; std::string implicit_value; std::string arg_help; bool is_container; bool is_boolean; }; struct HelpGroupDetails { std::string name{}; std::string description{}; std::vector options{}; }; class OptionValue { public: void parse ( const std::shared_ptr& details, const std::string& text ) { ensure_value(details); ++m_count; m_value->parse(text); m_long_name = &details->long_name(); } void parse_default(const std::shared_ptr& details) { ensure_value(details); m_default = true; m_long_name = &details->long_name(); m_value->parse(); } void parse_no_value(const std::shared_ptr& details) { m_long_name = &details->long_name(); } #if defined(CXXOPTS_NULL_DEREF_IGNORE) #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wnull-dereference" #endif CXXOPTS_NODISCARD size_t count() const noexcept { return m_count; } #if defined(CXXOPTS_NULL_DEREF_IGNORE) #pragma GCC diagnostic pop #endif // TODO: maybe default options should count towards the number of arguments CXXOPTS_NODISCARD bool has_default() const noexcept { return m_default; } template const T& as() const { if (m_value == nullptr) { throw_or_mimic( m_long_name == nullptr ? "" : *m_long_name); } #ifdef CXXOPTS_NO_RTTI return static_cast&>(*m_value).get(); #else return dynamic_cast&>(*m_value).get(); #endif } private: void ensure_value(const std::shared_ptr& details) { if (m_value == nullptr) { m_value = details->make_storage(); } } const std::string* m_long_name = nullptr; // Holding this pointer is safe, since OptionValue's only exist in key-value pairs, // where the key has the string we point to. std::shared_ptr m_value{}; size_t m_count = 0; bool m_default = false; }; class KeyValue { public: KeyValue(std::string key_, std::string value_) : m_key(std::move(key_)) , m_value(std::move(value_)) { } CXXOPTS_NODISCARD const std::string& key() const { return m_key; } CXXOPTS_NODISCARD const std::string& value() const { return m_value; } template T as() const { T result; values::parse_value(m_value, result); return result; } private: std::string m_key; std::string m_value; }; using ParsedHashMap = std::unordered_map; using NameHashMap = std::unordered_map; class ParseResult { public: class Iterator { public: using iterator_category = std::forward_iterator_tag; using value_type = KeyValue; using difference_type = void; using pointer = const KeyValue*; using reference = const KeyValue&; Iterator() = default; Iterator(const Iterator&) = default; Iterator(const ParseResult *pr, bool end=false) : m_pr(pr) , m_iter(end? pr->m_defaults.end(): pr->m_sequential.begin()) { } Iterator& operator++() { ++m_iter; if(m_iter == m_pr->m_sequential.end()) { m_iter = m_pr->m_defaults.begin(); return *this; } return *this; } Iterator operator++(int) { Iterator retval = *this; ++(*this); return retval; } bool operator==(const Iterator& other) const { return m_iter == other.m_iter; } bool operator!=(const Iterator& other) const { return !(*this == other); } const KeyValue& operator*() { return *m_iter; } const KeyValue* operator->() { return m_iter.operator->(); } private: const ParseResult* m_pr; std::vector::const_iterator m_iter; }; ParseResult() = default; ParseResult(const ParseResult&) = default; ParseResult(NameHashMap&& keys, ParsedHashMap&& values, std::vector sequential, std::vector default_opts, std::vector&& unmatched_args) : m_keys(std::move(keys)) , m_values(std::move(values)) , m_sequential(std::move(sequential)) , m_defaults(std::move(default_opts)) , m_unmatched(std::move(unmatched_args)) { } ParseResult& operator=(ParseResult&&) = default; ParseResult& operator=(const ParseResult&) = default; Iterator begin() const { return Iterator(this); } Iterator end() const { return Iterator(this, true); } size_t count(const std::string& o) const { auto iter = m_keys.find(o); if (iter == m_keys.end()) { return 0; } auto viter = m_values.find(iter->second); if (viter == m_values.end()) { return 0; } return viter->second.count(); } const OptionValue& operator[](const std::string& option) const { auto iter = m_keys.find(option); if (iter == m_keys.end()) { throw_or_mimic(option); } auto viter = m_values.find(iter->second); if (viter == m_values.end()) { throw_or_mimic(option); } return viter->second; } const std::vector& arguments() const { return m_sequential; } const std::vector& unmatched() const { return m_unmatched; } const std::vector& defaults() const { return m_defaults; } const std::string arguments_string() const { std::string result; for(const auto& kv: m_sequential) { result += kv.key() + " = " + kv.value() + "\n"; } for(const auto& kv: m_defaults) { result += kv.key() + " = " + kv.value() + " " + "(default)" + "\n"; } return result; } private: NameHashMap m_keys{}; ParsedHashMap m_values{}; std::vector m_sequential{}; std::vector m_defaults{}; std::vector m_unmatched{}; }; struct Option { Option ( std::string opts, std::string desc, std::shared_ptr value = ::cxxopts::value(), std::string arg_help = "" ) : opts_(std::move(opts)) , desc_(std::move(desc)) , value_(std::move(value)) , arg_help_(std::move(arg_help)) { } std::string opts_; std::string desc_; std::shared_ptr value_; std::string arg_help_; }; using OptionMap = std::unordered_map>; using PositionalList = std::vector; using PositionalListIterator = PositionalList::const_iterator; class OptionParser { public: OptionParser(const OptionMap& options, const PositionalList& positional, bool allow_unrecognised) : m_options(options) , m_positional(positional) , m_allow_unrecognised(allow_unrecognised) { } ParseResult parse(int argc, const char* const* argv); bool consume_positional(const std::string& a, PositionalListIterator& next); void checked_parse_arg ( int argc, const char* const* argv, int& current, const std::shared_ptr& value, const std::string& name ); void add_to_option(OptionMap::const_iterator iter, const std::string& option, const std::string& arg); void parse_option ( const std::shared_ptr& value, const std::string& name, const std::string& arg = "" ); void parse_default(const std::shared_ptr& details); void parse_no_value(const std::shared_ptr& details); private: void finalise_aliases(); const OptionMap& m_options; const PositionalList& m_positional; std::vector m_sequential{}; std::vector m_defaults{}; bool m_allow_unrecognised; ParsedHashMap m_parsed{}; NameHashMap m_keys{}; }; class Options { public: explicit Options(std::string program, std::string help_string = "") : m_program(std::move(program)) , m_help_string(toLocalString(std::move(help_string))) , m_custom_help("[OPTION...]") , m_positional_help("positional parameters") , m_show_positional(false) , m_allow_unrecognised(false) , m_width(76) , m_tab_expansion(false) , m_options(std::make_shared()) { } Options& positional_help(std::string help_text) { m_positional_help = std::move(help_text); return *this; } Options& custom_help(std::string help_text) { m_custom_help = std::move(help_text); return *this; } Options& show_positional_help() { m_show_positional = true; return *this; } Options& allow_unrecognised_options() { m_allow_unrecognised = true; return *this; } Options& set_width(size_t width) { m_width = width; return *this; } Options& set_tab_expansion(bool expansion=true) { m_tab_expansion = expansion; return *this; } ParseResult parse(int argc, const char* const* argv); OptionAdder add_options(std::string group = ""); void add_options ( const std::string& group, std::initializer_list