CLI.cpp
6.43 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
/*
* CLI.cpp - CLI library for Arduino/ESP8266 and others implementation
*
* Version 1.0, latest version, documentation and bugtracker available at:
* https://gitlab.lindenaar.net/arduino/CLI
*
* Copyright (c) 2019 Frederik Lindenaar
*
* This library is free software: you can redistribute it and/or modify it under
* the terms of version 3 of the GNU General Public License as published by the
* Free Software Foundation, or (at your option) a later version of the license.
*
* This code is distributed in the hope that it will be useful but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
* A PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with
* this program. If not, visit <http://www.gnu.org/licenses/> to download it.
*/
#include <CLI.h>
#define CHAR_TAB '\t'
#define CHAR_BS '\b'
#define CHAR_DELETE char(127)
#define CHAR_CR '\r'
#define CHAR_LF '\n'
const char CLI_DEFAULT_PROMPT[] PROGMEM = "\n> ";
const char CLI_PREFIX_PARAMETER_ERROR[] PROGMEM = "Invalid parameters for ";
const char CLI_PREFIX_UNKNOWN_COMMAND[] PROGMEM = "Unknown command ";
size_t CLI::print_P (const char *str PROGMEM) {
size_t len = 0;
if (str) {
while (char c = pgm_read_byte(str++)) {
write(c);
len++;
}
}
return len;
}
void CLI::print2digits(uint8_t num, char filler, uint8_t base) {
if (num < base) print(filler);
print(num, base);
}
void CLI::print_mem(uint16_t addr, const uint8_t buff[], uint8_t len, uint8_t width) {
if (addr < 0x1000) print(' ');
if (addr < 0x100) print(' ');
if (addr < 0x10) print(' ');
print(addr, HEX);
print(':');
for (uint8_t i = 0; i < len; i++) {
print(' ');
if (buff[i] < 0x10) print(0);
print(buff[i], HEX);
}
while (width > len++) print_P(PSTR(" "));
print('\t');
for (uint8_t i = 0; i < len; i++)
write(isprint(buff[i]) ? buff[i] : '.');
println();
}
void CLI::printtab() {
print(CHAR_TAB);
};
CLI::CLI(Stream &stream, const char *banner PROGMEM, const char *prompt PROGMEM) : _stream(&stream), _banner(banner) {
_state = STATE_INIT;
_command_count = 0;
_prompt = (prompt) ? prompt : CLI_DEFAULT_PROMPT;
};
void CLI::add_command(CLI_Command *cmd) {
if (_command_count < CLI_MAX_CMDS)
_commands[_command_count++] = cmd;
};
CLI_Command *CLI::get_command(int index) {
return (index < _command_count) ? _commands[index] : NULL;
}
CLI_Command *CLI::get_command(const char *name, int namelen) {
for (int i = 0; i < _command_count; i++) {
if (_commands[i]->matches(name, namelen)) return _commands[i];
}
return NULL;
}
#if CLI_MAX_CMDS < 255
uint8_t CLI::find_command(const char *name, int namelen) {
uint8_t idx = _command_count;
#else
uint16_t CLI::find_command(const char *name, int namelen) {
uint16_t idx = _command_count;
#endif
while (idx-- && !_commands[idx]->matches(name, namelen));
return idx;
}
bool CLI::process() { // Workhorse of the CLI
if (_state == STATE_INIT) { // INIT: print banner (if set)
if (_banner) {
print_P(_banner);
println();
}
_state = STATE_READY;
} else if (_state == STATE_READY) { // READY: print prompt & init
print_P(_prompt);
memset(_cmdbuffer, 0, _line_len);
_line_len = 0;
_current_cmd = NULL;
_state = STATE_INPUT;
} else if (_state == STATE_INPUT) { // INPUT: process user input
while (available()) { // loop while stream input
int c = read(); // get next char
if (c == CHAR_DELETE) { // Process BACKSPACE key
print_P(PSTR("\b \b"));
if (_line_len) _cmdbuffer[_line_len--] = 0;
} else if (c == CHAR_CR) { // Process ENTER key
println();
_state = STATE_PARSE; // Change state to PARSE
return false; // and stop processing input
} else if (c != CHAR_LF && isprint(c)) { // Process other valid input
if (_line_len < CLI_MAX_LINE - 1) { // add to line if space left
_cmdbuffer[_line_len++] = c;
write(c); // print char to echo input
} else { // if not, beep & ignore
write(char(7));
}
}
}
} else if (_state == STATE_PARSE) { // PARSE: parse user command
const char *cmd = CLI_STR_skipSep(_cmdbuffer); // skip initial whitespace
if (*cmd && cmd - _cmdbuffer < _line_len) { // do we have a command?
for (char *p = _cmdbuffer + _line_len - 1; *p == ' '; p--, _line_len--)
*p = 0; // clear trailing whitespace
const char *sep = CLI_STR_findSep(cmd); // find end of command
if (_current_cmd = get_command(cmd, sep - cmd)) { // known command?
sep = CLI_STR_skipSep(sep); // get start of params
if (_current_cmd->setparams((*sep) ? sep : 0)) {// parse params
_state = STATE_EXEC; // all OK, change state
return true; // and stop processing
} else print_P(CLI_PREFIX_PARAMETER_ERROR); // print invalid parameters
} else print_P(CLI_PREFIX_UNKNOWN_COMMAND) ; // print unknown command
_stream->write(cmd, sep - cmd); // print command name
println(); // and add newline
}
_state = STATE_READY; // if we get here: READY
} else if (_state == STATE_EXEC) { // EXEC: execute the command
if (_current_cmd->execute(*this)) { // Execute, false when done
return true; // not yet done, return true
} else {
_state = STATE_READY; // done, READY
}
}
return false; // default result false (done)
}
CLI_Command::CLI_Command(CLI &cli, const char *command, const char *description, const char *usagehelp) : command(command), description(description), usagehelp(usagehelp) {
cli.add_command(this);
};
bool CLI_Command::matches(const char *cmd, uint8_t cmdlen) {
return pgm_read_byte(&command[cmdlen]) == 0 && strncmp_P(cmd, command, cmdlen) == 0;
};
bool CLI_Command::setparams(const char *params) {
return !params;
}