9.9 KiB
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)
// 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
--flagand--flag=value - No environment variable support
- Hard to test
After (Clap Derive)
// 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
--flagand--flag=value - Built-in env var support with
envattribute - Easy to test
Files Modified
Core Changes
-
socktop/Cargo.toml- Added clap dependencies -
socktop/src/cli.rs- NEW: CLI definition using clap derive -
socktop/src/main.rs- Updated to useCli::parse_args() -
socktop/build.rs- NEW: Auto-generates man pages at build time -
socktop_agent/Cargo.toml- Added clap dependencies -
socktop_agent/src/cli.rs- NEW: CLI definition using clap derive -
socktop_agent/src/main.rs- Updated to useCli::parse_args() -
socktop_agent/build.rs- Updated to auto-generate man pages
Documentation
docs/AUTO_MAN_PAGES.md- Comprehensive guide to auto-generated man pagesdocs/CLAP_MIGRATION.md- This fileREADME.md- Updated Man Pages sectionscripts/install-with-man.sh- NEW: Installation script that includes man pages
Man Page Generation
How It Works
-
Build Time - When you run
cargo build, thebuild.rsscript:- Includes the CLI definition from
src/cli.rs - Creates a clap
Commandinstance - Uses
clap_mangento generate a man page - Saves it to
$OUT_DIR/man/*.1
- Includes the CLI definition from
-
Location - Generated man pages are at:
target/release/build/socktop-*/out/man/socktop.1 target/release/build/socktop_agent-*/out/man/socktop_agent.1 -
Installation - Use the installation script:
./scripts/install-with-man.sh # User install sudo ./scripts/install-with-man.sh --system # System install -
Viewing - After installation:
man socktop man socktop_agent
Man Page Content
The man pages include:
- NAME - From
aboutattribute - SYNOPSIS - Auto-generated from arguments
- DESCRIPTION - From
long_aboutattribute - OPTIONS - From field doc comments and
#[arg(...)]attributes - VERSION - From Cargo.toml
- AUTHORS - From Cargo.toml
CLI Definition Format
Structure
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
/// 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:
#[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:
cargo test --package socktop cli::
cargo test --package socktop_agent cli::
Manual Testing
Test the help output:
cargo run --package socktop -- --help
cargo run --package socktop_agent -- --help
Test argument parsing:
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:
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:
# 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:
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
// 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
#[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
#[arg(value_parser = clap::value_parser!(u16).range(1..=65535))]
pub port: Option<u16>,
Custom Help Sections
#[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.rswith derive macros - Update
main.rsto useCli::parse() - Create/update
build.rsfor 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
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.