# 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>(args: I) -> Result { let mut it = args.into_iter(); let prog = it.next().unwrap_or_else(|| "socktop".into()); let mut url: Option = None; let mut tls_ca: Option = 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, /// Path to TLS certificate PEM file for WSS connections #[arg(short = 't', long = "tls-ca", value_name = "CERT_PEM")] pub tls_ca: Option, // ... 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, } ``` ### Common Attributes | Attribute | Purpose | Example | |-----------|---------|---------| | `short = 'x'` | Short flag | `-x` | | `long = "example"` | Long flag | `--example` | | `value_name = "FOO"` | Display name | `--thing ` | | `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, ``` 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, ``` ### 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.