clish 0.1.0-beta.3

Elegant CLI framework for Rust.
Documentation
#![deny(clippy::all)]
#![warn(missing_docs)]

//! # clish
//!
//! The most elegant CLI framework for Rust.
//!
//! Inspired by Typer. Define commands as plain functions. The argument types
//! determine how each parameter is parsed from the command line. Validation,
//! help generation, and error reporting are automatic.
//!
//! ```rust,ignore
//! use clish::prelude::*;
//!
//! #[command]
//! /// Deploy the application
//! fn deploy(target: Pos<String>, env: Named<String>, force: bool) {
//!     println!("Deploying {} to {}", target, env);
//! }
//!
//! fn main() {
//!     app!().run();
//! }
//! ```
//!
//! # Argument types
//!
//! |         Type          |          Behavior         | Example |
//! |-----------------------|---------------------------|---|
//! | [`Pos<T>`]            | Positional, required      | `myapp cmd foo` |
//! | [`Pos<Option<T>>`]    | Positional, optional      | `myapp cmd` or `myapp cmd foo` |
//! | [`Pos<Vec<T>>`]       | Positional, variadic      | `myapp cmd a b c` |
//! | [`Named<T>`]          | `--name val`, required    | `myapp cmd --env prod` |
//! | [`Named<Option<T>>`]  | `--name val`, optional    | |
//! | [`Named<Vec<T>>`]     | `--name val`, repeatable  | `myapp cmd --tag a --tag b` |
//! | `bool`                | `--flag` presence         | `myapp cmd --force` |
//!
//! `T` must implement [`FromStr`](std::str::FromStr). Type mismatches produce colored errors
//! at runtime. Invalid type combinations (`Option<Vec<T>>`, `Option<bool>`)
//! are rejected at compile time.
//!
//! # Parsing features
//!
//! - `--name=value` and `--name value` forms for named options
//! - `-n value` and `-nvalue` forms with short aliases
//! - Bundled short flags: `-abc` is equivalent to `-a -b -c`
//! - `--` separator: everything after is treated as positional
//! - Environment variable fallback via `param(x, env = "VAR")`
//! - Default values via `param(x, default = "val")`
//! - Value constraints via `param(x, choices = ["a", "b"])`
//! - Mutual exclusion via `param(x, conflicts_with = ["y"])`
//! - Prerequisites via `param(x, requires = ["y"])`
//!
//! # Re-exports
//!
//! This crate re-exports everything you need from `clish-core` and `clish-macros`.
//! See [`prelude`] for a single-use convenience import.

pub use clish_core::app::App;
pub use clish_core::help;
pub use clish_core::parse::ErrorKind;
pub use clish_core::types::{Flag, Named, Pos};
pub use clish_macros::command;

#[doc(hidden)]
pub use clish_core::inventory;
#[doc(hidden)]
pub use clish_core::parse;
#[doc(hidden)]
pub use clish_core::parse::CommandEntry;

/// Convenience module for a single `use` import.
///
/// ```rust
/// use clish::prelude::*;
/// ```
///
/// Re-exports: [`App`], [`Flag`], [`Named`], [`Pos`], [`app!`](crate::app),
/// [`command`].
pub mod prelude {
    pub use crate::{App, Flag, Named, Pos, app, command};
}

/// Construct an [`App`] with metadata from `Cargo.toml`.
///
/// Reads `CARGO_PKG_NAME`, `CARGO_PKG_VERSION`, and `CARGO_PKG_DESCRIPTION`
/// at compile time. If `CARGO_PKG_DESCRIPTION` is not set, it defaults to
/// an empty string. Override any field with the builder methods on [`App`]:
///
/// ```rust,ignore
/// use clish::prelude::*;
///
/// app!()
///     .name("myapp")
///     .version("1.0.0")
///     .description("Does things.")
///     .details("Longer description shown on --help.")
///     .run();
/// ```
///
/// ## One-shot mode
///
/// Pass a command function to run in one-shot mode (no subcommand dispatch):
///
/// ```rust,ignore
/// use clish::prelude::*;
///
/// #[command]
/// fn main_cmd(target: Pos<String>, verbose: bool) { ... }
///
/// fn main() {
///     app!(main_cmd).run()
/// }
/// ```
///
/// This runs `main_cmd` directly with all arguments. The command must not have
/// custom `name`, `aliases`, `hidden`, or `deprecated` attributes, and no other
/// `#[command]` functions may be registered in the binary. Violations produce
/// a panic at startup.
#[macro_export]
macro_rules! app {
    () => {
        $crate::App {
            name: env!("CARGO_PKG_NAME"),
            version: env!("CARGO_PKG_VERSION"),
            description: option_env!("CARGO_PKG_DESCRIPTION").unwrap_or(""),
            details: "",
            styles: $crate::help::AppStyles::default(),
            oneshot: None,
        }
    };
    ($cmd:ident) => {{
        let mut __app = $crate::App {
            name: env!("CARGO_PKG_NAME"),
            version: env!("CARGO_PKG_VERSION"),
            description: option_env!("CARGO_PKG_DESCRIPTION").unwrap_or(""),
            details: "",
            styles: $crate::help::AppStyles::default(),
            oneshot: None,
        };
        let __commands: Vec<&$crate::CommandEntry> = $crate::inventory::iter().collect();
        if __commands.len() != 1 {
            panic!(
                "oneshot mode requires exactly one command, found {} commands. \
                 Use `app!()` for multi-command mode.",
                __commands.len()
            );
        }
        let __cmd = __commands[0];
        if !__cmd.aliases.is_empty() {
            panic!(
                "oneshot command '{}' must not have aliases. \
                 Remove aliases or use `app!()` for multi-command mode.",
                stringify!($cmd)
            );
        }
        if __cmd.name != stringify!($cmd) {
            panic!(
                "oneshot command '{}' must not have a custom name (currently: '{}'). \
                 Remove the name attribute or use `app!()` for multi-command mode.",
                stringify!($cmd),
                __cmd.name
            );
        }
        if __cmd.hidden {
            panic!(
                "oneshot command '{}' must not be hidden. \
                 Remove hidden or use `app!()` for multi-command mode.",
                stringify!($cmd)
            );
        }
        if __cmd.deprecated {
            panic!(
                "oneshot command '{}' must not be deprecated. \
                 Remove deprecated or use `app!()` for multi-command mode.",
                stringify!($cmd)
            );
        }
        __app.oneshot = Some(__cmd);
        __app
    }};
}