A Rust library to validate that clap Subcommand enums are sorted alphabetically.
When using clap's derive API, it's a good practice to keep subcommands sorted alphabetically for easier maintenance and better UX. This crate helps enforce that convention by validating the clap Command structure at runtime.
- Validates that clap subcommands are sorted alphabetically
- Works with both builder and derive APIs
- Easy integration via unit tests
- Zero dependencies beyond clap
- Lightweight and fast
Add to your Cargo.toml:
[dev-dependencies]
clap-sort = "0.1"The best way to use clap-sort is to add a unit test to your CLI project:
#[cfg(test)]
mod tests {
use clap::CommandFactory;
#[test]
fn test_subcommands_are_sorted() {
let cmd = cli::Cli::command();
clap_sort::assert_sorted(&cmd);
}
}This approach ensures that:
- Your subcommands stay sorted as part of your normal test suite
- CI/CD will catch any unsorted commands before merge
- Developers get immediate feedback when running
cargo test
use clap::{Parser, Subcommand};
#[derive(Parser)]
struct Cli {
#[command(subcommand)]
command: Commands,
}
#[derive(Subcommand)]
enum Commands {
Add, // ✓ Sorted
Delete, // ✓ Sorted
List, // ✓ Sorted
}
#[cfg(test)]
mod tests {
use super::*;
use clap::CommandFactory;
#[test]
fn verify_cli_sorted() {
clap_sort::assert_sorted(&Cli::command());
}
}If you prefer Result-based error handling:
use clap::CommandFactory;
#[test]
fn test_subcommands() {
let cmd = Cli::command();
match clap_sort::is_sorted(&cmd) {
Ok(()) => println!("Commands are sorted!"),
Err(msg) => panic!("{}", msg),
}
}The library validates the runtime Command structure by:
- Extracting all subcommand names from the clap
Command - Comparing them with their alphabetically sorted order
- Panicking (or returning an error) if they don't match
This approach works with both the builder API and derive API, and validates the actual command structure as clap sees it.
#[derive(Subcommand)]
enum Commands {
Add,
Delete,
List,
}#[derive(Subcommand)]
enum Commands {
#[command(name = "add")]
AddCmd,
#[command(name = "delete")]
DeleteCmd,
#[command(name = "list")]
ListCmd,
}#[derive(Subcommand)]
enum Commands {
List, // Should be third
Add, // Should be first
Delete, // Should be second
}Run the test suite:
cargo testThe library includes comprehensive tests covering:
- Sorted enums
- Unsorted enums
- Custom command names
- Non-Subcommand enums (should be ignored)
- Multiple enums in one file
Here's a complete example showing how unsorted commands are caught:
use clap::{Parser, Subcommand};
#[derive(Parser)]
struct Cli {
#[command(subcommand)]
command: Commands,
}
#[derive(Subcommand)]
enum Commands {
List, // ❌ Should be third
Add, // ❌ Should be first
Delete, // ❌ Should be second
}
#[cfg(test)]
mod tests {
use super::*;
use clap::CommandFactory;
#[test]
fn test_sorted() {
clap_sort::assert_sorted(&Cli::command());
}
}When you run cargo test, this will fail with:
thread 'tests::test_sorted' panicked at 'Subcommands are not sorted alphabetically!
Actual order: ["list", "add", "delete"]
Expected order: ["add", "delete", "list"]'
MIT or Apache-2.0 (your choice)