├── .github ├── FUNDING.yml ├── stale.yml └── workflows │ └── version.yml ├── .gitignore ├── LICENSE ├── README.md ├── examples ├── BoundlessCommand │ └── BoundlessCommand.ino ├── ESP8266WebCLI │ └── ESP8266WebCLI.ino ├── HelpCommand │ └── HelpCommand.ino ├── Ping │ └── Ping.ino ├── PingWithArguments │ └── PingWithArguments.ino ├── PingWithCallbacks │ └── PingWithCallbacks.ino ├── PingWithTemplates │ └── PingWithTemplates.ino ├── SingleArgumentCommand │ └── SingleArgumentCommand.ino └── VideoExample │ └── VideoExample.ino ├── img ├── cowsay.gif ├── ping.gif └── simplecli.gif ├── keywords.txt ├── library.json ├── library.properties └── src ├── Argument.cpp ├── Argument.h ├── Command.cpp ├── Command.h ├── CommandError.cpp ├── CommandError.h ├── SimpleCLI.cpp ├── SimpleCLI.h ├── StringCLI.h └── c ├── arg.c ├── arg.h ├── arg_types.h ├── cmd.c ├── cmd.h ├── cmd_error.c ├── cmd_error.h ├── cmd_error_types.h ├── cmd_types.h ├── comparator.c ├── comparator.h ├── parser.c ├── parser.h └── parser_types.h /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: spacehuhntech 4 | patreon: # 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: spacehuhn 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: ['spacehuhn.com/store/'] 13 | -------------------------------------------------------------------------------- /.github/stale.yml: -------------------------------------------------------------------------------- 1 | # Number of days of inactivity before an issue becomes stale 2 | daysUntilStale: 90 3 | # Number of days of inactivity before a stale issue is closed 4 | daysUntilClose: 7 5 | # Issues with these labels will never be considered stale 6 | exemptLabels: 7 | - pinned 8 | - bug 9 | - translation 10 | - feature request 11 | # Label to use when marking an issue as stale 12 | staleLabel: stale 13 | # Comment to post when marking an issue as stale. Set to `false` to disable 14 | markComment: > 15 | This issue has been automatically marked as stale because it has not had 16 | recent activity. It will be closed if no further activity occurs. Thank you 17 | for your contributions. 18 | # Comment to post when closing a stale issue. Set to `false` to disable 19 | closeComment: false 20 | -------------------------------------------------------------------------------- /.github/workflows/version.yml: -------------------------------------------------------------------------------- 1 | name: Check version numbers on release 2 | 3 | on: 4 | push: 5 | tags: 6 | - '*' 7 | 8 | jobs: 9 | version-check: 10 | name: "Check version number" 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v1 14 | 15 | - name: Print versions 16 | run: | 17 | echo "Release tag: $GITHUB_REF/refs\/tags\//}" 18 | echo "src/SimpleCLI.h: $(grep -E "SIMPLECLI_VERSION \"[0-9]?.[0-9]?.[0-9]?\"$" src/SimpleCLI.h | grep -oE "[0-9]?\.[0-9]?\.[0-9]?")" 19 | echo "library.json: $(grep -E "\"version\": \"[0-9]?.[0-9]?.[0-9]?\",$" library.json | grep -oE "[0-9]?\.[0-9]?\.[0-9]?")" 20 | echo "library.properties: $(grep -E "version=[0-9]?.[0-9]?.[0-9]?$" library.properties | grep -oE "[0-9]?\.[0-9]?\.[0-9]?")" 21 | 22 | # - name: Delete latest Release 23 | # if: env.RELEASE_VERSION != env.SOURCE_VERSION || env.RELEASE_VERSION != env.JSON_VERSION || env.RELEASE_VERSION != env.PROPERTIES_VERSION 24 | # uses: ame-yu/action-delete-latest-release@v2 25 | # with: 26 | # github_token: ${{ secrets.GITHUB_TOKEN }} 27 | 28 | - name: Fail job 29 | if: env.RELEASE_VERSION != env.SOURCE_VERSION || env.RELEASE_VERSION != env.JSON_VERSION || env.RELEASE_VERSION != env.PROPERTIES_VERSION 30 | run: exit 1 31 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Compiled Object files 5 | *.slo 6 | *.lo 7 | *.o 8 | *.obj 9 | 10 | # Precompiled Headers 11 | *.gch 12 | *.pch 13 | 14 | # Compiled Dynamic libraries 15 | *.so 16 | *.dylib 17 | *.dll 18 | 19 | # Fortran module files 20 | *.mod 21 | *.smod 22 | 23 | # Compiled Static libraries 24 | *.lai 25 | *.la 26 | *.a 27 | *.lib 28 | 29 | # Executables 30 | *.exe 31 | *.out 32 | *.app 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Stefan Kremser 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SimpleCLI 2 | 3 |

SimpleCLI Logo

4 | 5 |

6 | A Command Line Interface Library for Arduino!
7 | Add commands to your project without hassle.
8 |
9 | Ardu Badge for SimpleCLI Library
10 |
11 | Buy Me a Coffee at ko-fi.com 12 |

