clish 0.1.0-beta.1

Elegant CLI framework for Rust.
Documentation
use clish::prelude::*;

#[command(
    help = "Deploy the application to an environment",
    details = "Runs pre-flight checks, builds the release artifact, then pushes it.\nUse --force to skip checks in emergencies.",
    aliases = ["ship", "release"],
    param(target, help = "Target host or cluster", short = 't', placeholder = "HOST"),
    param(env, help = "Environment name", name = "environment", short = 'e', env = "DEPLOY_ENV"),
    param(force, help = "Skip pre-flight checks", short = 'f')
)]
fn deploy(target: Pos<String>, env: Named<String>, force: bool) {
    if force {
        println!("Force deploying {} to {}...", target, env);
    } else {
        println!("Deploying {} to {}...", target, env);
    }
}

#[command(
    help = "Install a package",
    param(package, help = "Package to install", placeholder = "PKG"),
    param(version, help = "Pin to a specific version", short = 'v'),
    param(tag, help = "Tags to apply (repeatable)", short = 't'),
    param(force, help = "Overwrite an existing installation", short = 'f')
)]
fn install(
    package: Pos<String>,
    version: Pos<Option<String>>,
    tag: Named<Vec<String>>,
    force: bool,
) {
    println!(
        "Installing {}{}{}",
        package,
        version
            .as_deref()
            .map(|v| format!("@{}", v))
            .unwrap_or_default(),
        if force { " (forced)" } else { "" },
    );
    if !tag.is_empty() {
        println!("Tags: {}", tag.join(", "));
    }
}

#[command(
    help = "Search for packages",
    hidden = true,
    param(query, help = "Search term", short = 'q'),
    param(limit, help = "Maximum results to return", short = 'n')
)]
fn search(query: Pos<String>, limit: Named<Option<u32>>) {
    match limit {
        Some(n) => println!("Searching for '{}' (limit: {})", query, n),
        None => println!("Searching for '{}'...", query),
    }
}

#[command(
    help = "Publish files to the registry",
    deprecated = true,
    deprecation_note = "use 'upload' instead",
    param(files, help = "Files to publish"),
    param(
        dry_run,
        help = "Print what would be published without doing it",
        name = "dry-run",
        short = 'n'
    )
)]
fn publish(files: Pos<Vec<String>>, dry_run: bool) {
    if files.is_empty() {
        println!("No files specified.");
        return;
    }
    if dry_run {
        println!("Dry run: would publish: {}", files.join(", "));
    } else {
        println!("Publishing: {}", files.join(", "));
    }
}

#[command(
    help = "Set configuration values",
    param(key, help = "Configuration key", short = 'k', choices = ["debug", "log_level", "port"]),
    param(value, help = "Value to set", short = 'v', placeholder = "VAL")
)]
fn config(key: Named<String>, value: Pos<String>) {
    println!("Setting {} = {}", key, value);
}

fn main() {
    app!()
        .details("You shouldn't manually wire commands to functions when the functions themselves are the commands.")
        .run();
}