# EXAMPLES
Practical implementation patterns for using **nut-shell** in your embedded projects.
## Table of Contents
1. [Complete Platform Examples](#complete-platform-examples)
2. [Command Patterns](#command-patterns)
3. [Trait Implementations](#trait-implementations)
4. [Configuration](#configuration)
---
## Complete Platform Examples
Working examples in `examples/` directory:
| **Native (sync)** | `examples/native/bin/basic/` | Stdio with immediate flush |
| **Native (async)** | `examples/native/bin/async/` | Async command execution, Tokio runtime |
| **RP2040 (bare-metal)** | `examples/rp-pico/bin/basic/` | Blocking USB-CDC writes |
| **RP2040 (Embassy)** | `examples/rp-pico/bin/embassy/` | Async USB-CDC with buffered output |
| **RP2040 (build-time auth)** | `examples/rp-pico-buildtime/` | Build-time credential generation from TOML |
| **STM32F072** | `examples/stm32f072/bin/basic/` | Blocking UART writes |
Each example includes complete `CharIo` implementation in `io.rs`.
**Build-time credentials example** (`rp-pico-buildtime`) demonstrates using the `nut-shell-credgen` tool to generate pre-hashed credentials at build time from a TOML configuration file. See [SECURITY.md](SECURITY.md#build-time-credential-generation) for details.
---
## Command Patterns
### Synchronous Commands
```rust
// 1. Define command metadata
const STATUS: CommandMeta<MyAccessLevel> = CommandMeta {
id: "status",
name: "status",
description: "Show system status",
access_level: MyAccessLevel::User,
kind: CommandKind::Sync,
min_args: 0,
max_args: 0,
};
// 2. Implement command function
fn status_fn<C: ShellConfig>(args: &[&str]) -> Result<Response<C>, CliError> {
Ok(Response::success("System OK"))
}
// 3. Add to tree
const SYSTEM: Directory<MyAccessLevel> = Directory {
name: "system",
description: "System commands",
access_level: MyAccessLevel::User,
children: &[Node::Command(&STATUS)],
};
// 4. Dispatch in handler
impl CommandHandler<MyConfig> for MyHandler {
fn execute_sync(&self, id: &str, args: &[&str]) -> Result<Response<MyConfig>, CliError> {
match id {
"status" => status_fn::<MyConfig>(args),
_ => Err(CliError::CommandNotFound),
}
}
}
```
### Async Commands
```rust
// 1. Define metadata (mark as Async)
const FETCH: CommandMeta<MyAccessLevel> = CommandMeta {
id: "fetch",
name: "fetch",
description: "Fetch data from network",
access_level: MyAccessLevel::User,
kind: CommandKind::Async, // Async marker
min_args: 1,
max_args: 1,
};
// 2. Implement async command function
async fn fetch_fn<C: ShellConfig>(args: &[&str]) -> Result<Response<C>, CliError> {
let data = HTTP_CLIENT.get(args[0]).await?;
Ok(Response::success(&data))
}
// 3. Dispatch in async handler
#[cfg(feature = "async")]
impl CommandHandler<MyConfig> for MyHandler {
async fn execute_async(&self, id: &str, args: &[&str]) -> Result<Response<MyConfig>, CliError> {
match id {
"fetch" => fetch_fn::<MyConfig>(args).await,
_ => Err(CliError::CommandNotFound),
}
}
}
// 4. Use process_char_async in main loop
loop {
let c = io.read().await;
shell.process_char_async(c).await?;
}
```
**See [DESIGN.md](DESIGN.md) for metadata/execution separation architecture.**
### Stateful Handlers
```rust
struct MyHandler<'a> {
system_state: &'a SystemState,
config: &'a Config,
}
impl<'a> CommandHandler<MyConfig> for MyHandler<'a> {
fn execute_sync(&self, id: &str, args: &[&str]) -> Result<Response<MyConfig>, CliError> {
match id {
"status" => {
let info = self.system_state.get_status();
Ok(Response::success(&info))
}
"config_get" => {
let value = self.config.get(args[0]);
Ok(Response::success(value))
}
_ => Err(CliError::CommandNotFound),
}
}
}
```
---
## Trait Implementations
### `AccessLevel`
Define access hierarchy using the derive macro:
```rust
use nut_shell_macros::AccessLevel;
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, AccessLevel)]
pub enum MyAccessLevel {
Guest = 0,
User = 1,
Admin = 2,
}
```
The macro generates `from_str` and `as_str` methods for string conversion. Higher numeric values inherit permissions from lower levels due to `PartialOrd`.
**See [SECURITY.md](SECURITY.md#access-control-system) for access control patterns.**
### `CommandHandler`
Maps command IDs to execution functions:
```rust
struct MyHandler;
impl CommandHandler<MyConfig> for MyHandler {
fn execute_sync(&self, id: &str, args: &[&str]) -> Result<Response<MyConfig>, CliError> {
match id {
"status" => status_fn::<MyConfig>(args),
"info" => info_fn::<MyConfig>(args),
_ => Err(CliError::CommandNotFound),
}
}
#[cfg(feature = "async")]
async fn execute_async(&self, id: &str, args: &[&str]) -> Result<Response<MyConfig>, CliError> {
match id {
"fetch" => fetch_fn::<MyConfig>(args).await,
_ => Err(CliError::CommandNotFound),
}
}
}
```
**See [Command Patterns](#command-patterns) for complete examples.**
### `CharIo`
Platform I/O abstraction for character input/output:
```rust
struct MyIo { /* platform-specific fields */ }
impl CharIo for MyIo {
type Error = MyError;
fn get_char(&mut self) -> Result<Option<char>, Self::Error> {
// Return Some(char) if available, None if no input ready
}
fn put_char(&mut self, c: char) -> Result<(), Self::Error> {
// Write character to output
}
}
```
**See [CHAR_IO.md](CHAR_IO.md) for platform adapter patterns and buffering strategies.**
---
## Configuration
### Pre-Defined Configs
| `DefaultConfig` | 128 | 256 | 10 | 1.9 KB (with history)<br>0.6 KB (without) | Standard applications |
| `MinimalConfig` | 64 | 128 | 4 | 0.7 KB (with history)<br>0.2 KB (without) | RAM-constrained systems |
**Note:** Due to const generics limitations, buffer sizes are currently hardcoded to `DefaultConfig` values. `MinimalConfig` RAM usage reflects intended values when const generics are fully supported. Currently both configs use identical RAM (`DefaultConfig` values).
```rust
use nut_shell::{Shell, DefaultConfig, MinimalConfig};
// Standard
let mut shell: Shell<_, _, _, _, DefaultConfig> = Shell::new(&ROOT, handler, io);
// Minimal
let mut shell: Shell<_, _, _, _, MinimalConfig> = Shell::new(&ROOT, handler, io);
```
### Custom `ShellConfig`
```rust
struct MyConfig;
impl ShellConfig for MyConfig {
const MAX_INPUT: usize = 96;
const MAX_RESPONSE: usize = 192;
const HISTORY_SIZE: usize = 6;
const MSG_WELCOME: &'static str = "MyDevice v1.0";
// ... other buffer sizes and messages
}
let mut shell: Shell<_, _, _, _, MyConfig> = Shell::new(&ROOT, handler, io);
```
**Currently customizable:**
- Message strings (`MSG_WELCOME`, `MSG_LOGIN_PROMPT`, etc.)
**Not yet functional:** Buffer size constants (`MAX_INPUT`, `MAX_RESPONSE`, etc.) are hardcoded pending const generics stabilization. See `src/config.rs` for details.
### Feature Flags
| `completion` | ✅ Enabled | Tab completion for interactive use |
| `history` | ✅ Enabled | Arrow key command recall |
| `authentication` | ❌ Disabled | User login and access control |
| `async` | ❌ Disabled | Async command execution (Embassy, etc.) |
```toml
# Default (completion + history)
nut-shell = "0.1"
# With authentication
nut-shell = { version = "0.1", features = ["authentication"] }
# Minimal (no optional features)
nut-shell = { version = "0.1", default-features = false }
# Async runtime support
nut-shell = { version = "0.1", features = ["async"] }
```
**See [size-analysis/README.md](../size-analysis/README.md) for detailed flash/RAM cost breakdown.**