13 | 14 | ![Cowsay command example](img/cowsay.gif) 15 | 16 | ## Projects 17 | A list of projects that make use of this library: 18 | - [Control ESP32 with Command Line Interface Over the Internet](https://www.hackster.io/donowak/control-esp32-with-command-line-interface-over-the-internet-fa9634) 19 | - [WiFiDuck](https://github.com/spacehuhn/WiFiDuck) 20 | - [ESP8266 Deauther V3](https://github.com/SpacehuhnTech/esp8266_deauther/tree/v3) 21 | 22 | ## Overview 23 | 24 | - [About](#about) 25 | - [Supported Devices](#supported-devices) 26 | - [Installation](#installation) 27 | - [Usage](#usage) 28 | - [Examples](#examples) 29 | - [Include Library](#include-library) 30 | - [Create SimpleCLI instance](#create-simplecli-instance) 31 | - [Adding Commands](#adding-commands) 32 | - [Adding Commands with callback](#adding-commands-with-callback) 33 | - [Adding Arguments](#adding-arguments) 34 | - [Templates](#templates) 35 | - [Parsing Input](#parsing-input) 36 | - [Reacting on Commands](#reacting-on-commands) 37 | - [Reacting on Errors](#reacting-on-errors) 38 | - [Classes & Methods](#classes--methods) 39 | - [SimpleCLI](#simplecli) 40 | - [CommandType](#commandtype) 41 | - [Command](#command) 42 | - [CommandErrorType](#commanderrortype) 43 | - [CommandError](#commanderror) 44 | - [ArgumentType](#argumenttype) 45 | - [Argument](#argument) 46 | - [License](#license) 47 | 48 | ## About 49 | The goal of this library is to control your Arduino projects using commands similar to the Linux CLI. 50 | Because parsing and validating strings in C/C++ can be quite a pain, this library aims to simplify the process as much as possible. 51 | 52 | ## Supported Devices 53 | Strings take up a good amount of memory, so **it's strongly recommended to chose a development board with at least 32 KB RAM**. 54 | It doesn't make much sense to run this library on an Uno or Nano, because it will quickly take up a most of the resources. 55 | Here's a list of tested hardware (feel free to contribute by making a Pull-Request): 56 | 57 | | Chipset | Board(s) | Flash | RAM | Support | 58 | | ------- | -------- | ----- | --- | ------- | 59 | | ATtiny85 | Digispark | 8 KB | 512 Byte | No! (Does not compile C++11) | 60 | | ATmega328P | Arduino Nano, Arduino Uno | 32 KB | 2 KB | Works for small projects | 61 | | ATmega32u4 | Arduino Leonardo, Pro Micro | 32 KB | 2,560 Byte | Works for small projects | 62 | | ATSAMD21G18 | Arduino MKR WiFi 1010 | 256 KB | 32 KB | Yes! | 63 | | ATSAMD51G19 | Adafruit ItsyBitsy M4 Express | 512 KB | 192 KB | Yes! | 64 | | ESP8266 | NodeMCU, D1 Mini | 512 KB - 16 MB | 80 KB | Yes! | 65 | | ESP32 | DSTIKE D-duino-32 | 1 MB - 16 MB |520 KB | Yes! | 66 | 67 | *Some flash and RAM values depend on the development board or module being used.* 68 | 69 | ## Installation 70 | 71 | 1) Click [Download Zip](https://github.com/spacehuhn/SimpleCLI/archive/master.zip) to download the source code from GitHub. 72 | 2) Unzip and rename the Folder name to "SimpleCLI". 73 | 3) Paste it in your library folder (usually located somewhere at documents/Arduino/libraries). 74 | 4) Restart the Arduino IDE. 75 | 76 | ## Usage 77 | 78 | [![SimpleCLI YouTube Tutorial](https://img.youtube.com/vi/UyW-wICdnKo/0.jpg)](https://www.youtube.com/watch?v=UyW-wICdnKo) 79 | 80 | ### Examples 81 | 82 | Please check out the [example sketches](https://github.com/spacehuhn/SimpleCLI/tree/master/examples/), it's the quickest way to understand how this library works. 83 | The following sections are for reference. 84 | 85 | ![Ping with arguments command example](img/ping.gif) 86 | 87 | ### Include Library 88 | 89 | ```c++ 90 | #include 91 | ``` 92 | 93 | ### Create SimpleCLI instance 94 | 95 | ```c++ 96 | SimpleCLI cli; 97 | 98 | SimpleCLI cli(COMMAND_QUEUE_SIZE, ERROR_QUEUE_SIZE); 99 | ``` 100 | 101 | `COMMAND_QUEUE_SIZE` and `ERROR_QUEUE_SIZE` are `int`s set to 10 commands and 10 errors by default. 102 | The oldest command or error will be deleted automatically if the queue gets full. 103 | You can most likely ignore the queue sizes, as those are just a safety mechanism and won't be important for most use cases. 104 | 105 | ### Adding Commands 106 | 107 | Command names should only contain upper-, lowercase letters and numbers! 108 | Recommended are names with only lowercase letters and no numbers. 109 | 110 | ```c++ 111 | // Normal command with a defined number of arguments 112 | // For example: echo -str "Hello" -n 3 113 | Command myCommand = cli.addCommand("myCommandName"); 114 | Command myCommand = cli.addCmd("myCmdName"); 115 | 116 | // Single-Argument-Command that saves everything after the command name in the first argument 117 | // For example: echo this will be a single string -even with hyphen and in "quotes" 118 | // => "this will be a single string -even with hyphen and in "quotes\"" will be the argument value 119 | Command mySingleArgumentCommand = cli.addSingleArgumentCommand("mySingleArgumentCommandName"); 120 | Command mySingleArgCmd = cli.addSingleArgCmd("mySingleArgCmdName"); 121 | 122 | // Boundless-Command that accepts any amount of arguments separated by spaces 123 | // For example: sum 1 2 3 124 | // => "1", "2", "3" will the argument values 125 | Command myBoundlessCommand = cli.addBoundlessCommand("myBoundlessCommandName"); 126 | Command myBoundlessCmd = cli.addBoundlessCmd("myBoundlessCmdName"); 127 | ``` 128 | 129 | ### Adding Commands with callback 130 | 131 | Sometimes it's useful to give the command a callback function that will be executed automatically when the command was entered. 132 | You must define these callback functions as a global void function with a `cmd` pointer as shown here: 133 | ```c++ 134 | void myCallback(cmd* commandPointer) { 135 | Command cmd(commandPointer); // Create wrapper class instance for the pointer 136 | // .. 137 | } 138 | ``` 139 | 140 | Now you can create a command and pass it the function pointer: 141 | ```c++ 142 | Command myCommand = cli.addCommand("myCommandName", myCallback); 143 | Command myCommand = cli.addBoundlessCommand("myCommandName", myCallback); 144 | Command myCommand = cli.addSingleArgumentCommand("myCommandName", myCallback); 145 | 146 | Command myCommand = cli.addCmd("myCommandName", myCallback); 147 | Command myCommand = cli.addBoundlessCmd("myCommandName", myCallback); 148 | Command myCommand = cli.addSingleArgCmd("myCommandName", myCallback); 149 | ``` 150 | 151 | ### Adding Arguments 152 | 153 | Keep in mind that you can only add arguments to `Command`s and **not** to `SingleArgumentCommand`s and `BoundlessCommand`s. 154 | 155 | ```c++ 156 | // myCommandName -argumentName "argumentValue" 157 | Argument myArg = myCommand.addArgument("argumentName"); 158 | Argument myArg = myCommand.addArg("argumentName"); 159 | 160 | // Giving the argument a default value, means that the user does not have to specify the argument 161 | // myCommandName 162 | // myCommandName -argumentName "argumentValue" 163 | Argument myArg = myCommand.addArgument("argumentName", "DefaultValue"); 164 | Argument myArg = myCommand.addArg("argumentName", "DefaultValue"); 165 | 166 | 167 | // Positional arguments have a certain position and do not have to be named 168 | // myCommandName "argumentValue" 169 | // myCommandName -argumentName "argumentValue" 170 | Argument myArg = myCommand.addPositionalArgument("argumentName"); 171 | Argument myArg = myCommand.addPosArg("argumentName"); 172 | 173 | // Those can also have default values 174 | // myCommandName 175 | // myCommandName "argumentValue" 176 | // myCommandName -argumentName "argumentValue" 177 | Argument myArg = myCommand.addPositionalArgument("argumentName", "DefaultValue"); 178 | Argument myArg = myCommand.addPosArg("argumentName", "DefaultValue"); 179 | 180 | 181 | // Flag arguments can either be specified (set) or not, but they don't accept any value 182 | // myCommandName 183 | // myCommandName -argumentName 184 | Argument myArg = myCommand.addFlagArgument("argumentName"); 185 | Argument myArg = myCommand.addFlagArg("argumentName"); 186 | ```` 187 | 188 | ### Templates 189 | 190 | With this neat feature, you can give commands and arguments multiple names. 191 | - A comma (`,`) separates multiple names. 192 | - A forward slash (`/`) declares everything after it optional (until the next comma, or the end of the string). 193 | 194 | You can combine them together. 195 | 196 | **This means a command or argument name should not use `,` and `/` as a part of the regular name!** 197 | These characters will always be interpreted as a separator. 198 | 199 | Here are some examples: 200 | 201 | | Name-String | Results | 202 | | ----------- | ------- | 203 | | `a,b,c,d,efg` | `a`, `b`, `c`, `d`, `efg` | 204 | | `ping,pong,test` | `ping`, `pong`, `test` | 205 | | `p/ping` | `p`, `ping` | 206 | | `p/ing/s` | `p`, `ping`, `pings` | 207 | | `p/ing/s,pong` | `p`, `ping`, `pings`, `pong` | 208 | | `p/ing/s,pong/s` | `p`, `ping`, `pings`, `pong`, `pongs` | 209 | 210 | ### Parsing Input 211 | 212 | ```c++ 213 | // Inline 214 | cli.parse("myCommand"); 215 | 216 | // From string 217 | String input = "myCommand"; 218 | cli.parse(input); 219 | 220 | // From serial 221 | String input = Serial.readString(); 222 | cli.parse(input); 223 | ``` 224 | 225 | ### Reacting on Commands 226 | 227 | Be aware that this is only necessary if you have commands that do not have a callback function. 228 | Callbacks will be run automatically and the command will not wait in the queue. 229 | ```c++ 230 | // First check if a newly parsed command is available 231 | if(cli.available()) { 232 | 233 | // Get the command out of the queue 234 | Command cmd = cli.getCommand(); 235 | 236 | // Check if it's the command you're looking for 237 | if(cmd == myCommand) { 238 | 239 | // Get the Argument(s) you want 240 | Argument myArgument = cmd.getArgument("argumentName"); // via name 241 | Argument myOtherArgument = cmd.getArgument(2); // via index 242 | 243 | // Do stuff 244 | // ... 245 | } 246 | 247 | } 248 | ``` 249 | 250 | ### Reacting on Errors 251 | 252 | ```c++ 253 | // Check if a new error occurred 254 | if(cli.errored()) { 255 | CommandError e = cli.getError(); 256 | 257 | // Print the error, or do whatever you want with it 258 | Serial.println(e.toString()); 259 | } 260 | ``` 261 | 262 | You can also make a error callback function, like this one: 263 | 264 | ```c++ 265 | void errorCallback(cmd_error* e) { 266 | CommandError cmdError(e); // Create wrapper object 267 | 268 | // Print error 269 | Serial.print("ERROR: "); 270 | Serial.println(cmdError.toString()); 271 | 272 | // Print command usage 273 | if (cmdError.hasCommand()) { 274 | Serial.print("Did you mean \""); 275 | Serial.print(cmdError.getCommand().toString()); 276 | Serial.println("\"?"); 277 | } 278 | } 279 | ``` 280 | 281 | Just don't forget to add the error callback function to the SimpleCLI instance: 282 | 283 | ```c++ 284 | cli.setOnError(errorCallback); 285 | ``` 286 | 287 | ## Classes & Methods 288 | 289 | Here is a plain overview of all classes and their methods: 290 | 291 | ### SimpleCLI 292 | 293 | ```c++ 294 | SimpleCLI(int commandQueueSize = 10, int errorQueueSize = 10); 295 | 296 | void pause(); 297 | void unpause(); 298 | 299 | void parse(String& input); 300 | void parse(const char* input); 301 | void parse(const char* input, size_t input_len); 302 | 303 | bool available() const; 304 | bool errored() const; 305 | bool paused() const; 306 | 307 | int countCmdQueue() const; 308 | int countErrorQueue() const; 309 | 310 | Command getCmd(); 311 | Command getCmd(String name); 312 | Command getCmd(const char* name); 313 | 314 | Command getCommand(); 315 | Command getCommand(String name); 316 | Command getCommand(const char* name); 317 | 318 | CommandError getError(); 319 | 320 | Command addCmd(const char* name, void (* callback)(cmd* c) = NULL); 321 | Command addBoundlessCmd(const char* name, void (* callback)(cmd* c) = NULL); 322 | Command addSingleArgCmd(const char* name, void (* callback)(cmd* c) = NULL); 323 | 324 | Command addCommand(const char* name, void (* callback)(cmd* c) = NULL); 325 | Command addBoundlessCommand(const char* name, void (* callback)(cmd* c) = NULL); 326 | Command addSingleArgumentCommand(const char* name, void (* callback)(cmd* c) = NULL); 327 | 328 | String toString(bool descriptions = true) const; 329 | void toString(String& s, bool descriptions = true) const; 330 | 331 | void setCaseSensetive(bool caseSensetive = true); 332 | void setOnError(void (* onError)(cmd_error* e)); 333 | ``` 334 | 335 | ### CommandType 336 | 337 | ```c++ 338 | enum class CommandType { NORMAL, BOUNDLESS, SINGLE }; 339 | ``` 340 | 341 | ### Command 342 | 343 | ```c++ 344 | Command(cmd* cmdPointer = NULL, bool persistent = COMMAND_PERSISTENT); 345 | Command(const Command& c); 346 | Command(Command&& c); 347 | 348 | Command& operator=(const Command& c); 349 | Command& operator=(Command&& c); 350 | 351 | bool operator==(const Command& c) const; 352 | bool operator!=(const Command& c) const; 353 | 354 | operator bool() const; 355 | 356 | bool setCaseSensetive(bool caseSensetive = true); 357 | bool setCallback(void (* callback)(cmd* c)); 358 | 359 | void setDescription(const char* description); 360 | 361 | Argument addArg(const char* name, const char* defaultValue); 362 | Argument addArg(const char* name); 363 | Argument addPosArg(const char* name, const char* defaultValue); 364 | Argument addPosArg(const char* name); 365 | Argument addFlagArg(const char* name, const char* defaultValue = ""); 366 | 367 | Argument addArgument(const char* name, const char* defaultValue); 368 | Argument addArgument(const char* name); 369 | Argument addPositionalArgument(const char* name, const char* defaultValue); 370 | Argument addPositionalArgument(const char* name); 371 | Argument addFlagArgument(const char* name, const char* defaultValue = ""); 372 | 373 | bool equals(String name) const; 374 | bool equals(const char* name) const; 375 | bool equals(const Command& c) const; 376 | 377 | String getName() const; 378 | int countArgs() const; 379 | 380 | Argument getArgument(int i = 0) const; 381 | Argument getArgument(const char* name) const; 382 | Argument getArgument(String name) const; 383 | Argument getArgument(const Argument& a) const; 384 | 385 | Argument getArg(int i = 0) const; 386 | Argument getArg(const char* name) const; 387 | Argument getArg(String name) const; 388 | Argument getArg(const Argument& a) const; 389 | 390 | CommandType getType() const; 391 | 392 | bool hasDescription() const; 393 | String getDescription() const; 394 | 395 | String toString(bool description = true) const; 396 | void toString(String& s, bool description = true) const; 397 | 398 | void run() const; 399 | 400 | cmd* getPtr(); 401 | ``` 402 | 403 | ### CommandErrorType 404 | 405 | ```c++ 406 | enum class CommandErrorType { NULL_POINTER, EMPTY_LINE, PARSE_SUCCESSFUL, 407 | COMMAND_NOT_FOUND, UNKNOWN_ARGUMENT, MISSING_ARGUMENT, 408 | MISSING_ARGUMENT_VALUE, UNCLOSED_QUOTE }; 409 | ``` 410 | 411 | ### CommandError 412 | 413 | ```c++ 414 | CommandError(cmd_error* errorPointer = NULL, bool persistent = COMMAND_ERROR_PERSISTENT); 415 | CommandError(const CommandError& e); 416 | CommandError(CommandError&& e); 417 | 418 | CommandError& operator=(const CommandError& e); 419 | CommandError& operator=(CommandError&& e); 420 | 421 | bool operator==(const CommandError& e) const; 422 | bool operator!=(const CommandError& e) const; 423 | 424 | bool operator>(const CommandError& e) const; 425 | bool operator<(const CommandError& e) const; 426 | 427 | bool operator>=(const CommandError& e) const; 428 | bool operator<=(const CommandError& e) const; 429 | 430 | operator bool() const; 431 | 432 | bool hasCommand() const; 433 | bool hasArgument() const; 434 | bool hasData() const; 435 | 436 | bool hasCmd() const; 437 | bool hasArg() const; 438 | 439 | CommandErrorType getType() const; 440 | Command getCommand() const; 441 | Argument getArgument() const; 442 | String getData() const; 443 | String getMessage() const; 444 | 445 | Command getCmd() const; 446 | Argument getArg() const; 447 | String getMsg() const; 448 | 449 | String toString() const; 450 | void toString(String& s) const; 451 | 452 | cmd_error* getPtr(); 453 | ``` 454 | 455 | ### ArgumentType 456 | 457 | ```c++ 458 | enum class ArgumentType { NORMAL, POSITIONAL, FLAG }; 459 | ``` 460 | 461 | ### Argument 462 | 463 | ```c++ 464 | Argument(arg* argPointer = NULL, bool persistent = ARGUMENT_PERSISTENT); 465 | Argument(const Argument& a); 466 | Argument(Argument&& a); 467 | 468 | Argument& operator=(const Argument& a); 469 | Argument& operator=(Argument&& a); 470 | 471 | bool operator==(const Argument& a) const; 472 | bool operator!=(const Argument& a) const; 473 | 474 | operator bool() const; 475 | 476 | bool isSet() const; 477 | bool isRequired() const; 478 | bool isOptional() const; 479 | bool hasDefaultValue() const; 480 | 481 | bool isReq() const; 482 | bool isOpt() const; 483 | 484 | String getName() const; 485 | String getValue() const; 486 | 487 | ArgumentType getType() const; 488 | 489 | String toString() const; 490 | void toString(String& s) const; 491 | 492 | bool equals(String name, bool caseSensetive = false) const; 493 | bool equals(const char* name, bool caseSensetive = false) const; 494 | bool equals(const Argument& a, bool caseSensetive = false) const; 495 | 496 | arg* getPtr(); 497 | ``` 498 | 499 | ## License 500 | 501 | This software is licensed under the MIT License. See the [license file](LICENSE) for details. 502 | -------------------------------------------------------------------------------- /examples/BoundlessCommand/BoundlessCommand.ino: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019 Stefan Kremser 3 | This software is licensed under the MIT License. See the license file for details. 4 | Source: github.com/spacehuhn/SimpleCLI 5 | */ 6 | 7 | // Include Library 8 | #include 9 | 10 | // Create CLI Object 11 | SimpleCLI cli; 12 | 13 | // Commands 14 | Command sum; 15 | 16 | // Callback function for sum command 17 | void sumCallback(cmd* c) { 18 | Command cmd(c); // Create wrapper object 19 | 20 | int argNum = cmd.countArgs(); // Get number of arguments 21 | int sum = 0; 22 | 23 | // Go through all arguments and add their value up 24 | for (int i = 0; i 0) { 30 | if (i > 0) Serial.print('+'); 31 | Serial.print(argIntValue); 32 | 33 | sum += argIntValue; 34 | } 35 | } 36 | 37 | // Print result 38 | Serial.print(" = "); 39 | Serial.println(sum); 40 | } 41 | 42 | // Callback in case of an error 43 | void errorCallback(cmd_error* e) { 44 | CommandError cmdError(e); // Create wrapper object 45 | 46 | Serial.print("ERROR: "); 47 | Serial.println(cmdError.toString()); 48 | 49 | if (cmdError.hasCommand()) { 50 | Serial.print("Did you mean \""); 51 | Serial.print(cmdError.getCommand().toString()); 52 | Serial.println("\"?"); 53 | } 54 | } 55 | 56 | void setup() { 57 | // Startup stuff 58 | Serial.begin(9600); 59 | delay(2000); 60 | Serial.println("Started!"); 61 | 62 | cli.setOnError(errorCallback); // Set error Callback 63 | 64 | 65 | // Create the sum command, accepting infinite number of arguments 66 | // Each argument is seperated by a space 67 | // For example: sum 1 2 3 68 | sum = cli.addBoundlessCommand("sum", sumCallback); 69 | 70 | 71 | Serial.println("Type: sum 1 2 3"); 72 | } 73 | 74 | void loop() { 75 | // Check if user typed something into the serial monitor 76 | if (Serial.available()) { 77 | // Read out string from the serial monitor 78 | String input = Serial.readStringUntil('\n'); 79 | 80 | Serial.print("# "); 81 | Serial.println(input); 82 | 83 | // Parse the user input into the CLI 84 | cli.parse(input); 85 | } 86 | 87 | if (cli.errored()) { 88 | CommandError cmdError = cli.getError(); 89 | 90 | Serial.print("ERROR: "); 91 | Serial.println(cmdError.toString()); 92 | 93 | if (cmdError.hasCommand()) { 94 | Serial.print("Did you mean \""); 95 | Serial.print(cmdError.getCommand().toString()); 96 | Serial.println("\"?"); 97 | } 98 | } 99 | } -------------------------------------------------------------------------------- /examples/ESP8266WebCLI/ESP8266WebCLI.ino: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019 Stefan Kremser 3 | This software is licensed under the MIT License. See the license file for details. 4 | Source: github.com/spacehuhn/SimpleCLI 5 | 6 | This example is based on the HelloServer example for the ESP8266. 7 | You connect to the Access-Point, go to 192.168.4.1 and type in "led on" or "led off". 8 | The parsing magic is happening in handleRoot(). 9 | */ 10 | 11 | #include 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | #ifndef STASSID 19 | #define STASSID "your-ssid" 20 | #define STAPSK "your-password" 21 | #endif // ifndef STASSID 22 | 23 | const char* ssid = STASSID; 24 | const char* password = STAPSK; 25 | 26 | ESP8266WebServer server(80); 27 | SimpleCLI cli; 28 | 29 | String answer; 30 | Command cmdLed; 31 | 32 | void handleRoot() { 33 | // If data was sent 34 | if (server.args() > 0) { 35 | // Echo the input on the serial interface 36 | Serial.print("# "); 37 | Serial.println(server.arg("cmd")); 38 | 39 | // Parse input 40 | cli.parse(server.arg("cmd")); 41 | 42 | // Check for commands 43 | if (cli.available()) { 44 | Command cmd = cli.getCommand(); 45 | 46 | if (cmd == cmdLed) { 47 | String mode = cmd.getArgument("mode").getValue(); 48 | 49 | if (mode == "on") { 50 | pinMode(2, HIGH); 51 | answer = "turned LED on"; 52 | } else if (mode == "off") { 53 | pinMode(2, LOW); 54 | answer = "turned LED off"; 55 | } 56 | } 57 | } 58 | 59 | // Check for errors 60 | if (cli.errored()) { 61 | answer = cli.getError().toString(); 62 | } 63 | } 64 | 65 | // Build HTML string 66 | String html = 67 | "" 68 | "" 69 | "" 70 | "" 71 | "SimpleCLI" 72 | "" 73 | "" 74 | "

"; 75 | 76 | html += answer; 77 | 78 | html += 79 | "

