├── .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 |

4 |
5 |
6 | A Command Line Interface Library for Arduino!
7 | Add commands to your project without hassle.
8 |
9 | 
10 |
11 |
12 |
13 |
14 | 
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 | [](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 | 
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 | ""
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 */
--------------------------------------------------------------------------------