chant 0.1.1

Shell glamour - beautiful prompts and output for scripts 🪄
Documentation
//! Rune CLI - Shell glamour from the command line.

use clap::{Parser, Subcommand};
use glyphs::Color;
use lacquer::Border;
use chant::{choose, confirm, filter, input, spin, style_text};
use scoria::SpinnerStyle;
use std::time::Duration;

#[derive(Parser)]
#[command(name = "rune")]
#[command(about = "Shell glamour - beautiful prompts and output 🪄")]
#[command(version)]
struct Cli {
    #[command(subcommand)]
    command: Commands,
}

#[derive(Subcommand)]
enum Commands {
    /// Get text input from user
    Input {
        /// Prompt text
        #[arg(short, long, default_value = "Enter value")]
        prompt: String,

        /// Placeholder text
        #[arg(long)]
        placeholder: Option<String>,

        /// Default value
        #[arg(short, long)]
        default: Option<String>,

        /// Password mode (hide input)
        #[arg(long)]
        password: bool,

        /// Header text
        #[arg(long)]
        header: Option<String>,
    },

    /// Confirm an action
    Confirm {
        /// Prompt text
        prompt: String,

        /// Default to yes
        #[arg(long)]
        default: Option<bool>,
    },

    /// Choose from a list
    Choose {
        /// Options to choose from
        options: Vec<String>,

        /// Header text
        #[arg(long)]
        header: Option<String>,

        /// Limit visible items
        #[arg(short, long)]
        limit: Option<usize>,
    },

    /// Filter through options
    Filter {
        /// Options to filter
        options: Vec<String>,

        /// Header text
        #[arg(long)]
        header: Option<String>,

        /// Limit visible items
        #[arg(short, long, default_value = "10")]
        limit: usize,
    },

    /// Show a spinner
    Spin {
        /// Spinner title
        title: String,

        /// Duration in seconds
        #[arg(short, long, default_value = "3")]
        duration: u64,

        /// Spinner style
        #[arg(short, long, default_value = "dots")]
        style: String,
    },

    /// Style text
    Style {
        /// Text to style
        text: String,

        /// Foreground color (hex)
        #[arg(long)]
        foreground: Option<String>,

        /// Background color (hex)
        #[arg(long)]
        background: Option<String>,

        /// Bold
        #[arg(long)]
        bold: bool,

        /// Italic
        #[arg(long)]
        italic: bool,

        /// Underline
        #[arg(long)]
        underline: bool,

        /// Border style
        #[arg(long)]
        border: Option<String>,

        /// Padding
        #[arg(long)]
        padding: Option<String>,

        /// Width
        #[arg(short, long)]
        width: Option<u16>,
    },
}

fn main() {
    let cli = Cli::parse();

    match cli.command {
        Commands::Input {
            prompt,
            placeholder,
            default,
            password,
            header,
        } => {
            let mut cmd = input(&prompt);
            if let Some(p) = placeholder {
                cmd = cmd.placeholder(p);
            }
            if let Some(d) = default {
                cmd = cmd.default(d);
            }
            if password {
                cmd = cmd.password();
            }
            if let Some(h) = header {
                cmd = cmd.header(h);
            }
            let result = cmd.run();
            println!("{}", result);
        }

        Commands::Confirm { prompt, default } => {
            let mut cmd = confirm(&prompt);
            if let Some(d) = default {
                cmd = cmd.default(d);
            }
            let result = cmd.run();
            std::process::exit(if result { 0 } else { 1 });
        }

        Commands::Choose {
            options,
            header,
            limit,
        } => {
            let mut cmd = choose(&options);
            if let Some(h) = header {
                cmd = cmd.header(h);
            }
            if let Some(l) = limit {
                cmd = cmd.limit(l);
            }
            if let Some(result) = cmd.run() {
                println!("{}", result);
            }
        }

        Commands::Filter {
            options,
            header,
            limit,
        } => {
            let mut cmd = filter(&options);
            if let Some(h) = header {
                cmd = cmd.header(h);
            }
            cmd = cmd.limit(limit);
            if let Some(result) = cmd.run() {
                println!("{}", result);
            }
        }

        Commands::Spin {
            title,
            duration,
            style,
        } => {
            let spinner_style = match style.to_lowercase().as_str() {
                "line" => SpinnerStyle::Line,
                "dots" => SpinnerStyle::Dots,
                "arrow" => SpinnerStyle::Arrow,
                "circle" => SpinnerStyle::Circle,
                "moon" => SpinnerStyle::Moon,
                "fire" => SpinnerStyle::Fire,
                _ => SpinnerStyle::Dots,
            };

            spin(&title).style(spinner_style).run(|| {
                std::thread::sleep(Duration::from_secs(duration));
            });
        }

        Commands::Style {
            text,
            foreground,
            background,
            bold,
            italic,
            underline,
            border,
            padding,
            width,
        } => {
            let mut cmd = style_text(&text);

            if let Some(fg) = foreground {
                cmd = cmd.foreground(Color::from_hex(&fg));
            }
            if let Some(bg) = background {
                cmd = cmd.background(Color::from_hex(&bg));
            }
            if bold {
                cmd = cmd.bold();
            }
            if italic {
                cmd = cmd.italic();
            }
            if underline {
                cmd = cmd.underline();
            }
            if let Some(b) = border {
                let border_style = match b.to_lowercase().as_str() {
                    "rounded" => Border::Rounded,
                    "double" => Border::Double,
                    "thick" => Border::Thick,
                    "normal" => Border::Normal,
                    "ascii" => Border::Ascii,
                    _ => Border::Rounded,
                };
                cmd = cmd.border(border_style);
            }
            if let Some(p) = padding {
                let parts: Vec<u16> = p.split(',').filter_map(|s| s.trim().parse().ok()).collect();
                if parts.len() == 2 {
                    cmd = cmd.padding(parts[0], parts[1]);
                } else if parts.len() == 1 {
                    cmd = cmd.padding(parts[0], parts[0]);
                }
            }
            if let Some(w) = width {
                cmd = cmd.width(w);
            }

            cmd.print();
        }
    }
}