Skip to main content

embassy_shell/
lib.rs

1//! A tiny `no_std` interactive shell in the spirit of bash, designed to run
2//! on top of [embassy](https://www.embassy.dev) (or any async executor) over
3//! any transport that implements [`embedded_io_async::Read`] /
4//! [`embedded_io_async::Write`] — UART, USB CDC, RTT-with-serial-gwakeup, ...
5//!
6//! # Features
7//!
8//! * Bash-like line editing: cursor keys, Home/End, Delete, Backspace,
9//!   Ctrl-U (kill line), Ctrl-W (delete word), Ctrl-L (clear screen).
10//! * `Tab` completion of command names and per-command argument completion
11//!   (fixed option lists or custom completer callbacks), with bash-style
12//!   common-prefix insertion and candidate listing.
13//! * Command history (Up/Down), configurable size.
14//! * Ctrl-C interrupts a *running* command (its handler future is dropped).
15//! * Simple command registration with closures; commands get an output handle
16//!   ([`Io`]) so they can `await` slow writes (e.g. to USB CDC).
17//! * Built-in `help`, `history`, `clear` (overridable by user commands).
18//! * No dependency on `futures`/`tokio`; only `embedded-io`/
19//!   `embedded-io-async`. Uses `alloc` (a global allocator is required).
20//!
21//! # Example
22//!
23//! ```
24//! use embassy_shell::Shell;
25//!
26//! let mut shell = Shell::new();
27//!
28//! shell.add_command("hello", "greet someone", |args, mut io| {
29//!     Box::pin(async move {
30//!         let name = args.get(0).unwrap_or("world");
31//!         io.println(&format!("Hello, {name}!")).await
32//!     })
33//! });
34//!
35//! shell.add_command_with_options(
36//!     "led",
37//!     "switch the led on or off",
38//!     &["on", "off"],
39//!     |args, mut io| Box::pin(async move {
40//!         io.println(match args.get(0) {
41//!             Some("on") => "led on",
42//!             Some("off") => "led off",
43//!             _ => "usage: led on|off",
44//!         })
45//!         .await
46//!     }),
47//! );
48//!
49//! let mut input: &[u8] = b"hello bob\nled on\n";
50//! let mut output: Vec<u8> = Vec::new();
51//! futures::executor::block_on(shell.run(&mut input, &mut output)).unwrap();
52//!
53//! let text = String::from_utf8_lossy(&output);
54//! assert!(text.contains("Hello, bob!"));
55//! assert!(text.contains("led on"));
56//! ```
57//!
58//! # Using it with embassy
59//!
60//! With embassy, run the shell as a task over a UART or a USB CDC serial
61//! port. Both implement the required traits:
62//!
63//! ```ignore
64//! // `uart` is an embassy-stm32/esp/nrf UART handle, or a USB CDC
65//! // `SerialPort` from embassy-usb — anything implementing
66//! // embedded-io-async Read + Write.
67//! #[embassy_executor::task]
68//! async fn shell_task(mut uart: Uart<'static, PERIPHERALS>) {
69//!     let mut shell = Shell::new();
70//!     shell.add_command("reboot", "reset the board", |_args, mut io| {
71//!         Box::pin(async move {
72//!             io.println("bye!").await?;
73//!             io.flush().await?;
74//!             cortex_m::peripheral::SCB::sys_reset();
75//!         })
76//!     });
77//!     shell.run(&mut uart, &mut uart).await.ok();
78//! }
79//! ```
80//!
81//! # Notes and limitations
82//!
83//! * Command handlers return [`BoxFuture`] (wrap an `async move` block in
84//!   `Box::pin`). This type-erases per-command future types so they can live
85//!   in one table. The boxed futures are not `Send`; this matches embassy's
86//!   executor model (tasks are pinned to one core).
87//! * Ctrl-C cancels the handler future; handlers should be cancel-safe (do
88//!   not hold locks across await points, or clean up in a guard).
89//! * Bytes typed while a command is running are not echoed; only the last
90//!   byte typed before/after the interrupt is retained for the next line.
91//! * Line editing assumes an ANSI/VT100-compatible terminal (putty, minicom,
92//!   `picocom`, modern Windows Terminal, ...).
93
94#![no_std]
95#![forbid(unsafe_code)]
96
97extern crate alloc;
98
99mod command;
100mod error;
101mod io;
102mod keys;
103mod parse;
104mod shell;
105mod util;
106
107pub use command::Args;
108pub use error::{Error, Result};
109pub use io::{BoxFuture, Io};
110pub use shell::Shell;