380 lines
9.9 KiB
Markdown
380 lines
9.9 KiB
Markdown
# Migration to Clap for Auto-Generated Man Pages
|
|
|
|
## Summary
|
|
|
|
Socktop has been migrated from manual argument parsing to **clap** (Command Line Argument Parser) with automatic man page generation via **clap_mangen**. This provides several benefits:
|
|
|
|
✅ **Auto-generated man pages** - Always in sync with CLI
|
|
✅ **Better help output** - Rich, formatted `--help` text
|
|
✅ **Type safety** - Compile-time checking of arguments
|
|
✅ **Environment variable support** - Built-in env var integration
|
|
✅ **Shell completions** - Easy to add bash/zsh/fish completions
|
|
✅ **Single source of truth** - CLI definitions generate everything
|
|
|
|
## What Changed
|
|
|
|
### Before (Manual Parsing)
|
|
|
|
```rust
|
|
// Old approach: Manual argument parsing
|
|
fn parse_args<I: IntoIterator<Item = String>>(args: I) -> Result<ParsedArgs, String> {
|
|
let mut it = args.into_iter();
|
|
let prog = it.next().unwrap_or_else(|| "socktop".into());
|
|
let mut url: Option<String> = None;
|
|
let mut tls_ca: Option<String> = None;
|
|
// ... lots of manual parsing code ...
|
|
while let Some(arg) = it.next() {
|
|
match arg.as_str() {
|
|
"-h" | "--help" => {
|
|
return Err(format!("Usage: {prog} ..."));
|
|
}
|
|
"--tls-ca" | "-t" => {
|
|
tls_ca = it.next();
|
|
}
|
|
// ... more matches ...
|
|
}
|
|
}
|
|
Ok(ParsedArgs { url, tls_ca, ... })
|
|
}
|
|
```
|
|
|
|
**Problems:**
|
|
- Manual parsing is error-prone
|
|
- Help text gets out of sync
|
|
- No automatic man page generation
|
|
- Duplicated logic for `--flag` and `--flag=value`
|
|
- No environment variable support
|
|
- Hard to test
|
|
|
|
### After (Clap Derive)
|
|
|
|
```rust
|
|
// New approach: Clap derive macros
|
|
use clap::Parser;
|
|
|
|
#[derive(Parser, Debug)]
|
|
#[command(
|
|
name = "socktop",
|
|
version,
|
|
author,
|
|
about = "Remote system monitor with a rich TUI over WebSocket",
|
|
long_about = "socktop is a remote system monitor..."
|
|
)]
|
|
pub struct Cli {
|
|
/// WebSocket URL to connect to
|
|
#[arg(value_name = "URL")]
|
|
pub url: Option<String>,
|
|
|
|
/// Path to TLS certificate PEM file for WSS connections
|
|
#[arg(short = 't', long = "tls-ca", value_name = "CERT_PEM")]
|
|
pub tls_ca: Option<String>,
|
|
|
|
// ... more fields ...
|
|
}
|
|
|
|
// Usage:
|
|
let cli = Cli::parse();
|
|
```
|
|
|
|
**Benefits:**
|
|
- Declarative and concise
|
|
- Auto-generated help and man pages
|
|
- Type-safe argument parsing
|
|
- Automatic support for `--flag` and `--flag=value`
|
|
- Built-in env var support with `env` attribute
|
|
- Easy to test
|
|
|
|
## Files Modified
|
|
|
|
### Core Changes
|
|
|
|
1. **`socktop/Cargo.toml`** - Added clap dependencies
|
|
2. **`socktop/src/cli.rs`** - NEW: CLI definition using clap derive
|
|
3. **`socktop/src/main.rs`** - Updated to use `Cli::parse_args()`
|
|
4. **`socktop/build.rs`** - NEW: Auto-generates man pages at build time
|
|
|
|
5. **`socktop_agent/Cargo.toml`** - Added clap dependencies
|
|
6. **`socktop_agent/src/cli.rs`** - NEW: CLI definition using clap derive
|
|
7. **`socktop_agent/src/main.rs`** - Updated to use `Cli::parse_args()`
|
|
8. **`socktop_agent/build.rs`** - Updated to auto-generate man pages
|
|
|
|
### Documentation
|
|
|
|
9. **`docs/AUTO_MAN_PAGES.md`** - Comprehensive guide to auto-generated man pages
|
|
10. **`docs/CLAP_MIGRATION.md`** - This file
|
|
11. **`README.md`** - Updated Man Pages section
|
|
12. **`scripts/install-with-man.sh`** - NEW: Installation script that includes man pages
|
|
|
|
## Man Page Generation
|
|
|
|
### How It Works
|
|
|
|
1. **Build Time** - When you run `cargo build`, the `build.rs` script:
|
|
- Includes the CLI definition from `src/cli.rs`
|
|
- Creates a clap `Command` instance
|
|
- Uses `clap_mangen` to generate a man page
|
|
- Saves it to `$OUT_DIR/man/*.1`
|
|
|
|
2. **Location** - Generated man pages are at:
|
|
```
|
|
target/release/build/socktop-*/out/man/socktop.1
|
|
target/release/build/socktop_agent-*/out/man/socktop_agent.1
|
|
```
|
|
|
|
3. **Installation** - Use the installation script:
|
|
```bash
|
|
./scripts/install-with-man.sh # User install
|
|
sudo ./scripts/install-with-man.sh --system # System install
|
|
```
|
|
|
|
4. **Viewing** - After installation:
|
|
```bash
|
|
man socktop
|
|
man socktop_agent
|
|
```
|
|
|
|
### Man Page Content
|
|
|
|
The man pages include:
|
|
- **NAME** - From `about` attribute
|
|
- **SYNOPSIS** - Auto-generated from arguments
|
|
- **DESCRIPTION** - From `long_about` attribute
|
|
- **OPTIONS** - From field doc comments and `#[arg(...)]` attributes
|
|
- **VERSION** - From Cargo.toml
|
|
- **AUTHORS** - From Cargo.toml
|
|
|
|
## CLI Definition Format
|
|
|
|
### Structure
|
|
|
|
```rust
|
|
use clap::Parser;
|
|
|
|
#[derive(Parser, Debug)]
|
|
#[command(
|
|
name = "myapp",
|
|
version, // Uses Cargo.toml version
|
|
author, // Uses Cargo.toml authors
|
|
about = "Short description",
|
|
long_about = "Longer description for man page and --help"
|
|
)]
|
|
pub struct Cli {
|
|
/// Short description
|
|
///
|
|
/// Longer description that appears in the man page.
|
|
/// Multiple paragraphs supported.
|
|
#[arg(short = 't', long = "thing", value_name = "VALUE")]
|
|
pub thing: Option<String>,
|
|
}
|
|
```
|
|
|
|
### Common Attributes
|
|
|
|
| Attribute | Purpose | Example |
|
|
|-----------|---------|---------|
|
|
| `short = 'x'` | Short flag | `-x` |
|
|
| `long = "example"` | Long flag | `--example` |
|
|
| `value_name = "FOO"` | Display name | `--thing <FOO>` |
|
|
| `env = "VAR"` | Environment variable | `env = "MY_VAR"` |
|
|
| `default_value = "x"` | Default value | Default: "x" |
|
|
| `hide = true` | Hide from help/man | For internal options |
|
|
| `value_parser = func` | Custom parser | Validation |
|
|
|
|
### Environment Variables
|
|
|
|
```rust
|
|
/// Port to listen on
|
|
///
|
|
/// Can be set via SOCKTOP_PORT environment variable.
|
|
#[arg(short = 'p', long = "port", env = "SOCKTOP_PORT")]
|
|
pub port: Option<u16>,
|
|
```
|
|
|
|
This automatically:
|
|
- Checks the environment variable
|
|
- Shows `[env: SOCKTOP_PORT=]` in help
|
|
- Documents it in the man page
|
|
|
|
## Testing
|
|
|
|
### Unit Tests
|
|
|
|
Both CLI modules include comprehensive unit tests:
|
|
|
|
```rust
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn test_basic_parsing() {
|
|
let cli = Cli::try_parse_from(&["socktop", "ws://localhost:8080/ws"]).unwrap();
|
|
assert_eq!(cli.url, Some("ws://localhost:8080/ws".to_string()));
|
|
}
|
|
|
|
#[test]
|
|
fn test_tls_options() {
|
|
let cli = Cli::try_parse_from(&[
|
|
"socktop",
|
|
"-t", "/path/to/cert.pem",
|
|
"--verify-hostname",
|
|
"wss://example.com:8443/ws",
|
|
]).unwrap();
|
|
assert_eq!(cli.tls_ca, Some("/path/to/cert.pem".to_string()));
|
|
assert!(cli.verify_hostname);
|
|
}
|
|
}
|
|
```
|
|
|
|
Run tests with:
|
|
```bash
|
|
cargo test --package socktop cli::
|
|
cargo test --package socktop_agent cli::
|
|
```
|
|
|
|
### Manual Testing
|
|
|
|
Test the help output:
|
|
```bash
|
|
cargo run --package socktop -- --help
|
|
cargo run --package socktop_agent -- --help
|
|
```
|
|
|
|
Test argument parsing:
|
|
```bash
|
|
cargo run --package socktop -- ws://localhost:8080/ws
|
|
cargo run --package socktop -- -t cert.pem wss://localhost:8443/ws
|
|
cargo run --package socktop_agent -- --port 8080
|
|
cargo run --package socktop_agent -- --enableSSL
|
|
```
|
|
|
|
Test environment variables:
|
|
```bash
|
|
SOCKTOP_PORT=9000 cargo run --package socktop_agent
|
|
SOCKTOP_ENABLE_SSL=1 cargo run --package socktop_agent
|
|
```
|
|
|
|
## Backwards Compatibility
|
|
|
|
### Command-Line Interface
|
|
|
|
✅ **Fully compatible** - All existing command-line arguments work exactly the same:
|
|
|
|
```bash
|
|
# Still works
|
|
socktop -t cert.pem wss://host:8443/ws
|
|
socktop --profile myprofile
|
|
socktop --demo
|
|
|
|
socktop_agent --port 8080
|
|
socktop_agent --enableSSL
|
|
```
|
|
|
|
### Environment Variables
|
|
|
|
✅ **Fully compatible** - All environment variables still work:
|
|
|
|
```bash
|
|
SOCKTOP_PORT=8080 socktop_agent
|
|
SOCKTOP_ENABLE_SSL=1 socktop_agent
|
|
```
|
|
|
|
### Breaking Changes
|
|
|
|
❌ **None** - This is a drop-in replacement for the old parser.
|
|
|
|
## Comparison: Manual vs Clap
|
|
|
|
| Feature | Manual Parsing | Clap |
|
|
|---------|---------------|------|
|
|
| Code lines | ~120 lines | ~50 lines |
|
|
| Man pages | Separate files | Auto-generated |
|
|
| Help text | Hardcoded strings | Auto-generated |
|
|
| Type safety | Runtime errors | Compile-time |
|
|
| Env vars | Manual `std::env::var` | Built-in `env` attribute |
|
|
| Testing | Hard to test | Easy with `try_parse_from` |
|
|
| Maintenance | High | Low |
|
|
| Consistency | Can drift | Always in sync |
|
|
| Completions | Manual | Auto-generate |
|
|
|
|
## Future Enhancements
|
|
|
|
Now that we're using clap, we can easily add:
|
|
|
|
### Shell Completions
|
|
|
|
```rust
|
|
// In build.rs
|
|
use clap_complete::{generate_to, shells::*};
|
|
|
|
let cmd = Cli::command();
|
|
generate_to(Bash, &mut cmd, "socktop", &out_dir)?;
|
|
generate_to(Zsh, &mut cmd, "socktop", &out_dir)?;
|
|
generate_to(Fish, &mut cmd, "socktop", &out_dir)?;
|
|
```
|
|
|
|
### Subcommands
|
|
|
|
```rust
|
|
#[derive(Parser)]
|
|
enum Commands {
|
|
/// Connect to an agent
|
|
Connect {
|
|
#[arg(value_name = "URL")]
|
|
url: String,
|
|
},
|
|
/// List saved profiles
|
|
Profiles,
|
|
/// Run demo mode
|
|
Demo,
|
|
}
|
|
```
|
|
|
|
### Better Validation
|
|
|
|
```rust
|
|
#[arg(value_parser = clap::value_parser!(u16).range(1..=65535))]
|
|
pub port: Option<u16>,
|
|
```
|
|
|
|
### Custom Help Sections
|
|
|
|
```rust
|
|
#[command(
|
|
after_help = "EXAMPLES:\n socktop ws://localhost:8080/ws\n socktop --demo"
|
|
)]
|
|
```
|
|
|
|
## Migration Checklist
|
|
|
|
If migrating other Rust projects to clap:
|
|
|
|
- [ ] Add clap and clap_mangen dependencies
|
|
- [ ] Create `src/cli.rs` with derive macros
|
|
- [ ] Update `main.rs` to use `Cli::parse()`
|
|
- [ ] Create/update `build.rs` for man page generation
|
|
- [ ] Write unit tests for CLI parsing
|
|
- [ ] Test all existing command-line arguments
|
|
- [ ] Test environment variables
|
|
- [ ] Update documentation
|
|
- [ ] Create installation scripts for man pages
|
|
- [ ] Consider adding shell completions
|
|
|
|
## Resources
|
|
|
|
- [Clap Documentation](https://docs.rs/clap/)
|
|
- [Clap Derive Tutorial](https://docs.rs/clap/latest/clap/_derive/index.html)
|
|
- [Clap Mangen](https://docs.rs/clap_mangen/)
|
|
- [Auto-Generated Man Pages Guide](AUTO_MAN_PAGES.md)
|
|
- [Manual Man Pages](man/README.md)
|
|
|
|
## Conclusion
|
|
|
|
The migration to clap provides:
|
|
- Better developer experience
|
|
- Auto-generated, always-in-sync documentation
|
|
- Reduced maintenance burden
|
|
- Professional-quality help output and man pages
|
|
- Foundation for future enhancements (completions, subcommands)
|
|
|
|
All with **zero breaking changes** to the existing CLI. |