My Project
shell.cpp
1 /*
2  * uuid-console - Microcontroller console shell
3  * Copyright 2019,2021-2022 Simon Arlott
4  *
5  * This program is free software: you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation, either version 3 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program. If not, see <http://www.gnu.org/licenses/>.
17  */
18 
19 #include <uuid/console.h>
20 
21 #include <Arduino.h>
22 #include <stdarg.h>
23 
24 #include <algorithm>
25 #include <memory>
26 #include <set>
27 #include <string>
28 #include <vector>
29 
30 #include <uuid/common.h>
31 #include <uuid/log.h>
32 
33 #if !defined(__cpp_lib_make_unique) && !defined(DOXYGEN)
34 namespace std {
35 
36 template<typename _Tp, typename... _Args>
37 inline unique_ptr<_Tp> make_unique(_Args&&... __args) {
38  return unique_ptr<_Tp>(new _Tp(std::forward<_Args>(__args)...));
39 }
40 
41 } // namespace std
42 #endif
43 
44 namespace uuid {
45 
46 namespace console {
47 
48 // cppcheck-suppress passedByValue
49 Shell::Shell(Stream &stream, std::shared_ptr<Commands> commands, unsigned int context, unsigned int flags)
50  : stream_(stream), commands_(std::move(commands)), flags_(flags) {
52 }
53 
54 void Shell::start() {
55  uuid::log::Logger::register_handler(this, uuid::log::Level::NOTICE);
59  registered_shells().insert(shared_from_this());
61  started();
62 };
63 
65 
66 }
67 
68 bool Shell::running() const {
69  return !stopped_;
70 }
71 
72 void Shell::stop() {
73  if (mode_ == Mode::BLOCKING) {
74  auto *blocking_data = reinterpret_cast<Shell::BlockingData*>(mode_data_.get());
75 
76  blocking_data->stop_ = true;
77  } else {
78  if (running()) {
79  stopped_ = true;
80  stopped();
81  }
82  }
83 }
84 
86 
87 }
88 
90  if (context_.size() > 1) {
91  context_.pop_back();
92  return true;
93  } else {
94  return false;
95  }
96 }
97 
99  return commands_->available_commands(*this);
100 }
101 
103  if (!running()) {
104  return;
105  }
106 
107  switch (mode_) {
108  case Mode::NORMAL:
109  output_logs();
110  loop_normal();
111  break;
112 
113  case Mode::PASSWORD:
114  output_logs();
115  loop_password();
116  break;
117 
118  case Mode::DELAY:
119  output_logs();
120  loop_delay();
121  break;
122 
123  case Mode::BLOCKING:
124  loop_blocking();
125  break;
126  }
127 }
128 
130  const int input = stream_.read();
131 
132  if (input < 0) {
134  return;
135  }
136 
137  const unsigned char c = input;
138 
139  switch (c) {
140  case '\x03':
141  // Interrupt (^C)
142  line_buffer_.clear();
143  println();
144  prompt_displayed_ = false;
145  display_prompt();
146  break;
147 
148  case '\x04':
149  // End of transmission (^D)
150  if (line_buffer_.empty()) {
152  }
153  break;
154 
155  case '\x08':
156  case '\x7F':
157  // Backspace (^H)
158  // Delete (^?)
159  if (!line_buffer_.empty()) {
160  erase_characters(1);
161  line_buffer_.pop_back();
162  }
163  break;
164 
165  case '\x09':
166  // Tab (^I)
168  break;
169 
170  case '\x0A':
171  // Line feed (^J)
172  if (previous_ != '\x0D') {
173  process_command();
174  }
175  break;
176 
177  case '\x0C':
178  // New page (^L)
180  prompt_displayed_ = false;
181  display_prompt();
182  break;
183 
184  case '\x0D':
185  // Carriage return (^M)
186  process_command();
187  break;
188 
189  case '\x15':
190  // Delete line (^U)
192  prompt_displayed_ = false;
193  line_buffer_.clear();
194  display_prompt();
195  break;
196 
197  case '\x17':
198  // Delete word (^W)
199  delete_buffer_word(true);
200  break;
201 
202  default:
203  if (c >= '\x20' && c <= '\x7E') {
204  // ASCII text
206  line_buffer_.push_back(c);
207  write((uint8_t)c);
208  }
209  }
210  break;
211  }
212 
213  previous_ = c;
214 
215  // This is a hack to let TelnetStream know that command
216  // execution is complete and that output can be flushed.
217  stream_.available();
218 
220 }
221 
222 Shell::PasswordData::PasswordData(const __FlashStringHelper *password_prompt, password_function &&password_function)
223  : password_prompt_(password_prompt), password_function_(std::move(password_function)) {
224 
225 }
226 
228  const int input = stream_.read();
229 
230  if (input < 0) {
232  return;
233  }
234 
235  const unsigned char c = input;
236 
237  switch (c) {
238  case '\x03':
239  // Interrupt (^C)
240  process_password(false);
241  break;
242 
243  case '\x08':
244  case '\x7F':
245  // Backspace (^H)
246  // Delete (^?)
247  if (!line_buffer_.empty()) {
248  line_buffer_.pop_back();
249  }
250  break;
251 
252  case '\x0A':
253  // Line feed (^J)
254  if (previous_ != '\x0D') {
255  process_password(true);
256  }
257  break;
258 
259  case '\x0C':
260  // New page (^L)
262  prompt_displayed_ = false;
263  display_prompt();
264  break;
265 
266  case '\x0D':
267  // Carriage return (^M)
268  process_password(true);
269  break;
270 
271  case '\x15':
272  // Delete line (^U)
273  line_buffer_.clear();
274  break;
275 
276  case '\x17':
277  // Delete word (^W)
278  delete_buffer_word(false);
279  break;
280 
281  default:
282  if (c >= '\x20' && c <= '\x7E') {
283  // ASCII text
285  line_buffer_.push_back(c);
286  }
287  }
288  break;
289  }
290 
291  previous_ = c;
292 
293  // This is a hack to let TelnetStream know that command
294  // execution is complete and that output can be flushed.
295  stream_.available();
296 
298 }
299 
301  : delay_time_(delay_time), delay_function_(std::move(delay_function)) {
302 
303 }
304 
306  auto *delay_data = reinterpret_cast<Shell::DelayData*>(mode_data_.get());
307 
308  if (uuid::get_uptime_ms() >= delay_data->delay_time_) {
309  auto function_copy = delay_data->delay_function_;
310 
312  mode_data_.reset();
313 
314  function_copy(*this);
315 
316  if (running()) {
317  display_prompt();
318  }
319 
321  }
322 }
323 
325  : blocking_function_(std::move(blocking_function)) {
326 
327 }
328 
330  auto *blocking_data = reinterpret_cast<Shell::BlockingData*>(mode_data_.get());
331 
332  /* It is not possible to change mode while executing this function,
333  * because that would require copying either the std::shared_ptr or
334  * the std::function on every loop execution (to ensure that the
335  * function captures aren't destroyed while executing).
336  */
337  if (blocking_data->blocking_function_(*this, blocking_data->stop_)) {
338  bool stop_pending = blocking_data->stop_;
339 
341  mode_data_.reset();
342 
343  if (stop_pending) {
344  stop();
345  }
346 
347  if (running()) {
348  display_prompt();
349  }
350 
352  }
353 }
354 
355 void Shell::enter_password(const __FlashStringHelper *prompt, password_function function) {
356  if (mode_ == Mode::NORMAL) {
358  mode_data_ = std::make_unique<Shell::PasswordData>(prompt, std::move(function));
359  }
360 }
361 
362 void Shell::delay_for(unsigned long ms, delay_function function) {
363  delay_until(uuid::get_uptime_ms() + ms, std::move(function));
364 }
365 
366 void Shell::delay_until(uint64_t ms, delay_function function) {
367  if (mode_ == Mode::NORMAL) {
368  mode_ = Mode::DELAY;
369  mode_data_ = std::make_unique<Shell::DelayData>(ms, std::move(function));
370  }
371 }
372 
374  if (mode_ == Mode::NORMAL) {
376  mode_data_ = std::make_unique<Shell::BlockingData>(std::move(function));
377  }
378 }
379 
380 void Shell::delete_buffer_word(bool display) {
381  size_t pos = line_buffer_.find_last_of(' ');
382 
383  if (pos == std::string::npos) {
384  line_buffer_.clear();
385  if (display) {
387  prompt_displayed_ = false;
388  display_prompt();
389  }
390  } else {
391  if (display) {
392  erase_characters(line_buffer_.length() - pos);
393  }
394  line_buffer_.resize(pos);
395  }
396 }
397 
400 }
401 
403  maximum_command_line_length_ = std::max((size_t)1, length);
405 }
406 
408  CommandLine command_line{line_buffer_};
409 
410  line_buffer_.clear();
411  println();
412  prompt_displayed_ = false;
413 
414  if (!command_line->empty()) {
415  if (commands_) {
416  auto execution = commands_->execute_command(*this, std::move(command_line));
417 
418  if (execution.error != nullptr) {
419  println(execution.error);
420  }
421  } else {
422  println(F("No commands configured"));
423  }
424  }
425 
426  if (running()) {
427  display_prompt();
428  }
429  ::yield();
430 }
431 
433  CommandLine command_line{line_buffer_};
434 
435  if (!command_line->empty() && commands_) {
436  auto completion = commands_->complete_command(*this, command_line);
437  bool redisplay = false;
438 
439  if (!completion.help.empty()) {
440  println();
441  redisplay = true;
442 
443  for (auto &help : completion.help) {
444  std::string help_line = help.to_string(maximum_command_line_length_);
445 
446  println(help_line);
447  }
448  }
449 
450  if (!completion.replacement->empty()) {
451  if (!redisplay) {
453  prompt_displayed_ = false;
454  redisplay = true;
455  }
456 
457  line_buffer_ = completion.replacement.to_string(maximum_command_line_length_);
458  }
459 
460  if (redisplay) {
461  display_prompt();
462  }
463  }
464 
465  ::yield();
466 }
467 
468 void Shell::process_password(bool completed) {
469  println();
470 
471  auto *password_data = reinterpret_cast<Shell::PasswordData*>(mode_data_.get());
472  auto function_copy = password_data->password_function_;
473 
475  mode_data_.reset();
476 
477  function_copy(*this, completed, line_buffer_);
478  line_buffer_.clear();
479 
480  if (running()) {
481  display_prompt();
482  }
483 }
484 
485 void Shell::invoke_command(const std::string &line) {
486  if (!line_buffer_.empty()) {
487  println();
488  prompt_displayed_ = false;
489  }
490  if (!prompt_displayed_) {
491  display_prompt();
492  }
493  line_buffer_ = line;
495  process_command();
496 }
497 
498 unsigned long Shell::idle_timeout() const {
499  return idle_timeout_ / 1000;
500 }
501 
502 void Shell::idle_timeout(unsigned long timeout) {
503  idle_timeout_ = (uint64_t)timeout * 1000;
504 }
505 
508  println();
509  stop();
510  }
511 }
512 
513 } // namespace console
514 
515 } // namespace uuid
uuid::console::Shell::maximum_command_line_length
size_t maximum_command_line_length() const
Get the maximum length of a command line.
Definition: shell.cpp:398
uuid::console::CommandLine
Representation of a command line, with parameters separated by spaces and an optional trailing space.
Definition: console.h:1642
uuid::console::Shell::available_commands
Commands::AvailableCommands available_commands() const
Get the available commands in the current context and with the current flags.
Definition: shell.cpp:98
uuid::console::Shell::end_of_transmission
virtual void end_of_transmission()
The end of transmission character has been received.
Definition: shell_prompt.cpp:47
uuid::console::Shell::display_prompt
void display_prompt()
Output a prompt on the shell.
Definition: shell_prompt.cpp:54
uuid::console::Shell::display_banner
virtual void display_banner()
Output the startup banner.
Definition: shell_prompt.cpp:27
uuid::console::Shell::BlockingData::BlockingData
BlockingData(blocking_function &&blocking_function)
Create Mode::DELAY shell mode data.
Definition: shell.cpp:324
uuid::console::Shell::idle_timeout_
uint64_t idle_timeout_
Definition: console.h:1633
uuid::console::Shell::print
size_t print(const std::string &data)
Output a string.
Definition: shell_print.cpp:30
uuid::console::Shell::PasswordData::password_function_
password_function password_function_
Definition: console.h:1422
uuid::console::Shell::enter_context
void enter_context(unsigned int context)
Push a new context onto the stack.
Definition: console.h:994
uuid::get_uptime_ms
uint64_t get_uptime_ms()
Get the current uptime as a 64-bit milliseconds value.
Definition: get_uptime_ms.cpp:30
uuid::console::Shell::commands_
std::shared_ptr< Commands > commands_
Definition: console.h:1616
uuid::console::Shell::exit_context
virtual bool exit_context()
Pop a context off the stack.
Definition: shell.cpp:89
uuid::console::Shell::delay_for
void delay_for(unsigned long ms, delay_function function)
Stop executing anything on this shell for a period of time.
Definition: shell.cpp:362
uuid::console::Shell::DelayData::delay_function_
delay_function delay_function_
Definition: console.h:1446
uuid::console::Shell::BlockingData::stop_
bool stop_
Definition: console.h:1469
uuid::console::Shell::delay_until
void delay_until(uint64_t ms, delay_function function)
Stop executing anything on this shell until a future time is reached.
Definition: shell.cpp:366
uuid::console::Shell::Mode::PASSWORD
@ PASSWORD
uuid::console::Shell::delay_function
std::function< void(Shell &shell)> delay_function
Function to handle the end of an execution delay.
Definition: console.h:795
uuid::console::Shell::stop
void stop()
Stop this shell from running.
Definition: shell.cpp:72
uuid::console::Shell::previous_
unsigned char previous_
Definition: console.h:1627
uuid::console::Shell::stopped_
bool stopped_
Definition: console.h:1630
uuid::console::Commands::AvailableCommands
Available commands in the shell's context with the current flags.
Definition: console.h:210
uuid::console::Shell::context
unsigned int context() const
Get the context at the top of the stack.
Definition: console.h:979
uuid::console::Shell::PasswordData
Data for the Mode::PASSWORD shell mode.
Definition: console.h:1405
uuid::console::Shell::println
size_t println(const std::string &data)
Output a string followed by CRLF end of line characters.
Definition: shell_print.cpp:38
uuid::console::Shell::line_buffer_
std::string line_buffer_
Definition: console.h:1625
uuid::console::Shell::idle_time_
uint64_t idle_time_
Definition: console.h:1632
uuid::console::Shell::Mode::BLOCKING
@ BLOCKING
uuid::console::Shell::mode_data_
std::unique_ptr< ModeData > mode_data_
Definition: console.h:1629
uuid::console::Shell::process_password
void process_password(bool completed)
Finish password entry.
Definition: shell.cpp:468
uuid::log::Logger::register_handler
static void register_handler(Handler *handler, Level level)
Register a log handler.
Definition: log.cpp:71
uuid::console::Shell::check_idle_timeout
void check_idle_timeout()
Check idle timeout expiry.
Definition: shell.cpp:506
uuid::console::Shell::blocking_function
std::function< bool(Shell &shell, bool stop)> blocking_function
Function to handle a blocking operation.
Definition: console.h:806
uuid::console::Shell::loop_one
void loop_one()
Perform one execution step of this shell.
Definition: shell.cpp:102
uuid::console::Shell::Shell
Shell(Stream &stream, std::shared_ptr< Commands > commands, unsigned int context=0, unsigned int flags=0)
Create a new Shell operating on a Stream with the given commands, default context and initial flags.
Definition: shell.cpp:49
uuid::console::Shell::process_command
void process_command()
Try to execute a command with the current command line.
Definition: shell.cpp:407
uuid::console::Shell::loop_delay
void loop_delay()
Perform one execution step in Mode::DELAY mode.
Definition: shell.cpp:305
uuid::console::Shell::Mode::DELAY
@ DELAY
uuid::console::Shell::stream_
Stream & stream_
Definition: console.h:1615
uuid
Common utilities.
Definition: get_uptime_ms.cpp:28
uuid::console::Shell::loop_blocking
void loop_blocking()
Perform one execution step in Mode::BLOCKING mode.
Definition: shell.cpp:329
uuid::console::Shell::delete_buffer_word
void delete_buffer_word(bool display)
Delete a word from the command line buffer.
Definition: shell.cpp:380
uuid::console::Shell::BlockingData
Data for the Mode::BLOCKING shell mode.
Definition: console.h:1454
uuid::console::Shell::write
size_t write(uint8_t data) final override
Write one byte to the output stream.
Definition: shell_stream.cpp:116
uuid::console::Shell::enter_password
void enter_password(const __FlashStringHelper *prompt, password_function function)
Prompt for a password to be entered on this shell.
Definition: shell.cpp:355
uuid::console::Shell::DelayData::DelayData
DelayData(uint64_t delay_time, delay_function &&delay_function)
Create Mode::DELAY shell mode data.
Definition: shell.cpp:300
uuid::console::Shell::registered_shells
static std::set< std::shared_ptr< Shell > > & registered_shells()
Get registered running shells to be executed.
Definition: shell_loop_all.cpp:28
uuid::console::Shell::DelayData
Data for the Mode::DELAY shell mode.
Definition: console.h:1430
uuid::console::Shell::erase_current_line
virtual void erase_current_line()
Output ANSI escape sequence to erase the current line.
Definition: shell_print.cpp:131
uuid::console::Shell::erase_characters
virtual void erase_characters(size_t count)
Output ANSI escape sequence to erase characters.
Definition: shell_print.cpp:135
uuid::console::Shell::invoke_command
void invoke_command(const std::string &line)
Invoke a command on the shell.
Definition: shell.cpp:485
uuid::console::Shell::PasswordData::PasswordData
PasswordData(const __FlashStringHelper *password_prompt, password_function &&password_function)
Create Mode::PASSWORD shell mode data.
Definition: shell.cpp:222
uuid::console::Shell::output_logs
void output_logs()
Output queued log messages for this shell.
Definition: shell_log.cpp:94
uuid::console::Shell::start
void start()
Perform startup process for this shell.
Definition: shell.cpp:54
uuid::console::Shell::stopped
virtual void stopped()
Stop request event.
Definition: shell.cpp:85
uuid::console::Shell::context_
std::deque< unsigned int > context_
Definition: console.h:1617
uuid::console::Shell::process_completion
void process_completion()
Try to complete a command from the current command line.
Definition: shell.cpp:432
uuid::console::Shell::loop_password
void loop_password()
Perform one execution step in Mode::PASSWORD mode.
Definition: shell.cpp:227
uuid::console::Shell::prompt_displayed_
bool prompt_displayed_
Definition: console.h:1631
uuid::console::Shell::mode_
Mode mode_
Definition: console.h:1628
uuid::console::Shell::started
virtual void started()
Startup complete event.
Definition: shell.cpp:64
uuid::console::Shell::loop_normal
void loop_normal()
Perform one execution step in Mode::NORMAL mode.
Definition: shell.cpp:129
uuid::console::Shell::idle_timeout
unsigned long idle_timeout() const
Get the idle timeout.
Definition: shell.cpp:498
uuid::console::Shell::Mode::NORMAL
@ NORMAL
uuid::console::Shell::running
bool running() const
Determine if this shell is still running.
Definition: shell.cpp:68
uuid::console::Shell::password_function
std::function< void(Shell &shell, bool completed, const std::string &password)> password_function
Function to handle the response to a password entry prompt.
Definition: console.h:788
uuid::console::Shell::maximum_command_line_length_
size_t maximum_command_line_length_
Definition: console.h:1626
uuid::console::Shell::block_with
void block_with(blocking_function function)
Execute a blocking function on this shell until it returns true.
Definition: shell.cpp:373