" 80 | "
" 81 | "" 82 | "" 83 | "
" 84 | "" 85 | ""; 86 | 87 | // Send HTML to user 88 | server.send(200, "text/html", html.c_str()); 89 | } 90 | 91 | void handleNotFound() { 92 | String message = "File Not Found\r\n\r\n"; 93 | 94 | message += "URI: "; 95 | message += server.uri(); 96 | message += "\r\nMethod: "; 97 | message += (server.method() == HTTP_GET) ? "GET" : "POST"; 98 | message += "\r\nArguments: "; 99 | message += server.args(); 100 | message += "\r\n"; 101 | 102 | for (uint8_t i = 0; i < server.args(); i++) { 103 | message += " " + server.argName(i) + ": " + server.arg(i) + "\r\n"; 104 | } 105 | server.send(404, "text/plain", message); 106 | } 107 | 108 | void setup(void) { 109 | Serial.begin(115200); 110 | WiFi.softAP(ssid, password); 111 | 112 | IPAddress myIP = WiFi.softAPIP(); 113 | Serial.print("AP IP address: "); 114 | Serial.println(myIP); 115 | server.on("/", handleRoot); 116 | server.begin(); 117 | Serial.println("HTTP server started"); 118 | 119 | if (MDNS.begin("esp8266")) { 120 | Serial.println("MDNS responder started"); 121 | } 122 | 123 | server.on("/", handleRoot); 124 | server.on("/index.html", handleRoot); 125 | 126 | server.on("/inline", []() { 127 | server.send(200, "text/plain", "this works as well"); 128 | }); 129 | 130 | server.onNotFound(handleNotFound); 131 | 132 | cmdLed = cli.addCommand("led"); 133 | cmdLed.addPositionalArgument("mode", "on"); 134 | 135 | server.begin(); 136 | Serial.println("HTTP server started"); 137 | } 138 | 139 | void loop(void) { 140 | server.handleClient(); 141 | MDNS.update(); 142 | } -------------------------------------------------------------------------------- /examples/HelpCommand/HelpCommand.ino: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019 Stefan Kremser 3 | This software is licensed under the MIT License. See the license file for details. 4 | Source: github.com/spacehuhn/SimpleCLI 5 | */ 6 | 7 | /* 8 | This is an example with all types of arguments and commands, error check and help command. 9 | No callbacks are used here, you can check the callback example for that. 10 | 11 | PLEASE NOTE: 115200 is the baud rate and Newline is enabled in the serial monitor 12 | */ 13 | 14 | // Include Library 15 | #include 16 | 17 | // Create CLI Object 18 | SimpleCLI cli; 19 | 20 | // Commands 21 | Command cmdPing; 22 | Command cmdMycommand; 23 | Command cmdEcho; 24 | Command cmdRm; 25 | Command cmdLs; 26 | Command cmdBoundless; 27 | Command cmdSingle; 28 | Command cmdHelp; 29 | 30 | void setup() { 31 | Serial.begin(9600); 32 | Serial.println("Hello World!"); 33 | 34 | cmdPing = cli.addCmd("ping"); 35 | cmdPing.addArg("n", "10"); 36 | cmdPing.setDescription(" Responds with a ping n-times"); 37 | 38 | cmdMycommand = cli.addCmd("mycommand"); 39 | cmdMycommand.addArg("o"); 40 | cmdMycommand.setDescription(" Says hi to o"); 41 | 42 | cmdEcho = cli.addCmd("echo"); 43 | cmdEcho.addPosArg("text", "something"); 44 | cmdEcho.setDescription(" Echos what you said"); 45 | 46 | cmdRm = cli.addCmd("rm"); 47 | cmdRm.addPosArg("file"); 48 | cmdRm.setDescription(" Removes specified file (but not actually)"); 49 | 50 | cmdLs = cli.addCmd("ls"); 51 | cmdLs.addFlagArg("a"); 52 | cmdLs.setDescription(" Lists files in directory (-a for all)"); 53 | 54 | cmdBoundless = cli.addBoundlessCmd("boundless"); 55 | cmdBoundless.setDescription(" A boundless command that echos your input"); 56 | 57 | cmdSingle = cli.addSingleArgCmd("single"); 58 | cmdSingle.setDescription(" A single command that echos your input"); 59 | 60 | cmdHelp = cli.addCommand("help"); 61 | cmdHelp.setDescription(" Get help!"); 62 | 63 | Serial.println("Started!"); 64 | } 65 | 66 | void loop() { 67 | if (Serial.available()) { 68 | String input = Serial.readStringUntil('\n'); 69 | 70 | if (input.length() > 0) { 71 | Serial.print("# "); 72 | Serial.println(input); 73 | 74 | cli.parse(input); 75 | } 76 | } 77 | 78 | if (cli.available()) { 79 | Command c = cli.getCmd(); 80 | 81 | int argNum = c.countArgs(); 82 | 83 | Serial.print("> "); 84 | Serial.print(c.getName()); 85 | Serial.print(' '); 86 | 87 | for (int i = 0; i0) Serial.print(","); 121 | Serial.print("\""); 122 | Serial.print(arg.getValue()); 123 | Serial.print("\""); 124 | } 125 | } else if (c == cmdSingle) { 126 | Serial.println("Single \"" + c.getArg(0).getValue() + "\""); 127 | } else if (c == cmdHelp) { 128 | Serial.println("Help:"); 129 | Serial.println(cli.toString()); 130 | } 131 | } 132 | 133 | if (cli.errored()) { 134 | CommandError cmdError = cli.getError(); 135 | 136 | Serial.print("ERROR: "); 137 | Serial.println(cmdError.toString()); 138 | 139 | if (cmdError.hasCommand()) { 140 | Serial.print("Did you mean \""); 141 | Serial.print(cmdError.getCommand().toString()); 142 | Serial.println("\"?"); 143 | } 144 | } 145 | } -------------------------------------------------------------------------------- /examples/Ping/Ping.ino: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019 Stefan Kremser 3 | This software is licensed under the MIT License. See the license file for details. 4 | Source: github.com/spacehuhn/SimpleCLI 5 | */ 6 | 7 | // Include Library 8 | #include 9 | 10 | // Create CLI Object 11 | SimpleCLI cli; 12 | 13 | // Commands 14 | Command ping; 15 | 16 | void setup() { 17 | // Enable Serial connection at given baud rate 18 | Serial.begin(9600); 19 | 20 | // Wait a bit until device is started 21 | delay(2000); 22 | 23 | // Print something to let us know that everything is working so far 24 | Serial.println("Hello World!"); 25 | 26 | // Create the ping command 27 | ping = cli.addCmd("ping"); 28 | 29 | // [Optional] Check if our command was successfully added 30 | if (!ping) { 31 | Serial.println("Something went wrong :("); 32 | } else { 33 | Serial.println("Ping was added to the CLI!"); 34 | } 35 | 36 | // Start the loop 37 | Serial.println("Type ping to see what happens!"); 38 | } 39 | 40 | void loop() { 41 | // Check if user typed something into the serial monitor 42 | if (Serial.available()) { 43 | // Read out string from the serial monitor 44 | String input = Serial.readStringUntil('\n'); 45 | 46 | // Echo the user input 47 | Serial.print("# "); 48 | Serial.println(input); 49 | 50 | // Parse the user input into the CLI 51 | cli.parse(input); 52 | } 53 | 54 | // Check for newly parsed commands 55 | if (cli.available()) { 56 | // Get command out of queue 57 | Command cmd = cli.getCmd(); 58 | 59 | // React on our ping command 60 | if (cmd == ping) { 61 | Serial.println("Pong!"); 62 | } 63 | } 64 | 65 | // Check for parsing errors 66 | if (cli.errored()) { 67 | // Get error out of queue 68 | CommandError cmdError = cli.getError(); 69 | 70 | // Print the error 71 | Serial.print("ERROR: "); 72 | Serial.println(cmdError.toString()); 73 | 74 | // Print correct command structure 75 | if (cmdError.hasCommand()) { 76 | Serial.print("Did you mean \""); 77 | Serial.print(cmdError.getCommand().toString()); 78 | Serial.println("\"?"); 79 | } 80 | } 81 | } -------------------------------------------------------------------------------- /examples/PingWithArguments/PingWithArguments.ino: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019 Stefan Kremser 3 | This software is licensed under the MIT License. See the license file for details. 4 | Source: github.com/spacehuhn/SimpleCLI 5 | */ 6 | 7 | // Include Library 8 | #include 9 | 10 | // Create CLI Object 11 | SimpleCLI cli; 12 | 13 | // Commands 14 | Command ping; 15 | 16 | // Callback function for ping command 17 | void pingCallback(cmd* c) { 18 | Command cmd(c); // Create wrapper object 19 | 20 | // Get arguments 21 | Argument numberArg = cmd.getArgument("number"); 22 | Argument strArg = cmd.getArgument("str"); 23 | Argument cArg = cmd.getArgument("c"); 24 | 25 | // Get values 26 | int numberValue = numberArg.getValue().toInt(); 27 | String strValue = strArg.getValue(); 28 | bool cValue = cArg.isSet(); 29 | 30 | if (cValue) strValue.toUpperCase(); 31 | 32 | // Print response 33 | for (int i = 0; i 9 | 10 | // Create CLI Object 11 | SimpleCLI cli; 12 | 13 | // Commands 14 | Command ping; 15 | 16 | // Callback function for ping command 17 | void pingCallback(cmd* c) { 18 | Command cmd(c); // Create wrapper object 19 | 20 | Serial.println("Pong!"); 21 | } 22 | 23 | // Callback in case of an error 24 | void errorCallback(cmd_error* e) { 25 | CommandError cmdError(e); // Create wrapper object 26 | 27 | Serial.print("ERROR: "); 28 | Serial.println(cmdError.toString()); 29 | 30 | if (cmdError.hasCommand()) { 31 | Serial.print("Did you mean \""); 32 | Serial.print(cmdError.getCommand().toString()); 33 | Serial.println("\"?"); 34 | } 35 | } 36 | 37 | void setup() { 38 | // Enable Serial connection at given baud rate 39 | Serial.begin(9600); 40 | 41 | // Wait a bit until device is started 42 | delay(2000); 43 | 44 | // Print something to let us know that everything is working so far 45 | Serial.println("Hello World!"); 46 | 47 | // Create the ping command with callback function 48 | ping = cli.addCmd("ping", pingCallback); 49 | 50 | // [Optional] Check if our command was successfully added 51 | if (!ping) { 52 | Serial.println("Something went wrong :("); 53 | } else { 54 | Serial.println("Ping was added to the CLI!"); 55 | } 56 | 57 | // Set error Callback 58 | cli.setOnError(errorCallback); 59 | 60 | // Start the loop 61 | Serial.println("Type ping to see what happens!"); 62 | } 63 | 64 | void loop() { 65 | // Check if user typed something into the serial monitor 66 | if (Serial.available()) { 67 | // Read out string from the serial monitor 68 | String input = Serial.readStringUntil('\n'); 69 | 70 | // Echo the user input 71 | Serial.print("# "); 72 | Serial.println(input); 73 | 74 | // Parse the user input into the CLI 75 | cli.parse(input); 76 | } 77 | } -------------------------------------------------------------------------------- /examples/PingWithTemplates/PingWithTemplates.ino: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019 Stefan Kremser 3 | This software is licensed under the MIT License. See the license file for details. 4 | Source: github.com/spacehuhn/SimpleCLI 5 | */ 6 | 7 | // Include Library 8 | #include 9 | 10 | // Create CLI Object 11 | SimpleCLI cli; 12 | 13 | // Commands 14 | Command ping; 15 | 16 | // Callback function for ping command 17 | void pingCallback(cmd* c) { 18 | Command cmd(c); // Create wrapper object 19 | 20 | // Get arguments 21 | Argument numberArg = cmd.getArgument("number"); 22 | Argument strArg = cmd.getArgument("str"); 23 | 24 | // Get values 25 | int numberValue = numberArg.getValue().toInt(); 26 | String strValue = strArg.getValue(); 27 | 28 | // Print response 29 | for (int i = 0; i 9 | 10 | // Create CLI Object 11 | SimpleCLI cli; 12 | 13 | // Commands 14 | Command cowsay; 15 | 16 | // Callback function for cowsay command 17 | void cowsayCallback(cmd* c) { 18 | Command cmd(c); // Create wrapper object 19 | 20 | // Get first (and only) Argument 21 | Argument arg = cmd.getArgument(0); 22 | 23 | // Get value of argument 24 | String argVal = arg.getValue(); 25 | 26 | // Print Value 27 | Serial.print(' '); 28 | 29 | for (int i = 0; i"); 35 | 36 | Serial.print(' '); 37 | 38 | for (int i = 0; i 10 | 11 | SimpleCLI cli; 12 | 13 | Command cmdPing; 14 | Command cmdPong; 15 | 16 | void errorCallback(cmd_error* errorPtr) { 17 | CommandError e(errorPtr); 18 | 19 | Serial.println("ERROR: " + e.toString()); 20 | 21 | if (e.hasCommand()) { 22 | Serial.println("Did you mean? " + e.getCommand().toString()); 23 | } else { 24 | Serial.println(cli.toString()); 25 | } 26 | } 27 | 28 | void pongCallback(cmd* cmdPtr) { 29 | Command cmd(cmdPtr); 30 | 31 | int argNum = cmd.countArgs(); 32 | 33 | for (int i = 0; i < argNum; i++) { 34 | Serial.println(cmd.getArgument(i).getValue()); 35 | } 36 | } 37 | 38 | void pingCallback(cmd* cmdPtr) { 39 | Command cmd(cmdPtr); 40 | 41 | Argument argN = cmd.getArgument("num"); 42 | String argVal = argN.getValue(); 43 | int n = argVal.toInt(); 44 | 45 | Argument argStr = cmd.getArgument("str"); 46 | String strVal = argStr.getValue(); 47 | 48 | Argument argC = cmd.getArgument("c"); 49 | bool c = argC.isSet(); 50 | 51 | if (c) strVal.toUpperCase(); 52 | 53 | for (int i = 0; i < n; i++) { 54 | Serial.println(strVal); 55 | } 56 | } 57 | 58 | void setup() { 59 | Serial.begin(9600); 60 | Serial.println("Hello World"); 61 | 62 | cmdPing = cli.addCommand("ping", pingCallback); 63 | cmdPing.addPositionalArgument("str", "pong"); 64 | cmdPing.addArgument("n/um/ber,anzahl", "1"); 65 | cmdPing.addFlagArgument("c"); 66 | 67 | cmdPong = cli.addBoundlessCommand("pong,hello", pongCallback); 68 | 69 | cli.setOnError(errorCallback); 70 | } 71 | 72 | void loop() { 73 | if (Serial.available()) { 74 | String input = Serial.readStringUntil('\n'); 75 | Serial.println("# " + input); 76 | 77 | cli.parse(input); 78 | } 79 | } -------------------------------------------------------------------------------- /img/cowsay.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SpacehuhnTech/SimpleCLI/37d6e51d1447c1270c71d894ae45a5e92b52f330/img/cowsay.gif -------------------------------------------------------------------------------- /img/ping.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SpacehuhnTech/SimpleCLI/37d6e51d1447c1270c71d894ae45a5e92b52f330/img/ping.gif -------------------------------------------------------------------------------- /img/simplecli.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SpacehuhnTech/SimpleCLI/37d6e51d1447c1270c71d894ae45a5e92b52f330/img/simplecli.gif -------------------------------------------------------------------------------- /keywords.txt: -------------------------------------------------------------------------------- 1 | ####################################### 2 | # Syntax Coloring Map For SimpleCLI 3 | ####################################### 4 | 5 | ####################################### 6 | # Datatypes (KEYWORD1) 7 | ####################################### 8 | 9 | SimpleCLI KEYWORD1 10 | Command KEYWORD1 11 | CommandError KEYWORD1 12 | Argument KEYWORD1 13 | ArgumentType KEYWORD1 14 | CommandType KEYWORD1 15 | CommandErrorType KEYWORD1 16 | 17 | ####################################### 18 | # Methods and Functions (KEYWORD2) 19 | ####################################### 20 | 21 | # SimpleCLI 22 | parse KEYWORD2 23 | errored KEYWORD2 24 | countCmdQueue KEYWORD2 25 | countErrorQueue KEYWORD2 26 | getCmd KEYWORD2 27 | getCommand KEYWORD2 28 | getError KEYWORD2 29 | addCmd KEYWORD2 30 | addBoundlessCmd KEYWORD2 31 | addSingleArgCmd KEYWORD2 32 | addCommand KEYWORD2 33 | addBoundlessCommand KEYWORD2 34 | addSingleArgumentCommand KEYWORD2 35 | toString KEYWORD2 36 | setCaseSensetive KEYWORD2 37 | setCaseSensitive KEYWORD2 38 | setOnError KEYWORD2 39 | 40 | # CommandError 41 | CommandError KEYWORD2 42 | hasCommand KEYWORD2 43 | hasArgument KEYWORD2 44 | hasData KEYWORD2 45 | hasCmd KEYWORD2 46 | hasArg KEYWORD2 47 | getType KEYWORD2 48 | getArgument KEYWORD2 49 | getData KEYWORD2 50 | getMessage KEYWORD2 51 | getCmd KEYWORD2 52 | getArg KEYWORD2 53 | getMsg KEYWORD2 54 | 55 | # Command 56 | setCallback KEYWORD2 57 | addArg KEYWORD2 58 | addPosArg KEYWORD2 59 | addFlagArg KEYWORD2 60 | addArgument KEYWORD2 61 | addPositionalArgument KEYWORD2 62 | addFlagArgument KEYWORD2 63 | getName KEYWORD2 64 | countArgs KEYWORD2 65 | getArg KEYWORD2 66 | run KEYWORD2 67 | 68 | # Argument 69 | isSet KEYWORD2 70 | isRequired KEYWORD2 71 | isOptional KEYWORD2 72 | hasDefaultValue KEYWORD2 73 | isReq KEYWORD2 74 | isOpt KEYWORD2 75 | getName KEYWORD2 76 | getValue KEYWORD2 77 | 78 | ####################################### 79 | # Instances (KEYWORD2) 80 | ####################################### 81 | 82 | ####################################### 83 | # Constants (LITERAL1) 84 | ####################################### 85 | 86 | # Command 87 | NORMAL LITERAL1 88 | BOUNDLESS LITERAL1 89 | SINGLE LITERAL1 90 | 91 | # CommandError 92 | NULL_POINTER LITERAL1 93 | EMPTY_LINE LITERAL1 94 | PARSE_SUCCESSFUL LITERAL1 95 | COMMAND_NOT_FOUND LITERAL1 96 | UNKNOWN_ARGUMENT LITERAL1 97 | MISSING_ARGUMENT LITERAL1 98 | MISSING_ARGUMENT_VALUE LITERAL1 99 | UNCLOSED_QUOTE LITERAL1 100 | 101 | # Argument 102 | POSITIONAL LITERAL1 103 | FLAG LITERAL1 104 | -------------------------------------------------------------------------------- /library.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "SimpleCLI", 3 | "version": "1.1.4", 4 | "keywords": "cli, parser, command, line, interface", 5 | "description": "Add a command line interface to your project the easy way", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/spacehuhntech/SimpleCLI.git" 9 | }, 10 | "authors": [{ 11 | "name": "Spacehuhn Technologies", 12 | "email": "arduinolib@spacehuhn.com", 13 | "url": "https://spacehuhn.com" 14 | }], 15 | "frameworks": "arduino", 16 | "platforms": "*", 17 | "headers": "SimpleCLI.h" 18 | } 19 | -------------------------------------------------------------------------------- /library.properties: -------------------------------------------------------------------------------- 1 | name=SimpleCLI 2 | version=1.1.4 3 | author=Spacehuhn 4 | maintainer=Spacehuhn Technologies 5 | sentence=A Command Line Interface Library for Arduino 6 | paragraph=Add a command line interface to your project the easy way 7 | category=Data Processing 8 | url=https://github.com/spacehuhntech/SimpleCLI 9 | architectures=* 10 | includes=SimpleCLI.h 11 | -------------------------------------------------------------------------------- /src/Argument.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019 Stefan Kremser 3 | This software is licensed under the MIT License. See the license file for details. 4 | Source: github.com/spacehuhn/SimpleCLI 5 | */ 6 | 7 | #include "Argument.h" 8 | 9 | extern "C" { 10 | #include "c/arg.h" 11 | } 12 | 13 | Argument::Argument(arg* argPointer, bool persistent) : argPointer(argPointer), persistent(persistent) { 14 | if (!persistent) argPointer = arg_copy(argPointer); 15 | } 16 | 17 | Argument::Argument(const Argument& a) { 18 | argPointer = a.argPointer; 19 | persistent = a.persistent; 20 | if (!persistent) argPointer = arg_copy(argPointer); 21 | } 22 | 23 | Argument::Argument(Argument&& a) { 24 | argPointer = a.argPointer; 25 | persistent = a.persistent; 26 | a.argPointer = NULL; 27 | } 28 | 29 | Argument::~Argument() { 30 | if (!persistent) arg_destroy(argPointer); 31 | } 32 | 33 | Argument& Argument::operator=(const Argument& a) { 34 | argPointer = a.argPointer; 35 | persistent = a.persistent; 36 | if (!persistent) argPointer = arg_copy(argPointer); 37 | 38 | return *this; 39 | } 40 | 41 | Argument& Argument::operator=(Argument&& a) { 42 | argPointer = a.argPointer; 43 | persistent = a.persistent; 44 | a.argPointer = NULL; 45 | 46 | return *this; 47 | } 48 | 49 | bool Argument::operator==(const Argument& a) const { 50 | return equals(a); 51 | } 52 | 53 | bool Argument::operator!=(const Argument& a) const { 54 | return !equals(a); 55 | } 56 | 57 | Argument::operator bool() const { 58 | return argPointer; 59 | } 60 | 61 | bool Argument::isSet() const { 62 | return argPointer && argPointer->set == ARG_SET; 63 | } 64 | 65 | bool Argument::isRequired() const { 66 | return argPointer && argPointer->req == ARG_REQ; 67 | } 68 | 69 | bool Argument::isOptional() const { 70 | return !isRequired(); 71 | } 72 | 73 | bool Argument::hasDefaultValue() const { 74 | return isOptional(); 75 | } 76 | 77 | bool Argument::isReq() const { 78 | return isRequired(); 79 | } 80 | 81 | bool Argument::isOpt() const { 82 | return isOptional(); 83 | } 84 | 85 | String Argument::getName() const { 86 | if (argPointer) return String(argPointer->name); 87 | return String(); 88 | } 89 | 90 | String Argument::getValue() const { 91 | if (argPointer) return String(arg_get_value(argPointer)); 92 | return String(); 93 | } 94 | 95 | ArgumentType Argument::getType() const { 96 | if (argPointer) { 97 | if (argPointer->mode == ARG_DEFAULT) return ArgumentType::NORMAL; 98 | if (argPointer->mode == ARG_POS) return ArgumentType::POSITIONAL; 99 | if (argPointer->mode == ARG_FLAG) return ArgumentType::FLAG; 100 | } 101 | return ArgumentType::NORMAL; 102 | } 103 | 104 | String Argument::toString() const { 105 | String s; 106 | 107 | toString(s); 108 | return s; 109 | } 110 | 111 | void Argument::toString(String& s) const { 112 | if (isOptional()) s += '['; 113 | 114 | String n = getName(); 115 | String v = getValue(); 116 | 117 | switch (getType()) { 118 | case ArgumentType::NORMAL: 119 | case ArgumentType::POSITIONAL: 120 | s += '-'; 121 | s += n; 122 | s += ' '; 123 | s += '<'; 124 | s += v.length() > 0 ? v : "value"; 125 | s += '>'; 126 | break; 127 | 128 | case ArgumentType::FLAG: 129 | s += '-'; 130 | s += n; 131 | break; 132 | } 133 | 134 | if (isOptional()) s += ']'; 135 | } 136 | 137 | bool Argument::equals(String name, bool caseSensetive) const { 138 | return equals(name.c_str(), caseSensetive); 139 | } 140 | 141 | bool Argument::equals(const char* name, bool caseSensetive) const { 142 | return argPointer && name && arg_name_equals(argPointer, name, strlen(name), caseSensetive); 143 | } 144 | 145 | bool Argument::equals(const Argument& a, bool caseSensetive) const { 146 | return argPointer && a.argPointer && arg_equals(argPointer, a.argPointer, caseSensetive); 147 | } 148 | 149 | arg* Argument::getPtr() { 150 | return argPointer; 151 | } -------------------------------------------------------------------------------- /src/Argument.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019 Stefan Kremser 3 | This software is licensed under the MIT License. See the license file for details. 4 | Source: github.com/spacehuhn/SimpleCLI 5 | */ 6 | 7 | #ifndef Argument_h 8 | #define Argument_h 9 | 10 | #include "StringCLI.h" 11 | 12 | extern "C" { 13 | #include "c/arg_types.h" // arg 14 | } 15 | 16 | #define ARGUMENT_TEMPORARY false 17 | #define ARGUMENT_PERSISTENT true 18 | 19 | enum class ArgumentType { NORMAL, POSITIONAL, FLAG }; 20 | 21 | class Argument { 22 | friend class Command; 23 | friend class SimpleCLI; 24 | 25 | private: 26 | arg* argPointer; 27 | bool persistent; 28 | 29 | public: 30 | Argument(arg* argPointer = NULL, bool persistent = ARGUMENT_PERSISTENT); 31 | Argument(const Argument& a); 32 | Argument(Argument&& a); 33 | 34 | ~Argument(); 35 | 36 | Argument& operator=(const Argument& a); 37 | Argument& operator=(Argument&& a); 38 | 39 | bool operator==(const Argument& a) const; 40 | bool operator!=(const Argument& a) const; 41 | 42 | operator bool() const; 43 | 44 | bool isSet() const; 45 | bool isRequired() const; 46 | bool isOptional() const; 47 | bool hasDefaultValue() const; 48 | 49 | bool isReq() const; 50 | bool isOpt() const; 51 | 52 | String getName() const; 53 | String getValue() const; 54 | 55 | ArgumentType getType() const; 56 | 57 | String toString() const; 58 | void toString(String& s) const; 59 | 60 | bool equals(String name, bool caseSensetive = false) const; 61 | bool equals(const char* name, bool caseSensetive = false) const; 62 | bool equals(const Argument& a, bool caseSensetive = false) const; 63 | 64 | arg* getPtr(); 65 | }; 66 | 67 | #endif /* ifndef Argument_h */ -------------------------------------------------------------------------------- /src/Command.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019 Stefan Kremser 3 | This software is licensed under the MIT License. See the license file for details. 4 | Source: github.com/spacehuhn/SimpleCLI 5 | */ 6 | 7 | #include "Command.h" 8 | 9 | extern "C" { 10 | #include "c/cmd.h" 11 | #include "c/arg.h" 12 | } 13 | 14 | Command::Command(cmd* cmdPointer, bool persistent) : cmdPointer(cmdPointer), persistent(persistent) { 15 | if (!persistent) this->cmdPointer = cmd_copy(cmdPointer); 16 | } 17 | 18 | Command::Command(const Command& c) { 19 | persistent = c.persistent; 20 | cmdPointer = c.cmdPointer; 21 | if (!persistent) cmdPointer = cmd_copy(c.cmdPointer); 22 | } 23 | 24 | Command::Command(Command&& c) { 25 | persistent = c.persistent; 26 | cmdPointer = c.cmdPointer; 27 | c.cmdPointer = NULL; 28 | } 29 | 30 | Command::~Command() { 31 | if (!persistent) cmd_destroy(cmdPointer); 32 | } 33 | 34 | Command& Command::operator=(const Command& c) { 35 | persistent = c.persistent; 36 | cmdPointer = c.cmdPointer; 37 | if (!persistent) cmdPointer = cmd_copy(c.cmdPointer); 38 | 39 | return *this; 40 | } 41 | 42 | Command& Command::operator=(Command&& c) { 43 | persistent = c.persistent; 44 | cmdPointer = c.cmdPointer; 45 | c.cmdPointer = NULL; 46 | 47 | return *this; 48 | } 49 | 50 | bool Command::operator==(const Command& c) const { 51 | return equals(c); 52 | } 53 | 54 | bool Command::operator!=(const Command& c) const { 55 | return !equals(c); 56 | } 57 | 58 | Command::operator bool() const { 59 | return cmdPointer; 60 | } 61 | 62 | bool Command::setCaseSensetive(bool caseSensetive) { 63 | if (cmdPointer) { 64 | cmdPointer->case_sensetive = caseSensetive; 65 | return true; 66 | } 67 | return false; 68 | } 69 | 70 | bool Command::setCaseSensitive(bool caseSensitive) { 71 | return setCaseSensetive(caseSensitive); 72 | } 73 | 74 | bool Command::setCallback(void (* callback)(cmd* c)) { 75 | if (cmdPointer && callback) { 76 | cmdPointer->callback = callback; 77 | return true; 78 | } 79 | return false; 80 | } 81 | 82 | void Command::setDescription(const char* description) { 83 | cmd_set_description(cmdPointer, description); 84 | } 85 | 86 | Argument Command::addArg(const char* name, const char* defaultValue) { 87 | if (cmdPointer && (cmdPointer->mode == CMD_DEFAULT)) { 88 | arg* a = arg_create_opt(name, defaultValue); 89 | 90 | cmd_add_arg(cmdPointer, a); 91 | return Argument(a); 92 | } 93 | return Argument(); 94 | } 95 | 96 | Argument Command::addArg(const char* name) { 97 | if (cmdPointer && (cmdPointer->mode == CMD_DEFAULT)) { 98 | arg* a = arg_create_req(name); 99 | 100 | cmd_add_arg(cmdPointer, a); 101 | return Argument(a); 102 | } 103 | return Argument(); 104 | } 105 | 106 | Argument Command::addPosArg(const char* name, const char* defaultValue) { 107 | if (cmdPointer && (cmdPointer->mode == CMD_DEFAULT)) { 108 | arg* a = arg_create_opt_positional(name, defaultValue); 109 | 110 | cmd_add_arg(cmdPointer, a); 111 | return Argument(a); 112 | } 113 | return Argument(); 114 | } 115 | 116 | Argument Command::addPosArg(const char* name) { 117 | if (cmdPointer && (cmdPointer->mode == CMD_DEFAULT)) { 118 | arg* a = arg_create_req_positional(name); 119 | 120 | cmd_add_arg(cmdPointer, a); 121 | return Argument(a); 122 | } 123 | return Argument(); 124 | } 125 | 126 | Argument Command::addFlagArg(const char* name, const char* defaultValue) { 127 | if (cmdPointer && (cmdPointer->mode == CMD_DEFAULT)) { 128 | arg* a = arg_create_flag(name, defaultValue); 129 | 130 | cmd_add_arg(cmdPointer, a); 131 | return Argument(a); 132 | } 133 | return Argument(); 134 | } 135 | 136 | Argument Command::addArgument(const char* name, const char* defaultValue) { 137 | return addArg(name, defaultValue); 138 | } 139 | 140 | Argument Command::addArgument(const char* name) { 141 | return addArg(name); 142 | } 143 | 144 | Argument Command::addPositionalArgument(const char* name, const char* defaultValue) { 145 | return addPosArg(name, defaultValue); 146 | } 147 | 148 | Argument Command::addPositionalArgument(const char* name) { 149 | return addPosArg(name); 150 | } 151 | 152 | Argument Command::addFlagArgument(const char* name, const char* defaultValue) { 153 | return addFlagArg(name, defaultValue); 154 | } 155 | 156 | bool Command::equals(String name) const { 157 | return equals(name.c_str()); 158 | } 159 | 160 | bool Command::equals(const char* name) const { 161 | if (cmdPointer && name) return cmd_name_equals(cmdPointer, name, strlen(name), cmdPointer->case_sensetive) == CMD_NAME_EQUALS; 162 | return false; 163 | } 164 | 165 | bool Command::equals(const Command& c) const { 166 | if (cmdPointer && c.cmdPointer) return cmd_equals(cmdPointer, c.cmdPointer, cmdPointer->case_sensetive) == CMD_NAME_EQUALS; 167 | return false; 168 | } 169 | 170 | String Command::getName() const { 171 | if (cmdPointer) { 172 | return String(cmdPointer->name); 173 | } 174 | return String(); 175 | } 176 | 177 | int Command::countArgs() const { 178 | int i = 0; 179 | 180 | if (cmdPointer) { 181 | arg* h = cmdPointer->arg_list; 182 | 183 | while (h) { 184 | ++i; 185 | h = h->next; 186 | } 187 | } 188 | return i; 189 | } 190 | 191 | Argument Command::getArgument(int i) const { 192 | if (!cmdPointer) return Argument(); 193 | 194 | arg* h = cmdPointer->arg_list; 195 | int j = 0; 196 | 197 | while (j < i && h) { 198 | h = h->next; 199 | ++j; 200 | } 201 | 202 | return Argument(j == i ? h : NULL); 203 | } 204 | 205 | Argument Command::getArgument(const char* name) const { 206 | if (!cmdPointer || !name) return Argument(); 207 | 208 | arg* h = cmdPointer->arg_list; 209 | int j = 0; 210 | 211 | while (h) { 212 | if (arg_name_equals(h, name, strlen(name), cmdPointer->case_sensetive) == ARG_NAME_EQUALS) return h; 213 | h = h->next; 214 | ++j; 215 | } 216 | 217 | return Argument(); 218 | } 219 | 220 | Argument Command::getArgument(String name) const { 221 | return getArgument(name.c_str()); 222 | } 223 | 224 | Argument Command::getArgument(const Argument& a) const { 225 | return getArgument(a.argPointer ? a.argPointer->name : NULL); 226 | } 227 | 228 | Argument Command::getArg(int i) const { 229 | return getArgument(i); 230 | } 231 | 232 | Argument Command::getArg(const char* name) const { 233 | return getArgument(name); 234 | } 235 | 236 | Argument Command::getArg(String name) const { 237 | return getArgument(name); 238 | } 239 | 240 | Argument Command::getArg(const Argument& a) const { 241 | return getArgument(a); 242 | } 243 | 244 | String Command::toString(bool description) const { 245 | String s; 246 | 247 | toString(s, description); 248 | return s; 249 | } 250 | 251 | CommandType Command::getType() const { 252 | if (cmdPointer) { 253 | switch (cmdPointer->mode) { 254 | case CMD_DEFAULT: 255 | return CommandType::NORMAL; 256 | case CMD_BOUNDLESS: 257 | return CommandType::BOUNDLESS; 258 | case CMD_SINGLE: 259 | return CommandType::SINGLE; 260 | } 261 | } 262 | return CommandType::NORMAL; 263 | } 264 | 265 | bool Command::hasDescription() const { 266 | return cmdPointer && cmdPointer->description; 267 | } 268 | 269 | String Command::getDescription() const { 270 | return String(cmd_get_description(cmdPointer)); 271 | } 272 | 273 | void Command::toString(String& s, bool description) const { 274 | if (cmdPointer) { 275 | s += String(cmdPointer->name); 276 | 277 | if (cmdPointer->mode == CMD_BOUNDLESS) { 278 | s += ' '; 279 | s += " ..."; 280 | } else if (cmdPointer->mode == CMD_SINGLE) { 281 | s += ' '; 282 | s += "<...>"; 283 | } else { 284 | arg* h = cmdPointer->arg_list; 285 | 286 | while (h) { 287 | s += ' '; 288 | Argument(h).toString(s); 289 | 290 | h = h->next; 291 | } 292 | } 293 | 294 | if (description && hasDescription()) { 295 | s += "\r\n" + getDescription(); 296 | } 297 | } 298 | } 299 | 300 | void Command::run() const { 301 | if (cmdPointer && cmdPointer->callback) cmdPointer->callback(cmdPointer); 302 | } 303 | 304 | cmd* Command::getPtr() { 305 | return cmdPointer; 306 | } -------------------------------------------------------------------------------- /src/Command.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019 Stefan Kremser 3 | This software is licensed under the MIT License. See the license file for details. 4 | Source: github.com/spacehuhn/SimpleCLI 5 | */ 6 | 7 | #ifndef Command_h 8 | #define Command_h 9 | 10 | #include "StringCLI.h" 11 | #include "Argument.h" // Argument 12 | 13 | extern "C" { 14 | #include "c/cmd_types.h" // cmd 15 | } 16 | 17 | #define COMMAND_TEMPORARY false 18 | #define COMMAND_PERSISTENT true 19 | 20 | enum class CommandType { NORMAL, BOUNDLESS, SINGLE }; 21 | 22 | class Command { 23 | friend class SimpleCLI; 24 | 25 | private: 26 | cmd* cmdPointer; 27 | bool persistent; 28 | 29 | public: 30 | Command(cmd* cmdPointer = NULL, bool persistent = COMMAND_PERSISTENT); 31 | Command(const Command& c); 32 | Command(Command&& c); 33 | ~Command(); 34 | 35 | Command& operator=(const Command& c); 36 | Command& operator=(Command&& c); 37 | 38 | bool operator==(const Command& c) const; 39 | bool operator!=(const Command& c) const; 40 | 41 | operator bool() const; 42 | 43 | bool setCaseSensetive(bool caseSensetive = true); 44 | bool setCaseSensitive(bool caseSensitive = true); 45 | bool setCallback(void (* callback)(cmd* c)); 46 | 47 | void setDescription(const char* description); 48 | 49 | Argument addArg(const char* name, const char* defaultValue); 50 | Argument addArg(const char* name); 51 | Argument addPosArg(const char* name, const char* defaultValue); 52 | Argument addPosArg(const char* name); 53 | Argument addFlagArg(const char* name, const char* defaultValue = ""); 54 | 55 | Argument addArgument(const char* name, const char* defaultValue); 56 | Argument addArgument(const char* name); 57 | Argument addPositionalArgument(const char* name, const char* defaultValue); 58 | Argument addPositionalArgument(const char* name); 59 | Argument addFlagArgument(const char* name, const char* defaultValue = ""); 60 | 61 | bool equals(String name) const; 62 | bool equals(const char* name) const; 63 | bool equals(const Command& c) const; 64 | 65 | String getName() const; 66 | int countArgs() const; 67 | 68 | Argument getArgument(int i = 0) const; 69 | Argument getArgument(const char* name) const; 70 | Argument getArgument(String name) const; 71 | Argument getArgument(const Argument& a) const; 72 | 73 | Argument getArg(int i = 0) const; 74 | Argument getArg(const char* name) const; 75 | Argument getArg(String name) const; 76 | Argument getArg(const Argument& a) const; 77 | 78 | CommandType getType() const; 79 | 80 | bool hasDescription() const; 81 | String getDescription() const; 82 | 83 | String toString(bool description = true) const; 84 | void toString(String& s, bool description = true) const; 85 | 86 | void run() const; 87 | 88 | cmd* getPtr(); 89 | }; 90 | 91 | #endif /* ifndef Command_h */ -------------------------------------------------------------------------------- /src/CommandError.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019 Stefan Kremser 3 | This software is licensed under the MIT License. See the license file for details. 4 | Source: github.com/spacehuhn/SimpleCLI 5 | */ 6 | 7 | #include "CommandError.h" 8 | 9 | extern "C" { 10 | #include "c/cmd_error.h" // cmd_error_create, cmd_error_destroy 11 | } 12 | 13 | CommandError::CommandError(cmd_error* errorPointer, bool persistent) : errorPointer(errorPointer), persistent(persistent) { 14 | if (!persistent) this->errorPointer = cmd_error_copy(errorPointer); 15 | } 16 | 17 | CommandError::CommandError(const CommandError& e) { 18 | persistent = e.persistent; 19 | errorPointer = e.errorPointer; 20 | if (!persistent) errorPointer = cmd_error_copy(errorPointer); 21 | } 22 | 23 | CommandError::CommandError(CommandError&& e) { 24 | persistent = e.persistent; 25 | errorPointer = e.errorPointer; 26 | e.errorPointer = NULL; 27 | } 28 | 29 | CommandError::~CommandError() { 30 | if (!persistent) cmd_error_destroy(errorPointer); 31 | } 32 | 33 | CommandError& CommandError::operator=(const CommandError& e) { 34 | persistent = e.persistent; 35 | errorPointer = e.errorPointer; 36 | if (!persistent) errorPointer = cmd_error_copy(errorPointer); 37 | return *this; 38 | } 39 | 40 | CommandError& CommandError::operator=(CommandError&& e) { 41 | persistent = e.persistent; 42 | errorPointer = e.errorPointer; 43 | e.errorPointer = NULL; 44 | return *this; 45 | } 46 | 47 | bool CommandError::operator==(const CommandError& e) const { 48 | return errorPointer == e.errorPointer || 49 | (errorPointer && e.errorPointer && 50 | errorPointer->mode == e.errorPointer->mode && 51 | errorPointer->command == e.errorPointer->command && 52 | errorPointer->argument == e.errorPointer->argument && 53 | errorPointer->data == e.errorPointer->data); 54 | } 55 | 56 | bool CommandError::operator!=(const CommandError& e) const { 57 | return !(*this == e); 58 | } 59 | 60 | bool CommandError::operator>(const CommandError& e) const { 61 | return errorPointer && e.errorPointer && errorPointer->mode > e.errorPointer->mode; 62 | } 63 | 64 | bool CommandError::operator<(const CommandError& e) const { 65 | return errorPointer && e.errorPointer && errorPointer->mode < e.errorPointer->mode; 66 | } 67 | 68 | bool CommandError::operator>=(const CommandError& e) const { 69 | return errorPointer && e.errorPointer && errorPointer->mode >= e.errorPointer->mode; 70 | } 71 | 72 | bool CommandError::operator<=(const CommandError& e) const { 73 | return errorPointer && e.errorPointer && errorPointer->mode <= e.errorPointer->mode; 74 | } 75 | 76 | CommandError::operator bool() const { 77 | return errorPointer && errorPointer->mode != CMD_PARSE_SUCCESS; 78 | } 79 | 80 | bool CommandError::hasCommand() const { 81 | return errorPointer && errorPointer->command; 82 | } 83 | 84 | bool CommandError::hasArgument() const { 85 | return errorPointer && errorPointer->argument; 86 | } 87 | 88 | bool CommandError::hasData() const { 89 | return errorPointer && errorPointer->data; 90 | } 91 | 92 | CommandErrorType CommandError::getType() const { 93 | if (errorPointer) { 94 | switch (errorPointer->mode) { 95 | case CMD_NULL_PTR: return CommandErrorType::NULL_POINTER; 96 | case CMD_EMPTY_LINE: return CommandErrorType::EMPTY_LINE; 97 | case CMD_PARSE_SUCCESS: return CommandErrorType::PARSE_SUCCESSFUL; 98 | case CMD_NOT_FOUND: return CommandErrorType::COMMAND_NOT_FOUND; 99 | case CMD_UNKOWN_ARG: return CommandErrorType::UNKNOWN_ARGUMENT; 100 | case CMD_MISSING_ARG: return CommandErrorType::MISSING_ARGUMENT; 101 | case CMD_MISSING_ARG_VALUE: return CommandErrorType::MISSING_ARGUMENT_VALUE; 102 | case CMD_UNCLOSED_QUOTE: return CommandErrorType::UNCLOSED_QUOTE; 103 | } 104 | } 105 | return CommandErrorType::PARSE_SUCCESSFUL; 106 | } 107 | 108 | bool CommandError::hasCmd() const { 109 | return hasCommand(); 110 | } 111 | 112 | bool CommandError::hasArg() const { 113 | return hasArgument(); 114 | } 115 | 116 | Command CommandError::getCommand() const { 117 | return Command(errorPointer ? errorPointer->command : NULL); 118 | } 119 | 120 | Argument CommandError::getArgument() const { 121 | return Argument(errorPointer ? errorPointer->argument : NULL); 122 | } 123 | 124 | String CommandError::getData() const { 125 | if (!errorPointer || !errorPointer->data) return String(); 126 | return String(errorPointer->data); 127 | } 128 | 129 | String CommandError::getMessage() const { 130 | if (errorPointer) { 131 | switch (errorPointer->mode) { 132 | case CMD_NULL_PTR: return String(F("NULL Pointer")); 133 | case CMD_EMPTY_LINE: return String(F("Empty input")); 134 | case CMD_PARSE_SUCCESS: return String(F("No error")); 135 | case CMD_NOT_FOUND: return String(F("Command not found")); 136 | case CMD_UNKOWN_ARG: return String(F("Unknown argument")); 137 | case CMD_MISSING_ARG: return String(F("Missing argument")); 138 | case CMD_MISSING_ARG_VALUE: return String(F("Missing argument value")); 139 | case CMD_UNCLOSED_QUOTE: return String(F("Unclosed quote")); 140 | } 141 | } 142 | return String(); 143 | } 144 | 145 | Command CommandError::getCmd() const { 146 | return getCommand(); 147 | } 148 | 149 | Argument CommandError::getArg() const { 150 | return getArgument(); 151 | } 152 | 153 | String CommandError::getMsg() const { 154 | return getMessage(); 155 | } 156 | 157 | String CommandError::toString() const { 158 | String s; 159 | 160 | toString(s); 161 | return s; 162 | } 163 | 164 | void CommandError::toString(String& s) const { 165 | s += getMessage(); 166 | if (hasCommand()) s += String(F(" at command '")) + getCommand().getName() + String(F("'")); 167 | if (hasArgument()) s += String(F(" at argument '")) + getArgument().toString() + String(F("'")); 168 | if (hasData()) s += String(F(" at '")) + getData() + String(F("'")); 169 | } 170 | 171 | cmd_error* CommandError::getPtr() { 172 | return errorPointer; 173 | } -------------------------------------------------------------------------------- /src/CommandError.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019 Stefan Kremser 3 | This software is licensed under the MIT License. See the license file for details. 4 | Source: github.com/spacehuhn/SimpleCLI 5 | */ 6 | 7 | #ifndef CommandError_h 8 | #define CommandError_h 9 | 10 | #include "StringCLI.h" 11 | #include "Command.h" // Command 12 | 13 | extern "C" { 14 | #include "c/cmd_error_types.h" // cmd_error 15 | } 16 | 17 | #define COMMAND_ERROR_TEMPORARY false 18 | #define COMMAND_ERROR_PERSISTENT true 19 | 20 | enum class CommandErrorType { NULL_POINTER, EMPTY_LINE, PARSE_SUCCESSFUL, 21 | COMMAND_NOT_FOUND, UNKNOWN_ARGUMENT, MISSING_ARGUMENT, 22 | MISSING_ARGUMENT_VALUE, UNCLOSED_QUOTE }; 23 | 24 | class CommandError { 25 | private: 26 | cmd_error* errorPointer; 27 | bool persistent; 28 | 29 | public: 30 | CommandError(cmd_error* errorPointer = NULL, bool persistent = COMMAND_ERROR_PERSISTENT); 31 | CommandError(const CommandError& e); 32 | CommandError(CommandError&& e); 33 | ~CommandError(); 34 | 35 | CommandError& operator=(const CommandError& e); 36 | CommandError& operator=(CommandError&& e); 37 | 38 | bool operator==(const CommandError& e) const; 39 | bool operator!=(const CommandError& e) const; 40 | 41 | bool operator>(const CommandError& e) const; 42 | bool operator<(const CommandError& e) const; 43 | 44 | bool operator>=(const CommandError& e) const; 45 | bool operator<=(const CommandError& e) const; 46 | 47 | operator bool() const; 48 | 49 | bool hasCommand() const; 50 | bool hasArgument() const; 51 | bool hasData() const; 52 | 53 | bool hasCmd() const; 54 | bool hasArg() const; 55 | 56 | CommandErrorType getType() const; 57 | Command getCommand() const; 58 | Argument getArgument() const; 59 | String getData() const; 60 | String getMessage() const; 61 | 62 | Command getCmd() const; 63 | Argument getArg() const; 64 | String getMsg() const; 65 | 66 | String toString() const; 67 | void toString(String& s) const; 68 | 69 | cmd_error* getPtr(); 70 | }; 71 | 72 | #endif /* ifndef CommandError_h */ -------------------------------------------------------------------------------- /src/SimpleCLI.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019 Stefan Kremser 3 | This software is licensed under the MIT License. See the license file for details. 4 | Source: github.com/spacehuhn/SimpleCLI 5 | */ 6 | 7 | #include "SimpleCLI.h" 8 | 9 | extern "C" { 10 | #include "c/cmd.h" // cmd 11 | #include "c/parser.h" // parse_lines 12 | #include "c/cmd_error.h" // cmd_error_destroy 13 | } 14 | 15 | SimpleCLI::SimpleCLI(int commandQueueSize, int errorQueueSize) : commandQueueSize(commandQueueSize), errorQueueSize(errorQueueSize) {} 16 | 17 | SimpleCLI::~SimpleCLI() { 18 | cmd_destroy_rec(cmdList); 19 | cmd_destroy_rec(cmdQueue); 20 | cmd_error_destroy_rec(errorQueue); 21 | } 22 | 23 | void SimpleCLI::pause() { 24 | pauseParsing = true; 25 | } 26 | 27 | void SimpleCLI::unpause() { 28 | pauseParsing = false; 29 | 30 | // Go through queued errors 31 | while (onError && errored()) { 32 | onError(getError().getPtr()); 33 | } 34 | 35 | // Go through queued commands 36 | if(available()) { 37 | cmd* prev = NULL; 38 | cmd* current = cmdQueue; 39 | cmd* next = NULL; 40 | 41 | while(current) { 42 | next = current->next; 43 | 44 | // Has callback, then run it and remove from queue 45 | if(current->callback) { 46 | current->callback(current); 47 | if(prev) prev->next = next; 48 | cmd_destroy(current); 49 | } else { 50 | prev = current; 51 | } 52 | 53 | current = next; 54 | } 55 | } 56 | } 57 | 58 | void SimpleCLI::parse(const String& input) { 59 | parse(input.c_str(), input.length()); 60 | } 61 | 62 | void SimpleCLI::parse(const char* input) { 63 | if (input) parse(input, strlen(input)); 64 | } 65 | 66 | void SimpleCLI::parse(const char* str, size_t len) { 67 | // Split str into a list of lines 68 | line_list* l = parse_lines(str, len); 69 | 70 | // Go through all lines and try to find a matching command 71 | line_node* n = l->first; 72 | 73 | while (n) { 74 | cmd* h = cmdList; 75 | bool success = false; 76 | bool errored = false; 77 | 78 | while (h && !success && !errored) { 79 | cmd_error* e = cmd_parse(h, n); 80 | 81 | // When parsing was successful 82 | if (e->mode == CMD_PARSE_SUCCESS) { 83 | if (h->callback && !pauseParsing) h->callback(h); 84 | else cmdQueue = cmd_push(cmdQueue, cmd_copy(h), commandQueueSize); 85 | 86 | success = true; 87 | } 88 | 89 | // When command name matches but something else went wrong, exit with error 90 | else if (e->mode > CMD_NOT_FOUND) { 91 | if (onError && !pauseParsing) { 92 | onError(e); 93 | } else { 94 | errorQueue = cmd_error_push(errorQueue, cmd_error_copy(e), errorQueueSize); 95 | } 96 | 97 | errored = true; 98 | } 99 | 100 | // When command name does not match 101 | 102 | /*else (e->mode <= CMD_NOT_FOUND) { 103 | 104 | }*/ 105 | 106 | cmd_error_destroy(e); 107 | 108 | cmd_reset_cli(h); 109 | 110 | h = h->next; 111 | } 112 | 113 | // No error but no success either => Command could not be found 114 | if (!errored && !success) { 115 | cmd_error* e = cmd_error_create_not_found(NULL, n->words->first); 116 | 117 | if (onError && !pauseParsing) { 118 | onError(e); 119 | } else { 120 | errorQueue = cmd_error_push(errorQueue, cmd_error_copy(e), errorQueueSize); 121 | } 122 | 123 | cmd_error_destroy(e); 124 | 125 | errored = true; 126 | } 127 | 128 | n = n->next; 129 | } 130 | 131 | line_list_destroy(l); 132 | } 133 | 134 | bool SimpleCLI::available() const { 135 | return cmdQueue; 136 | } 137 | 138 | bool SimpleCLI::errored() const { 139 | return errorQueue; 140 | } 141 | 142 | bool SimpleCLI::paused() const { 143 | return pauseParsing; 144 | } 145 | 146 | int SimpleCLI::countCmdQueue() const { 147 | cmd* h = cmdQueue; 148 | int i = 0; 149 | 150 | while (h) { 151 | h = h->next; 152 | ++i; 153 | } 154 | 155 | return i; 156 | } 157 | 158 | int SimpleCLI::countErrorQueue() const { 159 | cmd_error* h = errorQueue; 160 | int i = 0; 161 | 162 | while (h) { 163 | h = h->next; 164 | ++i; 165 | } 166 | 167 | return i; 168 | } 169 | 170 | Command SimpleCLI::getCmd() { 171 | if (!cmdQueue) return Command(); 172 | 173 | // "Pop" cmd pointer from queue 174 | cmd* ptr = cmdQueue; 175 | 176 | cmdQueue = cmdQueue->next; 177 | 178 | // Create wrapper class and copy cmd 179 | Command c(ptr, COMMAND_TEMPORARY); 180 | 181 | // Destroy old cmd from queue 182 | cmd_destroy(ptr); 183 | 184 | return c; 185 | } 186 | 187 | Command SimpleCLI::getCmd(String name) { 188 | return getCmd(name.c_str()); 189 | } 190 | 191 | Command SimpleCLI::getCmd(const char* name) { 192 | if (name) { 193 | cmd* h = cmdList; 194 | 195 | while (h) { 196 | if (cmd_name_equals(h, name, strlen(name), h->case_sensetive) == CMD_NAME_EQUALS) return Command(h); 197 | h = h->next; 198 | } 199 | } 200 | return Command(); 201 | } 202 | 203 | Command SimpleCLI::getCommand() { 204 | return getCmd(); 205 | } 206 | 207 | Command SimpleCLI::getCommand(String name) { 208 | return getCmd(name); 209 | } 210 | 211 | Command SimpleCLI::getCommand(const char* name) { 212 | return getCmd(name); 213 | } 214 | 215 | CommandError SimpleCLI::getError() { 216 | if (!errorQueue) return CommandError(); 217 | 218 | // "Pop" cmd_error pointer from queue 219 | cmd_error* ptr = errorQueue; 220 | errorQueue = errorQueue->next; 221 | 222 | // Create wrapper class and copy cmd_error 223 | CommandError e(ptr, COMMAND_ERROR_TEMPORARY); 224 | 225 | // Destroy old cmd_error from queue 226 | cmd_error_destroy(ptr); 227 | 228 | return e; 229 | } 230 | 231 | void SimpleCLI::addCmd(Command& c) { 232 | if (!cmdList) { 233 | cmdList = c.cmdPointer; 234 | } else { 235 | cmd* h = cmdList; 236 | 237 | while (h->next) h = h->next; 238 | h->next = c.cmdPointer; 239 | } 240 | 241 | c.setCaseSensetive(caseSensetive); 242 | c.persistent = true; 243 | } 244 | 245 | Command SimpleCLI::addCmd(const char* name, void (* callback)(cmd* c)) { 246 | Command c(cmd_create_default(name)); 247 | 248 | c.setCallback(callback); 249 | addCmd(c); 250 | 251 | return c; 252 | } 253 | 254 | Command SimpleCLI::addBoundlessCmd(const char* name, void (* callback)(cmd* c)) { 255 | Command c(cmd_create_boundless(name)); 256 | 257 | c.setCallback(callback); 258 | addCmd(c); 259 | 260 | return c; 261 | } 262 | 263 | Command SimpleCLI::addSingleArgCmd(const char* name, void (* callback)(cmd* c)) { 264 | Command c(cmd_create_single(name)); 265 | 266 | c.setCallback(callback); 267 | addCmd(c); 268 | 269 | return c; 270 | } 271 | 272 | Command SimpleCLI::addCommand(const char* name, void (* callback)(cmd* c)) { 273 | return addCmd(name, callback); 274 | } 275 | 276 | Command SimpleCLI::addBoundlessCommand(const char* name, void (* callback)(cmd* c)) { 277 | return addBoundlessCmd(name, callback); 278 | } 279 | 280 | Command SimpleCLI::addSingleArgumentCommand(const char* name, void (* callback)(cmd* c)) { 281 | return addSingleArgCmd(name, callback); 282 | } 283 | 284 | String SimpleCLI::toString(bool descriptions) const { 285 | String s; 286 | 287 | toString(s, descriptions); 288 | return s; 289 | } 290 | 291 | void SimpleCLI::toString(String& s, bool descriptions) const { 292 | cmd* h = cmdList; 293 | 294 | while (h) { 295 | Command(h).toString(s, descriptions); 296 | if (descriptions) s += "\r\n"; 297 | s += "\r\n"; 298 | h = h->next; 299 | } 300 | } 301 | 302 | void SimpleCLI::setCaseSensetive(bool caseSensetive) { 303 | this->caseSensetive = caseSensetive; 304 | 305 | cmd* h = cmdList; 306 | 307 | while (h) { 308 | h->case_sensetive = caseSensetive; 309 | h = h->next; 310 | } 311 | } 312 | 313 | void SimpleCLI::setCaseSensitive(bool caseSensitive) { 314 | setCaseSensetive(caseSensitive); 315 | } 316 | 317 | void SimpleCLI::setOnError(void (* onError)(cmd_error* e)) { 318 | this->onError = onError; 319 | } 320 | 321 | void SimpleCLI::setErrorCallback(void (* onError)(cmd_error* e)) { 322 | setOnError(onError); 323 | } -------------------------------------------------------------------------------- /src/SimpleCLI.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2021 Spacehuhn Technologies 3 | This software is licensed under the MIT License. See the license file for details. 4 | Source: github.com/spacehuhn/SimpleCLI 5 | */ 6 | 7 | #ifndef SimpleCLI_h 8 | #define SimpleCLI_h 9 | 10 | #include "CommandError.h" // CommandError, Command, Argument 11 | 12 | #define SIMPLECLI_VERSION "1.1.4" 13 | #define SIMPLECLI_VERSION_MAJOR 1 14 | #define SIMPLECLI_VERSION_MINOR 1 15 | #define SIMPLECLI_VERSION_REVISION 3 16 | 17 | class SimpleCLI { 18 | private: 19 | bool caseSensetive { false }; 20 | bool pauseParsing { false }; 21 | 22 | cmd* cmdList { NULL }; // List of accessible commands 23 | cmd* cmdQueue { NULL }; // Queue with parsed commands the user has typed in 24 | cmd_error* errorQueue { NULL }; // Queue with parser errors 25 | 26 | int commandQueueSize; 27 | int errorQueueSize; 28 | 29 | void (* onError)(cmd_error* e) = NULL; 30 | 31 | cmd* getNextCmd(cmd* begin, const char* name, size_t name_len); 32 | void parseLine(const char* input, size_t input_len); 33 | 34 | void addCmd(Command& c); 35 | 36 | public: 37 | SimpleCLI(int commandQueueSize = 10, int errorQueueSize = 10); 38 | ~SimpleCLI(); 39 | 40 | void pause(); 41 | void unpause(); 42 | 43 | void parse(const String& input); 44 | void parse(const char* input); 45 | void parse(const char* input, size_t input_len); 46 | 47 | bool available() const; 48 | bool errored() const; 49 | bool paused() const; 50 | 51 | int countCmdQueue() const; 52 | int countErrorQueue() const; 53 | 54 | Command getCmd(); 55 | Command getCmd(String name); 56 | Command getCmd(const char* name); 57 | 58 | Command getCommand(); 59 | Command getCommand(String name); 60 | Command getCommand(const char* name); 61 | 62 | CommandError getError(); 63 | 64 | Command addCmd(const char* name, void (* callback)(cmd* c) = NULL); 65 | Command addBoundlessCmd(const char* name, void (* callback)(cmd* c) = NULL); 66 | Command addSingleArgCmd(const char* name, void (* callback)(cmd* c) = NULL); 67 | 68 | Command addCommand(const char* name, void (* callback)(cmd* c) = NULL); 69 | Command addBoundlessCommand(const char* name, void (* callback)(cmd* c) = NULL); 70 | Command addSingleArgumentCommand(const char* name, void (* callback)(cmd* c) = NULL); 71 | 72 | String toString(bool descriptions = true) const; 73 | void toString(String& s, bool descriptions = true) const; 74 | 75 | void setCaseSensetive(bool caseSensetive = true); 76 | void setCaseSensitive(bool caseSensitive = true); 77 | void setOnError(void (* onError)(cmd_error* e)); 78 | void setErrorCallback(void (* onError)(cmd_error* e)); 79 | }; 80 | 81 | #endif // ifndef SimpleCLI_h 82 | -------------------------------------------------------------------------------- /src/StringCLI.h: -------------------------------------------------------------------------------- 1 | #ifndef StringCLI_h 2 | #define StringCLI_h 3 | 4 | #if defined(ARDUINO) 5 | #include "Arduino.h" // String 6 | #else 7 | #include // std::string 8 | #include // strlen() 9 | typedef std::string String; 10 | inline String F(String s) { return s; }; 11 | #endif 12 | 13 | #endif // ifndef StringCLI_h -------------------------------------------------------------------------------- /src/c/arg.c: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019 Stefan Kremser 3 | This software is licensed under the MIT License. See the license file for details. 4 | Source: github.com/spacehuhn/SimpleCLI 5 | */ 6 | 7 | #include "c/arg.h" 8 | 9 | #include // malloc() 10 | #include // memcpy() 11 | 12 | #include "c/comparator.h" // compare 13 | 14 | // ===== Arg ===== // 15 | 16 | // Constructors 17 | arg* arg_create(const char* name, const char* default_val, unsigned int mode, unsigned int req) { 18 | arg* a = (arg*)malloc(sizeof(arg)); 19 | 20 | a->name = name; 21 | a->default_val = default_val; 22 | a->val = NULL; 23 | a->mode = mode % 3; 24 | a->req = req % 2; 25 | a->set = ARG_UNSET; 26 | a->next = NULL; 27 | 28 | return a; 29 | } 30 | 31 | arg* arg_create_opt(const char* name, const char* default_val) { 32 | return arg_create(name, default_val, ARG_DEFAULT, ARG_OPT); 33 | } 34 | 35 | arg* arg_create_req(const char* name) { 36 | return arg_create(name, NULL, ARG_DEFAULT, ARG_REQ); 37 | } 38 | 39 | arg* arg_create_opt_positional(const char* name, const char* default_value) { 40 | return arg_create(name, default_value, ARG_POS, ARG_OPT); 41 | } 42 | 43 | arg* arg_create_req_positional(const char* name) { 44 | return arg_create(name, NULL, ARG_POS, ARG_REQ); 45 | } 46 | 47 | arg* arg_create_flag(const char* name, const char* default_value) { 48 | return arg_create(name, default_value, ARG_FLAG, ARG_OPT); 49 | } 50 | 51 | // Copy & Move Constructors 52 | 53 | arg* arg_copy(arg* a) { 54 | if (!a) return NULL; 55 | 56 | arg* na = (arg*)malloc(sizeof(arg)); 57 | 58 | na->name = a->name; 59 | na->default_val = a->default_val; 60 | na->val = NULL; 61 | na->mode = a->mode; 62 | na->req = a->req; 63 | na->set = a->set; 64 | na->next = NULL; 65 | 66 | if (a->val) { 67 | na->val = (char*)malloc(strlen(a->val) + 1); 68 | strcpy(na->val, a->val); 69 | na->set = ARG_SET; 70 | } 71 | 72 | return na; 73 | } 74 | 75 | arg* arg_copy_rec(arg* a) { 76 | if (!a) return NULL; 77 | 78 | arg* na = arg_copy(a); 79 | na->next = arg_copy_rec(a->next); 80 | 81 | return na; 82 | } 83 | 84 | arg* arg_move(arg* a) { 85 | if (!a) return NULL; 86 | 87 | arg* na = (arg*)malloc(sizeof(arg)); 88 | 89 | na->name = a->name; 90 | na->default_val = a->default_val; 91 | na->val = a->val; 92 | na->mode = a->mode; 93 | na->req = a->req; 94 | na->set = a->set; 95 | na->next = NULL; 96 | 97 | a->val = NULL; 98 | a->set = ARG_UNSET; 99 | 100 | return na; 101 | } 102 | 103 | arg* arg_move_rec(arg* a) { 104 | if (!a) return NULL; 105 | 106 | arg* na = arg_move(a); 107 | na->next = arg_move_rec(a->next); 108 | 109 | return na; 110 | } 111 | 112 | // Destructors 113 | arg* arg_destroy(arg* a) { 114 | if (a) { 115 | arg_reset(a); 116 | free(a); 117 | } 118 | return NULL; 119 | } 120 | 121 | arg* arg_destroy_rec(arg* a) { 122 | if (a) { 123 | arg_destroy_rec(a->next); 124 | arg_destroy(a); 125 | } 126 | return NULL; 127 | } 128 | 129 | // Reset 130 | void arg_reset(arg* a) { 131 | if (a) { 132 | if (a->val) { 133 | free(a->val); 134 | a->val = NULL; 135 | } 136 | a->set = ARG_UNSET; 137 | } 138 | } 139 | 140 | void arg_reset_rec(arg* a) { 141 | if (a) { 142 | arg_reset(a); 143 | arg_reset_rec(a->next); 144 | } 145 | } 146 | 147 | // Comparisons 148 | int arg_name_equals(arg* a, const char* name, size_t name_len, int case_sensetive) { 149 | if (!a) return ARG_NAME_UNEQUALS; 150 | return compare(name, name_len, a->name, case_sensetive) == COMPARE_EQUAL ? ARG_NAME_EQUALS : ARG_NAME_UNEQUALS; 151 | } 152 | 153 | int arg_equals(arg* a, arg* b, int case_sensetive) { 154 | if (a == b) return ARG_NAME_EQUALS; 155 | if (!a || !b) return ARG_NAME_UNEQUALS; 156 | return arg_name_equals(a, b->name, strlen(b->name), case_sensetive); 157 | } 158 | 159 | // Getter 160 | const char* arg_get_value(arg* a) { 161 | if (a) { 162 | if (a->val) return a->val; 163 | if (a->default_val) return a->default_val; 164 | } 165 | return ""; 166 | } 167 | 168 | // Setter 169 | int arg_set_value(arg* a, const char* val, size_t val_size) { 170 | if (a) { 171 | if (val && (val_size > 0)) { 172 | if (a->set) arg_reset(a); 173 | a->val = (char*)malloc(val_size + 1); 174 | 175 | size_t i = 0; 176 | size_t j = 0; 177 | 178 | int escaped = 0; 179 | int in_quote = 0; 180 | 181 | while (i < val_size) { 182 | if ((val[i] == '\\') && (escaped == 0)) { 183 | escaped = 1; 184 | } else if ((val[i] == '"') && (escaped == 0)) { 185 | in_quote = !in_quote; 186 | } else { 187 | a->val[j++] = val[i]; 188 | escaped = 0; 189 | } 190 | 191 | ++i; 192 | } 193 | 194 | if (in_quote) { 195 | free(a->val); 196 | a->val = NULL; 197 | return ARG_VALUE_FAIL; 198 | } 199 | 200 | while (j <= val_size) { 201 | a->val[j++] = '\0'; 202 | } 203 | } 204 | 205 | a->set = ARG_SET; 206 | return ARG_VALUE_SUCCESS; 207 | } 208 | 209 | return ARG_VALUE_FAIL; 210 | } -------------------------------------------------------------------------------- /src/c/arg.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019 Stefan Kremser 3 | This software is licensed under the MIT License. See the license file for details. 4 | Source: github.com/spacehuhn/SimpleCLI 5 | */ 6 | 7 | #ifndef arg_h 8 | #define arg_h 9 | 10 | #include "c/arg_types.h" // arg 11 | #include // size_t 12 | 13 | // ===== Arg ===== // 14 | 15 | // Constructors 16 | arg* arg_create(const char* name, const char* default_val, unsigned int mode, unsigned int req); 17 | arg* arg_create_opt(const char* name, const char* default_val); 18 | arg* arg_create_req(const char* name); 19 | arg* arg_create_opt_positional(const char* name, const char* default_value); 20 | arg* arg_create_req_positional(const char* name); 21 | arg* arg_create_flag(const char* name, const char* default_value); 22 | 23 | // Copy & Move Constructors 24 | arg* arg_copy(arg* a); 25 | arg* arg_copy_rec(arg* a); 26 | arg* arg_move(arg* a); 27 | arg* arg_move_rec(arg* a); 28 | 29 | // Destructors 30 | arg* arg_destroy(arg* a); 31 | arg* arg_destroy_rec(arg* a); 32 | 33 | // Reset 34 | void arg_reset(arg* a); 35 | void arg_reset_rec(arg* a); 36 | 37 | // Comparisons 38 | int arg_name_equals(arg* a, const char* name, size_t name_len, int case_sensetive); 39 | int arg_equals(arg* a, arg* b, int case_sensetive); 40 | 41 | // Getter 42 | const char* arg_get_value(arg* a); 43 | 44 | // Setter 45 | int arg_set_value(arg* a, const char* val, size_t val_size); 46 | 47 | #endif /* ifndef arg_h */ -------------------------------------------------------------------------------- /src/c/arg_types.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019 Stefan Kremser 3 | This software is licensed under the MIT License. See the license file for details. 4 | Source: github.com/spacehuhn/SimpleCLI 5 | */ 6 | 7 | #ifndef arg_types_h 8 | #define arg_types_h 9 | 10 | #define ARG_DEFAULT 0 11 | #define ARG_POS 1 12 | #define ARG_FLAG 2 13 | 14 | #define ARG_OPT 0 15 | #define ARG_REQ 1 16 | 17 | #define ARG_UNSET 0 18 | #define ARG_SET 1 19 | 20 | #define ARG_NAME_UNEQUALS 0 21 | #define ARG_NAME_EQUALS 1 22 | 23 | #define ARG_VALUE_FAIL 0 24 | #define ARG_VALUE_SUCCESS 1 25 | 26 | typedef struct arg { 27 | const char * name; 28 | const char * default_val; 29 | char * val; 30 | unsigned int mode : 2; 31 | unsigned int req : 1; 32 | unsigned int set : 1; 33 | struct arg * next; 34 | } arg; 35 | 36 | #endif /* ifndef arg_types_h */ -------------------------------------------------------------------------------- /src/c/cmd.c: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019 Stefan Kremser 3 | This software is licensed under the MIT License. See the license file for details. 4 | Soruce: github.com/spacehuhn/SimpleCLI 5 | */ 6 | 7 | #include // malloc 8 | #include // strlen 9 | 10 | #include "c/comparator.h" // compare 11 | 12 | #include "c/cmd.h" 13 | 14 | #include "c/cmd_error.h" 15 | #include "c/arg.h" 16 | 17 | // ===== CMD ===== // 18 | 19 | // CMD Constructors 20 | cmd* cmd_create(const char* name, unsigned int mode) { 21 | if (!name) return NULL; 22 | 23 | cmd* c = (cmd*)malloc(sizeof(cmd)); 24 | 25 | c->name = name; 26 | c->mode = mode % 3; 27 | c->arg_list = NULL; 28 | c->case_sensetive = COMPARE_CASE_INSENSETIVE; 29 | c->callback = NULL; 30 | c->description = NULL; 31 | c->next = NULL; 32 | 33 | return c; 34 | } 35 | 36 | cmd* cmd_create_default(const char* name) { 37 | return cmd_create(name, CMD_DEFAULT); 38 | } 39 | 40 | cmd* cmd_create_boundless(const char* name) { 41 | return cmd_create(name, CMD_BOUNDLESS); 42 | } 43 | 44 | cmd* cmd_create_single(const char* name) { 45 | cmd* c = cmd_create(name, CMD_SINGLE); 46 | 47 | c->arg_list = arg_create_opt_positional(NULL, NULL); 48 | return c; 49 | } 50 | 51 | // Copy & Move Constructors 52 | cmd* cmd_copy(cmd* c) { 53 | if (!c) return NULL; 54 | 55 | cmd* nc = (cmd*)malloc(sizeof(cmd)); 56 | 57 | nc->name = c->name; 58 | nc->mode = c->mode; 59 | nc->arg_list = arg_copy_rec(c->arg_list); 60 | nc->case_sensetive = c->case_sensetive; 61 | nc->callback = c->callback; 62 | nc->description = c->description; 63 | nc->next = NULL; 64 | 65 | return nc; 66 | } 67 | 68 | cmd* cmd_copy_rec(cmd* c) { 69 | if (!c) return NULL; 70 | 71 | cmd* nc = cmd_copy(c); 72 | nc->next = cmd_copy_rec(c->next); 73 | 74 | return nc; 75 | } 76 | 77 | cmd* cmd_move(cmd* c) { 78 | if (!c) return NULL; 79 | 80 | cmd* nc = (cmd*)malloc(sizeof(cmd)); 81 | 82 | nc->name = c->name; 83 | nc->mode = c->mode; 84 | nc->arg_list = arg_move_rec(c->arg_list); 85 | nc->case_sensetive = c->case_sensetive; 86 | nc->callback = c->callback; 87 | nc->description = c->description; 88 | nc->next = NULL; 89 | 90 | return nc; 91 | } 92 | 93 | cmd* cmd_move_rec(cmd* c) { 94 | if (!c) return NULL; 95 | 96 | cmd* nc = cmd_move(c); 97 | nc->next = cmd_move_rec(c->next); 98 | 99 | return nc; 100 | } 101 | 102 | // Destructors 103 | cmd* cmd_destroy(cmd* c) { 104 | if (c) { 105 | arg_destroy_rec(c->arg_list); 106 | free(c); 107 | } 108 | return NULL; 109 | } 110 | 111 | cmd* cmd_destroy_rec(cmd* c) { 112 | if (c) { 113 | cmd_destroy_rec(c->next); 114 | cmd_destroy(c); 115 | } 116 | return NULL; 117 | } 118 | 119 | // Push CMD and Push Arg 120 | cmd* cmd_push(cmd* l, cmd* c, int max_size) { 121 | if (max_size < 1) { 122 | cmd_destroy_rec(l); 123 | cmd_destroy(c); 124 | return NULL; 125 | } 126 | 127 | if (!l) return c; 128 | 129 | cmd* h = l; 130 | int i = 1; 131 | 132 | while (h->next) { 133 | h = h->next; 134 | ++i; 135 | } 136 | 137 | h->next = c; 138 | 139 | // Remove first element if list is too big 140 | if (i > max_size) { 141 | cmd* ptr = l; 142 | l = l->next; 143 | cmd_destroy(ptr); 144 | } 145 | 146 | return l; 147 | } 148 | 149 | cmd* cmd_add_arg(cmd* c, arg* a) { 150 | if (c && a) { 151 | arg* h = c->arg_list; 152 | 153 | if (!h) { 154 | c->arg_list = a; 155 | } else { 156 | while (h->next) h = h->next; 157 | h->next = a; 158 | } 159 | 160 | a->next = NULL; 161 | } 162 | return c; 163 | } 164 | 165 | // Reset CMD 166 | void cmd_reset_cli(cmd* c) { 167 | if (c) { 168 | if (c->mode == CMD_BOUNDLESS) { 169 | arg_destroy_rec(c->arg_list); 170 | c->arg_list = NULL; 171 | } else { 172 | arg_reset_rec(c->arg_list); 173 | } 174 | } 175 | } 176 | 177 | void cmd_reset_cli_rec(cmd* c) { 178 | if (c) { 179 | cmd_reset_cli(c); 180 | cmd_reset_cli_rec(c->next); 181 | } 182 | } 183 | 184 | // Comparisons 185 | int cmd_name_equals(cmd* c, const char* name, size_t name_len, int case_sensetive) { 186 | if (!c || !name) return CMD_NAME_UNEQUALS; 187 | if (c->name == name) return CMD_NAME_EQUALS; 188 | return compare(name, name_len, c->name, case_sensetive) == COMPARE_EQUAL ? CMD_NAME_EQUALS : CMD_NAME_UNEQUALS; 189 | } 190 | 191 | int cmd_equals(cmd* a, cmd* b, int case_sensetive) { 192 | if (!a || !b) return CMD_NAME_UNEQUALS; 193 | if (a == b) return CMD_NAME_EQUALS; 194 | return cmd_name_equals(a, b->name, strlen(b->name), case_sensetive); 195 | } 196 | 197 | // Parser 198 | cmd_error* cmd_parse(cmd* c, line_node* n) { 199 | if (!c || !n) return cmd_error_create_null_ptr(c); 200 | if (!n->words || (n->words->size == 0) || !n->words->first) return cmd_error_create_empty_line(c); 201 | 202 | word_list* wl = n->words; 203 | word_node* cmd_name = wl->first; 204 | word_node* first_arg = cmd_name->next; 205 | 206 | // Check if name equals command name 207 | if (compare(cmd_name->str, cmd_name->len, c->name, c->case_sensetive) == COMPARE_UNEQUAL) return cmd_error_create_not_found(c, cmd_name); 208 | 209 | // When command boundless, set all words as anonymous args 210 | if (c->mode == CMD_BOUNDLESS) { 211 | // Delete all old args 212 | arg_destroy_rec(c->arg_list); 213 | c->arg_list = NULL; 214 | 215 | // Fill command with an anonymous arg for each word 216 | word_node* w = first_arg; 217 | 218 | while (w) { 219 | arg* a = arg_create_req_positional(NULL); 220 | arg_set_value(a, w->str, w->len); 221 | cmd_add_arg(c, a); 222 | w = w->next; 223 | } 224 | 225 | return cmd_error_create_parse_success(c); 226 | } 227 | 228 | // When command single-arg, set full string as first arg 229 | if (c->mode == CMD_SINGLE) { 230 | if (!c->arg_list) c->arg_list = arg_create_opt_positional(NULL, NULL); 231 | if (wl->size > 1) arg_set_value(c->arg_list, first_arg->str, n->len - cmd_name->len - 1); 232 | return cmd_error_create_parse_success(c); 233 | } 234 | 235 | // Go through all words and try to find a matching arg 236 | word_node* w = first_arg; 237 | word_node* nw = w ? w->next : NULL; 238 | 239 | while (w) { 240 | // Look for arg which matches the word name 241 | arg* a = c->arg_list; 242 | char prefix = w->str[0]; 243 | 244 | while (a) { 245 | if (a->set == ARG_UNSET) { 246 | if ((prefix != '-') && (a->mode == ARG_POS)) break; 247 | if ((prefix == '-') && (compare(&w->str[1], w->len - 1, a->name, c->case_sensetive) == COMPARE_EQUAL)) break; 248 | } 249 | a = a->next; 250 | } 251 | 252 | // No mathing arg found 253 | if (!a) return cmd_error_create_unknown_arg(c, w); 254 | 255 | switch (a->mode) { 256 | // Anonym, Template Arg -> value = value 257 | case ARG_POS: 258 | if (prefix != '-') { 259 | arg_set_value(a, w->str, w->len); 260 | break; 261 | } 262 | 263 | // Default Arg -> value in next word 264 | case ARG_DEFAULT: 265 | if (!nw) return cmd_error_create_missing_arg(c, a); 266 | if (arg_set_value(a, nw->str, nw->len) == ARG_VALUE_FAIL) return cmd_error_create_unclosed_quote(c, a, nw); 267 | w = w->next; 268 | nw = w ? w->next : NULL; 269 | break; 270 | 271 | // Empty Arg -> no value 272 | case ARG_FLAG: 273 | arg_set_value(a, NULL, 0); 274 | break; 275 | } 276 | 277 | // Next word 278 | if (w) { 279 | w = w->next; 280 | nw = w ? w->next : NULL; 281 | } 282 | } 283 | 284 | // Check if all required args have been set 285 | arg* a = c->arg_list; 286 | 287 | while (a) { 288 | if (a->req && !a->set) { 289 | return cmd_error_create_missing_arg(c, a); 290 | } 291 | a = a->next; 292 | } 293 | 294 | return cmd_error_create_parse_success(c); 295 | } 296 | 297 | // Getter 298 | const char* cmd_get_description(cmd* c) { 299 | if (!c) return NULL; 300 | return c->description; 301 | } 302 | 303 | // Setter 304 | void cmd_set_description(cmd* c, const char* description) { 305 | if (c) { 306 | c->description = description; 307 | } 308 | } -------------------------------------------------------------------------------- /src/c/cmd.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019 Stefan Kremser 3 | This software is licensed under the MIT License. See the license file for details. 4 | Soruce: github.com/spacehuhn/SimpleCLI 5 | */ 6 | 7 | #ifndef cmd_h 8 | #define cmd_h 9 | 10 | #include "c/cmd_types.h" // cmd, cmd_parse_error 11 | #include "c/parser_types.h" // line_node, word_list, word_node, ... 12 | #include "c/cmd_error_types.h" // cmd_error 13 | 14 | // ===== CMD ===== // 15 | 16 | // Constructors 17 | cmd* cmd_create(const char* name, unsigned int mode); 18 | cmd* cmd_create_default(const char* name); 19 | cmd* cmd_create_boundless(const char* name); 20 | cmd* cmd_create_single(const char* name); 21 | 22 | // Copy & Move Constructors 23 | cmd* cmd_copy(cmd* c); 24 | cmd* cmd_copy_rec(cmd* c); 25 | cmd* cmd_move(cmd* c); 26 | cmd* cmd_move_rec(cmd* c); 27 | 28 | // Destructors 29 | cmd* cmd_destroy(cmd* c); 30 | cmd* cmd_destroy_rec(cmd* c); 31 | 32 | // Push CMD and Push Arg 33 | cmd* cmd_push(cmd* l, cmd* c, int max_size); 34 | cmd* cmd_add_arg(cmd* c, arg* a); 35 | 36 | // Reset CMD 37 | void cmd_reset_cli(cmd* c); 38 | void cmd_reset_cli_rec(cmd* c); 39 | 40 | // Comparisons 41 | int cmd_name_equals(cmd* c, const char* name, size_t name_len, int case_sensetive); 42 | int cmd_equals(cmd* a, cmd* b, int case_sensetive); 43 | 44 | // Parser 45 | cmd_error* cmd_parse(cmd* c, line_node* n); 46 | 47 | // Getter 48 | const char* cmd_get_description(cmd* c); 49 | 50 | // Setter 51 | void cmd_set_description(cmd* c, const char* description); 52 | 53 | #endif /* ifndef cmd_h */ -------------------------------------------------------------------------------- /src/c/cmd_error.c: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019 Stefan Kremser 3 | This software is licensed under the MIT License. See the license file for details. 4 | Source: github.com/spacehuhn/SimpleCLI 5 | */ 6 | 7 | #include "c/cmd_error.h" 8 | #include // malloc, memcpy 9 | #include // strlen, strcpy 10 | 11 | // ===== CMD Parse Error ===== // 12 | 13 | // Constructors 14 | cmd_error* cmd_error_create(int mode, cmd* command, arg* argument, word_node* data) { 15 | cmd_error* e = (cmd_error*)malloc(sizeof(cmd_error)); 16 | 17 | e->mode = mode; 18 | e->command = command; 19 | e->argument = argument; 20 | e->data = NULL; 21 | e->next = NULL; 22 | 23 | if (data && (data->len > 0)) { 24 | e->data = (char*)malloc(data->len + 1); 25 | memcpy(e->data, data->str, data->len); 26 | e->data[data->len] = '\0'; 27 | } 28 | 29 | return e; 30 | } 31 | 32 | cmd_error* cmd_error_create_null_ptr(cmd* c) { 33 | return cmd_error_create(CMD_NULL_PTR, c, NULL, NULL); 34 | }; 35 | 36 | cmd_error* cmd_error_create_empty_line(cmd* c) { 37 | return cmd_error_create(CMD_EMPTY_LINE, c, NULL, NULL); 38 | } 39 | 40 | cmd_error* cmd_error_create_parse_success(cmd* c) { 41 | return cmd_error_create(CMD_PARSE_SUCCESS, c, NULL, NULL); 42 | } 43 | 44 | cmd_error* cmd_error_create_not_found(cmd* c, word_node* cmd_name) { 45 | return cmd_error_create(CMD_NOT_FOUND, c, NULL, cmd_name); 46 | } 47 | 48 | cmd_error* cmd_error_create_unknown_arg(cmd* c, word_node* arg_name) { 49 | return cmd_error_create(CMD_UNKOWN_ARG, c, NULL, arg_name); 50 | } 51 | 52 | cmd_error* cmd_error_create_missing_arg(cmd* c, arg* a) { 53 | return cmd_error_create(CMD_MISSING_ARG, c, a, NULL); 54 | } 55 | 56 | cmd_error* cmd_error_create_unclosed_quote(cmd* c, arg* a, word_node* arg_value) { 57 | return cmd_error_create(CMD_UNCLOSED_QUOTE, c, a, arg_value); 58 | } 59 | 60 | // Copy Constructors 61 | cmd_error* cmd_error_copy(cmd_error* e) { 62 | if (!e) return NULL; 63 | 64 | cmd_error* ne = (cmd_error*)malloc(sizeof(cmd_error)); 65 | 66 | ne->mode = e->mode; 67 | ne->command = e->command; 68 | ne->argument = e->argument; 69 | ne->data = e->data; 70 | ne->next = e->next; 71 | 72 | if (ne->data) { 73 | ne->data = (char*)malloc(strlen(e->data) + 1); 74 | strcpy(ne->data, e->data); 75 | } 76 | 77 | return ne; 78 | } 79 | 80 | cmd_error* cmd_error_copy_rec(cmd_error* e) { 81 | if (!e) return NULL; 82 | 83 | cmd_error* ne = cmd_error_copy(e); 84 | ne->next = cmd_error_copy_rec(e->next); 85 | 86 | return ne; 87 | } 88 | 89 | // Destructors 90 | cmd_error* cmd_error_destroy(cmd_error* e) { 91 | if (e) { 92 | if (e->data) free(e->data); 93 | free(e); 94 | } 95 | return NULL; 96 | } 97 | 98 | cmd_error* cmd_error_destroy_rec(cmd_error* e) { 99 | if (e) { 100 | cmd_error_destroy_rec(e->next); 101 | cmd_error_destroy(e); 102 | } 103 | return NULL; 104 | } 105 | 106 | // Push 107 | cmd_error* cmd_error_push(cmd_error* l, cmd_error* e, int max_size) { 108 | if (max_size < 1) { 109 | cmd_error_destroy_rec(l); 110 | cmd_error_destroy(e); 111 | return NULL; 112 | } 113 | 114 | if (!l) return e; 115 | 116 | cmd_error* h = l; 117 | int i = 1; 118 | 119 | while (h->next) { 120 | h = h->next; 121 | ++i; 122 | } 123 | 124 | h->next = e; 125 | 126 | // Remove first element if list is too big 127 | if (i > max_size) { 128 | cmd_error* ptr = l; 129 | l = l->next; 130 | cmd_error_destroy(ptr); 131 | } 132 | 133 | return l; 134 | } -------------------------------------------------------------------------------- /src/c/cmd_error.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019 Stefan Kremser 3 | This software is licensed under the MIT License. See the license file for details. 4 | Source: github.com/spacehuhn/SimpleCLI 5 | */ 6 | 7 | #ifndef cmd_error_h 8 | #define cmd_error_h 9 | 10 | #include "c/cmd_error_types.h" // cmd_error 11 | 12 | // ===== CMD Parse Error ===== // 13 | 14 | // Constructors 15 | cmd_error* cmd_error_create(int mode, cmd* command, arg* argument, word_node* data); 16 | cmd_error* cmd_error_create_null_ptr(cmd* c); 17 | cmd_error* cmd_error_create_empty_line(cmd* c); 18 | cmd_error* cmd_error_create_parse_success(cmd* c); 19 | cmd_error* cmd_error_create_not_found(cmd* c, word_node* cmd_name); 20 | cmd_error* cmd_error_create_unknown_arg(cmd* c, word_node* arg_name); 21 | cmd_error* cmd_error_create_missing_arg(cmd* c, arg* a); 22 | cmd_error* cmd_error_create_unclosed_quote(cmd* c, arg* a, word_node* arg_value); 23 | 24 | // Copy Constructors 25 | cmd_error* cmd_error_copy(cmd_error* e); 26 | cmd_error* cmd_error_copy_rec(cmd_error* e); 27 | 28 | // Destructors 29 | cmd_error* cmd_error_destroy(cmd_error* e); 30 | cmd_error* cmd_error_destroy_rec(cmd_error* e); 31 | 32 | // Push 33 | cmd_error* cmd_error_push(cmd_error* l, cmd_error* e, int max_size); 34 | 35 | #endif /* ifndef cmd_error_h */ -------------------------------------------------------------------------------- /src/c/cmd_error_types.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019 Stefan Kremser 3 | This software is licensed under the MIT License. See the license file for details. 4 | Source: github.com/spacehuhn/SimpleCLI 5 | */ 6 | 7 | #ifndef cmd_error_types_h 8 | #define cmd_error_types_h 9 | 10 | #include "c/cmd_types.h" // cmd, arg 11 | #include "c/parser_types.h" // word_node 12 | 13 | #define CMD_NULL_PTR -2 14 | #define CMD_EMPTY_LINE -1 15 | #define CMD_PARSE_SUCCESS 0 16 | #define CMD_NOT_FOUND 1 17 | #define CMD_UNKOWN_ARG 2 18 | #define CMD_MISSING_ARG 3 19 | #define CMD_MISSING_ARG_VALUE 4 20 | #define CMD_UNCLOSED_QUOTE 5 21 | 22 | typedef struct cmd_error { 23 | int mode; 24 | cmd * command; 25 | arg * argument; 26 | char * data; 27 | struct cmd_error* next; 28 | } cmd_error; 29 | 30 | #endif /* ifndef cmd_error_types_h */ -------------------------------------------------------------------------------- /src/c/cmd_types.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019 Stefan Kremser 3 | This software is licensed under the MIT License. See the license file for details. 4 | Source: github.com/spacehuhn/SimpleCLI 5 | */ 6 | 7 | #ifndef cmd_types_h 8 | #define cmd_types_h 9 | 10 | #include "c/arg_types.h" // arg 11 | 12 | #define CMD_DEFAULT 0 13 | #define CMD_BOUNDLESS 1 14 | #define CMD_SINGLE 2 15 | 16 | #define CMD_NAME_UNEQUALS 0 17 | #define CMD_NAME_EQUALS 1 18 | 19 | #define CMD_CASE_INSENSETIVE 0 20 | #define CMD_CASE_SENSETIVE 1 21 | 22 | typedef struct cmd { 23 | const char * name; 24 | unsigned int mode : 2; 25 | struct arg * arg_list; 26 | unsigned int case_sensetive : 1; 27 | void (* callback)(struct cmd* c); 28 | const char* description; 29 | struct cmd* next; 30 | } cmd; 31 | 32 | #endif /* ifndef cmd_types_h */ -------------------------------------------------------------------------------- /src/c/comparator.c: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019 Stefan Kremser 3 | This software is licensed under the MIT License. See the license file for details. 4 | Source: github.com/spacehuhn/SimpleCLI 5 | */ 6 | 7 | #include // strlen 8 | 9 | #include "c/comparator.h" 10 | 11 | // My own implementation, because the default one in ctype.h make problems on older ESP8266 SDKs 12 | char to_lower(char c) { 13 | if ((c >= 65) && (c <= 90)) { 14 | return (char)(c + 32); 15 | } 16 | return c; 17 | } 18 | 19 | int compare(const char* user_str, size_t user_str_len, const char* templ_str, int case_sensetive) { 20 | if (user_str == templ_str) return COMPARE_EQUAL; 21 | 22 | // null check string pointers 23 | if (!user_str || !templ_str) return COMPARE_UNEQUAL; 24 | 25 | // string lengths 26 | size_t str_len = user_str_len; // strlen(user_str); 27 | size_t key_len = strlen(templ_str); 28 | 29 | // when same length, it there is no need to check for slashes or commas 30 | if (str_len == key_len) { 31 | for (size_t i = 0; i < key_len; i++) { 32 | if (case_sensetive == COMPARE_CASE_SENSETIVE) { 33 | if (user_str[i] != templ_str[i]) return COMPARE_UNEQUAL; 34 | } else { 35 | if (to_lower(user_str[i]) != to_lower(templ_str[i])) return COMPARE_UNEQUAL; 36 | } 37 | } 38 | return COMPARE_EQUAL; 39 | } 40 | 41 | // string can't be longer than templ_str (but can be smaller because of '/' and ',') 42 | if (str_len > key_len) return COMPARE_UNEQUAL; 43 | 44 | unsigned int res_i = 0; 45 | unsigned int a = 0; 46 | unsigned int b = 0; 47 | unsigned int res = 1; 48 | 49 | while (a < str_len && b < key_len) { 50 | if (templ_str[b] == '/') { 51 | // skip slash in templ_str 52 | ++b; 53 | } else if (templ_str[b] == ',') { 54 | // on comma increment res_i and reset str-index 55 | ++b; 56 | a = 0; 57 | ++res_i; 58 | } 59 | 60 | // compare character 61 | if (case_sensetive == COMPARE_CASE_SENSETIVE) { 62 | if (user_str[a] != templ_str[b]) res = 0; 63 | } else { 64 | if (to_lower(user_str[a]) != to_lower(templ_str[b])) res = 0; 65 | } 66 | 67 | // comparison incorrect or string checked until the end and templ_str not checked until the end 68 | if (!res || ((a == str_len - 1) && 69 | (templ_str[b + 1] != ',') && 70 | (templ_str[b + 1] != '/') && 71 | (templ_str[b + 1] != '\0'))) { 72 | // fast forward to next comma 73 | while (b < key_len && templ_str[b] != ',') b++; 74 | res = 1; 75 | } else { 76 | // otherwise icrement indices 77 | ++a; 78 | ++b; 79 | } 80 | } 81 | 82 | // comparison correct AND string checked until the end AND templ_str checked until the end 83 | if (res && (a == str_len) && 84 | ((templ_str[b] == ',') || 85 | (templ_str[b] == '/') || 86 | (templ_str[b] == '\0'))) return COMPARE_EQUAL; // res_i 87 | 88 | return COMPARE_UNEQUAL; 89 | } -------------------------------------------------------------------------------- /src/c/comparator.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019 Stefan Kremser 3 | This software is licensed under the MIT License. See the license file for details. 4 | Source: github.com/spacehuhn/SimpleCLI 5 | */ 6 | 7 | #ifndef comparator_h 8 | #define comparator_h 9 | 10 | #include // size_t 11 | 12 | #define COMPARE_UNEQUAL 0 13 | #define COMPARE_EQUAL 1 14 | 15 | #define COMPARE_CASE_INSENSETIVE 0 16 | #define COMPARE_CASE_SENSETIVE 1 17 | 18 | int compare(const char* user_str, size_t user_str_len, const char* templ_str, int case_sensetive); 19 | 20 | #endif /* ifndef comparator_h */ -------------------------------------------------------------------------------- /src/c/parser.c: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019 Stefan Kremser 3 | This software is licensed under the MIT License. See the license file for details. 4 | Source: github.com/spacehuhn/SimpleCLI 5 | */ 6 | 7 | #include "c/parser.h" 8 | 9 | #include // malloc 10 | 11 | // ===== Word Node ===== // 12 | word_node* word_node_create(const char* str, size_t len) { 13 | word_node* n = (word_node*)malloc(sizeof(word_node)); 14 | 15 | n->str = str; 16 | n->len = len; 17 | n->next = NULL; 18 | return n; 19 | } 20 | 21 | word_node* word_node_destroy(word_node* n) { 22 | if (n) { 23 | free(n); 24 | } 25 | return NULL; 26 | } 27 | 28 | word_node* word_node_destroy_rec(word_node* n) { 29 | if (n) { 30 | word_node_destroy_rec(n->next); 31 | word_node_destroy(n); 32 | } 33 | return NULL; 34 | } 35 | 36 | // ===== Word List ===== // 37 | word_list* word_list_create() { 38 | word_list* l = (word_list*)malloc(sizeof(word_list)); 39 | 40 | l->first = NULL; 41 | l->last = NULL; 42 | l->size = 0; 43 | return l; 44 | } 45 | 46 | word_list* word_list_destroy(word_list* l) { 47 | if (l) { 48 | word_node_destroy_rec(l->first); 49 | free(l); 50 | } 51 | return NULL; 52 | } 53 | 54 | void word_list_push(word_list* l, word_node* n) { 55 | if (l && n) { 56 | if (l->last) { 57 | l->last->next = n; 58 | } else { 59 | l->first = n; 60 | } 61 | 62 | l->last = n; 63 | ++(l->size); 64 | } 65 | } 66 | 67 | word_node* word_list_get(word_list* l, size_t i) { 68 | if (!l) return NULL; 69 | 70 | size_t j; 71 | word_node* h = l->first; 72 | 73 | for (j = 0; j < i && h; ++j) { 74 | h = h->next; 75 | } 76 | 77 | return h; 78 | } 79 | 80 | // ===== Line Node ==== // 81 | line_node* line_node_create(const char* str, size_t len) { 82 | line_node* n = (line_node*)malloc(sizeof(line_node)); 83 | 84 | n->str = str; 85 | n->len = len; 86 | n->words = NULL; 87 | n->next = NULL; 88 | 89 | return n; 90 | } 91 | 92 | word_node* line_node_destroy(line_node* n) { 93 | if (n) { 94 | word_list_destroy(n->words); 95 | free(n); 96 | } 97 | return NULL; 98 | } 99 | 100 | word_node* line_node_destroy_rec(line_node* n) { 101 | if (n) { 102 | line_node_destroy_rec(n->next); 103 | line_node_destroy(n); 104 | } 105 | return NULL; 106 | } 107 | 108 | // ===== Line List ===== // 109 | line_list* line_list_create() { 110 | line_list* l = (line_list*)malloc(sizeof(line_list)); 111 | 112 | l->first = NULL; 113 | l->last = NULL; 114 | l->size = 0; 115 | 116 | return l; 117 | } 118 | 119 | line_list* line_list_destroy(line_list* l) { 120 | if (l) { 121 | line_node_destroy_rec(l->first); 122 | free(l); 123 | } 124 | return NULL; 125 | } 126 | 127 | void line_list_push(line_list* l, line_node* n) { 128 | if (l && n) { 129 | if (l->last) { 130 | l->last->next = n; 131 | } else { 132 | l->first = n; 133 | } 134 | 135 | l->last = n; 136 | 137 | ++(l->size); 138 | } 139 | } 140 | 141 | line_node* line_list_get(line_list* l, size_t i) { 142 | if (!l) return NULL; 143 | 144 | size_t j; 145 | line_node* h = l->first; 146 | 147 | for (j = 0; j < i && h; ++j) { 148 | h = h->next; 149 | } 150 | 151 | return h; 152 | } 153 | 154 | // ===== Parser ===== // 155 | word_list* parse_words(const char* str, size_t len) { 156 | word_list* l = word_list_create(); 157 | 158 | if (len == 0) return l; 159 | 160 | // Go through string and look for space to split it into words 161 | word_node* n = NULL; 162 | 163 | size_t i = 0; // current index 164 | size_t j = 0; // start index of word 165 | 166 | int escaped = 0; 167 | int ignore_space = 0; 168 | 169 | for (i = 0; i <= len; ++i) { 170 | if ((str[i] == '\\') && (escaped == 0)) { 171 | escaped = 1; 172 | } else if ((str[i] == '"') && (escaped == 0)) { 173 | ignore_space = !ignore_space; 174 | } else if ((i == len) || ((str[i] == ' ') && (ignore_space == 0) && (escaped == 0))) { 175 | size_t k = i - j; // length of word 176 | 177 | // for every word, add to list 178 | if (k > 0) { 179 | n = word_node_create(&str[j], k); 180 | word_list_push(l, n); 181 | } 182 | 183 | j = i + 1; // reset start index of word 184 | } else if (escaped == 1) { 185 | escaped = 0; 186 | } 187 | } 188 | 189 | return l; 190 | } 191 | 192 | line_list* parse_lines(const char* str, size_t len) { 193 | line_list* l = line_list_create(); 194 | 195 | if (len == 0) return l; 196 | 197 | // Go through string and look for /r and /n to split it into lines 198 | line_node* n = NULL; 199 | 200 | size_t i = 0; // current index 201 | size_t j = 0; // start index of line 202 | 203 | int ignore_delimiter = 0; 204 | int delimiter = 0; 205 | int linebreak = 0; 206 | int endofline = 0; 207 | 208 | for (i = 0; i <= len; ++i) { 209 | if ((str[i] == '"') && ((str[i-1] != '\\') || (i==0))) ignore_delimiter = !ignore_delimiter; 210 | 211 | delimiter = (str[i] == ';' && str[i+1] == ';' && !ignore_delimiter && (i == 0 || str[i-1] != '\\')); 212 | linebreak = ((str[i] == '\r') || (str[i] == '\n')) && !ignore_delimiter; 213 | endofline = (i == len); 214 | 215 | if (linebreak || endofline || delimiter) { 216 | size_t k = i - j; // length of line 217 | 218 | // for every line, parse_words and add to list 219 | if (k > 0) { 220 | n = line_node_create(&str[j], k); 221 | n->words = parse_words(&str[j], k); 222 | line_list_push(l, n); 223 | } 224 | 225 | if (delimiter) ++i; 226 | 227 | j = i+1; // reset start index of line 228 | } 229 | } 230 | 231 | return l; 232 | } -------------------------------------------------------------------------------- /src/c/parser.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019 Stefan Kremser 3 | This software is licensed under the MIT License. See the license file for details. 4 | Source: github.com/spacehuhn/SimpleCLI 5 | */ 6 | 7 | #ifndef parser_h 8 | #define parser_h 9 | 10 | #include "c/parser_types.h" 11 | 12 | // ===== Word Node ===== // 13 | word_node* word_node_create(const char* str, size_t len); 14 | word_node* word_node_destroy(word_node* n); 15 | word_node* word_node_destroy_rec(word_node* n); 16 | 17 | // ===== Word List ===== // 18 | word_list* word_list_create(); 19 | word_list* word_list_destroy(word_list* l); 20 | 21 | void word_list_push(word_list* l, word_node* n); 22 | word_node* word_list_get(word_list* l, size_t i); 23 | 24 | // ===== Line Node ==== // 25 | line_node* line_node_create(const char* str, size_t len); 26 | word_node* line_node_destroy(line_node* n); 27 | word_node* line_node_destroy_rec(line_node* n); 28 | 29 | // ===== Line List ===== // 30 | line_list* line_list_create(); 31 | line_list* line_list_destroy(line_list* l); 32 | 33 | void line_list_push(line_list* l, line_node* n); 34 | line_node* line_list_get(line_list* l, size_t i); 35 | 36 | // ===== Parser ===== // 37 | word_list* parse_words(const char* str, size_t len); 38 | line_list* parse_lines(const char* str, size_t len); 39 | 40 | #endif /* ifndef parser_h */ -------------------------------------------------------------------------------- /src/c/parser_types.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019 Stefan Kremser 3 | This software is licensed under the MIT License. See the license file for details. 4 | Source: github.com/spacehuhn/SimpleCLI 5 | */ 6 | 7 | #ifndef parser_types_h 8 | #define parser_types_h 9 | 10 | #include // size_t 11 | 12 | typedef struct word_node { 13 | const char * str; 14 | size_t len; 15 | struct word_node* next; 16 | } word_node; 17 | 18 | typedef struct word_list { 19 | struct word_node* first; 20 | struct word_node* last; 21 | size_t size; 22 | } word_list; 23 | 24 | typedef struct line_node { 25 | const char * str; 26 | size_t len; 27 | struct word_list* words; 28 | struct line_node* next; 29 | } line_node; 30 | 31 | typedef struct line_list { 32 | struct line_node* first; 33 | struct line_node* last; 34 | size_t size; 35 | } line_list; 36 | 37 | #endif /* ifndef parser_types_h */ --------------------------------------------------------------------------------