[
  {
    "path": ".github/workflows/arduino-ci.yaml",
    "content": "name: Arduino Library CI\n\non: [pull_request, push, repository_dispatch]\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n\n    steps:\n    - uses: actions/setup-python@v5\n      with:\n        python-version: '3.x'\n    - uses: actions/checkout@v4\n\n    - uses: actions/checkout@v4\n      with:\n         repository: adafruit/ci-arduino\n         path: ci\n\n    - name: pre-install\n      run: bash ci/actions_install.sh\n\n    - name: test platforms\n      run: python3 ci/build_platform.py --no_warn esp32\n\n    #- name: clang\n    #  run: python3 ci/run-clang-format.py -e \"ci/*\" -e \"bin/*\" -r . \n\n    - name: doxygen\n      env:\n        GH_REPO_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n        PRETTYNAME : \"ESP32Console Library\"\n      run: bash ci/doxy_gen_and_deploy.sh "
  },
  {
    "path": ".github/workflows/arduino-lint.yaml",
    "content": "on: [push, pull_request]\njobs:\n  lint:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n      - uses: arduino/arduino-lint-action@v1\n        with:\n          library-manager: update\n          compliance: strict"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2022 Jan Böhmer\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE."
  },
  {
    "path": "README.md",
    "content": "# ESP32Console\n\n[![PlatformIO Registry](https://badges.registry.platformio.org/packages/jbtronics/library/ESP32Console.svg)](https://registry.platformio.org/libraries/jbtronics/ESP32Console)\n\nArduino library to add a serial console to your ESP32 console. It includes some useful commands and allows to easily add your own custom commands.\n\nThis easily allows you to control and configure your control via serial console in a comfortable and easy way.\n\nThis library encapsulates the Console component included in the ESP-IDF and allows to use it in an easy \"Arduino-way\".\n\n![Screenshot of console output](extras/screenshot.png)\n\n## Features\n* Simple to use\n* Navigatable history, autocompletion with tab for commands (when using an ANSI terminal)\n* Persistent history if wished (history gets saved across reboots)\n* Many useful commands included like showing infos about system, memory, network and more (see [commands.md](commands.md) for more info)\n* Console works in its own asynchronous task, so you can use your arduino loop() function as you like\n* Support for environment variables and variable interpolation in commands\n* Easy to implement own commands\n* Easy to use argument parsing using cxxopts (see `argparser` example)\n* Customizable prompt\n* Ships a simple file editor to modify and create files locally on system if wanted\n\n\n## Usage\n\n### Installation\nThis 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.\n\n### Basic\n\nTo use this library you have to do an `#include <ESP32Console.h>` 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).\n\nYou 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.\n\nUsing `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:\n* `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.\n* `ConsoleCommandD`: Similar to `ConsoleCommand` but allows to pass an `std::function` object as handler. This allows you to use lambda-functions and `std::bind`\n* `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.\n\n### Included commands\nESP32Console 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.\n\n### Environment variables\nThe 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`.\nYou can change and predefine env variables from your code. See `examples/gpio` for more info.\n\n### Computer side\nYou 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).\nThis kind of terminal is needed for more complex functions like auto-complete with TAB, history scrolling, colors and more.\n\nIf you use a VT100 compatible terminal you can use the keybinds, when in prompt:\n* `Ctrl + L`: Clear screen\n* `Ctrl + A`: Jump cursor to begin of line\n* `Ctrl + E`: Jump cursor to end of line\n* `Ctrl + U`: Delete whole line\n* `Ctrl + K`: Delete from current position to end of line\n* `Ctrl + W`: Delete previous word\n* `Ctrl + T`: Swap current character with previous one\n\n## Examples\nA simple usage can be seen here (see `examples/simple.ino`):\nMore advanced usages can be found in `examples/` folder.\n\n```\n#include <Arduino.h>\n\n#include \"ESP32Console.h\"\n\nusing namespace ESP32Console;\n\nConsole console;\n\nconstexpr int LED = 2;\n\nint led(int argc, char **argv)\n{\n    //Ensure that we have an argument to parse\n    if (argc != 2)\n    {\n        printf(\"You have to give 'on' or 'off' as a argument (e.g. 'led on')\\n\");\n        \n        //Return EXIT_FAILURE if something did not worked.\n        return EXIT_FAILURE;\n    }\n    \n    //Take the first argument...\n    auto arg = String(argv[1]);\n\n    //and use it to decide what to do with the LED\n    if (arg == \"on\") {\n        digitalWrite(LED, HIGH);\n        printf(\"LED is now on\\n\");\n    } else if(arg == \"off\") {\n        digitalWrite(LED, LOW);\n        printf(\"LED is now off\\n\");\n    } else {\n        printf(\"Unknown argument!\\n\");\n        return EXIT_FAILURE;\n    }\n    \n    //Return EXIT_SUCCESS if everything worked as intended.\n    return EXIT_SUCCESS;\n}\n\nvoid setup()\n{\n    pinMode(LED, OUTPUT);\n\n    //You can change the console prompt before calling begin(). By default it is \"ESP32>\"\n    console.setPrompt(\"MyConsole> \");\n\n    //You can change the baud rate and pin numbers similar to Serial.begin() here.\n    console.begin(115200);\n\n    //Register builtin commands like 'reboot', 'sysinfo', or 'meminfo'\n    console.registerSystemCommands();\n\n    //Register our own command\n    //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.\n    console.registerCommand(ConsoleCommand(\"led\", &led, \"Turn the LED on or off\"));\n\n    //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.\n    console.registerCommand(ConsoleCommandD(\"test\", [](int argc, char **argv) -> int {\n        printf(\"Lambda function test\\n\");\n        return EXIT_SUCCESS;\n    }, \"Just a test command!\"));\n\n    //When console is in use, we can not use Serial.print but you can use printf to output text\n    printf(\"\\n\\nWelcome to ESP32Console example. Try out typing 'led off' and 'led on' (without quotes) or see 'help' for all commands.\");\n}\n\nvoid loop()\n{\n    //Console works async in its own task, so you can do whatever you want in your loop() function.\n}\n```\n\n## Credits\n* This library utilizes the console component of ESP-IDF written by Espressif at core.\n* Argument parsing is done by [cxxopts](https://github.com/jarro2783/cxxopts).\n* As editor a modified version of [kilo](https://github.com/antirez/kilo) is used.\n\n## LICENSE\nESP32Console is licensed under MIT LICENSE. See [LICENSE file](LICENSE) for more info.\n\nkilo shipped with ESP32Console is licensed under BSD-2 clause license. See the respective file for more info. \n\n## TODO\n* Add more useful commands\n* Easy integration of colors and console styles\n* Support of command batch files\n* Add support for ESP8266 (this should be possible in theory as the old RTOS-SDK already ships the console parts)\n* Check if more complex terminal stuff, like pipes, output redirection and similar is possible (difficult due only having one global stdout)\n"
  },
  {
    "path": "commands.md",
    "content": "# Included command reference\n\n## Core commands (automatically loaded, when begin() is called)\n\n* `help`: Show a list of all possible commands with descriptions\n* `clear`: Clear terminal screen using ANSI commands (only working when using an ANSI compatible terminal)\n* `echo`: Echo the parameter strings\n* `history`: Show the recent command history\n* `multiline_mode`: Switch multiline mode on or off. When it is on, lines get break into a second line, if it gets too long.\n* `env`: List all environment variables\n* `declare`: Change the value of an environment variable (syntax: `declare VARIABLE Value` or `declare VARIABLE \"Long Value\"`)\n\n## System commands (loaded by calling registerSystemCommands())\n\n* `sysinfo`: Prints info about the chip model, revision, ESP-IDF version, EFuse MAC, flash and PSRAM\n* `restart`: Restarts the system\n* `meminfo`: Show informations about used and free Heap\n* `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`).\n\n## Network commands (loaded by calling registerNetworkCommands())\n\n* `ipconfig`: Shows information about WiFi connection\n* `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`.\n\n## VFS commands (loaded by calling registerVFSCommands())\n\nThis 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.\nThe following commands allow an unified access on it.\n\n* `pwd`: Show the current working directory (the directory we are currently in)\n* `cd [PATH]`: Change the current directory\n* `ls [PATH]`: List the contents of the current or given directory\n* `cat [FILES...]`: Show the content of the given files\n* `rm [FILE]`: Delete the given file\n* `rmdir [DIR]`: Delete the given director\n* `mv [ORIGIN] [TARGET]`: Moves/Rename the file to new name/posiion\n* `cp [ORIGIN] [TARGET]`: Copies the contents of origin to target file\n* `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.\n\n## GPIO commands (loaded by calling registerGPIOCommands())\nThe commands allow you to read and change the states of the ESPs GPIO pins. They are similar to the arduino included functions:\n\n* `pinMode [PIN] [MODE]`: Change the pinMode of an GPIO\n* `digitalRead [PIN]`: Reads the state of an digital GPIO\n* `digitalWrite [PIN] [LEVEL]`: Changes the state of an digital GPIO\n* `analogRead [PIN]`: Reads the voltage applied to an analog GPIO in millivolts"
  },
  {
    "path": "examples/argparser/argparser.ino",
    "content": "/**\n * This example demonstrates the usage of OptionsConsoleCommand, which allows you to easily build commands with complex options and argument parsing.\n */\n\n#include <Arduino.h>\n\n#include \"ESP32Console.h\"\n\nusing namespace ESP32Console;\n\nConsole console;\n\nvoid setup()\n{\n    console.begin(115200);\n\n    // 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.\n    OptionsConsoleCommand test(\n        \"test\",                                                                 // The command name\n        [](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\n            \n            printf(\"Lambda function test\\n\");\n\n            // Check if argument was passed, then print it:\n            if (result.count(\"i\"))\n            {\n                int i = result[\"i\"].as<int>();\n                printf(\"Selected mode: %d\\n\", i);\n            }\n\n            return EXIT_SUCCESS;\n        },\n        \"Just a test command!\",\n        \"v1.0.0\"); //You can define a version number which is shown when the command is called with --version\n\n    //Customize the options of the console object. See https://github.com/jarro2783/cxxopts for explaination\n    test.options.add_options()(\"i,integer\", \"Int param\", cxxopts::value<int>());\n\n    //Register it like any other command\n    console.registerCommand(test);\n}\n\nvoid loop()\n{\n    // Console works async in its own task, so you can do whatever you want in your loop() function.\n}"
  },
  {
    "path": "examples/basic/basic.ino",
    "content": "#include <Arduino.h>\n\n#include \"ESP32Console.h\"\n\nusing namespace ESP32Console;\n\nConsole console;\n\nconstexpr int LED = 2;\n\nint led(int argc, char **argv)\n{\n    //Ensure that we have an argument to parse\n    if (argc != 2)\n    {\n        printf(\"You have to give 'on' or 'off' as a argument (e.g. 'led on')\\n\");\n        \n        //Return EXIT_FAILURE if something did not worked.\n        return EXIT_FAILURE;\n    }\n    \n    //Take the first argument...\n    auto arg = String(argv[1]);\n\n    //and use it to decide what to do with the LED\n    if (arg == \"on\") {\n        digitalWrite(LED, HIGH);\n        printf(\"LED is now on\\n\");\n    } else if(arg == \"off\") {\n        digitalWrite(LED, LOW);\n        printf(\"LED is now off\\n\");\n    } else {\n        printf(\"Unknown argument!\\n\");\n        return EXIT_FAILURE;\n    }\n    \n    //Return EXIT_SUCCESS if everything worked as intended.\n    return EXIT_SUCCESS;\n}\n\nvoid setup()\n{\n    pinMode(LED, OUTPUT);\n\n    //You can change the console prompt before calling begin(). By default it is \"ESP32>\"\n    console.setPrompt(\"MyConsole> \");\n\n    //You can change the baud rate and pin numbers similar to Serial.begin() here.\n    console.begin(115200);\n\n    //Register builtin commands like 'reboot', 'version', or 'meminfo'\n    console.registerSystemCommands();\n\n    //Register our own command\n    //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.\n    console.registerCommand(ConsoleCommand(\"led\", &led, \"Turn the LED on or off\"));\n\n    //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.\n    console.registerCommand(ConsoleCommandD(\"test\", [](int argc, char **argv) -> int {\n        printf(\"Lambda function test\\n\");\n        return EXIT_SUCCESS;\n    }, \"Just a test command!\"));\n\n    //When console is in use, we can not use Serial.print but you can use printf to output text\n    printf(\"\\n\\nWelcome to ESP32Console example. Try out typing 'led off' and 'led on' (without quotes) or see 'help' for all commands.\");\n}\n\nvoid loop()\n{\n    //Console works async in its own task, so you can do whatever you want in your loop() function.\n}"
  },
  {
    "path": "examples/gpio/gpio.ino",
    "content": "/**\n * This example demonstrates the usage of the internal GPIO functions and the env variables interpolation\n */\n\n#include <Arduino.h>\n#include \"ESP32Console.h\"\n\n#include \"ESP32Console/Helpers/PWDHelpers.h\"\n\nusing namespace ESP32Console;\n\nConsole console;\n\nvoid setup()\n{    \n\n    console.begin(115200);\n    console.registerSystemCommands();\n    \n    /**\n     * Set environment variables, so users can access pin numbers easily in console.\n     * Users can do commands like `digitalWrite $LED HIGH` which will then be expanded to `digitalWrite 10 HIGH`.\n     * Users can change these env variables or create new ones dynamically in the console by `declare LED 10`.\n     */\n    setenv(\"LED\", \"10\", 1);\n    setenv(\"BUTTON\", \"4\", 1);\n\n    /**\n     * Users can list all defined env variables and their values by `env` command\n     */\n\n    //Register GPIO commands\n    console.registerGPIOCommands();\n}\n\nvoid loop()\n{\n    \n}"
  },
  {
    "path": "examples/network/network.ino",
    "content": "/**\n * This example demonstrates the usage of the builtin network functions\n */\n\n#include <Arduino.h>\n#include \"ESP32Console.h\"\n#include <WiFi.h>\n\n#include \"ESP32Console/Helpers/PWDHelpers.h\"\n\nusing namespace ESP32Console;\n\nConsole console;\n\nconst char *ssid = \"yourSSID\";\nconst char *password = \"yourWLAN\";\n\nvoid setup()\n{    \n    //Connect to WiFi\n    WiFi.begin(ssid, password);\n\n    console.begin(115200);\n    console.registerSystemCommands();\n    \n    //Register network commands\n    console.registerNetworkCommands();\n}\n\nvoid loop()\n{\n    \n}"
  },
  {
    "path": "examples/other_uart_channel/other_uart_channel.ino",
    "content": "/**\n * This example demonstrates the usage of ESP32Console on a seperate UART channel, using its own Pins, which allows you to use\n * the console besides normal Serial.* commands\n */\n\n#include <Arduino.h>\n#include \"ESP32Console.h\"\n\nusing namespace ESP32Console;\n\nConsole console;\n\nvoid setup()\n{   \n    //Initialize Serial port on UART0 (the onboard USB Serial Converter)\n    Serial.begin(115200);\n    \n    //Enable ESP32Console on Pin 12 & 14 on UART1\n    console.begin(115200, 12, 14, 1);\n\n\n    console.registerSystemCommands();\n    console.registerNetworkCommands();\n}\n\nvoid loop()\n{\n    //We can use Serial on UART0 as usual, while ESP32Console is available on the Pins above.\n    Serial.println(\"Test\");\n    delay(1000);\n}"
  },
  {
    "path": "examples/vfs/vfs.ino",
    "content": "/**\n * This example demonstrates the usage of the builtin VFS commands and persistent history\n */\n\n#include <Arduino.h>\n#include \"ESP32Console.h\"\n#include <FS.h>\n#include \"SPIFFS.h\"\n#include \"ESP32Console/Helpers/PWDHelpers.h\"\n\nusing namespace ESP32Console;\n\nConsole console;\n\nvoid setup()\n{\n    //Initalize SPIFFS and mount it on /spiffs\n    SPIFFS.begin(true, \"/spiffs\");\n\n    //Modify prompt to show current file path (%pwd% get replaced by the filepath)\n    console.setPrompt(\"ESP32 %pwd%> \");\n    \n    //Set HOME env for easier navigating (type cd to jump to home)\n    setenv(\"HOME\", \"/spiffs\", 1);\n    //Set PWD to env\n    console_chdir(\"/spiffs\");\n    \n    console.begin(115200);\n\n    //Enable the saving of our command history to SPIFFS. You will be able to see it, when you type ls in your console.\n    console.enablePersistentHistory(\"/spiffs/.history.txt\");\n\n    console.registerSystemCommands();\n\n    //Register the VFS specific commands\n    console.registerVFSCommands();\n}\n\nvoid loop()\n{\n}"
  },
  {
    "path": "library.json",
    "content": "{\n    \"name\": \"ESP32Console\",\n    \"version\": \"1.3.0\",\n    \"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.\",\n    \"keywords\": \"esp32, console, ping, ifconfig, sysinfo, editor, file\",\n    \"repository\":\n    {\n      \"type\": \"git\",\n      \"url\": \"https://github.com/jbtronics/ESP32Console\"\n    },\n    \"authors\":\n    [\n      {\n        \"name\": \"Jan Böhmer\",\n        \"email\": \"mail@jan-boehmer.de\",\n        \"url\": \"https://github.com/jbtronics\",\n        \"maintainer\": true\n      }\n    ],\n    \"license\": \"MIT\",\n    \"homepage\": \"https://github.com/jbtronics/ESP32Console\",\n    \"dependencies\": {\n    },\n    \"exclude\": [\n      \".github\",\n      \"extras\"\n    ],\n    \"frameworks\": \"arduino\",\n    \"platforms\": \"espressif32\"\n  }\n"
  },
  {
    "path": "library.properties",
    "content": "name=ESP32Console\nversion=1.3.0\nauthor=Jan Böhmer <mail@jan-boehmer.de>\nmaintainer=Jan Böhmer <mail@jan-boehmer.de>\nsentence=Extensible UART console for ESP32 with useful included commands.\nparagraph=This library encapsules the console component of ESP-IDF and make them easy to use in an Arduino environment\ncategory=Communication\nurl=https://github.com/jbtronics/ESP32Console\narchitectures=esp32\n"
  },
  {
    "path": "src/ESP32Console/Commands/CoreCommands.cpp",
    "content": "#include \"./CoreCommands.h\"\n#include \"linenoise/linenoise.h\"\n#include \"Arduino.h\"\n#include \"soc/soc_caps.h\"\n//#include \"argparse/argparse.hpp\"\n\nstatic int clear(int argc, char **argv)\n{\n    // If we are on a dumb erminal clearing does not work\n    if (linenoiseProbe())\n    {\n        printf(\"\\nYour terminal does not support escape sequences. Clearing screen does not work!\\n\");\n        return EXIT_FAILURE;\n    }\n\n    linenoiseClearScreen();\n    return EXIT_SUCCESS;\n}\n\nstatic int echo(int argc, char **argv)\n{\n    for (int n = 1; n<argc; n++)\n    {\n        printf(\"%s \", argv[n]);\n    }\n    printf(\"\\n\");\n\n    return EXIT_SUCCESS;\n}\n\nstatic int set_multiline_mode(int argc, char **argv)\n{\n    if (argc != 2)\n    {\n        printf(\"You have to give 'on' or 'off' as an argument!\\n\");\n        return EXIT_FAILURE;\n    }\n\n    // Get argument\n    auto mode = String(argv[1]);\n    // Normalize\n    mode.toLowerCase();\n\n    if (mode == \"on\")\n    {\n        linenoiseSetMultiLine(1);\n    }\n    else if (mode == \"off\")\n    {\n        linenoiseSetMultiLine(0);\n    }\n    else\n    {\n        printf(\"Unknown option. Pass 'on' or 'off' (without quotes)!\\n\");\n        return EXIT_FAILURE;\n    }\n\n    printf(\"Multiline mode set.\\n\");\n\n    return EXIT_SUCCESS;\n}\n\nstatic int history_channel = 0;\n\nstatic int history(int argc, char **argv)\n{\n    // If arguments were passed check for clearing\n    /*if (argc > 1)\n    {\n        if (strcasecmp(argv[1], \"-c\"))\n        { // When -c option was detected clear history.\n            linenoiseHistorySetMaxLen(0);\n            printf(\"History cleared!\\n\");\n            linenoiseHistorySetMaxLen(10);\n            return EXIT_SUCCESS;\n        }\n        else\n        {\n            printf(\"Invalid argument. Use -c to clear history.\\n\");\n\n            return EXIT_FAILURE;\n        }\n    }\n    else*/\n    { // Without arguments we just output the history\n      // 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.\n        char path[12] = {0};\n        snprintf(path, 12, \"/dev/uart/%d\", history_channel);\n\n        // If we found the correct one, let linoise save (output) them.\n        linenoiseHistorySave(path);\n        return EXIT_SUCCESS;\n    }\n\n    return EXIT_FAILURE;\n}\n\nextern char **environ;\n\nstatic int env(int argc, char **argv)\n{\n    char **s = environ;\n\n    for (; *s; s++)\n    {\n        printf(\"%s\\n\", *s);\n    }\n    return EXIT_SUCCESS;\n}\n\nstatic int declare(int argc, char **argv)\n{\n    if (argc != 3) {\n        fprintf(stderr, \"Syntax: declare VAR short OR declare VARIABLE \\\"Long Value\\\"\\n\");\n        return EXIT_FAILURE; \n    }\n\n    setenv(argv[1], argv[2], 1);\n\n    return EXIT_SUCCESS;\n}\n\nnamespace ESP32Console::Commands\n{\n    const ConsoleCommand getClearCommand()\n    {\n        return ConsoleCommand(\"clear\", &clear, \"Clears the screen using ANSI codes\");\n    }\n\n    const ConsoleCommand getEchoCommand()\n    {\n        return ConsoleCommand(\"echo\", &echo, \"Echos the text supplied as argument\");\n    }\n\n    const ConsoleCommand getSetMultilineCommand()\n    {\n        return ConsoleCommand(\"multiline_mode\", &set_multiline_mode, \"Sets the multiline mode of the console\");\n    }\n\n    const ConsoleCommand getHistoryCommand(int uart_channel)\n    {\n        history_channel = uart_channel;\n        return ConsoleCommand(\"history\", &history, \"Shows and clear command history (using -c parameter)\");\n    }\n\n    const ConsoleCommand getEnvCommand()\n    {\n        return ConsoleCommand(\"env\", &env, \"List all environment variables.\");\n    }\n\n    const ConsoleCommand getDeclareCommand()\n    {\n        return ConsoleCommand(\"declare\", &declare, \"Change enviroment variables\");\n    }\n}"
  },
  {
    "path": "src/ESP32Console/Commands/CoreCommands.h",
    "content": "#pragma once\n\n#include \"ESP32Console/ConsoleCommand.h\"\n\nnamespace ESP32Console::Commands\n{\n    const ConsoleCommand getClearCommand();\n\n    const ConsoleCommand getEchoCommand();\n\n    const ConsoleCommand getSetMultilineCommand();\n\n    const ConsoleCommand getHistoryCommand(int uart_channel=0);\n\n    const ConsoleCommand getEnvCommand();\n\n    const ConsoleCommand getDeclareCommand();\n}"
  },
  {
    "path": "src/ESP32Console/Commands/GPIOCommands.cpp",
    "content": "#include \"./GPIOCommands.h\"\n#include \"Arduino.h\"\n#include <string>\n#include <stdexcept>\n\nstatic int _pinmode(int argc, char **argv)\n{\n    if (argc != 3)\n    {\n        printf(\"You have to pass a pin number and mode. Syntax: pinMode [GPIO] [MODE]\\n\");\n        return 1;\n    }\n\n    char *pin_str = argv[1];\n    String mode_str = String(argv[2]);\n\n    unsigned long pin = 0;\n\n    try\n    {\n        pin = std::stoul(pin_str);\n    }\n    catch (std::invalid_argument ex)\n    {\n        fprintf(stderr, \"Invalid argument for pin: %s\\n\", ex.what());\n        return 1;\n    }\n\n    if (pin > 255 || !digitalPinIsValid(pin)) {\n        fprintf(stderr, \"%d is not a GPIO pin\\n\", pin);\n        return 1;\n    }\n\n    int mode = INPUT;\n\n    if (mode_str.equalsIgnoreCase(\"INPUT\"))\n    {\n        mode = INPUT;\n    }\n    else if (mode_str.equalsIgnoreCase(\"OUTPUT\"))\n    {\n        mode = OUTPUT;\n    }\n    else if (mode_str.equalsIgnoreCase(\"INPUT_PULLUP\"))\n    {\n        mode = INPUT_PULLUP;\n    }\n    else if (mode_str.equalsIgnoreCase(\"INPUT_PULLDOWN\"))\n    {\n        mode = INPUT_PULLDOWN;\n    }\n    else if (mode_str.equalsIgnoreCase(\"OUTPUT_OPEN_DRAIN\"))\n    {\n        mode = OUTPUT_OPEN_DRAIN;\n    }\n    else\n    {\n        fprintf(stderr, \"Invalid mode: Allowed modes are INPUT, OUTPUT, INPUT_PULLUP, INPUT_PULLDOWN, OUTPUT_OPEN_DRAIN\\n\");\n    }\n\n    pinMode(pin, mode);\n    printf(\"Mode set successful.\\n\");\n\n    return 0;\n}\n\nstatic int _digitalWrite(int argc, char** argv)\n{\n    if (argc != 3)\n    {\n        printf(\"You have to pass an pin number and level. Syntax: digitalWrite [GPIO] [Level]\\n\");\n        return 1;\n    }\n\n    char *pin_str = argv[1];\n    String mode_str = String(argv[2]);\n\n    unsigned long pin = 0;\n\n    try\n    {\n        pin = std::stoul(pin_str);\n    }\n    catch (std::invalid_argument ex)\n    {\n        fprintf(stderr, \"Invalid argument for pin: %s\\n\", ex.what());\n        return 1;\n    }\n\n    if (pin > 255 || !digitalPinCanOutput(pin)) {\n        fprintf(stderr, \"%d is not a GPIO pin\\n\", pin);\n        return 1;\n    }\n\n    int mode = LOW;\n\n    if (mode_str.equalsIgnoreCase(\"HIGH\")  || mode_str.equalsIgnoreCase(\"1\"))\n    {\n        mode = HIGH;\n    }\n    else if (mode_str.equalsIgnoreCase(\"LOW\") || mode_str.equalsIgnoreCase(\"0\"))\n    {\n        mode = LOW;\n    } else\n    {\n        fprintf(stderr, \"Invalid mode: Allowed levels are HIGH, LOW, 0 and 1\\n\");\n    }\n\n    pinMode(pin, mode);\n    printf(\"Output set successful.\\n\");\n\n    return 0;\n}\n\nstatic int _digitalRead(int argc, char** argv)\n{\n    if (argc != 2)\n    {\n        printf(\"You have to pass an pin number to read\\n\");\n        return 1;\n    }\n\n    char *pin_str = argv[1];\n\n    unsigned long pin = 0;\n\n    try\n    {\n        pin = std::stoul(pin_str);\n    }\n    catch (std::invalid_argument ex)\n    {\n        fprintf(stderr, \"Invalid argument for pin: %s\\n\", ex.what());\n        return 1;\n    }\n\n    if (pin > 255 || !digitalPinCanOutput(pin)) {\n        fprintf(stderr, \"%d is not a GPIO pin\\n\", pin);\n        return 1;\n    }\n\n    auto level = digitalRead(pin);\n\n    if(level == HIGH) {\n        printf(\"HIGH\\n\");\n    } else if(level == LOW) {\n        printf(\"LOW\\n\");\n    } else {\n        fprintf(stderr, \"Unknown state (%u) of pin %u!\\n\", level, pin);\n        return 1;\n    }\n\n    return 0;\n}\n\nstatic int _analogRead(int argc, char** argv)\n{\n    if (argc != 2)\n    {\n        printf(\"You have to pass an pin number to read\\n\");\n        return 1;\n    }\n\n    char *pin_str = argv[1];\n\n    unsigned long pin = 0;\n\n    try\n    {\n        pin = std::stoul(pin_str);\n    }\n    catch (std::invalid_argument ex)\n    {\n        fprintf(stderr, \"Invalid argument for pin: %s\\n\", ex.what());\n        return 1;\n    }\n\n    if (pin > 255 || digitalPinToAnalogChannel(pin) == -1) {\n        fprintf(stderr, \"%d is not a ADC pin\\n\", pin);\n        return 1;\n    }\n\n    auto value = analogReadMilliVolts(pin);\n    \n    printf(\"%u mV\\n\", value);\n\n    return 0;\n}\n\n\n\nnamespace ESP32Console::Commands\n{\n    const ConsoleCommand getPinModeCommand()\n    {\n        return ConsoleCommand(\"pinMode\", &_pinmode, \"Changes the pinmode of an GPIO pin (similar to Arduino function)\");\n    }\n\n    const ConsoleCommand getDigitalWriteCommand()\n    {\n        return ConsoleCommand(\"digitalWrite\", &_digitalWrite, \"Writes the state of an ouput pin (similar to Arduino function)\");\n    }\n\n    const ConsoleCommand getDigitalReadCommand()\n    {\n        return ConsoleCommand(\"digitalRead\", &_digitalRead, \"Reads the state of an input pin (similar to Arduino function)\");\n    }\n\n    const ConsoleCommand getAnalogReadCommand()\n    {\n        return ConsoleCommand(\"analogRead\", &_analogRead, \"Show the voltage at an analog pin in millivollts.\");\n    }\n}"
  },
  {
    "path": "src/ESP32Console/Commands/GPIOCommands.h",
    "content": "#pragma once\n\n#include \"ESP32Console/ConsoleCommand.h\"\n\nnamespace ESP32Console::Commands\n{\n    const ConsoleCommand getPinModeCommand();\n\n    const ConsoleCommand getDigitalWriteCommand();\n\n    const ConsoleCommand getDigitalReadCommand();\n\n    const ConsoleCommand getAnalogReadCommand();\n}"
  },
  {
    "path": "src/ESP32Console/Commands/NetworkCommands.cpp",
    "content": "#include \"./NetworkCommands.h\"\n#include <Arduino.h>\n\n#include <esp_ping.h>\n#include <ping/ping.h>\n#include <ping/ping_sock.h>\n#include \"lwip/inet.h\"\n#include \"lwip/netdb.h\"\n#include \"lwip/sockets.h\"\n\n#include <unistd.h>\n\n#include \"WiFi.h\"\n\nstatic const char *wlstatus2string(wl_status_t status)\n{\n    switch (status)\n    {\n    case WL_NO_SHIELD:\n        return \"Not initialized\";\n    case WL_CONNECT_FAILED:\n        return \"Connection failed\";\n    case WL_CONNECTED:\n        return \"Connected\";\n    case WL_CONNECTION_LOST:\n        return \"Connection lost\";\n    case WL_DISCONNECTED:\n        return \"Disconnected\";\n    case WL_IDLE_STATUS:\n        return \"Idle status\";\n    case WL_NO_SSID_AVAIL:\n        return \"No SSID available\";\n    case WL_SCAN_COMPLETED:\n        return \"Scan completed\";\n    default:\n        return \"Unknown\";\n    }\n}\n\nconst char* wlmode2string(wifi_mode_t mode)\n{\n    switch(mode) {\n        case WIFI_MODE_NULL:\n            return \"Not initialized\";\n        case WIFI_MODE_AP:\n            return \"Accesspoint\";\n        case WIFI_MODE_STA:\n            return \"Station\";\n        case WIFI_MODE_APSTA:\n            return \"Station + Accesspoint\";\n        default:\n            return \"Unknown\";\n    }\n}\n\nstatic void on_ping_success(esp_ping_handle_t hdl, void *args)\n{\n    uint8_t ttl;\n    uint16_t seqno;\n    uint32_t elapsed_time, recv_len;\n    ip_addr_t target_addr;\n    esp_ping_get_profile(hdl, ESP_PING_PROF_SEQNO, &seqno, sizeof(seqno));\n    esp_ping_get_profile(hdl, ESP_PING_PROF_TTL, &ttl, sizeof(ttl));\n    esp_ping_get_profile(hdl, ESP_PING_PROF_IPADDR, &target_addr, sizeof(target_addr));\n    esp_ping_get_profile(hdl, ESP_PING_PROF_SIZE, &recv_len, sizeof(recv_len));\n    esp_ping_get_profile(hdl, ESP_PING_PROF_TIMEGAP, &elapsed_time, sizeof(elapsed_time));\n    printf(\"%u bytes from %s icmp_seq=%d ttl=%d time=%u ms\\n\",\n           recv_len, inet_ntoa(target_addr.u_addr.ip4), seqno, ttl, elapsed_time);\n}\n\nstatic void on_ping_timeout(esp_ping_handle_t hdl, void *args)\n{\n    uint16_t seqno;\n    ip_addr_t target_addr;\n    esp_ping_get_profile(hdl, ESP_PING_PROF_SEQNO, &seqno, sizeof(seqno));\n    esp_ping_get_profile(hdl, ESP_PING_PROF_IPADDR, &target_addr, sizeof(target_addr));\n    printf(\"From %s icmp_seq=%u timeout\\n\", inet_ntoa(target_addr.u_addr.ip4), seqno);\n}\n\nstatic void on_ping_end(esp_ping_handle_t hdl, void *args)\n{\n}\n\nstatic int ping(int argc, char **argv)\n{\n    //By default do 5 pings\n    int number_of_pings = 5;\n\n    int opt;\n    while ((opt = getopt(argc, argv, \"n:\")) != -1) {\n        switch(opt) {\n            case 'n':\n                number_of_pings = atoi(optarg);\n                break;\n            case '?':\n                printf(\"Unknown option: %c\\n\", optopt);\n                break;\n            case ':':\n                printf(\"Missing arg for %c\\n\", optopt);\n                break;\n\n            default:\n                fprintf(stderr, \"Usage: ping -n 5 [HOSTNAME]\\n\");\n                fprintf(stderr, \"-n: The number of pings. 0 means infinite. Can be aborted with Ctrl+D or Ctrl+C.\");\n                return 1;\n        }\n    }\n\n    int argind = optind;\n\n    //Get hostname\n    if (argind >= argc) {\n        fprintf(stderr, \"You need to pass an hostname!\\n\");\n        return EXIT_FAILURE;\n    }\n\n    char* hostname = argv[argind];\n\n    /* convert hostname to IP address */\n    ip_addr_t target_addr;\n    struct addrinfo hint;\n    struct addrinfo *res = NULL;\n    memset(&hint, 0, sizeof(hint));\n    memset(&target_addr, 0, sizeof(target_addr));\n    auto result = getaddrinfo(hostname, NULL, &hint, &res);\n\n    if (result) {\n        fprintf(stderr, \"Could not resolve hostname! (getaddrinfo returned %d)\\n\", result);\n        return 1;\n    }\n\n    struct in_addr addr4 = ((struct sockaddr_in *) (res->ai_addr))->sin_addr;\n    inet_addr_to_ip4addr(ip_2_ip4(&target_addr), &addr4);\n    freeaddrinfo(res);\n\n    //Configure ping session\n    esp_ping_config_t ping_config = ESP_PING_DEFAULT_CONFIG();\n    ping_config.task_stack_size = 4096;\n    ping_config.target_addr = target_addr;          // target IP address\n    ping_config.count = number_of_pings;   // 0 means infinite ping\n\n    /* set callback functions */\n    esp_ping_callbacks_t cbs;\n    cbs.on_ping_success = on_ping_success;\n    cbs.on_ping_timeout = on_ping_timeout;\n    cbs.on_ping_end = on_ping_end;\n    //Pass a variable as pointer so the sub tasks can decrease it\n    //cbs.cb_args = &number_of_pings_remaining;\n\n    esp_ping_handle_t ping;\n    esp_ping_new_session(&ping_config, &cbs, &ping);\n\n    esp_ping_start(ping);\n\n    char c = 0;\n    \n    uint16_t seqno;\n    esp_ping_get_profile(ping, ESP_PING_PROF_SEQNO, &seqno, sizeof(seqno));\n    \n    //Make stdin input non blocking so we can query for input AND check ping seqno\n    int flags = fcntl(fileno(stdin), F_GETFL, 0);\n    fcntl(fileno(stdin), F_SETFL, flags | O_NONBLOCK);\n\n    //Wait for Ctrl+D or Ctr+C or that our task finishes\n    //The async tasks decrease number_of_pings, so wait for it to get to 0\n    while((number_of_pings == 0 || seqno < number_of_pings) && c != 4 && c != 3) {\n        esp_ping_get_profile(ping, ESP_PING_PROF_SEQNO, &seqno, sizeof(seqno));\n        c = getc(stdin);\n        delay(50);\n    }\n\n    //Reset flags, so we dont end up destroying our terminal env later, when linenoise takes over again\n    fcntl(fileno(stdin), F_SETFL, flags);\n\n    esp_ping_stop(ping);\n    //Print total statistics\n    uint32_t transmitted;\n    uint32_t received;\n    uint32_t total_time_ms;\n    esp_ping_get_profile(ping, ESP_PING_PROF_REQUEST, &transmitted, sizeof(transmitted));\n    esp_ping_get_profile(ping, ESP_PING_PROF_REPLY, &received, sizeof(received));\n    esp_ping_get_profile(ping, ESP_PING_PROF_DURATION, &total_time_ms, sizeof(total_time_ms));\n    printf(\"%u packets transmitted, %u received, time %u ms\\n\", transmitted, received, total_time_ms);\n\n\n\n    esp_ping_delete_session(ping);\n\n    return EXIT_SUCCESS;\n}\n\nstatic void ipconfig_wlan()\n{\n    printf(\"==== WLAN ====\\n\");\n    auto status = WiFi.status();\n    printf(\"Mode: %s\\n\", wlmode2string(WiFi.getMode()));\n    printf(\"Status: %s\\n\", wlstatus2string(status));\n\n    if (status == WL_NO_SHIELD) {\n        return;\n    }\n    \n    printf(\"\\n\");\n    printf(\"SSID: %s\\n\", WiFi.SSID().c_str());\n    printf(\"BSSID: %s\\n\", WiFi.BSSIDstr().c_str());\n    printf(\"Channel: %d\\n\", WiFi.channel());\n\n    printf(\"\\n\");\n    printf(\"IP: %s\\n\", WiFi.localIP().toString().c_str());\n    printf(\"Subnet Mask: %s (/%d)\\n\", WiFi.subnetMask().toString().c_str(), WiFi.subnetCIDR());\n    printf(\"Gateway: %s\\n\", WiFi.gatewayIP().toString().c_str());\n#if !defined(ESP_ARDUINO_VERSION_MAJOR) || ESP_ARDUINO_VERSION_MAJOR < 3\n    printf(\"IPv6: %s\\n\", WiFi.localIPv6().toString().c_str());\n#endif\n    \n    printf(\"\\n\");\n    printf(\"Hostname: %s\\n\", WiFi.getHostname());\n    printf(\"DNS1: %s\\n\", WiFi.dnsIP(0).toString().c_str());\n    printf(\"DNS2: %s\\n\", WiFi.dnsIP(0).toString().c_str());\n}\n\nstatic int ipconfig(int argc, char **argv)\n{\n    ipconfig_wlan();\n    return EXIT_SUCCESS;\n}\n\nnamespace ESP32Console::Commands\n{\n    const ConsoleCommand getPingCommand()\n    {\n        return ConsoleCommand(\"ping\", &ping, \"Ping host\");\n    }\n\n    const ConsoleCommand getIpconfigCommand()\n    {\n        return ConsoleCommand(\"ipconfig\", &ipconfig, \"Show IP and connection informations\");\n    }\n}"
  },
  {
    "path": "src/ESP32Console/Commands/NetworkCommands.h",
    "content": "#pragma once\n\n#include \"ESP32Console/ConsoleCommand.h\"\n\nnamespace ESP32Console::Commands\n{\n    const ConsoleCommand getPingCommand();\n\n\n    const ConsoleCommand getIpconfigCommand();\n}"
  },
  {
    "path": "src/ESP32Console/Commands/SystemCommands.cpp",
    "content": "#include \"./SystemCommands.h\"\n#include \"ESP32Console.h\"\n#include <Arduino.h>\n#include <esp_partition.h>\n#include <esp_ota_ops.h>\n#include <esp_system.h>\n#if __has_include(<esp_chip_info.h>)\n#include <esp_chip_info.h>\n#endif\n#if __has_include(<core_version.h>)\n#include <core_version.h>\n#endif\n#include <getopt.h>\n\n// For XTSTR macros (Xtensa-specific)\n#if __has_include(<xtensa/xtruntime.h>)\n#include <xtensa/xtruntime.h>\n#else\n// Provide a fallback stringification macro for non-Xtensa architectures (e.g. RISC-V)\n#ifndef XTSTR\n#define _XTSTR(x) #x\n#define XTSTR(x) _XTSTR(x)\n#endif\n#endif\n\nstatic String mac2String(uint64_t mac)\n{\n    byte *ar = (byte *)&mac;\n    String s;\n    for (byte i = 0; i < 6; ++i)\n    {\n        char buf[3];\n        sprintf(buf, \"%02X\", ar[i]); // J-M-L: slight modification, added the 0 in the format for padding\n        s += buf;\n        if (i < 5)\n            s += ':';\n    }\n    return s;\n}\n\nstatic const char *getFlashModeStr()\n{\n    switch (ESP.getFlashChipMode())\n    {\n    case FM_DIO:\n        return \"DIO\";\n    case FM_DOUT:\n        return \"DOUT\";\n    case FM_FAST_READ:\n        return \"FAST READ\";\n    case FM_QIO:\n        return \"QIO\";\n    case FM_QOUT:\n        return \"QOUT\";\n    case FM_SLOW_READ:\n        return \"SLOW READ\";\n    case FM_UNKNOWN:\n    default:\n        return \"UNKNOWN\";\n    }\n}\n\nstatic const char *getResetReasonStr()\n{\n    switch (esp_reset_reason())\n    {\n    case ESP_RST_BROWNOUT:\n        return \"Brownout reset (software or hardware)\";\n    case ESP_RST_DEEPSLEEP:\n        return \"Reset after exiting deep sleep mode\";\n    case ESP_RST_EXT:\n        return \"Reset by external pin (not applicable for ESP32)\";\n    case ESP_RST_INT_WDT:\n        return \"Reset (software or hardware) due to interrupt watchdog\";\n    case ESP_RST_PANIC:\n        return \"Software reset due to exception/panic\";\n    case ESP_RST_POWERON:\n        return \"Reset due to power-on event\";\n    case ESP_RST_SDIO:\n        return \"Reset over SDIO\";\n    case ESP_RST_SW:\n        return \"Software reset via esp_restart\";\n    case ESP_RST_TASK_WDT:\n        return \"Reset due to task watchdog\";\n    case ESP_RST_WDT:\n        return \"ESP_RST_WDT\";\n\n    case ESP_RST_UNKNOWN:\n    default:\n        return \"Unknown\";\n    }\n}\n\nstatic int sysInfo(int argc, char **argv)\n{\n    esp_chip_info_t info;\n    esp_chip_info(&info);\n\n    printf(\"ESP32Console version: %s\\n\", ESP32CONSOLE_VERSION);\n#if defined(ARDUINO_ESP32_GIT_DESC)\n    printf(\"Arduino Core version: %s (%x)\\n\", XTSTR(ARDUINO_ESP32_GIT_DESC), ARDUINO_ESP32_GIT_VER);\n#endif\n    printf(\"ESP-IDF Version: %s\\n\", ESP.getSdkVersion());\n\n    printf(\"\\n\");\n    printf(\"Chip info:\\n\");\n    printf(\"\\tModel: %s\\n\", ESP.getChipModel());\n    printf(\"\\tRevison number: %d\\n\", ESP.getChipRevision());\n    printf(\"\\tCores: %d\\n\", ESP.getChipCores());\n    printf(\"\\tClock: %d MHz\\n\", ESP.getCpuFreqMHz());\n    printf(\"\\tFeatures:%s%s%s%s%s\\r\\n\",\n           info.features & CHIP_FEATURE_WIFI_BGN ? \" 802.11bgn \" : \"\",\n           info.features & CHIP_FEATURE_BLE ? \" BLE \" : \"\",\n           info.features & CHIP_FEATURE_BT ? \" BT \" : \"\",\n           info.features & CHIP_FEATURE_EMB_FLASH ? \" Embedded-Flash \" : \" External-Flash \",\n           info.features & CHIP_FEATURE_EMB_PSRAM ? \" Embedded-PSRAM\" : \"\");\n\n    printf(\"EFuse MAC: %s\\n\", mac2String(ESP.getEfuseMac()).c_str());\n\n    printf(\"Flash size: %d MB (mode: %s, speed: %d MHz)\\n\", ESP.getFlashChipSize() / (1024 * 1024), getFlashModeStr(), ESP.getFlashChipSpeed() / (1024 * 1024));\n    printf(\"PSRAM size: %d MB\\n\", ESP.getPsramSize() / (1024 * 1024));\n\n    printf(\"Sketch size: %d KB\\n\", ESP.getSketchSize() / (1024));\n    printf(\"Sketch MD5: %s\\n\", ESP.getSketchMD5().c_str());\n\n#ifndef CONFIG_APP_REPRODUCIBLE_BUILD\n    printf(\"Compilation datetime: \" __DATE__ \" \" __TIME__ \"\\n\");\n#endif\n\n    printf(\"\\nReset reason: %s\\n\", getResetReasonStr());\n\n    printf(\"\\n\");\n    printf(\"CPU temperature: %.01f °C\\n\", temperatureRead());\n\n    return EXIT_SUCCESS;\n}\n\nstatic int restart(int argc, char **argv)\n{\n    printf(\"Restarting...\");\n    ESP.restart();\n    return EXIT_SUCCESS;\n}\n\nstatic int meminfo(int argc, char **argv)\n{\n    uint32_t free = ESP.getFreeHeap() / 1024;\n    uint32_t total = ESP.getHeapSize() / 1024;\n    uint32_t used = total - free;\n    uint32_t min = ESP.getMinFreeHeap() / 1024;\n\n    printf(\"Heap: %u KB free, %u KB used, (%u KB total)\\n\", free, used, total);\n    printf(\"Minimum free heap size during uptime was: %u KB\\n\", min);\n    return EXIT_SUCCESS;\n}\n\nstatic int date(int argc, char **argv)\n{\n    bool set_time = false;\n    char *target = nullptr;\n\n    int c;\n    opterr = 0;\n\n    // Set timezone from env variable\n    tzset();\n\n    while ((c = getopt(argc, argv, \"s\")) != -1)\n        switch (c)\n        {\n        case 's':\n            set_time = true;\n            break;\n        case '?':\n            printf(\"Unknown option: %c\\n\", optopt);\n            return 1;\n        case ':':\n            printf(\"Missing arg for %c\\n\", optopt);\n            return 1;\n        }\n\n    if (optind < argc)\n    {\n        target = argv[optind];\n    }\n\n    if (set_time)\n    {\n        if (!target)\n        {\n            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\");\n            return 1;\n        }\n\n        tm t;\n\n        if (!strptime(target, \"%Y-%m-%d %H:%M:%S\", &t))\n        {\n            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\");\n            return 1;\n        }\n\n        timeval tv = {\n            .tv_sec = mktime(&t),\n            .tv_usec = 0};\n\n        if (settimeofday(&tv, nullptr))\n        {\n            fprintf(stderr, \"Could not set system time: %s\", strerror(errno));\n            return 1;\n        }\n\n        time_t tmp = time(nullptr);\n\n        constexpr int buffer_size = 100;\n        char buffer[buffer_size];\n        strftime(buffer, buffer_size, \"%a %b %e %H:%M:%S %Z %Y\", localtime(&tmp));\n        printf(\"Time set: %s\\n\", buffer);\n\n        return 0;\n    }\n\n    // If no target was supplied put a default one (similar to coreutils date)\n    if (!target)\n    {\n        target = (char*) \"+%a %b %e %H:%M:%S %Z %Y\";\n    }\n\n    // Ensure the format string is correct\n    if (target[0] != '+')\n    {\n        fprintf(stderr, \"Format string must start with an +!\\n\");\n        return 1;\n    }\n\n    // Ignore + by moving pointer one step forward\n    target++;\n\n    constexpr int buffer_size = 100;\n    char buffer[buffer_size];\n    time_t t = time(nullptr);\n    strftime(buffer, buffer_size, target, localtime(&t));\n    printf(\"%s\\n\", buffer);\n    return 0;\n\n    return EXIT_SUCCESS;\n}\n\nnamespace ESP32Console::Commands\n{\n    const ConsoleCommand getRestartCommand()\n    {\n        return ConsoleCommand(\"restart\", &restart, \"Restart / Reboot the system\");\n    }\n\n    const ConsoleCommand getSysInfoCommand()\n    {\n        return ConsoleCommand(\"sysinfo\", &sysInfo, \"Shows informations about the system like chip model and ESP-IDF version\");\n    }\n\n    const ConsoleCommand getMemInfoCommand()\n    {\n        return ConsoleCommand(\"meminfo\", &meminfo, \"Shows information about heap usage\");\n    }\n\n    const ConsoleCommand getDateCommand()\n    {\n        return ConsoleCommand(\"date\", &date, \"Shows and modify the system time\");\n    }\n}"
  },
  {
    "path": "src/ESP32Console/Commands/SystemCommands.h",
    "content": "#pragma once\n\n#include \"ESP32Console/ConsoleCommand.h\"\n\nnamespace ESP32Console::Commands\n{\n    const ConsoleCommand getSysInfoCommand();\n\n    const ConsoleCommand getRestartCommand();\n\n    const ConsoleCommand getMemInfoCommand();\n\n    const ConsoleCommand getDateCommand();\n};"
  },
  {
    "path": "src/ESP32Console/Commands/VFSCommands.cpp",
    "content": "#include \"./VFSCommands.h\"\n#include <Arduino.h>\n#include <string>\n#include <stdio.h>\n#include <stdlib.h>\n#include <dirent.h>\n#include <sys/stat.h>\n#include <sys/types.h>\n#include \"ESP32Console/Helpers/PWDHelpers.h\"\n\n#include \"kilo/kilo.h\"\n\nchar *canonicalize_file_name(const char *path);\n\nint cat(int argc, char **argv)\n{\n    if (argc == 1)\n    {\n        fprintf(stderr, \"You have to pass at least one file path!\\n\");\n        return EXIT_SUCCESS;\n    }\n\n    for (int n = 1; n < argc; n++)\n    {\n        char filename[PATH_MAX];\n        // We have manually do resolving of . and .., as VFS does not do it\n        ESP32Console::console_realpath(argv[n], filename);\n\n        FILE *file = fopen(filename, \"r\");\n        if (file == nullptr)\n        {\n            fprintf(stderr, \"%s %s : %s\\n\", argv[0], filename,\n                    strerror(errno));\n            return errno;\n        }\n\n        int chr;\n        while ((chr = getc(file)) != EOF)\n            fprintf(stdout, \"%c\", chr);\n        fclose(file);\n    }\n\n    return EXIT_SUCCESS;\n}\n\nint pwd(int argc, char **argv)\n{\n    printf(\"%s\\n\", ESP32Console::console_getpwd());\n    return EXIT_SUCCESS;\n}\n\nint cd(int argc, char **argv)\n{\n    const char *path;\n\n    if (argc != 2)\n    {\n        path = getenv(\"HOME\");\n        if (!path)\n        {\n            fprintf(stderr, \"No HOME env variable set!\\n\");\n            return EXIT_FAILURE;\n        }\n    }\n    else\n    {\n        path = argv[1];\n    }\n\n    // Check if target path is a file\n    char resolved[PATH_MAX];\n    ESP32Console::console_realpath(path, resolved);\n    FILE *file = fopen(resolved, \"r\");\n\n    // If we can open it, then we can not chdir into it.\n    if (file)\n    {\n        fclose(file);\n        fprintf(stderr, \"Can not cd into a file!\\n\");\n        return 1;\n    }\n\n    if (ESP32Console::console_chdir(path))\n    {\n        fprintf(stderr, \"Error: %s\\n\", strerror(errno));\n        return 1;\n    }\n\n    const char *pwd = ESP32Console::console_getpwd();\n\n    // Check if the new PWD exists, and show a warning if not\n    DIR *dir = opendir(pwd);\n    if (dir)\n    {\n        closedir(dir);\n    }\n    else if (ENOENT == errno)\n    {\n        fprintf(stderr, \"Choosen directory maybe does not exists!\\n\");\n    }\n\n    return EXIT_SUCCESS;\n}\n\nint ls(int argc, char **argv)\n{\n    char *inpath;\n    if (argc == 1)\n    {\n        inpath = (char *)\".\";\n    }\n    else if (argc == 2)\n    {\n        inpath = argv[1];\n    }\n    else\n    {\n        printf(\"You can pass only one filename!\\n\");\n        return 1;\n    }\n\n    char path[PATH_MAX];\n    ESP32Console::console_realpath(inpath, path);\n\n    DIR *dir = opendir(path);\n    if (!dir)\n    {\n        fprintf(stderr, \"Could not open filepath: %s\\n\", strerror(errno));\n        return 1;\n    }\n\n    struct dirent *d;\n    while ((d = readdir(dir)) != NULL)\n    {\n        printf(\"%s\\n\", d->d_name);\n    }\n\n    closedir(dir);\n    return EXIT_SUCCESS;\n}\n\nint mv(int argc, char **argv)\n{\n    if (argc != 3)\n    {\n        fprintf(stderr, \"Syntax is mv [ORIGIN] [TARGET]\\n\");\n        return 1;\n    }\n\n    char old_name[PATH_MAX], new_name[PATH_MAX];\n\n    // Resolve arguments to full path\n    ESP32Console::console_realpath(argv[1], old_name);\n    ESP32Console::console_realpath(argv[2], new_name);\n\n    // Do rename\n    if (rename(old_name, new_name))\n    {\n        printf(\"Error moving: %s\\n\", strerror(errno));\n        return EXIT_FAILURE;\n    }\n\n    return EXIT_SUCCESS;\n}\n\nint cp(int argc, char **argv)\n{\n    //TODO: Shows weird error message\n    if (argc != 3)\n    {\n        fprintf(stderr, \"Syntax is cp [ORIGIN] [TARGET]\\n\");\n        return 1;\n    }\n\n    char old_name[PATH_MAX], new_name[PATH_MAX];\n\n    // Resolve arguments to full path\n    ESP32Console::console_realpath(argv[1], old_name);\n    ESP32Console::console_realpath(argv[2], new_name);\n\n    // Do copy\n    FILE *origin = fopen(old_name, \"r\");\n    if (!origin)\n    {\n        fprintf(stderr, \"Error opening origin file: %s\\n\", strerror(errno));\n        return 1;\n    }\n\n    FILE *target = fopen(new_name, \"w\");\n    if (!target)\n    {\n        fclose(origin);\n        fprintf(stderr, \"Error opening target file: %s\\n\", strerror(errno));\n        return 1;\n    }\n\n    int buffer;\n\n    // Clear existing errors\n    auto error = errno;\n\n    while ((buffer = getc(origin)) != EOF)\n    {\n        if(fputc(buffer, target) == EOF) {\n            fprintf(stderr, \"Error writing: %s\\n\", strerror(errno));\n            fclose(origin); fclose(target);\n            return 1;\n        }\n    }\n\n    error = errno;\n    if (error && !feof(origin))\n    {\n        fprintf(stderr, \"Error copying: %s\\n\", strerror(error));\n        fclose(origin);\n        fclose(target);\n        return 1;\n    }\n\n    fclose(origin);\n    fclose(target);\n\n    return EXIT_SUCCESS;\n}\n\nint rm(int argc, char **argv)\n{\n    if (argc != 2)\n    {\n        fprintf(stderr, \"You have to pass exactly one file. Syntax rm [FIILE]\\n\");\n        return EXIT_SUCCESS;\n    }\n\n    char filename[PATH_MAX];\n    ESP32Console::console_realpath(argv[1], filename);\n\n    if(unlink(filename)) {\n        fprintf(stderr, \"Error deleting %s: %s\\n\", filename, strerror(errno));\n    }\n\n    return EXIT_SUCCESS;\n}\n\nint rmdir(int argc, char **argv)\n{\n    if (argc != 2)\n    {\n        fprintf(stderr, \"You have to pass exactly one file. Syntax rmdir [FIILE]\\n\");\n        return EXIT_SUCCESS;\n    }\n\n    char filename[PATH_MAX];\n    ESP32Console::console_realpath(argv[1], filename);\n\n    if(rmdir(filename)) {\n        fprintf(stderr, \"Error deleting %s: %s\\n\", filename, strerror(errno));\n    }\n\n    return EXIT_SUCCESS;\n}\n\nnamespace ESP32Console::Commands\n{\n    const ConsoleCommand getCatCommand()\n    {\n        return ConsoleCommand(\"cat\", &cat, \"Show the content of one or more files.\");\n    }\n\n    const ConsoleCommand getPWDCommand()\n    {\n        return ConsoleCommand(\"pwd\", &pwd, \"Show the current working dir\");\n    }\n\n    const ConsoleCommand getCDCommand()\n    {\n        return ConsoleCommand(\"cd\", &cd, \"Change the working directory\");\n    }\n\n    const ConsoleCommand getLsCommand()\n    {\n        return ConsoleCommand(\"ls\", &ls, \"List the contents of the given path\");\n    }\n\n    const ConsoleCommand getMvCommand()\n    {\n        return ConsoleCommand(\"mv\", &mv, \"Move the given file to another place or name\");\n    }\n\n    const ConsoleCommand getCPCommand()\n    {\n        return ConsoleCommand(\"cp\", &cp, \"Copy the given file to another place or name\");\n    }\n\n    const ConsoleCommand getRMCommand()\n    {\n         return ConsoleCommand(\"rm\", &rm, \"Permanenty deletes the given file.\");\n    }\n\n    const ConsoleCommand getRMDirCommand()\n    {\n        return ConsoleCommand(\"rmdir\", &rmdir, \"Permanenty deletes the given folder. Folder must be empty!\");\n    }\n\n    const ConsoleCommand getEditCommand()\n    {\n        return ConsoleCommand(\"edit\", &ESP32Console::Kilo::kilo, \"Edit files\");\n    }\n}"
  },
  {
    "path": "src/ESP32Console/Commands/VFSCommands.h",
    "content": "#pragma once\n\n#include \"ESP32Console/ConsoleCommand.h\"\n\nnamespace ESP32Console::Commands\n{\n    const ConsoleCommand getCatCommand();\n\n    const ConsoleCommand getPWDCommand();\n\n    const ConsoleCommand getCDCommand();\n\n    const ConsoleCommand getLsCommand();\n\n    const ConsoleCommand getMvCommand();\n\n    const ConsoleCommand getCPCommand();\n\n    const ConsoleCommand getRMCommand();\n\n    const ConsoleCommand getRMDirCommand();\n\n    const ConsoleCommand getEditCommand();\n}"
  },
  {
    "path": "src/ESP32Console/Console.cpp",
    "content": "#include \"./Console.h\"\n#include \"soc/soc_caps.h\"\n#include \"esp_err.h\"\n#include \"esp_idf_version.h\"\n#include \"ESP32Console/Commands/CoreCommands.h\"\n#include \"ESP32Console/Commands/SystemCommands.h\"\n#include \"ESP32Console/Commands/NetworkCommands.h\"\n#include \"ESP32Console/Commands/VFSCommands.h\"\n#include \"ESP32Console/Commands/GPIOCommands.h\"\n#include \"driver/uart.h\"\n#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)\n#include \"driver/uart_vfs.h\"\n#else\n#include \"esp_vfs_dev.h\"\n#endif\n#include \"linenoise/linenoise.h\"\n#include \"ESP32Console/Helpers/PWDHelpers.h\"\n#include \"ESP32Console/Helpers/InputParser.h\"\n\nstatic const char *TAG = \"ESP32Console\";\n\nusing namespace ESP32Console::Commands;\n\nnamespace ESP32Console\n{\n    void Console::registerCoreCommands()\n    {\n        registerCommand(getClearCommand());\n        registerCommand(getHistoryCommand());\n        registerCommand(getEchoCommand());\n        registerCommand(getSetMultilineCommand());\n        registerCommand(getEnvCommand());\n        registerCommand(getDeclareCommand());\n    }\n\n    void Console::registerSystemCommands()\n    {\n        registerCommand(getSysInfoCommand());\n        registerCommand(getRestartCommand());\n        registerCommand(getMemInfoCommand());\n        registerCommand(getDateCommand());\n    }\n\n    void ESP32Console::Console::registerNetworkCommands()\n    {\n        registerCommand(getPingCommand());\n        registerCommand(getIpconfigCommand());\n    }\n\n    void Console::registerVFSCommands()\n    {\n        registerCommand(getCatCommand());\n        registerCommand(getCDCommand());\n        registerCommand(getPWDCommand());\n        registerCommand(getLsCommand());\n        registerCommand(getMvCommand());\n        registerCommand(getCPCommand());\n        registerCommand(getRMCommand());\n        registerCommand(getRMDirCommand());\n        registerCommand(getEditCommand());\n    }\n\n    void Console::registerGPIOCommands()\n    {\n        registerCommand(getPinModeCommand());\n        registerCommand(getDigitalReadCommand());\n        registerCommand(getDigitalWriteCommand());\n        registerCommand(getAnalogReadCommand());\n    }\n\n    void Console::beginCommon()\n    {\n        /* Tell linenoise where to get command completions and hints */\n        linenoiseSetCompletionCallback(&esp_console_get_completion);\n        linenoiseSetHintsCallback((linenoiseHintsCallback *)&esp_console_get_hint);\n\n        /* Set command history size */\n        linenoiseHistorySetMaxLen(max_history_len_);\n\n        /* Set command maximum length */\n        linenoiseSetMaxLineLen(max_cmdline_len_);\n\n        // Load history if defined\n        if (history_save_path_)\n        {\n            linenoiseHistoryLoad(history_save_path_);\n        }\n\n        // Register core commands like echo\n        esp_console_register_help_command();\n        registerCoreCommands();\n    }\n\n    void Console::begin(int baud, int rxPin, int txPin, uart_port_t channel)\n    {\n        log_d(\"Initialize console\");\n\n        if (channel >= SOC_UART_NUM)\n        {\n            log_e(\"Serial number is invalid, please use numers from 0 to %u\", SOC_UART_NUM - 1);\n            return;\n        }\n\n        this->uart_channel_ = channel;\n\n        //Reinit the UART driver if the channel was already in use\n        if (uart_is_driver_installed(channel)) {\n            uart_driver_delete(channel);\n        }\n\n        /* Drain stdout before reconfiguring it */\n        fflush(stdout);\n        fsync(fileno(stdout));\n\n        /* Disable buffering on stdin */\n        setvbuf(stdin, NULL, _IONBF, 0);\n\n        /* Minicom, screen, idf_monitor send CR when ENTER key is pressed */\n#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)\n        uart_vfs_dev_port_set_rx_line_endings(channel, ESP_LINE_ENDINGS_CR);\n        /* Move the caret to the beginning of the next line on '\\n' */\n        uart_vfs_dev_port_set_tx_line_endings(channel, ESP_LINE_ENDINGS_CRLF);\n#else\n        esp_vfs_dev_uart_port_set_rx_line_endings(channel, ESP_LINE_ENDINGS_CR);\n        /* Move the caret to the beginning of the next line on '\\n' */\n        esp_vfs_dev_uart_port_set_tx_line_endings(channel, ESP_LINE_ENDINGS_CRLF);\n#endif\n\n        /* Configure UART. Note that REF_TICK is used so that the baud rate remains\n         * correct while APB frequency is changing in light sleep mode.\n         */\n        const uart_config_t uart_config = {\n            .baud_rate = baud,\n            .data_bits = UART_DATA_8_BITS,\n            .parity = UART_PARITY_DISABLE,\n            .stop_bits = UART_STOP_BITS_1,\n#if SOC_UART_SUPPORT_REF_TICK\n            .source_clk = UART_SCLK_REF_TICK,\n#elif SOC_UART_SUPPORT_XTAL_CLK\n            .source_clk = UART_SCLK_XTAL,\n#endif\n        };\n    \n\n        ESP_ERROR_CHECK(uart_param_config(channel, &uart_config));\n\n        // Set the correct pins for the UART of needed\n        if (rxPin > 0 || txPin > 0) {\n            if (rxPin < 0 || txPin < 0) {\n                log_e(\"Both rxPin and txPin has to be passed!\");\n            }\n            uart_set_pin(channel, txPin, rxPin, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE);\n        }\n\n        /* Install UART driver for interrupt-driven reads and writes */\n        ESP_ERROR_CHECK(uart_driver_install(channel, 256, 0, 0, NULL, 0));\n\n        /* Tell VFS to use UART driver */\n#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)\n        uart_vfs_dev_use_driver(channel);\n#else\n        esp_vfs_dev_uart_use_driver(channel);\n#endif\n\n        esp_console_config_t console_config = {\n            .max_cmdline_length = max_cmdline_len_,\n            .max_cmdline_args = max_cmdline_args_,\n            .hint_color = 333333};\n\n        ESP_ERROR_CHECK(esp_console_init(&console_config));\n\n        beginCommon();\n\n        // Start REPL task\n        if (xTaskCreate(&Console::repl_task, \"console_repl\", 4096, this, 2, &task_) != pdTRUE)\n        {\n            log_e(\"Could not start REPL task!\");\n        }\n    }\n\n    static void resetAfterCommands()\n    {\n        //Reset all global states a command could change\n\n        //Reset getopt parameters\n        optind = 0;\n    }\n\n    void Console::repl_task(void *args)\n    {\n        Console const &console = *(static_cast<Console *>(args));\n\n        /* Change standard input and output of the task if the requested UART is\n         * NOT the default one. This block will replace stdin, stdout and stderr.\n         * We have to do this in the repl task (not in the begin, as these settings are only valid for the current task)\n         */\n        if (console.uart_channel_ != CONFIG_ESP_CONSOLE_UART_NUM)\n        {\n            char path[13] = {0};\n            snprintf(path, 13, \"/dev/uart/%d\", console.uart_channel_);\n\n            stdin = fopen(path, \"r\");\n            stdout = fopen(path, \"w\");\n            stderr = stdout;\n        }\n\n        setvbuf(stdin, NULL, _IONBF, 0);\n\n        /* This message shall be printed here and not earlier as the stdout\n         * has just been set above. */\n        printf(\"\\r\\n\"\n               \"Type 'help' to get the list of commands.\\r\\n\"\n               \"Use UP/DOWN arrows to navigate through command history.\\r\\n\"\n               \"Press TAB when typing command name to auto-complete.\\r\\n\");\n\n        // Probe terminal status\n        int probe_status = linenoiseProbe();\n        if (probe_status)\n        {\n            linenoiseSetDumbMode(1);\n        }\n\n        if (linenoiseIsDumbMode())\n        {\n            printf(\"\\r\\n\"\n                   \"Your terminal application does not support escape sequences.\\n\\n\"\n                   \"Line editing and history features are disabled.\\n\\n\"\n                   \"On Windows, try using Putty instead.\\r\\n\");\n        }\n\n        linenoiseSetMaxLineLen(console.max_cmdline_len_);\n        while (1)\n        {\n            String prompt = console.prompt_;\n\n            // Insert current PWD into prompt if needed\n            prompt.replace(\"%pwd%\", console_getpwd());\n\n            char *line = linenoise(prompt.c_str());\n            if (line == NULL)\n            {\n                ESP_LOGD(TAG, \"empty line\");\n                /* Ignore empty lines */\n                continue;\n            }\n\n            log_v(\"Line received from linenoise: %s\\n\", line);\n\n            /* Add the command to the history */\n            linenoiseHistoryAdd(line);\n            \n            /* Save command history to filesystem */\n            if (console.history_save_path_)\n            {\n                linenoiseHistorySave(console.history_save_path_);\n            }\n\n            //Interpolate the input line\n            String interpolated_line = interpolateLine(line);\n            log_v(\"Interpolated line: %s\\n\", interpolated_line.c_str());\n\n            /* Try to run the command */\n            int ret;\n            esp_err_t err = esp_console_run(interpolated_line.c_str(), &ret);\n            \n            //Reset global state\n            resetAfterCommands();\n\n            if (err == ESP_ERR_NOT_FOUND)\n            {\n                printf(\"Unrecognized command\\n\");\n            }\n            else if (err == ESP_ERR_INVALID_ARG)\n            {\n                // command was empty\n            }\n            else if (err == ESP_OK && ret != ESP_OK)\n            {\n                printf(\"Command returned non-zero error code: 0x%x (%s)\\n\", ret, esp_err_to_name(ret));\n            }\n            else if (err != ESP_OK)\n            {\n                printf(\"Internal error: %s\\n\", esp_err_to_name(err));\n            }\n            /* linenoise allocates line buffer on the heap, so need to free it */\n            linenoiseFree(line);\n        }\n        ESP_LOGD(TAG, \"REPL task ended\");\n        vTaskDelete(NULL);\n    }\n\n    void Console::end()\n    {\n    }\n};"
  },
  {
    "path": "src/ESP32Console/Console.h",
    "content": "#pragma once\n\n#if !defined(ESP32)\n#error This library depends on ESP-IDF and only works on ESP32!\n#endif\n\n#include \"esp_console.h\"\n#include \"Arduino.h\"\n#include \"./ConsoleCommandBase.h\"\n#include \"freertos/task.h\"\n#include \"linenoise/linenoise.h\"\n#include \"driver/uart.h\"\n\nnamespace ESP32Console\n{\n    class Console\n    {\n    private:\n        const char *prompt_ = \"ESP32> \";\n        const uint32_t task_priority_;\n        const BaseType_t task_stack_size_;\n\n        uint16_t max_history_len_ = 40;\n        const char* history_save_path_ = nullptr;\n\n        const size_t max_cmdline_len_;\n        const size_t max_cmdline_args_;\n\n        uart_port_t uart_channel_;\n\n        TaskHandle_t task_;\n\n        static void repl_task(void *args);\n\n        void beginCommon();\n\n    public:\n        /**\n         * @brief Create a new ESP32Console with the default parameters\n         */\n        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){};\n\n        ~Console()\n        {\n            vTaskDelete(task_);\n            end();\n        }\n\n        /**\n         * @brief Register the given command, using the raw ESP-IDF structure.\n         *\n         * @param cmd The command that should be registered\n         * @return Return true, if the registration was successfull, false if not.\n         */\n        bool registerCommand(const esp_console_cmd_t *cmd)\n        {\n            log_v(\"Registering new command %s\", cmd->command);\n\n            auto code = esp_console_cmd_register(cmd);\n            if (code != ESP_OK)\n            {\n                log_e(\"Error registering command (Reason %s)\", esp_err_to_name(code));\n                return false;\n            }\n\n            return true;\n        }\n\n        /**\n         * @brief Register the given command\n         *\n         * @param cmd The command that should be registered\n         * @return true If the command was registered successful.\n         * @return false If the command was not registered because of an error.\n         */\n        bool registerCommand(const ConsoleCommandBase &cmd)\n        {\n            auto c = cmd.toCommandStruct();\n            return registerCommand(&c);\n        }\n\n        /**\n         * @brief Registers the given command\n         *\n         * @param command The name under which the command can be called (e.g. \"ls\"). Must not contain spaces.\n         * @param func A pointer to the function which should be run, when this command is called\n         * @param help A text shown in output of \"help\" command describing this command. When empty it is not shown in help.\n         * @param hint A text describing the usage of the command in help output\n         * @return true If the command was registered successful.\n         * @return false If the command was not registered because of an error.\n         */\n        bool registerCommand(const char *command, esp_console_cmd_func_t func, const char *help, const char *hint = \"\")\n        {\n            const esp_console_cmd_t cmd = {\n                .command = command,\n                .help = help,\n                .hint = hint,\n                .func = func,\n                .argtable = nullptr\n            };\n\n            return registerCommand(&cmd);\n        };\n\n        void registerCoreCommands();\n\n        void registerSystemCommands();\n\n        void registerNetworkCommands();\n\n        void registerVFSCommands();\n\n        void registerGPIOCommands();\n\n        /**\n         * @brief Set the command prompt. Default is \"ESP32>\".\n         *\n         * @param prompt\n         */\n        void setPrompt(const char *prompt) { prompt_ = prompt; };\n\n        /**\n         * @brief Set the History Max Length object\n         * \n         * @param max_length \n         */\n        void setHistoryMaxLength(uint16_t max_length)\n        {\n            max_history_len_ = max_length;\n            linenoiseHistorySetMaxLen(max_length);\n        }\n\n        /**\n         * @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.\n         *\n         * @param history_save_path The file which will be used to save command history. Set to nullptr to disable persistent saving\n         */\n        void enablePersistentHistory(const char *history_save_path = \"/spiffs/.history.txt\") { history_save_path_ = history_save_path; };\n\n        /**\n         * @brief Starts the console. Similar to the Serial.begin() function\n         * \n         * @param baud The baud rate with which the console should work. Recommended: 115200\n         * @param rxPin The pin to use for RX\n         * @param txPin The pin to use for TX\n         * @param channel The UART port to use\n         */\n        void begin(int baud, int rxPin = -1, int txPin = -1, uart_port_t channel = UART_NUM_0);\n\n        /**\n         * @brief Starts the console. Overload for backward compatibility with uint8_t channel argument.\n         *\n         * @param baud The baud rate with which the console should work. Recommended: 115200\n         * @param rxPin The pin to use for RX\n         * @param txPin The pin to use for TX\n         * @param channel The number of the UART to use (cast to uart_port_t)\n         */\n        void begin(int baud, int rxPin, int txPin, uint8_t channel)\n        {\n            begin(baud, rxPin, txPin, static_cast<uart_port_t>(channel));\n        }\n\n        void end();\n    };\n};"
  },
  {
    "path": "src/ESP32Console/ConsoleCommand.h",
    "content": "#pragma once\n\n#include \"./ConsoleCommandBase.h\"\n\nnamespace ESP32Console\n{\n\n    /**\n     * @brief A class for registering custom console commands via a passed function pointer.\n     * \n     */\n    class ConsoleCommand : public ConsoleCommandBase\n    {\n    protected:\n        const char *hint_;\n\n    public:\n        /**\n         * @brief Creates a new ConsoleCommand object with the given parameters.\n         * \n         * @param command The name under which the command will be called (e.g. \"ls\"). Must not contain spaces.\n         * @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.\n         * @param help The text shown in \"help\" output for this command. If set to empty string, then the command is not shown in help.\n         * @param hint A hint explaining the parameters in help output\n         */\n        ConsoleCommand(const char *command, esp_console_cmd_func_t func, const char* help, const char* hint = \"\") { command_ = command; func_ = func; help_= help; hint_ = hint;};\n\n        const esp_console_cmd_t toCommandStruct() const override\n        {\n            const esp_console_cmd_t cmd = {\n                .command = command_,\n                .help = help_,\n                .hint = hint_,\n                .func = func_,\n                .argtable = nullptr\n            };\n\n            return cmd;\n        }\n    };\n};"
  },
  {
    "path": "src/ESP32Console/ConsoleCommandBase.h",
    "content": "#pragma once\n\n#include \"esp_console.h\"\n\nnamespace ESP32Console {\n    class ConsoleCommandBase\n    {\n        protected:\n            const char *command_;\n            const char *help_;\n            esp_console_cmd_func_t func_;\n\n        public:\n            /**\n            * @brief Get the command name\n            * \n            * @return const char* \n            */\n            const char* getCommand() { return command_; };\n\n            const char* getHelp() { return help_; };\n\n            virtual const esp_console_cmd_t toCommandStruct() const = 0;  \n    };\n};"
  },
  {
    "path": "src/ESP32Console/ConsoleCommandD.cpp",
    "content": "#include \"./ConsoleCommandD.h\"\n#include \"Arduino.h\"\n\nconst static char *TAG = \"ConsoleCommandD\";\n\nnamespace ESP32Console\n{\n    std::unordered_map<std::string, delegateFunc> ConsoleCommandD::registry_ = std::unordered_map<std::string, delegateFunc>();\n\n    int ConsoleCommandD::delegateResolver(int argc, char **argv)\n    {\n        // Retrieve ConsoleCommandD from registry\n        auto it = registry_.find(argv[0]);\n        if (it == registry_.end())\n        {\n            log_e(\"Could not resolve the delegated function call!\");\n            return 1;\n        }\n\n        delegateFunc command = it->second;\n\n        int code = 0;\n\n        try\n        {\n            return command(argc, argv);\n        }\n        catch (const std::exception &err)\n        {\n            printf(\"%s\", err.what());\n            printf(\"\\n\");\n            return 1;\n        }\n    }\n}"
  },
  {
    "path": "src/ESP32Console/ConsoleCommandD.h",
    "content": "#pragma once\n\n#include \"./ConsoleCommand.h\"\n#include \"esp_console.h\"\n#include <string>\n#include <functional>\n#include <unordered_map>\n\n#include <type_traits>\n\nnamespace ESP32Console\n{\n    using delegateFunc = std::function<int(int, char **)>;\n\n    /**\n     * @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.\n     * 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.\n     * \n     */\n    class ConsoleCommandD : public ConsoleCommand\n    {\n    protected:\n        delegateFunc delegateFn_;\n\n        static int delegateResolver(int argc, char **argv);\n\n    public:\n        static std::unordered_map<std::string, delegateFunc> registry_;\n        \n        ConsoleCommandD(const char *command, delegateFunc func, const char* help, const char* hint = \"\"): ConsoleCommand(command, &delegateResolver, help, hint), delegateFn_(func) {};\n\n        const esp_console_cmd_t toCommandStruct() const override\n        {\n            const esp_console_cmd_t cmd = {\n                .command = command_,\n                .help = help_,\n                .hint = hint_,\n                .func = func_,\n                .argtable = nullptr\n                };\n\n            // When the command gets registered add it to our map, so we can access it later to resolve the delegated function call\n            registry_.insert({std::string(command_), std::move(delegateFn_)});\n\n            return cmd;\n        }\n\n        delegateFunc &getDelegateFunction() { return delegateFn_; }\n    };\n\n}"
  },
  {
    "path": "src/ESP32Console/Helpers/InputParser.cpp",
    "content": "#include \"./InputParser.h\"\n\n#include <Arduino.h>\n\nnamespace ESP32Console\n{\n    String interpolateLine(const char *in_)\n    {\n        String in(in_);\n        String out = in;\n\n        // Add a space at end of line, this does not change anything for our consoleLine and makes parsing easier\n        in = in + \" \";\n\n        // Interpolate each $ with the env variable if existing. If $ is the first character in a line it is not interpolated\n        int var_index = 1;\n        while ((var_index = in.indexOf(\"$\", var_index + 1)) > 0)\n        {\n            /**\n             * Extract the possible env variable\n             */\n            int variable_start = var_index + 1;\n            // If the char after $ is a { we look for an closing }. Otherwise we just look for an space\n            char delimiter = ' ';\n            if (in.charAt(variable_start) == '{')\n            {\n                // Our variable starts then at the character after ${\n                variable_start++;\n            }\n\n            int variable_end = in.indexOf(delimiter, variable_start + 1);\n            // If delimiter not found look for next possible env variable\n            if (variable_end == -1)\n            {\n                continue;\n            }\n\n            String env_var = in.substring(variable_start, variable_end);\n            env_var.trim();\n            // Depending on whether this is an variable string, we have to include the next character\n            String replace_target = in.substring(var_index, delimiter == '}' ? variable_end + 1 : variable_end);\n\n            // Check if we have an env with this name, then replace it\n            const char *value = getenv(env_var.c_str());\n            if (value)\n            {\n                out.replace(replace_target.c_str(), value);\n            }\n        }\n\n        return out.c_str();\n    }\n}"
  },
  {
    "path": "src/ESP32Console/Helpers/InputParser.h",
    "content": "#pragma once\n#include <Arduino.h>\n\nnamespace ESP32Console\n{\n    /**\n     * @brief Interpolate the given line using environment variables. $VAR and ${ENV} are replaced by the representive values of the environment variables.\n     * \n     * @param in \n     * @return String\n     */\n    String interpolateLine(const char *in);\n}"
  },
  {
    "path": "src/ESP32Console/Helpers/PWDHelpers.cpp",
    "content": "#include \"ESP32Console/Helpers/PWDHelpers.h\"\n\n#include <stdlib.h>\n#include <stdio.h>\n#include <Arduino.h>\n\nnamespace ESP32Console\n{\n    constexpr char *PWD_DEFAULT = (char*) \"/\";\n\n    const char *console_getpwd()\n    {\n        char *pwd = getenv(\"PWD\");\n        if (pwd)\n        { // If we have defined a PWD value, return it\n            return pwd;\n        }\n\n        // Otherwise set a default one\n        setenv(\"PWD\", PWD_DEFAULT, 1);\n        return PWD_DEFAULT;\n    }\n\n    const char *console_realpath(const char *path, char *resolvedPath)\n    {\n        String in = String(path);\n        String pwd = String(console_getpwd());\n        String result;\n        // If path is not absolute we prepend our pwd\n        if (!in.startsWith(\"/\"))\n        {\n            result = pwd + \"/\" + in;\n        }\n        else\n        {\n            result = in;\n        }\n        \n        realpath(result.c_str(), resolvedPath);\n        return resolvedPath;\n    }\n\n    int console_chdir(const char *path)\n    {\n        char buffer[PATH_MAX + 2];\n        console_realpath(path, buffer);\n\n        size_t buffer_len = strlen(buffer);\n        //If path does not end with slash, add it.\n        if(buffer[buffer_len - 1] != '/')\n        {\n            buffer[buffer_len] = '/';\n            buffer[buffer_len + 1] = '\\0';\n        }\n\n        setenv(\"PWD\", buffer, 1);\n\n        return 0;\n    }\n\n}"
  },
  {
    "path": "src/ESP32Console/Helpers/PWDHelpers.h",
    "content": "#pragma once\n\nnamespace ESP32Console\n{\n\n    /**\n     * @brief Returns the current console process working dir\n     *\n     * @return const char*\n     */\n    const char *console_getpwd();\n\n    /**\n     * @brief Resolves the given path using the console process working dir\n     *\n     * @return const char*\n     */\n    const char *console_realpath(const char *path, char *resolvedPath);\n\n    int console_chdir(const char *path);\n\n}"
  },
  {
    "path": "src/ESP32Console/OptionsConsoleCommand.cpp",
    "content": "#include \"./OptionsConsoleCommand.h\"\n#include \"Arduino.h\"\n\nconst static char *TAG = \"ArgParseCommand\";\n\nnamespace ESP32Console\n{\n    std::unordered_map<std::string, OptionsConsoleCommand> OptionsConsoleCommand::registry_ = std::unordered_map<std::string, OptionsConsoleCommand>();\n\n    int OptionsConsoleCommand::delegateResolver(int argc, char **argv)\n    {\n        // Retrieve ArgParserCommand from registry\n        auto it = registry_.find(argv[0]);\n        if (it == registry_.end())\n        {\n            log_e(\"Could not resolve the delegated function call!\");\n            return 1;\n        }\n\n        auto command = it->second;\n\n        int code = 0;\n\n        try\n        {\n            auto options = command.options;\n            auto result = options.parse(argc, argv);\n\n            //Print help on --help argument\n            if (result.count(\"help\")) {\n                printf(options.help().c_str());\n                printf(\"\\n\");\n                return EXIT_SUCCESS;\n            }\n\n            if (command.getVersion() && result.count(\"version\")) {\n                printf(\"Version: %s\\n\", command.getVersion());\n                return EXIT_SUCCESS;\n            }\n\n            return command.delegateFn_(argc, argv, result, options);\n        }\n        catch (const std::exception &err)\n        {\n            printf(err.what());\n            printf(\"\\n\");\n            return EXIT_FAILURE;\n        }\n    }\n}\n"
  },
  {
    "path": "src/ESP32Console/OptionsConsoleCommand.h",
    "content": "#pragma once\n\n#include \"./ConsoleCommandBase.h\"\n\n#include \"esp_console.h\"\n\n//This define is important, otherwise we get very high memory usage from regex\n#define CXXOPTS_NO_REGEX 1\n#define CXXOPTS_NO_RTTI 1\n#include \"cxxopts/cxxopts.hpp\"\n#include <functional>\n#include <unordered_map>\n#include <string>\n\nnamespace ESP32Console\n{\n    using cxxopts::Options;\n    using cxxopts::ParseResult;\n    using argParseFunc = std::function<int(int, char **, ParseResult&, Options&)>;\n\n    class OptionsConsoleCommand : public ConsoleCommandBase\n    {\n    protected:\n        argParseFunc delegateFn_;\n        const char *hint_;\n        const char *version_;\n\n        static int delegateResolver(int argc, char **argv);\n\n    public:\n        Options options;\n        static std::unordered_map<std::string, OptionsConsoleCommand> registry_;\n\n        OptionsConsoleCommand(const char *command, argParseFunc func, const char *help, const char* version = nullptr, const char *hint = nullptr): options(command, help)\n        {\n            command_ = command;\n            help_ = help;\n            version_ = version;\n\n            if (hint)\n            {\n                hint_ = hint;\n            }\n            else\n            {\n                    hint_ = \"Use --help option of command for more info\";\n            }\n\n            //Add an option \n            options.add_options()\n                (\"help\", \"Show help\", cxxopts::value<bool>()->default_value(\"false\"))\n            ;\n\n            if (version_)\n            {\n                options.add_options()\n                    (\"version\", \"Show version number of this command\", cxxopts::value<bool>()->default_value(\"false\"))\n                ;\n            }\n\n\n\n            delegateFn_ = func;\n            func_ = &delegateResolver;\n        }\n\n        const esp_console_cmd_t toCommandStruct() const override\n        {\n            const esp_console_cmd_t cmd = {\n                .command = command_,\n                .help = help_,\n                .hint = hint_,\n                .func = func_,\n                .argtable = nullptr\n                };\n\n            // When the command gets registered add it to our map, so we can access it later to resolve the delegated function call\n            registry_.insert({std::string(command_), std::move(*this)});\n\n            return cmd;\n        }\n\n        argParseFunc &getDelegateFunction() { return delegateFn_; }\n\n        const char* getVersion() const {return version_;};\n    };\n}"
  },
  {
    "path": "src/ESP32Console.h",
    "content": "#pragma once\n\n#if !defined(ESP32)\n#error This library depends on ESP-IDF and only works on ESP32!\n#endif\n\n#define ESP32CONSOLE_VERSION \"1.3.0\"\n\n#include \"ESP32Console/Console.h\"\n#include \"ESP32Console/ConsoleCommand.h\"\n#include \"ESP32Console/ConsoleCommandD.h\"\n#include \"ESP32Console/OptionsConsoleCommand.h\"\n"
  },
  {
    "path": "src/cxxopts/cxxopts.hpp",
    "content": "/*\n\nCopyright (c) 2014, 2015, 2016, 2017 Jarryd Beck\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n\n*/\n\n#ifndef CXXOPTS_HPP_INCLUDED\n#define CXXOPTS_HPP_INCLUDED\n\n#include <cctype>\n#include <cstring>\n#include <exception>\n//#include <iostream>\n#include <limits>\n#include <list>\n#include <map>\n#include <memory>\n#include <sstream>\n#include <string>\n#include <unordered_map>\n#include <unordered_set>\n#include <utility>\n#include <vector>\n#include <algorithm>\n\n#if defined(__GNUC__) && !defined(__clang__)\n#  if (__GNUC__ * 10 + __GNUC_MINOR__) < 49\n#    define CXXOPTS_NO_REGEX true\n#  endif\n#endif\n\n#ifndef CXXOPTS_NO_REGEX\n#  include <regex>\n#endif  // CXXOPTS_NO_REGEX\n\n// Nonstandard before C++17, which is coincidentally what we also need for <optional>\n#ifdef __has_include\n#  if __has_include(<optional>)\n#    include <optional>\n#    ifdef __cpp_lib_optional\n#      define CXXOPTS_HAS_OPTIONAL\n#    endif\n#  endif\n#endif\n\n#if __cplusplus >= 201603L\n#define CXXOPTS_NODISCARD [[nodiscard]]\n#else\n#define CXXOPTS_NODISCARD\n#endif\n\n#ifndef CXXOPTS_VECTOR_DELIMITER\n#define CXXOPTS_VECTOR_DELIMITER ','\n#endif\n\n#define CXXOPTS__VERSION_MAJOR 3\n#define CXXOPTS__VERSION_MINOR 0\n#define CXXOPTS__VERSION_PATCH 0\n\n#if (__GNUC__ < 10 || (__GNUC__ == 10 && __GNUC_MINOR__ < 1)) && __GNUC__ >= 6\n  #define CXXOPTS_NULL_DEREF_IGNORE\n#endif\n\nnamespace cxxopts\n{\n  static constexpr struct {\n    uint8_t major, minor, patch;\n  } version = {\n    CXXOPTS__VERSION_MAJOR,\n    CXXOPTS__VERSION_MINOR,\n    CXXOPTS__VERSION_PATCH\n  };\n} // namespace cxxopts\n\n//when we ask cxxopts to use Unicode, help strings are processed using ICU,\n//which results in the correct lengths being computed for strings when they\n//are formatted for the help output\n//it is necessary to make sure that <unicode/unistr.h> can be found by the\n//compiler, and that icu-uc is linked in to the binary.\n\n#ifdef CXXOPTS_USE_UNICODE\n#include <unicode/unistr.h>\n\nnamespace cxxopts\n{\n  using String = icu::UnicodeString;\n\n  inline\n  String\n  toLocalString(std::string s)\n  {\n    return icu::UnicodeString::fromUTF8(std::move(s));\n  }\n\n#if defined(__GNUC__)\n// GNU GCC with -Weffc++ will issue a warning regarding the upcoming class, we want to silence it:\n// warning: base class 'class std::enable_shared_from_this<cxxopts::Value>' has accessible non-virtual destructor\n#pragma GCC diagnostic push\n#pragma GCC diagnostic ignored \"-Wnon-virtual-dtor\"\n#pragma GCC diagnostic ignored \"-Weffc++\"\n// This will be ignored under other compilers like LLVM clang.\n#endif\n  class UnicodeStringIterator : public\n    std::iterator<std::forward_iterator_tag, int32_t>\n  {\n    public:\n\n    UnicodeStringIterator(const icu::UnicodeString* string, int32_t pos)\n    : s(string)\n    , i(pos)\n    {\n    }\n\n    value_type\n    operator*() const\n    {\n      return s->char32At(i);\n    }\n\n    bool\n    operator==(const UnicodeStringIterator& rhs) const\n    {\n      return s == rhs.s && i == rhs.i;\n    }\n\n    bool\n    operator!=(const UnicodeStringIterator& rhs) const\n    {\n      return !(*this == rhs);\n    }\n\n    UnicodeStringIterator&\n    operator++()\n    {\n      ++i;\n      return *this;\n    }\n\n    UnicodeStringIterator\n    operator+(int32_t v)\n    {\n      return UnicodeStringIterator(s, i + v);\n    }\n\n    private:\n    const icu::UnicodeString* s;\n    int32_t i;\n  };\n#if defined(__GNUC__)\n#pragma GCC diagnostic pop\n#endif\n\n  inline\n  String&\n  stringAppend(String&s, String a)\n  {\n    return s.append(std::move(a));\n  }\n\n  inline\n  String&\n  stringAppend(String& s, size_t n, UChar32 c)\n  {\n    for (size_t i = 0; i != n; ++i)\n    {\n      s.append(c);\n    }\n\n    return s;\n  }\n\n  template <typename Iterator>\n  String&\n  stringAppend(String& s, Iterator begin, Iterator end)\n  {\n    while (begin != end)\n    {\n      s.append(*begin);\n      ++begin;\n    }\n\n    return s;\n  }\n\n  inline\n  size_t\n  stringLength(const String& s)\n  {\n    return s.length();\n  }\n\n  inline\n  std::string\n  toUTF8String(const String& s)\n  {\n    std::string result;\n    s.toUTF8String(result);\n\n    return result;\n  }\n\n  inline\n  bool\n  empty(const String& s)\n  {\n    return s.isEmpty();\n  }\n}\n\nnamespace std\n{\n  inline\n  cxxopts::UnicodeStringIterator\n  begin(const icu::UnicodeString& s)\n  {\n    return cxxopts::UnicodeStringIterator(&s, 0);\n  }\n\n  inline\n  cxxopts::UnicodeStringIterator\n  end(const icu::UnicodeString& s)\n  {\n    return cxxopts::UnicodeStringIterator(&s, s.length());\n  }\n}\n\n//ifdef CXXOPTS_USE_UNICODE\n#else\n\nnamespace cxxopts\n{\n  using String = std::string;\n\n  template <typename T>\n  T\n  toLocalString(T&& t)\n  {\n    return std::forward<T>(t);\n  }\n\n  inline\n  size_t\n  stringLength(const String& s)\n  {\n    return s.length();\n  }\n\n  inline\n  String&\n  stringAppend(String&s, const String& a)\n  {\n    return s.append(a);\n  }\n\n  inline\n  String&\n  stringAppend(String& s, size_t n, char c)\n  {\n    return s.append(n, c);\n  }\n\n  template <typename Iterator>\n  String&\n  stringAppend(String& s, Iterator begin, Iterator end)\n  {\n    return s.append(begin, end);\n  }\n\n  template <typename T>\n  std::string\n  toUTF8String(T&& t)\n  {\n    return std::forward<T>(t);\n  }\n\n  inline\n  bool\n  empty(const std::string& s)\n  {\n    return s.empty();\n  }\n} // namespace cxxopts\n\n//ifdef CXXOPTS_USE_UNICODE\n#endif\n\nnamespace cxxopts\n{\n  namespace\n  {\n#ifdef _WIN32\n    const std::string LQUOTE(\"\\'\");\n    const std::string RQUOTE(\"\\'\");\n#else\n    const std::string LQUOTE(\"‘\");\n    const std::string RQUOTE(\"’\");\n#endif\n  } // namespace\n\n#if defined(__GNUC__)\n// GNU GCC with -Weffc++ will issue a warning regarding the upcoming class, we want to silence it:\n// warning: base class 'class std::enable_shared_from_this<cxxopts::Value>' has accessible non-virtual destructor\n#pragma GCC diagnostic push\n#pragma GCC diagnostic ignored \"-Wnon-virtual-dtor\"\n#pragma GCC diagnostic ignored \"-Weffc++\"\n// This will be ignored under other compilers like LLVM clang.\n#endif\n  class Value : public std::enable_shared_from_this<Value>\n  {\n    public:\n\n    virtual ~Value() = default;\n\n    virtual\n    std::shared_ptr<Value>\n    clone() const = 0;\n\n    virtual void\n    parse(const std::string& text) const = 0;\n\n    virtual void\n    parse() const = 0;\n\n    virtual bool\n    has_default() const = 0;\n\n    virtual bool\n    is_container() const = 0;\n\n    virtual bool\n    has_implicit() const = 0;\n\n    virtual std::string\n    get_default_value() const = 0;\n\n    virtual std::string\n    get_implicit_value() const = 0;\n\n    virtual std::shared_ptr<Value>\n    default_value(const std::string& value) = 0;\n\n    virtual std::shared_ptr<Value>\n    implicit_value(const std::string& value) = 0;\n\n    virtual std::shared_ptr<Value>\n    no_implicit_value() = 0;\n\n    virtual bool\n    is_boolean() const = 0;\n  };\n#if defined(__GNUC__)\n#pragma GCC diagnostic pop\n#endif\n  class OptionException : public std::exception\n  {\n    public:\n    explicit OptionException(std::string  message)\n    : m_message(std::move(message))\n    {\n    }\n\n    CXXOPTS_NODISCARD\n    const char*\n    what() const noexcept override\n    {\n      return m_message.c_str();\n    }\n\n    private:\n    std::string m_message;\n  };\n\n  class OptionSpecException : public OptionException\n  {\n    public:\n\n    explicit OptionSpecException(const std::string& message)\n    : OptionException(message)\n    {\n    }\n  };\n\n  class OptionParseException : public OptionException\n  {\n    public:\n    explicit OptionParseException(const std::string& message)\n    : OptionException(message)\n    {\n    }\n  };\n\n  class option_exists_error : public OptionSpecException\n  {\n    public:\n    explicit option_exists_error(const std::string& option)\n    : OptionSpecException(\"Option \" + LQUOTE + option + RQUOTE + \" already exists\")\n    {\n    }\n  };\n\n  class invalid_option_format_error : public OptionSpecException\n  {\n    public:\n    explicit invalid_option_format_error(const std::string& format)\n    : OptionSpecException(\"Invalid option format \" + LQUOTE + format + RQUOTE)\n    {\n    }\n  };\n\n  class option_syntax_exception : public OptionParseException {\n    public:\n    explicit option_syntax_exception(const std::string& text)\n    : OptionParseException(\"Argument \" + LQUOTE + text + RQUOTE +\n        \" starts with a - but has incorrect syntax\")\n    {\n    }\n  };\n\n  class option_not_exists_exception : public OptionParseException\n  {\n    public:\n    explicit option_not_exists_exception(const std::string& option)\n    : OptionParseException(\"Option \" + LQUOTE + option + RQUOTE + \" does not exist\")\n    {\n    }\n  };\n\n  class missing_argument_exception : public OptionParseException\n  {\n    public:\n    explicit missing_argument_exception(const std::string& option)\n    : OptionParseException(\n        \"Option \" + LQUOTE + option + RQUOTE + \" is missing an argument\"\n      )\n    {\n    }\n  };\n\n  class option_requires_argument_exception : public OptionParseException\n  {\n    public:\n    explicit option_requires_argument_exception(const std::string& option)\n    : OptionParseException(\n        \"Option \" + LQUOTE + option + RQUOTE + \" requires an argument\"\n      )\n    {\n    }\n  };\n\n  class option_not_has_argument_exception : public OptionParseException\n  {\n    public:\n    option_not_has_argument_exception\n    (\n      const std::string& option,\n      const std::string& arg\n    )\n    : OptionParseException(\n        \"Option \" + LQUOTE + option + RQUOTE +\n        \" does not take an argument, but argument \" +\n        LQUOTE + arg + RQUOTE + \" given\"\n      )\n    {\n    }\n  };\n\n  class option_not_present_exception : public OptionParseException\n  {\n    public:\n    explicit option_not_present_exception(const std::string& option)\n    : OptionParseException(\"Option \" + LQUOTE + option + RQUOTE + \" not present\")\n    {\n    }\n  };\n\n  class option_has_no_value_exception : public OptionException\n  {\n    public:\n    explicit option_has_no_value_exception(const std::string& option)\n    : OptionException(\n        !option.empty() ?\n        (\"Option \" + LQUOTE + option + RQUOTE + \" has no value\") :\n        \"Option has no value\")\n    {\n    }\n  };\n\n  class argument_incorrect_type : public OptionParseException\n  {\n    public:\n    explicit argument_incorrect_type\n    (\n      const std::string& arg\n    )\n    : OptionParseException(\n        \"Argument \" + LQUOTE + arg + RQUOTE + \" failed to parse\"\n      )\n    {\n    }\n  };\n\n  class option_required_exception : public OptionParseException\n  {\n    public:\n    explicit option_required_exception(const std::string& option)\n    : OptionParseException(\n        \"Option \" + LQUOTE + option + RQUOTE + \" is required but not present\"\n      )\n    {\n    }\n  };\n\n  template <typename T>\n  void throw_or_mimic(const std::string& text)\n  {\n    static_assert(std::is_base_of<std::exception, T>::value,\n                  \"throw_or_mimic only works on std::exception and \"\n                  \"deriving classes\");\n\n#ifndef CXXOPTS_NO_EXCEPTIONS\n    // If CXXOPTS_NO_EXCEPTIONS is not defined, just throw\n    throw T{text};\n#else\n    // Otherwise manually instantiate the exception, print what() to stderr,\n    // and exit\n    T exception{text};\n    std::cerr << exception.what() << std::endl;\n    std::exit(EXIT_FAILURE);\n#endif\n  }\n\n  namespace values\n  {\n    namespace parser_tool\n    {\n      struct IntegerDesc\n      {\n        std::string negative = \"\";\n        std::string base     = \"\";\n        std::string value    = \"\";\n      };\n      struct ArguDesc {\n        std::string arg_name  = \"\";\n        bool        grouping  = false;\n        bool        set_value = false;\n        std::string value     = \"\";\n      };\n#ifdef CXXOPTS_NO_REGEX\n      inline IntegerDesc SplitInteger(const std::string &text)\n      {\n        if (text.empty())\n        {\n          throw_or_mimic<argument_incorrect_type>(text);\n        }\n        IntegerDesc desc;\n        const char *pdata = text.c_str();\n        if (*pdata == '-')\n        {\n          pdata += 1;\n          desc.negative = \"-\";\n        }\n        if (strncmp(pdata, \"0x\", 2) == 0)\n        {\n          pdata += 2;\n          desc.base = \"0x\";\n        }\n        if (*pdata != '\\0')\n        {\n          desc.value = std::string(pdata);\n        }\n        else\n        {\n          throw_or_mimic<argument_incorrect_type>(text);\n        }\n        return desc;\n      }\n\n      inline bool IsTrueText(const std::string &text)\n      {\n        const char *pdata = text.c_str();\n        if (*pdata == 't' || *pdata == 'T')\n        {\n          pdata += 1;\n          if (strncmp(pdata, \"rue\\0\", 4) == 0)\n          {\n            return true;\n          }\n        }\n        else if (strncmp(pdata, \"1\\0\", 2) == 0)\n        {\n          return true;\n        }\n        return false;\n      }\n\n      inline bool IsFalseText(const std::string &text)\n      {\n        const char *pdata = text.c_str();\n        if (*pdata == 'f' || *pdata == 'F')\n        {\n          pdata += 1;\n          if (strncmp(pdata, \"alse\\0\", 5) == 0)\n          {\n            return true;\n          }\n        }\n        else if (strncmp(pdata, \"0\\0\", 2) == 0)\n        {\n          return true;\n        }\n        return false;\n      }\n\n      inline std::pair<std::string, std::string> SplitSwitchDef(const std::string &text)\n      {\n        std::string short_sw, long_sw;\n        const char *pdata = text.c_str();\n        if (isalnum(*pdata) && *(pdata + 1) == ',') {\n          short_sw = std::string(1, *pdata);\n          pdata += 2;\n        }\n        while (*pdata == ' ') { pdata += 1; }\n        if (isalnum(*pdata)) {\n          const char *store = pdata;\n          pdata += 1;\n          while (isalnum(*pdata) || *pdata == '-' || *pdata == '_') {\n            pdata += 1;\n          }\n          if (*pdata == '\\0') {\n            long_sw = std::string(store, pdata - store);\n          } else {\n            throw_or_mimic<invalid_option_format_error>(text);\n          }\n        }\n        return std::pair<std::string, std::string>(short_sw, long_sw);\n      }\n\n      inline ArguDesc ParseArgument(const char *arg, bool &matched)\n      {\n        ArguDesc argu_desc;\n        const char *pdata = arg;\n        matched = false;\n        if (strncmp(pdata, \"--\", 2) == 0)\n        {\n          pdata += 2;\n          if (isalnum(*pdata))\n          {\n            argu_desc.arg_name.push_back(*pdata);\n            pdata += 1;\n            while (isalnum(*pdata) || *pdata == '-' || *pdata == '_')\n            {\n              argu_desc.arg_name.push_back(*pdata);\n              pdata += 1;\n            }\n            if (argu_desc.arg_name.length() > 1)\n            {\n              if (*pdata == '=')\n              {\n                argu_desc.set_value = true;\n                pdata += 1;\n                if (*pdata != '\\0')\n                {\n                  argu_desc.value = std::string(pdata);\n                }\n                matched = true;\n              }\n              else if (*pdata == '\\0')\n              {\n                matched = true;\n              }\n            }\n          }\n        }\n        else if (strncmp(pdata, \"-\", 1) == 0)\n        {\n          pdata += 1;\n          argu_desc.grouping = true;\n          while (isalnum(*pdata))\n          {\n            argu_desc.arg_name.push_back(*pdata);\n            pdata += 1;\n          }\n          matched = !argu_desc.arg_name.empty() && *pdata == '\\0';\n        }\n        return argu_desc;\n      }\n\n#else  // CXXOPTS_NO_REGEX\n\n      namespace\n      {\n\n        std::basic_regex<char> integer_pattern\n          (\"(-)?(0x)?([0-9a-zA-Z]+)|((0x)?0)\");\n        std::basic_regex<char> truthy_pattern\n          (\"(t|T)(rue)?|1\");\n        std::basic_regex<char> falsy_pattern\n          (\"(f|F)(alse)?|0\");\n\n        std::basic_regex<char> option_matcher\n          (\"--([[:alnum:]][-_[:alnum:]]+)(=(.*))?|-([[:alnum:]]+)\");\n        std::basic_regex<char> option_specifier\n          (\"(([[:alnum:]]),)?[ ]*([[:alnum:]][-_[:alnum:]]*)?\");\n\n      } // namespace\n\n      inline IntegerDesc SplitInteger(const std::string &text)\n      {\n        std::smatch match;\n        std::regex_match(text, match, integer_pattern);\n\n        if (match.length() == 0)\n        {\n          throw_or_mimic<argument_incorrect_type>(text);\n        }\n\n        IntegerDesc desc;\n        desc.negative = match[1];\n        desc.base = match[2];\n        desc.value = match[3];\n\n        if (match.length(4) > 0)\n        {\n          desc.base = match[5];\n          desc.value = \"0\";\n          return desc;\n        }\n\n        return desc;\n      }\n\n      inline bool IsTrueText(const std::string &text)\n      {\n        std::smatch result;\n        std::regex_match(text, result, truthy_pattern);\n        return !result.empty();\n      }\n\n      inline bool IsFalseText(const std::string &text)\n      {\n        std::smatch result;\n        std::regex_match(text, result, falsy_pattern);\n        return !result.empty();\n      }\n\n      inline std::pair<std::string, std::string> SplitSwitchDef(const std::string &text)\n      {\n        std::match_results<const char*> result;\n        std::regex_match(text.c_str(), result, option_specifier);\n        if (result.empty())\n        {\n          throw_or_mimic<invalid_option_format_error>(text);\n        }\n\n        const std::string& short_sw = result[2];\n        const std::string& long_sw = result[3];\n\n        return std::pair<std::string, std::string>(short_sw, long_sw);\n      }\n\n      inline ArguDesc ParseArgument(const char *arg, bool &matched)\n      {\n        std::match_results<const char*> result;\n        std::regex_match(arg, result, option_matcher);\n        matched = !result.empty();\n\n        ArguDesc argu_desc;\n        if (matched) {\n          argu_desc.arg_name = result[1].str();\n          argu_desc.set_value = result[2].length() > 0;\n          argu_desc.value = result[3].str();\n          if (result[4].length() > 0)\n          {\n            argu_desc.grouping = true;\n            argu_desc.arg_name = result[4].str();\n          }\n        }\n\n        return argu_desc;\n      }\n\n#endif  // CXXOPTS_NO_REGEX\n#undef CXXOPTS_NO_REGEX\n  }\n\n    namespace detail\n    {\n      template <typename T, bool B>\n      struct SignedCheck;\n\n      template <typename T>\n      struct SignedCheck<T, true>\n      {\n        template <typename U>\n        void\n        operator()(bool negative, U u, const std::string& text)\n        {\n          if (negative)\n          {\n            if (u > static_cast<U>((std::numeric_limits<T>::min)()))\n            {\n              throw_or_mimic<argument_incorrect_type>(text);\n            }\n          }\n          else\n          {\n            if (u > static_cast<U>((std::numeric_limits<T>::max)()))\n            {\n              throw_or_mimic<argument_incorrect_type>(text);\n            }\n          }\n        }\n      };\n\n      template <typename T>\n      struct SignedCheck<T, false>\n      {\n        template <typename U>\n        void\n        operator()(bool, U, const std::string&) const {}\n      };\n\n      template <typename T, typename U>\n      void\n      check_signed_range(bool negative, U value, const std::string& text)\n      {\n        SignedCheck<T, std::numeric_limits<T>::is_signed>()(negative, value, text);\n      }\n    } // namespace detail\n\n    template <typename R, typename T>\n    void\n    checked_negate(R& r, T&& t, const std::string&, std::true_type)\n    {\n      // if we got to here, then `t` is a positive number that fits into\n      // `R`. So to avoid MSVC C4146, we first cast it to `R`.\n      // See https://github.com/jarro2783/cxxopts/issues/62 for more details.\n      r = static_cast<R>(-static_cast<R>(t-1)-1);\n    }\n\n    template <typename R, typename T>\n    void\n    checked_negate(R&, T&&, const std::string& text, std::false_type)\n    {\n      throw_or_mimic<argument_incorrect_type>(text);\n    }\n\n    template <typename T>\n    void\n    integer_parser(const std::string& text, T& value)\n    {\n      parser_tool::IntegerDesc int_desc = parser_tool::SplitInteger(text);\n\n      using US = typename std::make_unsigned<T>::type;\n      constexpr bool is_signed = std::numeric_limits<T>::is_signed;\n\n      const bool          negative    = int_desc.negative.length() > 0;\n      const uint8_t       base        = int_desc.base.length() > 0 ? 16 : 10;\n      const std::string & value_match = int_desc.value;\n\n      US result = 0;\n\n      for (char ch : value_match)\n      {\n        US digit = 0;\n\n        if (ch >= '0' && ch <= '9')\n        {\n          digit = static_cast<US>(ch - '0');\n        }\n        else if (base == 16 && ch >= 'a' && ch <= 'f')\n        {\n          digit = static_cast<US>(ch - 'a' + 10);\n        }\n        else if (base == 16 && ch >= 'A' && ch <= 'F')\n        {\n          digit = static_cast<US>(ch - 'A' + 10);\n        }\n        else\n        {\n          throw_or_mimic<argument_incorrect_type>(text);\n        }\n\n        const US next = static_cast<US>(result * base + digit);\n        if (result > next)\n        {\n          throw_or_mimic<argument_incorrect_type>(text);\n        }\n\n        result = next;\n      }\n\n      detail::check_signed_range<T>(negative, result, text);\n\n      if (negative)\n      {\n        checked_negate<T>(value, result, text, std::integral_constant<bool, is_signed>());\n      }\n      else\n      {\n        value = static_cast<T>(result);\n      }\n    }\n\n    template <typename T>\n    void stringstream_parser(const std::string& text, T& value)\n    {\n      std::stringstream in(text);\n      in >> value;\n      if (!in) {\n        throw_or_mimic<argument_incorrect_type>(text);\n      }\n    }\n\n    template <typename T,\n             typename std::enable_if<std::is_integral<T>::value>::type* = nullptr\n             >\n    void parse_value(const std::string& text, T& value)\n    {\n        integer_parser(text, value);\n    }\n\n    inline\n    void\n    parse_value(const std::string& text, bool& value)\n    {\n      if (parser_tool::IsTrueText(text))\n      {\n        value = true;\n        return;\n      }\n\n      if (parser_tool::IsFalseText(text))\n      {\n        value = false;\n        return;\n      }\n\n      throw_or_mimic<argument_incorrect_type>(text);\n    }\n\n    inline\n    void\n    parse_value(const std::string& text, std::string& value)\n    {\n      value = text;\n    }\n\n    // The fallback parser. It uses the stringstream parser to parse all types\n    // that have not been overloaded explicitly.  It has to be placed in the\n    // source code before all other more specialized templates.\n    template <typename T,\n             typename std::enable_if<!std::is_integral<T>::value>::type* = nullptr\n             >\n    void\n    parse_value(const std::string& text, T& value) {\n      stringstream_parser(text, value);\n    }\n\n    template <typename T>\n    void\n    parse_value(const std::string& text, std::vector<T>& value)\n    {\n      if (text.empty()) {\n        T v;\n        parse_value(text, v);\n        value.emplace_back(std::move(v));\n        return;\n      }\n      std::stringstream in(text);\n      std::string token;\n      while(!in.eof() && std::getline(in, token, CXXOPTS_VECTOR_DELIMITER)) {\n        T v;\n        parse_value(token, v);\n        value.emplace_back(std::move(v));\n      }\n    }\n\n#ifdef CXXOPTS_HAS_OPTIONAL\n    template <typename T>\n    void\n    parse_value(const std::string& text, std::optional<T>& value)\n    {\n      T result;\n      parse_value(text, result);\n      value = std::move(result);\n    }\n#endif\n\n    inline\n    void parse_value(const std::string& text, char& c)\n    {\n      if (text.length() != 1)\n      {\n        throw_or_mimic<argument_incorrect_type>(text);\n      }\n\n      c = text[0];\n    }\n\n    template <typename T>\n    struct type_is_container\n    {\n      static constexpr bool value = false;\n    };\n\n    template <typename T>\n    struct type_is_container<std::vector<T>>\n    {\n      static constexpr bool value = true;\n    };\n\n    template <typename T>\n    class abstract_value : public Value\n    {\n      using Self = abstract_value<T>;\n\n      public:\n      abstract_value()\n      : m_result(std::make_shared<T>())\n      , m_store(m_result.get())\n      {\n      }\n\n      explicit abstract_value(T* t)\n      : m_store(t)\n      {\n      }\n\n      ~abstract_value() override = default;\n\n      abstract_value& operator=(const abstract_value&) = default;\n\n      abstract_value(const abstract_value& rhs)\n      {\n        if (rhs.m_result)\n        {\n          m_result = std::make_shared<T>();\n          m_store = m_result.get();\n        }\n        else\n        {\n          m_store = rhs.m_store;\n        }\n\n        m_default = rhs.m_default;\n        m_implicit = rhs.m_implicit;\n        m_default_value = rhs.m_default_value;\n        m_implicit_value = rhs.m_implicit_value;\n      }\n\n      void\n      parse(const std::string& text) const override\n      {\n        parse_value(text, *m_store);\n      }\n\n      bool\n      is_container() const override\n      {\n        return type_is_container<T>::value;\n      }\n\n      void\n      parse() const override\n      {\n        parse_value(m_default_value, *m_store);\n      }\n\n      bool\n      has_default() const override\n      {\n        return m_default;\n      }\n\n      bool\n      has_implicit() const override\n      {\n        return m_implicit;\n      }\n\n      std::shared_ptr<Value>\n      default_value(const std::string& value) override\n      {\n        m_default = true;\n        m_default_value = value;\n        return shared_from_this();\n      }\n\n      std::shared_ptr<Value>\n      implicit_value(const std::string& value) override\n      {\n        m_implicit = true;\n        m_implicit_value = value;\n        return shared_from_this();\n      }\n\n      std::shared_ptr<Value>\n      no_implicit_value() override\n      {\n        m_implicit = false;\n        return shared_from_this();\n      }\n\n      std::string\n      get_default_value() const override\n      {\n        return m_default_value;\n      }\n\n      std::string\n      get_implicit_value() const override\n      {\n        return m_implicit_value;\n      }\n\n      bool\n      is_boolean() const override\n      {\n        return std::is_same<T, bool>::value;\n      }\n\n      const T&\n      get() const\n      {\n        if (m_store == nullptr)\n        {\n          return *m_result;\n        }\n        return *m_store;\n      }\n\n      protected:\n      std::shared_ptr<T> m_result{};\n      T* m_store{};\n\n      bool m_default = false;\n      bool m_implicit = false;\n\n      std::string m_default_value{};\n      std::string m_implicit_value{};\n    };\n\n    template <typename T>\n    class standard_value : public abstract_value<T>\n    {\n      public:\n      using abstract_value<T>::abstract_value;\n\n      CXXOPTS_NODISCARD\n      std::shared_ptr<Value>\n      clone() const override\n      {\n        return std::make_shared<standard_value<T>>(*this);\n      }\n    };\n\n    template <>\n    class standard_value<bool> : public abstract_value<bool>\n    {\n      public:\n      ~standard_value() override = default;\n\n      standard_value()\n      {\n        set_default_and_implicit();\n      }\n\n      explicit standard_value(bool* b)\n      : abstract_value(b)\n      {\n        set_default_and_implicit();\n      }\n\n      std::shared_ptr<Value>\n      clone() const override\n      {\n        return std::make_shared<standard_value<bool>>(*this);\n      }\n\n      private:\n\n      void\n      set_default_and_implicit()\n      {\n        m_default = true;\n        m_default_value = \"false\";\n        m_implicit = true;\n        m_implicit_value = \"true\";\n      }\n    };\n  } // namespace values\n\n  template <typename T>\n  std::shared_ptr<Value>\n  value()\n  {\n    return std::make_shared<values::standard_value<T>>();\n  }\n\n  template <typename T>\n  std::shared_ptr<Value>\n  value(T& t)\n  {\n    return std::make_shared<values::standard_value<T>>(&t);\n  }\n\n  class OptionAdder;\n\n  class OptionDetails\n  {\n    public:\n    OptionDetails\n    (\n      std::string short_,\n      std::string long_,\n      String desc,\n      std::shared_ptr<const Value> val\n    )\n    : m_short(std::move(short_))\n    , m_long(std::move(long_))\n    , m_desc(std::move(desc))\n    , m_value(std::move(val))\n    , m_count(0)\n    {\n      m_hash = std::hash<std::string>{}(m_long + m_short);\n    }\n\n    OptionDetails(const OptionDetails& rhs)\n    : m_desc(rhs.m_desc)\n    , m_value(rhs.m_value->clone())\n    , m_count(rhs.m_count)\n    {\n    }\n\n    OptionDetails(OptionDetails&& rhs) = default;\n\n    CXXOPTS_NODISCARD\n    const String&\n    description() const\n    {\n      return m_desc;\n    }\n\n    CXXOPTS_NODISCARD\n    const Value&\n    value() const {\n        return *m_value;\n    }\n\n    CXXOPTS_NODISCARD\n    std::shared_ptr<Value>\n    make_storage() const\n    {\n      return m_value->clone();\n    }\n\n    CXXOPTS_NODISCARD\n    const std::string&\n    short_name() const\n    {\n      return m_short;\n    }\n\n    CXXOPTS_NODISCARD\n    const std::string&\n    long_name() const\n    {\n      return m_long;\n    }\n\n    CXXOPTS_NODISCARD\n    const std::string&\n    essential_name() const\n    {\n      return m_long.empty() ? m_short : m_long;\n    }\n\n    size_t\n    hash() const\n    {\n      return m_hash;\n    }\n\n    private:\n    std::string m_short{};\n    std::string m_long{};\n    String m_desc{};\n    std::shared_ptr<const Value> m_value{};\n    int m_count;\n\n    size_t m_hash{};\n  };\n\n  struct HelpOptionDetails\n  {\n    std::string s;\n    std::string l;\n    String desc;\n    bool has_default;\n    std::string default_value;\n    bool has_implicit;\n    std::string implicit_value;\n    std::string arg_help;\n    bool is_container;\n    bool is_boolean;\n  };\n\n  struct HelpGroupDetails\n  {\n    std::string name{};\n    std::string description{};\n    std::vector<HelpOptionDetails> options{};\n  };\n\n  class OptionValue\n  {\n    public:\n    void\n    parse\n    (\n      const std::shared_ptr<const OptionDetails>& details,\n      const std::string& text\n    )\n    {\n      ensure_value(details);\n      ++m_count;\n      m_value->parse(text);\n      m_long_name = &details->long_name();\n    }\n\n    void\n    parse_default(const std::shared_ptr<const OptionDetails>& details)\n    {\n      ensure_value(details);\n      m_default = true;\n      m_long_name = &details->long_name();\n      m_value->parse();\n    }\n\n    void\n    parse_no_value(const std::shared_ptr<const OptionDetails>& details)\n    {\n      m_long_name = &details->long_name();\n    }\n\n#if defined(CXXOPTS_NULL_DEREF_IGNORE)\n#pragma GCC diagnostic push\n#pragma GCC diagnostic ignored \"-Wnull-dereference\"\n#endif\n\n    CXXOPTS_NODISCARD\n    size_t\n    count() const noexcept\n    {\n      return m_count;\n    }\n\n#if defined(CXXOPTS_NULL_DEREF_IGNORE)\n#pragma GCC diagnostic pop\n#endif\n\n    // TODO: maybe default options should count towards the number of arguments\n    CXXOPTS_NODISCARD\n    bool\n    has_default() const noexcept\n    {\n      return m_default;\n    }\n\n    template <typename T>\n    const T&\n    as() const\n    {\n      if (m_value == nullptr) {\n          throw_or_mimic<option_has_no_value_exception>(\n              m_long_name == nullptr ? \"\" : *m_long_name);\n      }\n\n#ifdef CXXOPTS_NO_RTTI\n      return static_cast<const values::standard_value<T>&>(*m_value).get();\n#else\n      return dynamic_cast<const values::standard_value<T>&>(*m_value).get();\n#endif\n    }\n\n    private:\n    void\n    ensure_value(const std::shared_ptr<const OptionDetails>& details)\n    {\n      if (m_value == nullptr)\n      {\n        m_value = details->make_storage();\n      }\n    }\n\n\n    const std::string* m_long_name = nullptr;\n    // Holding this pointer is safe, since OptionValue's only exist in key-value pairs,\n    // where the key has the string we point to.\n    std::shared_ptr<Value> m_value{};\n    size_t m_count = 0;\n    bool m_default = false;\n  };\n\n  class KeyValue\n  {\n    public:\n    KeyValue(std::string key_, std::string value_)\n    : m_key(std::move(key_))\n    , m_value(std::move(value_))\n    {\n    }\n\n    CXXOPTS_NODISCARD\n    const std::string&\n    key() const\n    {\n      return m_key;\n    }\n\n    CXXOPTS_NODISCARD\n    const std::string&\n    value() const\n    {\n      return m_value;\n    }\n\n    template <typename T>\n    T\n    as() const\n    {\n      T result;\n      values::parse_value(m_value, result);\n      return result;\n    }\n\n    private:\n    std::string m_key;\n    std::string m_value;\n  };\n\n  using ParsedHashMap = std::unordered_map<size_t, OptionValue>;\n  using NameHashMap = std::unordered_map<std::string, size_t>;\n\n  class ParseResult\n  {\n    public:\n    class Iterator\n    {\n      public:\n      using iterator_category = std::forward_iterator_tag;\n      using value_type = KeyValue;\n      using difference_type = void;\n      using pointer = const KeyValue*;\n      using reference = const KeyValue&;\n\n      Iterator() = default;\n      Iterator(const Iterator&) = default;\n\n      Iterator(const ParseResult *pr, bool end=false)\n      : m_pr(pr)\n      , m_iter(end? pr->m_defaults.end(): pr->m_sequential.begin())\n      {\n      }\n\n      Iterator& operator++()\n      {\n        ++m_iter;\n        if(m_iter == m_pr->m_sequential.end())\n        {\n          m_iter = m_pr->m_defaults.begin();\n          return *this;\n        }\n        return *this;\n      }\n\n      Iterator operator++(int)\n      {\n        Iterator retval = *this;\n        ++(*this);\n        return retval;\n      }\n\n      bool operator==(const Iterator& other) const\n      {\n        return m_iter == other.m_iter;\n      }\n\n      bool operator!=(const Iterator& other) const\n      {\n        return !(*this == other);\n      }\n\n      const KeyValue& operator*()\n      {\n        return *m_iter;\n      }\n\n      const KeyValue* operator->()\n      {\n        return m_iter.operator->();\n      }\n\n      private:\n      const ParseResult* m_pr;\n      std::vector<KeyValue>::const_iterator m_iter;\n    };\n\n    ParseResult() = default;\n    ParseResult(const ParseResult&) = default;\n\n    ParseResult(NameHashMap&& keys, ParsedHashMap&& values, std::vector<KeyValue> sequential, \n            std::vector<KeyValue> default_opts, std::vector<std::string>&& unmatched_args)\n    : m_keys(std::move(keys))\n    , m_values(std::move(values))\n    , m_sequential(std::move(sequential))\n    , m_defaults(std::move(default_opts))\n    , m_unmatched(std::move(unmatched_args))\n    {\n    }\n\n    ParseResult& operator=(ParseResult&&) = default;\n    ParseResult& operator=(const ParseResult&) = default;\n\n    Iterator\n    begin() const\n    {\n      return Iterator(this);\n    }\n\n    Iterator\n    end() const\n    {\n      return Iterator(this, true);\n    }\n\n    size_t\n    count(const std::string& o) const\n    {\n      auto iter = m_keys.find(o);\n      if (iter == m_keys.end())\n      {\n        return 0;\n      }\n\n      auto viter = m_values.find(iter->second);\n\n      if (viter == m_values.end())\n      {\n        return 0;\n      }\n\n      return viter->second.count();\n    }\n\n    const OptionValue&\n    operator[](const std::string& option) const\n    {\n      auto iter = m_keys.find(option);\n\n      if (iter == m_keys.end())\n      {\n        throw_or_mimic<option_not_present_exception>(option);\n      }\n\n      auto viter = m_values.find(iter->second);\n\n      if (viter == m_values.end())\n      {\n        throw_or_mimic<option_not_present_exception>(option);\n      }\n\n      return viter->second;\n    }\n\n    const std::vector<KeyValue>&\n    arguments() const\n    {\n      return m_sequential;\n    }\n\n    const std::vector<std::string>&\n    unmatched() const\n    {\n      return m_unmatched;\n    }\n\n    const std::vector<KeyValue>&\n    defaults() const\n    {\n      return m_defaults;\n    }\n\n    const std::string\n    arguments_string() const\n    {\n      std::string result;\n      for(const auto& kv: m_sequential)\n      {\n        result += kv.key() + \" = \" + kv.value() + \"\\n\";\n      }\n      for(const auto& kv: m_defaults)\n      {\n        result += kv.key() + \" = \" + kv.value() + \" \" + \"(default)\" + \"\\n\";\n      }\n      return result;\n    }\n\n    private:\n    NameHashMap m_keys{};\n    ParsedHashMap m_values{};\n    std::vector<KeyValue> m_sequential{};\n    std::vector<KeyValue> m_defaults{};\n    std::vector<std::string> m_unmatched{};\n  };\n\n  struct Option\n  {\n    Option\n    (\n      std::string opts,\n      std::string desc,\n      std::shared_ptr<const Value>  value = ::cxxopts::value<bool>(),\n      std::string arg_help = \"\"\n    )\n    : opts_(std::move(opts))\n    , desc_(std::move(desc))\n    , value_(std::move(value))\n    , arg_help_(std::move(arg_help))\n    {\n    }\n\n    std::string opts_;\n    std::string desc_;\n    std::shared_ptr<const Value> value_;\n    std::string arg_help_;\n  };\n\n  using OptionMap = std::unordered_map<std::string, std::shared_ptr<OptionDetails>>;\n  using PositionalList = std::vector<std::string>;\n  using PositionalListIterator = PositionalList::const_iterator;\n\n  class OptionParser\n  {\n    public:\n    OptionParser(const OptionMap& options, const PositionalList& positional, bool allow_unrecognised)\n    : m_options(options)\n    , m_positional(positional)\n    , m_allow_unrecognised(allow_unrecognised)\n    {\n    }\n\n    ParseResult\n    parse(int argc, const char* const* argv);\n\n    bool\n    consume_positional(const std::string& a, PositionalListIterator& next);\n\n    void\n    checked_parse_arg\n    (\n      int argc,\n      const char* const* argv,\n      int& current,\n      const std::shared_ptr<OptionDetails>& value,\n      const std::string& name\n    );\n\n    void\n    add_to_option(OptionMap::const_iterator iter, const std::string& option, const std::string& arg);\n\n    void\n    parse_option\n    (\n      const std::shared_ptr<OptionDetails>& value,\n      const std::string& name,\n      const std::string& arg = \"\"\n    );\n\n    void\n    parse_default(const std::shared_ptr<OptionDetails>& details);\n\n    void\n    parse_no_value(const std::shared_ptr<OptionDetails>& details);\n\n    private:\n\n    void finalise_aliases();\n\n    const OptionMap& m_options;\n    const PositionalList& m_positional;\n\n    std::vector<KeyValue> m_sequential{};\n    std::vector<KeyValue> m_defaults{};\n    bool m_allow_unrecognised;\n\n    ParsedHashMap m_parsed{};\n    NameHashMap m_keys{};\n  };\n\n  class Options\n  {\n    public:\n\n    explicit Options(std::string program, std::string help_string = \"\")\n    : m_program(std::move(program))\n    , m_help_string(toLocalString(std::move(help_string)))\n    , m_custom_help(\"[OPTION...]\")\n    , m_positional_help(\"positional parameters\")\n    , m_show_positional(false)\n    , m_allow_unrecognised(false)\n    , m_width(76)\n    , m_tab_expansion(false)\n    , m_options(std::make_shared<OptionMap>())\n    {\n    }\n\n    Options&\n    positional_help(std::string help_text)\n    {\n      m_positional_help = std::move(help_text);\n      return *this;\n    }\n\n    Options&\n    custom_help(std::string help_text)\n    {\n      m_custom_help = std::move(help_text);\n      return *this;\n    }\n\n    Options&\n    show_positional_help()\n    {\n      m_show_positional = true;\n      return *this;\n    }\n\n    Options&\n    allow_unrecognised_options()\n    {\n      m_allow_unrecognised = true;\n      return *this;\n    }\n\n    Options&\n    set_width(size_t width)\n    {\n      m_width = width;\n      return *this;\n    }\n\n    Options&\n    set_tab_expansion(bool expansion=true)\n    {\n      m_tab_expansion = expansion;\n      return *this;\n    }\n\n    ParseResult\n    parse(int argc, const char* const* argv);\n\n    OptionAdder\n    add_options(std::string group = \"\");\n\n    void\n    add_options\n    (\n      const std::string& group,\n      std::initializer_list<Option> options\n    );\n\n    void\n    add_option\n    (\n      const std::string& group,\n      const Option& option\n    );\n\n    void\n    add_option\n    (\n      const std::string& group,\n      const std::string& s,\n      const std::string& l,\n      std::string desc,\n      const std::shared_ptr<const Value>& value,\n      std::string arg_help\n    );\n\n    //parse positional arguments into the given option\n    void\n    parse_positional(std::string option);\n\n    void\n    parse_positional(std::vector<std::string> options);\n\n    void\n    parse_positional(std::initializer_list<std::string> options);\n\n    template <typename Iterator>\n    void\n    parse_positional(Iterator begin, Iterator end) {\n      parse_positional(std::vector<std::string>{begin, end});\n    }\n\n    std::string\n    help(const std::vector<std::string>& groups = {}) const;\n\n    std::vector<std::string>\n    groups() const;\n\n    const HelpGroupDetails&\n    group_help(const std::string& group) const;\n\n    const std::string& program() const\n    {\n      return m_program;\n    }\n\n    private:\n\n    void\n    add_one_option\n    (\n      const std::string& option,\n      const std::shared_ptr<OptionDetails>& details\n    );\n\n    String\n    help_one_group(const std::string& group) const;\n\n    void\n    generate_group_help\n    (\n      String& result,\n      const std::vector<std::string>& groups\n    ) const;\n\n    void\n    generate_all_groups_help(String& result) const;\n\n    std::string m_program{};\n    String m_help_string{};\n    std::string m_custom_help{};\n    std::string m_positional_help{};\n    bool m_show_positional;\n    bool m_allow_unrecognised;\n    size_t m_width;\n    bool m_tab_expansion;\n\n    std::shared_ptr<OptionMap> m_options;\n    std::vector<std::string> m_positional{};\n    std::unordered_set<std::string> m_positional_set{};\n\n    //mapping from groups to help options\n    std::map<std::string, HelpGroupDetails> m_help{};\n  };\n\n  class OptionAdder\n  {\n    public:\n\n    OptionAdder(Options& options, std::string group)\n    : m_options(options), m_group(std::move(group))\n    {\n    }\n\n    OptionAdder&\n    operator()\n    (\n      const std::string& opts,\n      const std::string& desc,\n      const std::shared_ptr<const Value>& value\n        = ::cxxopts::value<bool>(),\n      std::string arg_help = \"\"\n    );\n\n    private:\n    Options& m_options;\n    std::string m_group;\n  };\n\n  namespace\n  {\n    constexpr size_t OPTION_LONGEST = 30;\n    constexpr size_t OPTION_DESC_GAP = 2;\n\n    String\n    format_option\n    (\n      const HelpOptionDetails& o\n    )\n    {\n      const auto& s = o.s;\n      const auto& l = o.l;\n\n      String result = \"  \";\n\n      if (!s.empty())\n      {\n        result += \"-\" + toLocalString(s);\n        if (!l.empty())\n        {\n          result += \",\";\n        }\n      }\n      else\n      {\n        result += \"   \";\n      }\n\n      if (!l.empty())\n      {\n        result += \" --\" + toLocalString(l);\n      }\n\n      auto arg = !o.arg_help.empty() ? toLocalString(o.arg_help) : \"arg\";\n\n      if (!o.is_boolean)\n      {\n        if (o.has_implicit)\n        {\n          result += \" [=\" + arg + \"(=\" + toLocalString(o.implicit_value) + \")]\";\n        }\n        else\n        {\n          result += \" \" + arg;\n        }\n      }\n\n      return result;\n    }\n\n    String\n    format_description\n    (\n      const HelpOptionDetails& o,\n      size_t start,\n      size_t allowed,\n      bool tab_expansion\n    )\n    {\n      auto desc = o.desc;\n\n      if (o.has_default && (!o.is_boolean || o.default_value != \"false\"))\n      {\n        if(!o.default_value.empty())\n        {\n          desc += toLocalString(\" (default: \" + o.default_value + \")\");\n        }\n        else\n        {\n          desc += toLocalString(\" (default: \\\"\\\")\");\n        }\n      }\n\n      String result;\n\n      if (tab_expansion)\n      {\n        String desc2;\n        auto size = size_t{ 0 };\n        for (auto c = std::begin(desc); c != std::end(desc); ++c)\n        {\n          if (*c == '\\n')\n          {\n            desc2 += *c;\n            size = 0;\n          }\n          else if (*c == '\\t')\n          {\n            auto skip = 8 - size % 8;\n            stringAppend(desc2, skip, ' ');\n            size += skip;\n          }\n          else\n          {\n            desc2 += *c;\n            ++size;\n          }\n        }\n        desc = desc2;\n      }\n\n      desc += \" \";\n\n      auto current = std::begin(desc);\n      auto previous = current;\n      auto startLine = current;\n      auto lastSpace = current;\n\n      auto size = size_t{};\n\n      bool appendNewLine;\n      bool onlyWhiteSpace = true;\n\n      while (current != std::end(desc))\n      {\n        appendNewLine = false;\n\n        if (std::isblank(*previous))\n        {\n          lastSpace = current;\n        }\n\n        if (!std::isblank(*current))\n        {\n          onlyWhiteSpace = false;\n        }\n\n        while (*current == '\\n')\n        {\n          previous = current;\n          ++current;\n          appendNewLine = true;\n        }\n\n        if (!appendNewLine && size >= allowed)\n        {\n          if (lastSpace != startLine)\n          {\n            current = lastSpace;\n            previous = current;\n          }\n          appendNewLine = true;\n        }\n\n        if (appendNewLine)\n        {\n          stringAppend(result, startLine, current);\n          startLine = current;\n          lastSpace = current;\n\n          if (*previous != '\\n')\n          {\n            stringAppend(result, \"\\n\");\n          }\n\n          stringAppend(result, start, ' ');\n\n          if (*previous != '\\n')\n          {\n            stringAppend(result, lastSpace, current);\n          }\n\n          onlyWhiteSpace = true;\n          size = 0;\n        }\n\n        previous = current;\n        ++current;\n        ++size;\n      }\n\n      //append whatever is left but ignore whitespace\n      if (!onlyWhiteSpace)\n      {\n        stringAppend(result, startLine, previous);\n      }\n\n      return result;\n    }\n  } // namespace\n\ninline\nvoid\nOptions::add_options\n(\n  const std::string &group,\n  std::initializer_list<Option> options\n)\n{\n OptionAdder option_adder(*this, group);\n for (const auto &option: options)\n {\n   option_adder(option.opts_, option.desc_, option.value_, option.arg_help_);\n }\n}\n\ninline\nOptionAdder\nOptions::add_options(std::string group)\n{\n  return OptionAdder(*this, std::move(group));\n}\n\ninline\nOptionAdder&\nOptionAdder::operator()\n(\n  const std::string& opts,\n  const std::string& desc,\n  const std::shared_ptr<const Value>& value,\n  std::string arg_help\n)\n{\n  std::string short_sw, long_sw;\n  std::tie(short_sw, long_sw) = values::parser_tool::SplitSwitchDef(opts);\n\n  if (!short_sw.length() && !long_sw.length())\n  {\n    throw_or_mimic<invalid_option_format_error>(opts);\n  }\n  else if (long_sw.length() == 1 && short_sw.length())\n  {\n    throw_or_mimic<invalid_option_format_error>(opts);\n  }\n\n  auto option_names = []\n  (\n    const std::string &short_,\n    const std::string &long_\n  )\n  {\n    if (long_.length() == 1)\n    {\n      return std::make_tuple(long_, short_);\n    }\n    return std::make_tuple(short_, long_);\n  }(short_sw, long_sw);\n\n  m_options.add_option\n  (\n    m_group,\n    std::get<0>(option_names),\n    std::get<1>(option_names),\n    desc,\n    value,\n    std::move(arg_help)\n  );\n\n  return *this;\n}\n\ninline\nvoid\nOptionParser::parse_default(const std::shared_ptr<OptionDetails>& details)\n{\n  // TODO: remove the duplicate code here\n  auto& store = m_parsed[details->hash()];\n  store.parse_default(details);\n  m_defaults.emplace_back(details->essential_name(), details->value().get_default_value());\n}\n\ninline\nvoid\nOptionParser::parse_no_value(const std::shared_ptr<OptionDetails>& details)\n{\n  auto& store = m_parsed[details->hash()];\n  store.parse_no_value(details);\n}\n\ninline\nvoid\nOptionParser::parse_option\n(\n  const std::shared_ptr<OptionDetails>& value,\n  const std::string& /*name*/,\n  const std::string& arg\n)\n{\n  auto hash = value->hash();\n  auto& result = m_parsed[hash];\n  result.parse(value, arg);\n\n  m_sequential.emplace_back(value->essential_name(), arg);\n}\n\ninline\nvoid\nOptionParser::checked_parse_arg\n(\n  int argc,\n  const char* const* argv,\n  int& current,\n  const std::shared_ptr<OptionDetails>& value,\n  const std::string& name\n)\n{\n  if (current + 1 >= argc)\n  {\n    if (value->value().has_implicit())\n    {\n      parse_option(value, name, value->value().get_implicit_value());\n    }\n    else\n    {\n      throw_or_mimic<missing_argument_exception>(name);\n    }\n  }\n  else\n  {\n    if (value->value().has_implicit())\n    {\n      parse_option(value, name, value->value().get_implicit_value());\n    }\n    else\n    {\n      parse_option(value, name, argv[current + 1]);\n      ++current;\n    }\n  }\n}\n\ninline\nvoid\nOptionParser::add_to_option(OptionMap::const_iterator iter, const std::string& option, const std::string& arg)\n{\n  parse_option(iter->second, option, arg);\n}\n\ninline\nbool\nOptionParser::consume_positional(const std::string& a, PositionalListIterator& next)\n{\n  while (next != m_positional.end())\n  {\n    auto iter = m_options.find(*next);\n    if (iter != m_options.end())\n    {\n      if (!iter->second->value().is_container())\n      {\n        auto& result = m_parsed[iter->second->hash()];\n        if (result.count() == 0)\n        {\n          add_to_option(iter, *next, a);\n          ++next;\n          return true;\n        }\n        ++next;\n        continue;\n      }\n      add_to_option(iter, *next, a);\n      return true;\n    }\n    throw_or_mimic<option_not_exists_exception>(*next);\n  }\n\n  return false;\n}\n\ninline\nvoid\nOptions::parse_positional(std::string option)\n{\n  parse_positional(std::vector<std::string>{std::move(option)});\n}\n\ninline\nvoid\nOptions::parse_positional(std::vector<std::string> options)\n{\n  m_positional = std::move(options);\n\n  m_positional_set.insert(m_positional.begin(), m_positional.end());\n}\n\ninline\nvoid\nOptions::parse_positional(std::initializer_list<std::string> options)\n{\n  parse_positional(std::vector<std::string>(options));\n}\n\ninline\nParseResult\nOptions::parse(int argc, const char* const* argv)\n{\n  OptionParser parser(*m_options, m_positional, m_allow_unrecognised);\n\n  return parser.parse(argc, argv);\n}\n\ninline ParseResult\nOptionParser::parse(int argc, const char* const* argv)\n{\n  int current = 1;\n  bool consume_remaining = false;\n  auto next_positional = m_positional.begin();\n\n  std::vector<std::string> unmatched;\n\n  while (current != argc)\n  {\n    if (strcmp(argv[current], \"--\") == 0)\n    {\n      consume_remaining = true;\n      ++current;\n      break;\n    }\n    bool matched = false;\n    values::parser_tool::ArguDesc argu_desc =\n        values::parser_tool::ParseArgument(argv[current], matched);\n\n    if (!matched)\n    {\n      //not a flag\n\n      // but if it starts with a `-`, then it's an error\n      if (argv[current][0] == '-' && argv[current][1] != '\\0') {\n        if (!m_allow_unrecognised) {\n          throw_or_mimic<option_syntax_exception>(argv[current]);\n        }\n      }\n\n      //if true is returned here then it was consumed, otherwise it is\n      //ignored\n      if (consume_positional(argv[current], next_positional))\n      {\n      }\n      else\n      {\n        unmatched.emplace_back(argv[current]);\n      }\n      //if we return from here then it was parsed successfully, so continue\n    }\n    else\n    {\n      //short or long option?\n      if (argu_desc.grouping)\n      {\n        const std::string& s = argu_desc.arg_name;\n\n        for (std::size_t i = 0; i != s.size(); ++i)\n        {\n          std::string name(1, s[i]);\n          auto iter = m_options.find(name);\n\n          if (iter == m_options.end())\n          {\n            if (m_allow_unrecognised)\n            {\n              unmatched.push_back(std::string(\"-\") + s[i]);\n              continue;\n            }\n            //error\n            throw_or_mimic<option_not_exists_exception>(name);\n          }\n\n          auto value = iter->second;\n\n          if (i + 1 == s.size())\n          {\n            //it must be the last argument\n            checked_parse_arg(argc, argv, current, value, name);\n          }\n          else if (value->value().has_implicit())\n          {\n            parse_option(value, name, value->value().get_implicit_value());\n          }\n          else if (i + 1 < s.size())\n          {\n            std::string arg_value = s.substr(i + 1);\n            parse_option(value, name, arg_value);\n            break;\n          }\n          else\n          {\n            //error\n            throw_or_mimic<option_requires_argument_exception>(name);\n          }\n        }\n      }\n      else if (argu_desc.arg_name.length() != 0)\n      {\n        const std::string& name = argu_desc.arg_name;\n\n        auto iter = m_options.find(name);\n\n        if (iter == m_options.end())\n        {\n          if (m_allow_unrecognised)\n          {\n            // keep unrecognised options in argument list, skip to next argument\n            unmatched.emplace_back(argv[current]);\n            ++current;\n            continue;\n          }\n          //error\n          throw_or_mimic<option_not_exists_exception>(name);\n        }\n\n        auto opt = iter->second;\n\n        //equals provided for long option?\n        if (argu_desc.set_value)\n        {\n          //parse the option given\n\n          parse_option(opt, name, argu_desc.value);\n        }\n        else\n        {\n          //parse the next argument\n          checked_parse_arg(argc, argv, current, opt, name);\n        }\n      }\n\n    }\n\n    ++current;\n  }\n\n  for (auto& opt : m_options)\n  {\n    auto& detail = opt.second;\n    const auto& value = detail->value();\n\n    auto& store = m_parsed[detail->hash()];\n\n    if (value.has_default()) {\n      if (!store.count() && !store.has_default()) {\n        parse_default(detail);\n      }\n    }\n    else {\n      parse_no_value(detail);\n    }\n  }\n\n  if (consume_remaining)\n  {\n    while (current < argc)\n    {\n      if (!consume_positional(argv[current], next_positional)) {\n        break;\n      }\n      ++current;\n    }\n\n    //adjust argv for any that couldn't be swallowed\n    while (current != argc) {\n      unmatched.emplace_back(argv[current]);\n      ++current;\n    }\n  }\n\n  finalise_aliases();\n\n  ParseResult parsed(std::move(m_keys), std::move(m_parsed), std::move(m_sequential), std::move(m_defaults), std::move(unmatched));\n  return parsed;\n}\n\ninline\nvoid\nOptionParser::finalise_aliases()\n{\n  for (auto& option: m_options)\n  {\n    auto& detail = *option.second;\n    auto hash = detail.hash();\n    m_keys[detail.short_name()] = hash;\n    m_keys[detail.long_name()] = hash;\n\n    m_parsed.emplace(hash, OptionValue());\n  }\n}\n\ninline\nvoid\nOptions::add_option\n(\n  const std::string& group,\n  const Option& option\n)\n{\n    add_options(group, {option});\n}\n\ninline\nvoid\nOptions::add_option\n(\n  const std::string& group,\n  const std::string& s,\n  const std::string& l,\n  std::string desc,\n  const std::shared_ptr<const Value>& value,\n  std::string arg_help\n)\n{\n  auto stringDesc = toLocalString(std::move(desc));\n  auto option = std::make_shared<OptionDetails>(s, l, stringDesc, value);\n\n  if (!s.empty())\n  {\n    add_one_option(s, option);\n  }\n\n  if (!l.empty())\n  {\n    add_one_option(l, option);\n  }\n\n  //add the help details\n  auto& options = m_help[group];\n\n  options.options.emplace_back(HelpOptionDetails{s, l, stringDesc,\n      value->has_default(), value->get_default_value(),\n      value->has_implicit(), value->get_implicit_value(),\n      std::move(arg_help),\n      value->is_container(),\n      value->is_boolean()});\n}\n\ninline\nvoid\nOptions::add_one_option\n(\n  const std::string& option,\n  const std::shared_ptr<OptionDetails>& details\n)\n{\n  auto in = m_options->emplace(option, details);\n\n  if (!in.second)\n  {\n    throw_or_mimic<option_exists_error>(option);\n  }\n}\n\ninline\nString\nOptions::help_one_group(const std::string& g) const\n{\n  using OptionHelp = std::vector<std::pair<String, String>>;\n\n  auto group = m_help.find(g);\n  if (group == m_help.end())\n  {\n    return \"\";\n  }\n\n  OptionHelp format;\n\n  size_t longest = 0;\n\n  String result;\n\n  if (!g.empty())\n  {\n    result += toLocalString(\" \" + g + \" options:\\n\");\n  }\n\n  for (const auto& o : group->second.options)\n  {\n    if (m_positional_set.find(o.l) != m_positional_set.end() &&\n        !m_show_positional)\n    {\n      continue;\n    }\n\n    auto s = format_option(o);\n    longest = (std::max)(longest, stringLength(s));\n    format.push_back(std::make_pair(s, String()));\n  }\n  longest = (std::min)(longest, OPTION_LONGEST);\n\n  //widest allowed description -- min 10 chars for helptext/line\n  size_t allowed = 10;\n  if (m_width > allowed + longest + OPTION_DESC_GAP)\n  {\n    allowed = m_width - longest - OPTION_DESC_GAP;\n  }\n\n  auto fiter = format.begin();\n  for (const auto& o : group->second.options)\n  {\n    if (m_positional_set.find(o.l) != m_positional_set.end() &&\n        !m_show_positional)\n    {\n      continue;\n    }\n\n    auto d = format_description(o, longest + OPTION_DESC_GAP, allowed, m_tab_expansion);\n\n    result += fiter->first;\n    if (stringLength(fiter->first) > longest)\n    {\n      result += '\\n';\n      result += toLocalString(std::string(longest + OPTION_DESC_GAP, ' '));\n    }\n    else\n    {\n      result += toLocalString(std::string(longest + OPTION_DESC_GAP -\n        stringLength(fiter->first),\n        ' '));\n    }\n    result += d;\n    result += '\\n';\n\n    ++fiter;\n  }\n\n  return result;\n}\n\ninline\nvoid\nOptions::generate_group_help\n(\n  String& result,\n  const std::vector<std::string>& print_groups\n) const\n{\n  for (size_t i = 0; i != print_groups.size(); ++i)\n  {\n    const String& group_help_text = help_one_group(print_groups[i]);\n    if (empty(group_help_text))\n    {\n      continue;\n    }\n    result += group_help_text;\n    if (i < print_groups.size() - 1)\n    {\n      result += '\\n';\n    }\n  }\n}\n\ninline\nvoid\nOptions::generate_all_groups_help(String& result) const\n{\n  std::vector<std::string> all_groups;\n\n  std::transform(\n    m_help.begin(),\n    m_help.end(),\n    std::back_inserter(all_groups),\n    [] (const std::map<std::string, HelpGroupDetails>::value_type& group)\n    {\n      return group.first;\n    }\n  );\n\n  generate_group_help(result, all_groups);\n}\n\ninline\nstd::string\nOptions::help(const std::vector<std::string>& help_groups) const\n{\n  String result = m_help_string + \"\\nUsage:\\n  \" +\n    toLocalString(m_program) + \" \" + toLocalString(m_custom_help);\n\n  if (!m_positional.empty() && !m_positional_help.empty()) {\n    result += \" \" + toLocalString(m_positional_help);\n  }\n\n  result += \"\\n\\n\";\n\n  if (help_groups.empty())\n  {\n    generate_all_groups_help(result);\n  }\n  else\n  {\n    generate_group_help(result, help_groups);\n  }\n\n  return toUTF8String(result);\n}\n\ninline\nstd::vector<std::string>\nOptions::groups() const\n{\n  std::vector<std::string> g;\n\n  std::transform(\n    m_help.begin(),\n    m_help.end(),\n    std::back_inserter(g),\n    [] (const std::map<std::string, HelpGroupDetails>::value_type& pair)\n    {\n      return pair.first;\n    }\n  );\n\n  return g;\n}\n\ninline\nconst HelpGroupDetails&\nOptions::group_help(const std::string& group) const\n{\n  return m_help.at(group);\n}\n\n} // namespace cxxopts\n\n#endif //CXXOPTS_HPP_INCLUDED"
  },
  {
    "path": "src/kilo/CREDITS",
    "content": "Kilo editor from:\nhttps://github.com/antirez/kilo"
  },
  {
    "path": "src/kilo/kilo.cpp",
    "content": "/* Kilo -- A very simple editor in less than 1-kilo lines of code (as counted\n *         by \"cloc\"). Does not depend on libcurses, directly emits VT100\n *         escapes on the terminal.\n *\n * -----------------------------------------------------------------------\n *\n * Copyright (C) 2016 Salvatore Sanfilippo <antirez at gmail dot com>\n *\n * All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are\n * met:\n *\n *  *  Redistributions of source code must retain the above copyright\n *     notice, this list of conditions and the following disclaimer.\n *\n *  *  Redistributions in binary form must reproduce the above copyright\n *     notice, this list of conditions and the following disclaimer in the\n *     documentation and/or other materials provided with the distribution.\n *\n * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n * \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\n * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\n * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\n * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\n * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\n * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\n * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\n * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n */\n\n/**\n * Modified by Jan Boehmer 2022, to match the requirements of ESP32Console\n *\n */\n\n#pragma GCC diagnostic push\n#pragma GCC diagnostic ignored \"-Wwrite-strings\"\n\n#define KILO_VERSION \"0.0.1\"\n\n#ifdef __linux__\n#define _POSIX_C_SOURCE 200809L\n#endif\n\n#include <kilo/kilo.h>\n\n#include <termios.h>\n#include <stdlib.h>\n#include <stdio.h>\n#include <stdint.h>\n#include <errno.h>\n#include <string.h>\n#include <ctype.h>\n#include <time.h>\n#include <sys/types.h>\n#include <sys/ioctl.h>\n#include <sys/time.h>\n#include <unistd.h>\n#include <stdarg.h>\n#include <fcntl.h>\n#include <signal.h>\n\n/* Syntax highlight types */\n#define HL_NORMAL 0\n#define HL_NONPRINT 1\n#define HL_COMMENT 2   /* Single line comment. */\n#define HL_MLCOMMENT 3 /* Multi-line comment. */\n#define HL_KEYWORD1 4\n#define HL_KEYWORD2 5\n#define HL_STRING 6\n#define HL_NUMBER 7\n#define HL_MATCH 8 /* Search match. */\n\n#define HL_HIGHLIGHT_STRINGS (1 << 0)\n#define HL_HIGHLIGHT_NUMBERS (1 << 1)\n\nnamespace ESP32Console::Kilo\n{\n    struct editorSyntax\n    {\n        char **filematch;\n        char **keywords;\n        char singleline_comment_start[3];\n        char multiline_comment_start[3];\n        char multiline_comment_end[3];\n        int flags;\n    };\n\n    /* This structure represents a single line of the file we are editing. */\n    typedef struct erow\n    {\n        int idx;           /* Row index in the file, zero-based. */\n        int size;          /* Size of the row, excluding the null term. */\n        int rsize;         /* Size of the rendered row. */\n        char *chars;       /* Row content. */\n        char *render;      /* Row content \"rendered\" for screen (for TABs). */\n        unsigned char *hl; /* Syntax highlight type for each character in render.*/\n        int hl_oc;         /* Row had open comment at end in last syntax highlight\n                              check. */\n    } erow;\n\n    typedef struct hlcolor\n    {\n        int r, g, b;\n    } hlcolor;\n\n    struct editorConfig\n    {\n        int cx, cy;     /* Cursor x and y position in characters */\n        int rowoff;     /* Offset of row displayed. */\n        int coloff;     /* Offset of column displayed. */\n        int screenrows; /* Number of rows that we can show */\n        int screencols; /* Number of cols that we can show */\n        int numrows;    /* Number of rows */\n        int rawmode;    /* Is terminal raw mode enabled? */\n        erow *row;      /* Rows */\n        int dirty;      /* File modified but not saved. */\n        char *filename; /* Currently open filename */\n        char statusmsg[80];\n        time_t statusmsg_time;\n        struct editorSyntax *syntax; /* Current syntax highlight, or NULL. */\n    };\n\n    static struct editorConfig E;\n\n    enum KEY_ACTION\n    {\n        KEY_NULL = 0,    /* NULL */\n        CTRL_C = 3,      /* Ctrl-c */\n        CTRL_D = 4,      /* Ctrl-d */\n        CTRL_F = 6,      /* Ctrl-f */\n        CTRL_H = 8,      /* Ctrl-h */\n        TAB = 9,         /* Tab */\n        CTRL_L = 12,     /* Ctrl+l */\n        ENTER = 13,      /* Enter */\n        CTRL_Q = 17,     /* Ctrl-q */\n        CTRL_S = 19,     /* Ctrl-s */\n        CTRL_U = 21,     /* Ctrl-u */\n        ESC = 27,        /* Escape */\n        BACKSPACE = 127, /* Backspace */\n        /* The following are just soft codes, not really reported by the\n         * terminal directly. */\n        ARROW_LEFT = 1000,\n        ARROW_RIGHT,\n        ARROW_UP,\n        ARROW_DOWN,\n        DEL_KEY,\n        HOME_KEY,\n        END_KEY,\n        PAGE_UP,\n        PAGE_DOWN\n    };\n\n    void editorSetStatusMessage(const char *fmt, ...);\n\n    /* =========================== Syntax highlights DB =========================\n     *\n     * In order to add a new syntax, define two arrays with a list of file name\n     * matches and keywords. The file name matches are used in order to match\n     * a given syntax with a given file name: if a match pattern starts with a\n     * dot, it is matched as the last past of the filename, for example \".c\".\n     * Otherwise the pattern is just searched inside the filenme, like \"Makefile\").\n     *\n     * The list of keywords to highlight is just a list of words, however if they\n     * a trailing '|' character is added at the end, they are highlighted in\n     * a different color, so that you can have two different sets of keywords.\n     *\n     * Finally add a stanza in the HLDB global variable with two two arrays\n     * of strings, and a set of flags in order to enable highlighting of\n     * comments and numbers.\n     *\n     * The characters for single and multi line comments must be exactly two\n     * and must be provided as well (see the C language example).\n     *\n     * There is no support to highlight patterns currently. */\n\n    /* C / C++ */\n    char *C_HL_extensions[] = {\".c\", \".h\", \".cpp\", \".hpp\", \".cc\", NULL};\n    char *C_HL_keywords[] = {\n        /* C Keywords */\n        \"auto\", \"break\", \"case\", \"continue\", \"default\", \"do\", \"else\", \"enum\",\n        \"extern\", \"for\", \"goto\", \"if\", \"register\", \"return\", \"sizeof\", \"static\",\n        \"struct\", \"switch\", \"typedef\", \"union\", \"volatile\", \"while\", \"NULL\",\n\n        /* C++ Keywords */\n        \"alignas\", \"alignof\", \"and\", \"and_eq\", \"asm\", \"bitand\", \"bitor\", \"class\",\n        \"compl\", \"constexpr\", \"const_cast\", \"deltype\", \"delete\", \"dynamic_cast\",\n        \"explicit\", \"export\", \"false\", \"friend\", \"inline\", \"mutable\", \"namespace\",\n        \"new\", \"noexcept\", \"not\", \"not_eq\", \"nullptr\", \"operator\", \"or\", \"or_eq\",\n        \"private\", \"protected\", \"public\", \"reinterpret_cast\", \"static_assert\",\n        \"static_cast\", \"template\", \"this\", \"thread_local\", \"throw\", \"true\", \"try\",\n        \"typeid\", \"typename\", \"virtual\", \"xor\", \"xor_eq\",\n\n        /* C types */\n        \"int|\", \"long|\", \"double|\", \"float|\", \"char|\", \"unsigned|\", \"signed|\",\n        \"void|\", \"short|\", \"auto|\", \"const|\", \"bool|\", NULL};\n\n    /* Here we define an array of syntax highlights by extensions, keywords,\n     * comments delimiters and flags. */\n    struct editorSyntax HLDB[] = {\n        {/* C / C++ */\n         C_HL_extensions,\n         C_HL_keywords,\n         \"//\", \"/*\", \"*/\",\n         HL_HIGHLIGHT_STRINGS | HL_HIGHLIGHT_NUMBERS}};\n\n#define HLDB_ENTRIES (sizeof(HLDB) / sizeof(HLDB[0]))\n\n    /* ======================= Low level terminal handling ====================== */\n\n    static struct termios orig_termios; /* In order to restore at exit.*/\n\n    void disableRawMode(int fd)\n    {\n        /* Don't even check the return value as it's too late. */\n        if (E.rawmode)\n        {\n            tcsetattr(fd, TCSAFLUSH, &orig_termios);\n            E.rawmode = 0;\n        }\n    }\n\n    /* Called at exit to avoid remaining in raw mode. */\n    void editorAtExit(void)\n    {\n        disableRawMode(STDIN_FILENO);\n    }\n\n    /* Raw mode: 1960 magic shit. */\n    int enableRawMode(int fd)\n    {\n        struct termios raw;\n\n        if (E.rawmode)\n            return 0; /* Already enabled. */\n        if (!isatty(STDIN_FILENO))\n            goto fatal;\n        atexit(editorAtExit);\n        if (tcgetattr(fd, &orig_termios) == -1)\n            goto fatal;\n\n        raw = orig_termios; /* modify the original mode */\n        /* input modes: no break, no CR to NL, no parity check, no strip char,\n         * no start/stop output control. */\n        raw.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON);\n        /* output modes - disable post processing */\n        raw.c_oflag &= ~(OPOST);\n        /* control modes - set 8 bit chars */\n        raw.c_cflag |= (CS8);\n        /* local modes - choing off, canonical off, no extended functions,\n         * no signal chars (^Z,^C) */\n        raw.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG);\n        /* control chars - set return condition: min number of bytes and timer. */\n        raw.c_cc[VMIN] = 0;  /* Return each byte, or zero for timeout. */\n        raw.c_cc[VTIME] = 1; /* 100 ms timeout (unit is tens of second). */\n\n        /* put terminal in raw mode after flushing */\n        if (tcsetattr(fd, TCSAFLUSH, &raw) < 0)\n            goto fatal;\n        E.rawmode = 1;\n        return 0;\n\n    fatal:\n        errno = ENOTTY;\n        return -1;\n    }\n\n    /* Read a key from the terminal put in raw mode, trying to handle\n     * escape sequences. */\n    int editorReadKey(int fd)\n    {\n        int nread;\n        char c, seq[3];\n        while ((nread = read(fd, &c, 1)) == 0)\n            ;\n        if (nread == -1)\n            throw KiloException(1);\n\n        while (1)\n        {\n            switch (c)\n            {\n            case ESC: /* escape sequence */\n                /* If this is just an ESC, we'll timeout here. */\n                if (read(fd, seq, 1) == 0)\n                    return ESC;\n                if (read(fd, seq + 1, 1) == 0)\n                    return ESC;\n\n                /* ESC [ sequences. */\n                if (seq[0] == '[')\n                {\n                    if (seq[1] >= '0' && seq[1] <= '9')\n                    {\n                        /* Extended escape, read additional byte. */\n                        if (read(fd, seq + 2, 1) == 0)\n                            return ESC;\n                        if (seq[2] == '~')\n                        {\n                            switch (seq[1])\n                            {\n                            case '3':\n                                return DEL_KEY;\n                            case '5':\n                                return PAGE_UP;\n                            case '6':\n                                return PAGE_DOWN;\n                            }\n                        }\n                    }\n                    else\n                    {\n                        switch (seq[1])\n                        {\n                        case 'A':\n                            return ARROW_UP;\n                        case 'B':\n                            return ARROW_DOWN;\n                        case 'C':\n                            return ARROW_RIGHT;\n                        case 'D':\n                            return ARROW_LEFT;\n                        case 'H':\n                            return HOME_KEY;\n                        case 'F':\n                            return END_KEY;\n                        }\n                    }\n                }\n\n                /* ESC O sequences. */\n                else if (seq[0] == 'O')\n                {\n                    switch (seq[1])\n                    {\n                    case 'H':\n                        return HOME_KEY;\n                    case 'F':\n                        return END_KEY;\n                    }\n                }\n                break;\n            default:\n                return c;\n            }\n        }\n    }\n\n    /* Use the ESC [6n escape sequence to query the horizontal cursor position\n     * and return it. On error -1 is returned, on success the position of the\n     * cursor is stored at *rows and *cols and 0 is returned. */\n    int getCursorPosition(int ifd, int ofd, int *rows, int *cols)\n    {\n        char buf[32];\n        unsigned int i = 0;\n\n        /* Report cursor location */\n        if (write(ofd, \"\\x1b[6n\", 4) != 4)\n            return -1;\n\n        /* Read the response: ESC [ rows ; cols R */\n        while (i < sizeof(buf) - 1)\n        {\n            if (read(ifd, buf + i, 1) != 1)\n                break;\n            if (buf[i] == 'R')\n                break;\n            i++;\n        }\n        buf[i] = '\\0';\n\n        /* Parse it. */\n        if (buf[0] != ESC || buf[1] != '[')\n            return -1;\n        if (sscanf(buf + 2, \"%d;%d\", rows, cols) != 2)\n            return -1;\n        return 0;\n    }\n\n    /* Try to get the number of columns in the current terminal. If the ioctl()\n     * call fails the function will try to query the terminal itself.\n     * Returns 0 on success, -1 on error. */\n    int getWindowSize(int ifd, int ofd, int *rows, int *cols)\n    {\n        /* ioctl() failed. Try to query the terminal itself. */\n        int orig_row, orig_col, retval;\n\n        /* Get the initial position so we can restore it later. */\n        retval = getCursorPosition(ifd, ofd, &orig_row, &orig_col);\n        if (retval == -1)\n            goto failed;\n\n        /* Go to right/bottom margin and get position. */\n        if (write(ofd, \"\\x1b[999C\\x1b[999B\", 12) != 12)\n            goto failed;\n        retval = getCursorPosition(ifd, ofd, rows, cols);\n        if (retval == -1)\n            goto failed;\n\n        /* Restore position. */\n        char seq[32];\n        snprintf(seq, 32, \"\\x1b[%d;%dH\", orig_row, orig_col);\n        if (write(ofd, seq, strlen(seq)) == -1)\n        {\n            /* Can't recover... */\n        }\n        return 0;\n\n    failed:\n        return -1;\n    }\n\n    /* ====================== Syntax highlight color scheme  ==================== */\n\n    int is_separator(int c)\n    {\n        return c == '\\0' || isspace(c) || strchr(\",.()+-/*=~%[];\", c) != NULL;\n    }\n\n    /* Return true if the specified row last char is part of a multi line comment\n     * that starts at this row or at one before, and does not end at the end\n     * of the row but spawns to the next row. */\n    int editorRowHasOpenComment(erow *row)\n    {\n        if (row->hl && row->rsize && row->hl[row->rsize - 1] == HL_MLCOMMENT &&\n            (row->rsize < 2 || (row->render[row->rsize - 2] != '*' ||\n                                row->render[row->rsize - 1] != '/')))\n            return 1;\n        return 0;\n    }\n\n    /* Set every byte of row->hl (that corresponds to every character in the line)\n     * to the right syntax highlight type (HL_* defines). */\n    void editorUpdateSyntax(erow *row)\n    {\n        row->hl = (unsigned char *)realloc(row->hl, row->rsize);\n        memset(row->hl, HL_NORMAL, row->rsize);\n\n        if (E.syntax == NULL)\n            return; /* No syntax, everything is HL_NORMAL. */\n\n        int i, prev_sep, in_string, in_comment;\n        char *p;\n        char **keywords = E.syntax->keywords;\n        char *scs = E.syntax->singleline_comment_start;\n        char *mcs = E.syntax->multiline_comment_start;\n        char *mce = E.syntax->multiline_comment_end;\n\n        /* Point to the first non-space char. */\n        p = row->render;\n        i = 0; /* Current char offset */\n        while (*p && isspace(*p))\n        {\n            p++;\n            i++;\n        }\n        prev_sep = 1;   /* Tell the parser if 'i' points to start of word. */\n        in_string = 0;  /* Are we inside \"\" or '' ? */\n        in_comment = 0; /* Are we inside multi-line comment? */\n\n        /* If the previous line has an open comment, this line starts\n         * with an open comment state. */\n        if (row->idx > 0 && editorRowHasOpenComment(&E.row[row->idx - 1]))\n            in_comment = 1;\n\n        while (*p)\n        {\n            /* Handle // comments. */\n            if (prev_sep && *p == scs[0] && *(p + 1) == scs[1])\n            {\n                /* From here to end is a comment */\n                memset(row->hl + i, HL_COMMENT, row->size - i);\n                return;\n            }\n\n            /* Handle multi line comments. */\n            if (in_comment)\n            {\n                row->hl[i] = HL_MLCOMMENT;\n                if (*p == mce[0] && *(p + 1) == mce[1])\n                {\n                    row->hl[i + 1] = HL_MLCOMMENT;\n                    p += 2;\n                    i += 2;\n                    in_comment = 0;\n                    prev_sep = 1;\n                    continue;\n                }\n                else\n                {\n                    prev_sep = 0;\n                    p++;\n                    i++;\n                    continue;\n                }\n            }\n            else if (*p == mcs[0] && *(p + 1) == mcs[1])\n            {\n                row->hl[i] = HL_MLCOMMENT;\n                row->hl[i + 1] = HL_MLCOMMENT;\n                p += 2;\n                i += 2;\n                in_comment = 1;\n                prev_sep = 0;\n                continue;\n            }\n\n            /* Handle \"\" and '' */\n            if (in_string)\n            {\n                row->hl[i] = HL_STRING;\n                if (*p == '\\\\')\n                {\n                    row->hl[i + 1] = HL_STRING;\n                    p += 2;\n                    i += 2;\n                    prev_sep = 0;\n                    continue;\n                }\n                if (*p == in_string)\n                    in_string = 0;\n                p++;\n                i++;\n                continue;\n            }\n            else\n            {\n                if (*p == '\"' || *p == '\\'')\n                {\n                    in_string = *p;\n                    row->hl[i] = HL_STRING;\n                    p++;\n                    i++;\n                    prev_sep = 0;\n                    continue;\n                }\n            }\n\n            /* Handle non printable chars. */\n            if (!isprint(*p))\n            {\n                row->hl[i] = HL_NONPRINT;\n                p++;\n                i++;\n                prev_sep = 0;\n                continue;\n            }\n\n            /* Handle numbers */\n            if ((isdigit(*p) && (prev_sep || row->hl[i - 1] == HL_NUMBER)) ||\n                (*p == '.' && i > 0 && row->hl[i - 1] == HL_NUMBER))\n            {\n                row->hl[i] = HL_NUMBER;\n                p++;\n                i++;\n                prev_sep = 0;\n                continue;\n            }\n\n            /* Handle keywords and lib calls */\n            if (prev_sep)\n            {\n                int j;\n                for (j = 0; keywords[j]; j++)\n                {\n                    int klen = strlen(keywords[j]);\n                    int kw2 = keywords[j][klen - 1] == '|';\n                    if (kw2)\n                        klen--;\n\n                    if (!memcmp(p, keywords[j], klen) &&\n                        is_separator(*(p + klen)))\n                    {\n                        /* Keyword */\n                        memset(row->hl + i, kw2 ? HL_KEYWORD2 : HL_KEYWORD1, klen);\n                        p += klen;\n                        i += klen;\n                        break;\n                    }\n                }\n                if (keywords[j] != NULL)\n                {\n                    prev_sep = 0;\n                    continue; /* We had a keyword match */\n                }\n            }\n\n            /* Not special chars */\n            prev_sep = is_separator(*p);\n            p++;\n            i++;\n        }\n\n        /* Propagate syntax change to the next row if the open commen\n         * state changed. This may recursively affect all the following rows\n         * in the file. */\n        int oc = editorRowHasOpenComment(row);\n        if (row->hl_oc != oc && row->idx + 1 < E.numrows)\n            editorUpdateSyntax(&E.row[row->idx + 1]);\n        row->hl_oc = oc;\n    }\n\n    /* Maps syntax highlight token types to terminal colors. */\n    int editorSyntaxToColor(int hl)\n    {\n        switch (hl)\n        {\n        case HL_COMMENT:\n        case HL_MLCOMMENT:\n            return 36; /* cyan */\n        case HL_KEYWORD1:\n            return 33; /* yellow */\n        case HL_KEYWORD2:\n            return 32; /* green */\n        case HL_STRING:\n            return 35; /* magenta */\n        case HL_NUMBER:\n            return 31; /* red */\n        case HL_MATCH:\n            return 34; /* blu */\n        default:\n            return 37; /* white */\n        }\n    }\n\n    /* Select the syntax highlight scheme depending on the filename,\n     * setting it in the global state E.syntax. */\n    void editorSelectSyntaxHighlight(char *filename)\n    {\n        for (unsigned int j = 0; j < HLDB_ENTRIES; j++)\n        {\n            struct editorSyntax *s = HLDB + j;\n            unsigned int i = 0;\n            while (s->filematch[i])\n            {\n                char *p;\n                int patlen = strlen(s->filematch[i]);\n                if ((p = strstr(filename, s->filematch[i])) != NULL)\n                {\n                    if (s->filematch[i][0] != '.' || p[patlen] == '\\0')\n                    {\n                        E.syntax = s;\n                        return;\n                    }\n                }\n                i++;\n            }\n        }\n    }\n\n    /* ======================= Editor rows implementation ======================= */\n\n    /* Update the rendered version and the syntax highlight of a row. */\n    void editorUpdateRow(erow *row)\n    {\n        unsigned int tabs = 0, nonprint = 0;\n        int j, idx;\n\n        /* Create a version of the row we can directly print on the screen,\n         * respecting tabs, substituting non printable characters with '?'. */\n        free(row->render);\n        for (j = 0; j < row->size; j++)\n            if (row->chars[j] == TAB)\n                tabs++;\n\n        unsigned long long allocsize =\n            (unsigned long long)row->size + tabs * 8 + nonprint * 9 + 1;\n        if (allocsize > UINT32_MAX)\n        {\n            printf(\"Some line of the edited file is too long for kilo\\n\");\n            throw KiloException(1);\n        }\n\n        row->render = (char *)malloc(row->size + tabs * 8 + nonprint * 9 + 1);\n        idx = 0;\n        for (j = 0; j < row->size; j++)\n        {\n            if (row->chars[j] == TAB)\n            {\n                row->render[idx++] = ' ';\n                while ((idx + 1) % 8 != 0)\n                    row->render[idx++] = ' ';\n            }\n            else\n            {\n                row->render[idx++] = row->chars[j];\n            }\n        }\n        row->rsize = idx;\n        row->render[idx] = '\\0';\n\n        /* Update the syntax highlighting attributes of the row. */\n        editorUpdateSyntax(row);\n    }\n\n    /* Insert a row at the specified position, shifting the other rows on the bottom\n     * if required. */\n    void editorInsertRow(int at, char *s, size_t len)\n    {\n        if (at > E.numrows)\n            return;\n        E.row = (erow *)realloc(E.row, sizeof(erow) * (E.numrows + 1));\n        if (at != E.numrows)\n        {\n            memmove(E.row + at + 1, E.row + at, sizeof(E.row[0]) * (E.numrows - at));\n            for (int j = at + 1; j <= E.numrows; j++)\n                E.row[j].idx++;\n        }\n        E.row[at].size = len;\n        E.row[at].chars = (char *)malloc(len + 1);\n        memcpy(E.row[at].chars, s, len + 1);\n        E.row[at].hl = NULL;\n        E.row[at].hl_oc = 0;\n        E.row[at].render = NULL;\n        E.row[at].rsize = 0;\n        E.row[at].idx = at;\n        editorUpdateRow(E.row + at);\n        E.numrows++;\n        E.dirty++;\n    }\n\n    /* Free row's heap allocated stuff. */\n    void editorFreeRow(erow *row)\n    {\n        free(row->render);\n        free(row->chars);\n        free(row->hl);\n    }\n\n    /* Remove the row at the specified position, shifting the remainign on the\n     * top. */\n    void editorDelRow(int at)\n    {\n        erow *row;\n\n        if (at >= E.numrows)\n            return;\n        row = E.row + at;\n        editorFreeRow(row);\n        memmove(E.row + at, E.row + at + 1, sizeof(E.row[0]) * (E.numrows - at - 1));\n        for (int j = at; j < E.numrows - 1; j++)\n            E.row[j].idx++;\n        E.numrows--;\n        E.dirty++;\n    }\n\n    /* Turn the editor rows into a single heap-allocated string.\n     * Returns the pointer to the heap-allocated string and populate the\n     * integer pointed by 'buflen' with the size of the string, escluding\n     * the final nulterm. */\n    char *editorRowsToString(int *buflen)\n    {\n        char *buf = NULL, *p;\n        int totlen = 0;\n        int j;\n\n        /* Compute count of bytes */\n        for (j = 0; j < E.numrows; j++)\n            totlen += E.row[j].size + 1; /* +1 is for \"\\n\" at end of every row */\n        *buflen = totlen;\n        totlen++; /* Also make space for nulterm */\n\n        p = buf = (char *)malloc(totlen);\n        for (j = 0; j < E.numrows; j++)\n        {\n            memcpy(p, E.row[j].chars, E.row[j].size);\n            p += E.row[j].size;\n            *p = '\\n';\n            p++;\n        }\n        *p = '\\0';\n        return buf;\n    }\n\n    /* Insert a character at the specified position in a row, moving the remaining\n     * chars on the right if needed. */\n    void editorRowInsertChar(erow *row, int at, int c)\n    {\n        if (at > row->size)\n        {\n            /* Pad the string with spaces if the insert location is outside the\n             * current length by more than a single character. */\n            int padlen = at - row->size;\n            /* In the next line +2 means: new char and null term. */\n            row->chars = (char *)realloc(row->chars, row->size + padlen + 2);\n            memset(row->chars + row->size, ' ', padlen);\n            row->chars[row->size + padlen + 1] = '\\0';\n            row->size += padlen + 1;\n        }\n        else\n        {\n            /* If we are in the middle of the string just make space for 1 new\n             * char plus the (already existing) null term. */\n            row->chars = (char *)realloc(row->chars, row->size + 2);\n            memmove(row->chars + at + 1, row->chars + at, row->size - at + 1);\n            row->size++;\n        }\n        row->chars[at] = c;\n        editorUpdateRow(row);\n        E.dirty++;\n    }\n\n    /* Append the string 's' at the end of a row */\n    void editorRowAppendString(erow *row, char *s, size_t len)\n    {\n        row->chars = (char *)realloc(row->chars, row->size + len + 1);\n        memcpy(row->chars + row->size, s, len);\n        row->size += len;\n        row->chars[row->size] = '\\0';\n        editorUpdateRow(row);\n        E.dirty++;\n    }\n\n    /* Delete the character at offset 'at' from the specified row. */\n    void editorRowDelChar(erow *row, int at)\n    {\n        if (row->size <= at)\n            return;\n        memmove(row->chars + at, row->chars + at + 1, row->size - at);\n        editorUpdateRow(row);\n        row->size--;\n        E.dirty++;\n    }\n\n    /* Insert the specified char at the current prompt position. */\n    void editorInsertChar(int c)\n    {\n        int filerow = E.rowoff + E.cy;\n        int filecol = E.coloff + E.cx;\n        erow *row = (filerow >= E.numrows) ? NULL : &E.row[filerow];\n\n        /* If the row where the cursor is currently located does not exist in our\n         * logical representaion of the file, add enough empty rows as needed. */\n        if (!row)\n        {\n            while (E.numrows <= filerow)\n                editorInsertRow(E.numrows, \"\", 0);\n        }\n        row = &E.row[filerow];\n        editorRowInsertChar(row, filecol, c);\n        if (E.cx == E.screencols - 1)\n            E.coloff++;\n        else\n            E.cx++;\n        E.dirty++;\n    }\n\n    /* Inserting a newline is slightly complex as we have to handle inserting a\n     * newline in the middle of a line, splitting the line as needed. */\n    void editorInsertNewline(void)\n    {\n        int filerow = E.rowoff + E.cy;\n        int filecol = E.coloff + E.cx;\n        erow *row = (filerow >= E.numrows) ? NULL : &E.row[filerow];\n\n        if (!row)\n        {\n            if (filerow == E.numrows)\n            {\n                editorInsertRow(filerow, \"\", 0);\n                goto fixcursor;\n            }\n            return;\n        }\n        /* If the cursor is over the current line size, we want to conceptually\n         * think it's just over the last character. */\n        if (filecol >= row->size)\n            filecol = row->size;\n        if (filecol == 0)\n        {\n            editorInsertRow(filerow, \"\", 0);\n        }\n        else\n        {\n            /* We are in the middle of a line. Split it between two rows. */\n            editorInsertRow(filerow + 1, row->chars + filecol, row->size - filecol);\n            row = &E.row[filerow];\n            row->chars[filecol] = '\\0';\n            row->size = filecol;\n            editorUpdateRow(row);\n        }\n    fixcursor:\n        if (E.cy == E.screenrows - 1)\n        {\n            E.rowoff++;\n        }\n        else\n        {\n            E.cy++;\n        }\n        E.cx = 0;\n        E.coloff = 0;\n    }\n\n    /* Delete the char at the current prompt position. */\n    void editorDelChar()\n    {\n        int filerow = E.rowoff + E.cy;\n        int filecol = E.coloff + E.cx;\n        erow *row = (filerow >= E.numrows) ? NULL : &E.row[filerow];\n\n        if (!row || (filecol == 0 && filerow == 0))\n            return;\n        if (filecol == 0)\n        {\n            /* Handle the case of column 0, we need to move the current line\n             * on the right of the previous one. */\n            filecol = E.row[filerow - 1].size;\n            editorRowAppendString(&E.row[filerow - 1], row->chars, row->size);\n            editorDelRow(filerow);\n            row = NULL;\n            if (E.cy == 0)\n                E.rowoff--;\n            else\n                E.cy--;\n            E.cx = filecol;\n            if (E.cx >= E.screencols)\n            {\n                int shift = (E.screencols - E.cx) + 1;\n                E.cx -= shift;\n                E.coloff += shift;\n            }\n        }\n        else\n        {\n            editorRowDelChar(row, filecol - 1);\n            if (E.cx == 0 && E.coloff)\n                E.coloff--;\n            else\n                E.cx--;\n        }\n        if (row)\n            editorUpdateRow(row);\n        E.dirty++;\n    }\n\n    /* Load the specified program in the editor memory and returns 0 on success\n     * or 1 on error. */\n    int editorOpen(char *filename)\n    {\n        FILE *fp;\n\n        E.dirty = 0;\n        free(E.filename);\n        size_t fnlen = strlen(filename) + 1;\n        E.filename = (char *)malloc(fnlen);\n        memcpy(E.filename, filename, fnlen);\n\n        fp = fopen(filename, \"r\");\n        if (!fp)\n        {\n            if (errno != ENOENT)\n            {\n                perror(\"Opening file\");\n                throw KiloException(1);\n            }\n            return 1;\n        }\n\n        char *line = NULL;\n        size_t linecap = 0;\n        ssize_t linelen;\n        while ((linelen = __getline(&line, &linecap, fp)) != -1)\n        {\n            if (linelen && (line[linelen - 1] == '\\n' || line[linelen - 1] == '\\r'))\n                line[--linelen] = '\\0';\n            editorInsertRow(E.numrows, line, linelen);\n        }\n        free(line);\n        fclose(fp);\n        E.dirty = 0;\n        return 0;\n    }\n\n    /* Save the current file on disk. Return 0 on success, 1 on error. */\n    int editorSave(void)\n    {\n        int len;\n        char *buf = editorRowsToString(&len);\n\n        FILE* file = fopen(E.filename, \"wb\");\n        if(!file) {\n            goto writeerr;\n        }\n        \n        for(int n=0; n<len; n++) {\n            if(fputc(buf[n], file) == EOF) {\n                goto writeerr;\n            }\n        }\n        fflush(file);\n\n\n        /*int fd = open(E.filename, O_RDWR | O_CREAT, 0644);\n        if (fd == -1)\n            goto writeerr;\n\n        fwrite()\n\n        /* Use truncate + a single write(2) call in order to make saving\n         * a bit safer, under the limits of what we can do in a small editor.\n        // if (ftruncate(fd, len) == -1)\n        //     goto writeerr;\n        if (write(fd, buf, len) != len)\n            goto writeerr;\n\n        close(fd);*/\n        free(buf);\n\n        //Ensure that the file is really written to FS before closing it\n        fflush(file);\n        fsync(fileno(file));\n        fclose(file);\n        \n        E.dirty = 0;\n        editorSetStatusMessage(\"%d bytes written on disk\", len);\n        return 0;\n\n    writeerr:\n        free(buf);\n        if (file)\n            fclose(file);\n        editorSetStatusMessage(\"Can't save! I/O error: %s\", strerror(errno));\n        return 1;\n    }\n\n    /* ============================= Terminal update ============================ */\n\n    /* We define a very simple \"append buffer\" structure, that is an heap\n     * allocated string where we can append to. This is useful in order to\n     * write all the escape sequences in a buffer and flush them to the standard\n     * output in a single call, to avoid flickering effects. */\n    struct abuf\n    {\n        char *b;\n        int len;\n    };\n\n#define ABUF_INIT \\\n    {             \\\n        NULL, 0   \\\n    }\n\n    void abAppend(struct abuf *ab, const char *s, int len)\n    {\n        char *new_ = (char *)realloc(ab->b, ab->len + len);\n\n        if (new_ == NULL)\n            return;\n        memcpy(new_ + ab->len, s, len);\n        ab->b = new_;\n        ab->len += len;\n    }\n\n    void abFree(struct abuf *ab)\n    {\n        free(ab->b);\n    }\n\n    /* This function writes the whole screen using VT100 escape characters\n     * starting from the logical state of the editor in the global state 'E'. */\n    void editorRefreshScreen(void)\n    {\n        int y;\n        erow *r;\n        char buf[32];\n        struct abuf ab = ABUF_INIT;\n\n        abAppend(&ab, \"\\x1b[?25l\", 6); /* Hide cursor. */\n        abAppend(&ab, \"\\x1b[H\", 3);    /* Go home. */\n        for (y = 0; y < E.screenrows; y++)\n        {\n            int filerow = E.rowoff + y;\n\n            if (filerow >= E.numrows)\n            {\n                if (E.numrows == 0 && y == E.screenrows / 3)\n                {\n                    char welcome[80];\n                    int welcomelen = snprintf(welcome, sizeof(welcome),\n                                              \"Kilo editor -- verison %s\\x1b[0K\\r\\n\", KILO_VERSION);\n                    int padding = (E.screencols - welcomelen) / 2;\n                    if (padding)\n                    {\n                        abAppend(&ab, \"~\", 1);\n                        padding--;\n                    }\n                    while (padding--)\n                        abAppend(&ab, \" \", 1);\n                    abAppend(&ab, welcome, welcomelen);\n                }\n                else\n                {\n                    abAppend(&ab, \"~\\x1b[0K\\r\\n\", 7);\n                }\n                continue;\n            }\n\n            r = &E.row[filerow];\n\n            int len = r->rsize - E.coloff;\n            int current_color = -1;\n            if (len > 0)\n            {\n                if (len > E.screencols)\n                    len = E.screencols;\n                char *c = r->render + E.coloff;\n                unsigned char *hl = r->hl + E.coloff;\n                int j;\n                for (j = 0; j < len; j++)\n                {\n                    if (hl[j] == HL_NONPRINT)\n                    {\n                        char sym;\n                        abAppend(&ab, \"\\x1b[7m\", 4);\n                        if (c[j] <= 26)\n                            sym = '@' + c[j];\n                        else\n                            sym = '?';\n                        abAppend(&ab, &sym, 1);\n                        abAppend(&ab, \"\\x1b[0m\", 4);\n                    }\n                    else if (hl[j] == HL_NORMAL)\n                    {\n                        if (current_color != -1)\n                        {\n                            abAppend(&ab, \"\\x1b[39m\", 5);\n                            current_color = -1;\n                        }\n                        abAppend(&ab, c + j, 1);\n                    }\n                    else\n                    {\n                        int color = editorSyntaxToColor(hl[j]);\n                        if (color != current_color)\n                        {\n                            char buf[16];\n                            int clen = snprintf(buf, sizeof(buf), \"\\x1b[%dm\", color);\n                            current_color = color;\n                            abAppend(&ab, buf, clen);\n                        }\n                        abAppend(&ab, c + j, 1);\n                    }\n                }\n            }\n            abAppend(&ab, \"\\x1b[39m\", 5);\n            abAppend(&ab, \"\\x1b[0K\", 4);\n            abAppend(&ab, \"\\r\\n\", 2);\n        }\n\n        /* Create a two rows status. First row: */\n        abAppend(&ab, \"\\x1b[0K\", 4);\n        abAppend(&ab, \"\\x1b[7m\", 4);\n        char status[80], rstatus[80];\n        int len = snprintf(status, sizeof(status), \"%.20s - %d lines %s\",\n                           E.filename, E.numrows, E.dirty ? \"(modified)\" : \"\");\n        int rlen = snprintf(rstatus, sizeof(rstatus),\n                            \"%d/%d\", E.rowoff + E.cy + 1, E.numrows);\n        if (len > E.screencols)\n            len = E.screencols;\n        abAppend(&ab, status, len);\n        while (len < E.screencols)\n        {\n            if (E.screencols - len == rlen)\n            {\n                abAppend(&ab, rstatus, rlen);\n                break;\n            }\n            else\n            {\n                abAppend(&ab, \" \", 1);\n                len++;\n            }\n        }\n        abAppend(&ab, \"\\x1b[0m\\r\\n\", 6);\n\n        /* Second row depends on E.statusmsg and the status message update time. */\n        abAppend(&ab, \"\\x1b[0K\", 4);\n        int msglen = strlen(E.statusmsg);\n        if (msglen && time(NULL) - E.statusmsg_time < 5)\n            abAppend(&ab, E.statusmsg, msglen <= E.screencols ? msglen : E.screencols);\n\n        /* Put cursor at its current position. Note that the horizontal position\n         * at which the cursor is displayed may be different compared to 'E.cx'\n         * because of TABs. */\n        int j;\n        int cx = 1;\n        int filerow = E.rowoff + E.cy;\n        erow *row = (filerow >= E.numrows) ? NULL : &E.row[filerow];\n        if (row)\n        {\n            for (j = E.coloff; j < (E.cx + E.coloff); j++)\n            {\n                if (j < row->size && row->chars[j] == TAB)\n                    cx += 7 - ((cx) % 8);\n                cx++;\n            }\n        }\n        snprintf(buf, sizeof(buf), \"\\x1b[%d;%dH\", E.cy + 1, cx);\n        abAppend(&ab, buf, strlen(buf));\n        abAppend(&ab, \"\\x1b[?25h\", 6); /* Show cursor. */\n        write(STDOUT_FILENO, ab.b, ab.len);\n        abFree(&ab);\n    }\n\n    /* Set an editor status message for the second line of the status, at the\n     * end of the screen. */\n    void editorSetStatusMessage(const char *fmt, ...)\n    {\n        va_list ap;\n        va_start(ap, fmt);\n        vsnprintf(E.statusmsg, sizeof(E.statusmsg), fmt, ap);\n        va_end(ap);\n        E.statusmsg_time = time(NULL);\n    }\n\n    /* =============================== Find mode ================================ */\n\n#define KILO_QUERY_LEN 256\n\n    void editorFind(int fd)\n    {\n        char query[KILO_QUERY_LEN + 1] = {0};\n        int qlen = 0;\n        int last_match = -1;    /* Last line where a match was found. -1 for none. */\n        int find_next = 0;      /* if 1 search next, if -1 search prev. */\n        int saved_hl_line = -1; /* No saved HL */\n        char *saved_hl = NULL;\n\n#define FIND_RESTORE_HL                                                            \\\n    do                                                                             \\\n    {                                                                              \\\n        if (saved_hl)                                                              \\\n        {                                                                          \\\n            memcpy(E.row[saved_hl_line].hl, saved_hl, E.row[saved_hl_line].rsize); \\\n            free(saved_hl);                                                        \\\n            saved_hl = NULL;                                                       \\\n        }                                                                          \\\n    } while (0)\n\n        /* Save the cursor position in order to restore it later. */\n        int saved_cx = E.cx, saved_cy = E.cy;\n        int saved_coloff = E.coloff, saved_rowoff = E.rowoff;\n\n        while (1)\n        {\n            editorSetStatusMessage(\n                \"Search: %s (Use ESC/Arrows/Enter)\", query);\n            editorRefreshScreen();\n\n            int c = editorReadKey(fd);\n            if (c == DEL_KEY || c == CTRL_H || c == BACKSPACE)\n            {\n                if (qlen != 0)\n                    query[--qlen] = '\\0';\n                last_match = -1;\n            }\n            else if (c == ESC || c == ENTER)\n            {\n                if (c == ESC)\n                {\n                    E.cx = saved_cx;\n                    E.cy = saved_cy;\n                    E.coloff = saved_coloff;\n                    E.rowoff = saved_rowoff;\n                }\n                FIND_RESTORE_HL;\n                editorSetStatusMessage(\"\");\n                return;\n            }\n            else if (c == ARROW_RIGHT || c == ARROW_DOWN)\n            {\n                find_next = 1;\n            }\n            else if (c == ARROW_LEFT || c == ARROW_UP)\n            {\n                find_next = -1;\n            }\n            else if (isprint(c))\n            {\n                if (qlen < KILO_QUERY_LEN)\n                {\n                    query[qlen++] = c;\n                    query[qlen] = '\\0';\n                    last_match = -1;\n                }\n            }\n\n            /* Search occurrence. */\n            if (last_match == -1)\n                find_next = 1;\n            if (find_next)\n            {\n                char *match = NULL;\n                int match_offset = 0;\n                int i, current = last_match;\n\n                for (i = 0; i < E.numrows; i++)\n                {\n                    current += find_next;\n                    if (current == -1)\n                        current = E.numrows - 1;\n                    else if (current == E.numrows)\n                        current = 0;\n                    match = strstr(E.row[current].render, query);\n                    if (match)\n                    {\n                        match_offset = match - E.row[current].render;\n                        break;\n                    }\n                }\n                find_next = 0;\n\n                /* Highlight */\n                FIND_RESTORE_HL;\n\n                if (match)\n                {\n                    erow *row = &E.row[current];\n                    last_match = current;\n                    if (row->hl)\n                    {\n                        saved_hl_line = current;\n                        saved_hl = (char *)malloc(row->rsize);\n                        memcpy(saved_hl, row->hl, row->rsize);\n                        memset(row->hl + match_offset, HL_MATCH, qlen);\n                    }\n                    E.cy = 0;\n                    E.cx = match_offset;\n                    E.rowoff = current;\n                    E.coloff = 0;\n                    /* Scroll horizontally as needed. */\n                    if (E.cx > E.screencols)\n                    {\n                        int diff = E.cx - E.screencols;\n                        E.cx -= diff;\n                        E.coloff += diff;\n                    }\n                }\n            }\n        }\n    }\n\n    /* ========================= Editor events handling  ======================== */\n\n    /* Handle cursor position change because arrow keys were pressed. */\n    void editorMoveCursor(int key)\n    {\n        int filerow = E.rowoff + E.cy;\n        int filecol = E.coloff + E.cx;\n        int rowlen;\n        erow *row = (filerow >= E.numrows) ? NULL : &E.row[filerow];\n\n        switch (key)\n        {\n        case ARROW_LEFT:\n            if (E.cx == 0)\n            {\n                if (E.coloff)\n                {\n                    E.coloff--;\n                }\n                else\n                {\n                    if (filerow > 0)\n                    {\n                        E.cy--;\n                        E.cx = E.row[filerow - 1].size;\n                        if (E.cx > E.screencols - 1)\n                        {\n                            E.coloff = E.cx - E.screencols + 1;\n                            E.cx = E.screencols - 1;\n                        }\n                    }\n                }\n            }\n            else\n            {\n                E.cx -= 1;\n            }\n            break;\n        case ARROW_RIGHT:\n            if (row && filecol < row->size)\n            {\n                if (E.cx == E.screencols - 1)\n                {\n                    E.coloff++;\n                }\n                else\n                {\n                    E.cx += 1;\n                }\n            }\n            else if (row && filecol == row->size)\n            {\n                E.cx = 0;\n                E.coloff = 0;\n                if (E.cy == E.screenrows - 1)\n                {\n                    E.rowoff++;\n                }\n                else\n                {\n                    E.cy += 1;\n                }\n            }\n            break;\n        case ARROW_UP:\n            if (E.cy == 0)\n            {\n                if (E.rowoff)\n                    E.rowoff--;\n            }\n            else\n            {\n                E.cy -= 1;\n            }\n            break;\n        case ARROW_DOWN:\n            if (filerow < E.numrows)\n            {\n                if (E.cy == E.screenrows - 1)\n                {\n                    E.rowoff++;\n                }\n                else\n                {\n                    E.cy += 1;\n                }\n            }\n            break;\n        }\n        /* Fix cx if the current line has not enough chars. */\n        filerow = E.rowoff + E.cy;\n        filecol = E.coloff + E.cx;\n        row = (filerow >= E.numrows) ? NULL : &E.row[filerow];\n        rowlen = row ? row->size : 0;\n        if (filecol > rowlen)\n        {\n            E.cx -= filecol - rowlen;\n            if (E.cx < 0)\n            {\n                E.coloff += E.cx;\n                E.cx = 0;\n            }\n        }\n    }\n\n/* Process events arriving from the standard input, which is, the user\n * is typing stuff on the terminal. */\n#define KILO_QUIT_TIMES 3\n    void editorProcessKeypress(int fd)\n    {\n        /* When the file is modified, requires Ctrl-q to be pressed N times\n         * before actually quitting. */\n        static int quit_times = KILO_QUIT_TIMES;\n\n        int c = editorReadKey(fd);\n        switch (c)\n        {\n        case ENTER: /* Enter */\n            editorInsertNewline();\n            break;\n        case CTRL_C: /* Ctrl-c */\n            /* We ignore ctrl-c, it can't be so simple to lose the changes\n             * to the edited file. */\n            break;\n        case CTRL_Q: /* Ctrl-q */\n            /* Quit if the file was already saved. */\n            if (E.dirty && quit_times)\n            {\n                editorSetStatusMessage(\"WARNING!!! File has unsaved changes. \"\n                                       \"Press Ctrl-Q %d more times to quit.\",\n                                       quit_times);\n                quit_times--;\n                return;\n            }\n            throw KiloException(0);\n            break;\n        case CTRL_S: /* Ctrl-s */\n            editorSave();\n            break;\n        case CTRL_F:\n            editorFind(fd);\n            break;\n        case BACKSPACE: /* Backspace */\n        case CTRL_H:    /* Ctrl-h */\n        case DEL_KEY:\n            editorDelChar();\n            break;\n        case PAGE_UP:\n        case PAGE_DOWN:\n            if (c == PAGE_UP && E.cy != 0)\n                E.cy = 0;\n            else if (c == PAGE_DOWN && E.cy != E.screenrows - 1)\n                E.cy = E.screenrows - 1;\n            {\n                int times = E.screenrows;\n                while (times--)\n                    editorMoveCursor(c == PAGE_UP ? ARROW_UP : ARROW_DOWN);\n            }\n            break;\n\n        case ARROW_UP:\n        case ARROW_DOWN:\n        case ARROW_LEFT:\n        case ARROW_RIGHT:\n            editorMoveCursor(c);\n            break;\n        case CTRL_L: /* ctrl+l, clear screen */\n            /* Just refresht the line as side effect. */\n            break;\n        case ESC:\n            /* Nothing to do for ESC in this mode. */\n            break;\n        default:\n            editorInsertChar(c);\n            break;\n        }\n\n        quit_times = KILO_QUIT_TIMES; /* Reset it to the original value. */\n    }\n\n    int editorFileWasModified(void)\n    {\n        return E.dirty;\n    }\n\n    void updateWindowSize(void)\n    {\n        if (getWindowSize(STDIN_FILENO, STDOUT_FILENO,\n                          &E.screenrows, &E.screencols) == -1)\n        {\n            perror(\"Unable to query the screen for size (columns / rows)\");\n            throw KiloException(1);\n        }\n        E.screenrows -= 2; /* Get room for status bar. */\n    }\n\n    void handleSigWinCh(int unused __attribute__((unused)))\n    {\n        updateWindowSize();\n        if (E.cy > E.screenrows)\n            E.cy = E.screenrows - 1;\n        if (E.cx > E.screencols)\n            E.cx = E.screencols - 1;\n        editorRefreshScreen();\n    }\n\n    void initEditor(void)\n    {\n        E.cx = 0;\n        E.cy = 0;\n        E.rowoff = 0;\n        E.coloff = 0;\n        E.numrows = 0;\n        E.row = NULL;\n        E.dirty = 0;\n        E.filename = NULL;\n        E.syntax = NULL;\n        updateWindowSize();\n        // signal(SIGWINCH, handleSigWinCh);\n    }\n\n    int kilo(int argc, char **argv)\n    {\n        if (argc != 2)\n        {\n            fprintf(stderr, \"Usage: edit <filename>\\n\");\n            return 1;\n        }\n\n        char filename[512];\n        ESP32Console::console_realpath(argv[1], filename);\n\n        try\n        {\n            initEditor();\n            editorSelectSyntaxHighlight(filename);\n            editorOpen(filename);\n            enableRawMode(fileno(stdin));\n            editorSetStatusMessage(\n                \"HELP: Ctrl-S = save | Ctrl-Q = quit | Ctrl-F = find\");\n            while (1)\n            {\n                editorRefreshScreen();\n                editorProcessKeypress(fileno(stdin));\n            }\n        }\n        catch (KiloException ex)\n        {\n            // Ignore exception\n        }\n\n        editorAtExit();\n\n        //Free some heap\n        for (int n = 0; n<E.numrows; n++)\n        {\n            Kilo::editorFreeRow(&E.row[n]);\n        }\n        free(E.row);\n\n\n\n        linenoiseClearScreen();\n\n        return 0;\n    }\n}\n#pragma GCC diagnostic pop"
  },
  {
    "path": "src/kilo/kilo.h",
    "content": "#pragma once\n\n#include \"ESP32Console/Helpers/PWDHelpers.h\"\n#include <exception>\n#include \"linenoise/linenoise.h\"\n\nnamespace ESP32Console::Kilo\n{\n    int kilo(int argc, char **argv);\n\n    struct KiloException : public std::exception\n    {\n    private:\n        int code_;\n\n    public:\n        KiloException(int code) : code_(code){};\n\n        const char *what() const throw()\n        {\n            return \"Kilo exited\";\n        }\n    };\n}"
  }
]