clish 0.1.0-beta.3

Elegant CLI framework for Rust.
Documentation
use clish::help::{AppStyle, AppStyles};
use clish::prelude::*;

/// Create a new project scaffold.
///
/// Initializes a project directory with the specified template and
/// optional tags. Supports GitHub repository templates and local
/// boilerplate directories.
#[command(
    name = "create",
    aliases = ["init", "scaffold"],
    param(name, help = "Project name", placeholder = "NAME"),
    param(description, help = "Short project description"),
    param(template, help = "Template to use", short = 't', placeholder = "TMPL", default = "default"),
    param(tags, help = "Tags to apply (repeatable)", short = 'T'),
    param(force, help = "Overwrite existing directory", short = 'f'),
    param(git_init, help = "Initialize git repository", name = "git", short = 'g')
)]
fn create_project(
    name: Pos<String>,
    description: Pos<Option<String>>,
    template: Named<String>,
    tags: Named<Vec<String>>,
    force: bool,
    git_init: bool,
) {
    println!("Creating project '{}'", name);
    if let Some(desc) = description {
        println!("  Description: {}", desc);
    }
    println!("  Template: {}", template);
    if !tags.is_empty() {
        println!("  Tags: {}", tags.join(", "));
    }
    if force {
        println!("  (force: will overwrite existing)");
    }
    if git_init {
        println!("  Initializing git repository");
    }
}

/// Run the test suite.
///
/// Executes all tests for the specified target. Supports filtering
/// by test name and controlling output verbosity.
#[command(
    param(target, help = "Test target", short = 't', placeholder = "TARGET", default = "all"),
    param(format, help = "Output format", short = 'f', choices = ["pretty", "terse", "json"], default = "pretty"),
    param(jobs, help = "Number of parallel jobs", short = 'j', placeholder = "N"),
    param(coverage, help = "Generate coverage report", short = 'c'),
    param(verbose, help = "Verbose output", short = 'v'),
    param(filter, help = "Filter tests by name pattern", hide = true)
)]
fn test(
    target: Named<String>,
    format: Named<String>,
    jobs: Named<Option<u16>>,
    coverage: bool,
    verbose: bool,
    filter: Named<Option<String>>,
) {
    println!("Running tests for target: {}", target);
    println!("  Format: {}", format);
    if let Some(j) = jobs {
        println!("  Parallel jobs: {}", j);
    }
    if coverage {
        println!("  Coverage report enabled");
    }
    if verbose {
        println!("  Verbose mode");
    }
    if let Some(f) = filter {
        println!("  Filter: {}", f);
    }
}

/// Publish a release to the registry.
///
/// Packages the project and uploads it to the configured registry.
#[command(
    deprecated = true,
    deprecation_note = "use 'deploy' instead",
    param(channel, help = "Release channel", short = 'c', placeholder = "CHANNEL", choices = ["stable", "beta", "nightly"], default = "stable"),
    param(dry_run, help = "Validate without publishing", name = "dry-run", short = 'n'),
    param(sign, help = "Sign the release", short = 's'),
    param(skip_checks, help = "Skip pre-flight checks", name = "skip-checks"),
    param(verify, help = "Verify integrity after publish", short = 'V', requires = ["dry_run"])
)]
fn publish(channel: Named<String>, dry_run: bool, sign: bool, skip_checks: bool, verify: bool) {
    println!("Publishing to channel: {}", channel);
    if dry_run {
        println!("  (dry run — no changes made)");
        return;
    }
    if sign {
        println!("  Signing release");
    }
    if skip_checks {
        println!("  (skipping pre-flight checks)");
    }
    if verify {
        println!("  Verifying integrity");
    }
    println!("  Release published!");
}

/// Clean build artifacts.
///
/// Removes generated files from the project directory. Can target
/// specific artifact types or clean everything.
#[command(
    hidden = true,
    param(all, help = "Remove all artifacts", short = 'a'),
    param(path, help = "Target path for selective cleanup", hide = true),
    param(
        dry_run,
        help = "Print what would be removed",
        name = "dry-run",
        short = 'n'
    )
)]
fn clean(all: bool, path: Pos<Option<String>>, dry_run: bool) {
    if dry_run {
        if all {
            println!("Would remove all artifacts");
        } else if let Some(p) = path {
            println!("Would clean: {}", p);
        }
        return;
    }
    if all {
        println!("Removing all artifacts");
    } else if let Some(p) = path {
        println!("Cleaning: {}", p);
    } else {
        println!("Nothing to clean (use --all or specify a path)");
    }
}

/// Search the project index.
///
/// Queries the local project index for matching entries.
/// Multiple search terms are combined with OR logic.
#[command(
    help = "Search indexed projects",
    details = "Searches through all registered projects in the local index.\nResults are ranked by relevance score.",
    param(query, help = "Search terms (variadic)", placeholder = "TERM"),
    param(
        limit,
        help = "Maximum results",
        short = 'n',
        placeholder = "NUM",
        env = "CLISH_SEARCH_LIMIT"
    ),
    param(json, help = "Output as JSON", short = 'j')
)]
fn search(query: Pos<Vec<String>>, limit: Named<Option<u32>>, json: bool) {
    let limit = limit.unwrap_or(10);
    println!("Searching for: {}", query.join(" "));
    println!("  Limit: {}", limit);
    if json {
        println!("  Output: JSON");
    }
    for (i, term) in query.iter().enumerate() {
        if i as u32 >= limit {
            break;
        }
        println!("  [{}/{}] Results for '{}'", i + 1, limit, term);
    }
}

fn main() {
    app!()
        .name("rigen")
        .version("2.1.0")
        .description("Project lifecycle toolkit")
        .details("Rigen is a full-featured CLI for managing project scaffolds,\nrunning tests, publishing releases, and indexing the project catalog.")
        .styles(AppStyles {
            header: AppStyle::Markup("[bold cyan underline]"),
            brand: AppStyle::Markup("[bold magenta]"),
            ..Default::default()
        })
        .run();
}