cli_tool/
cli_tool.rs

1use ansi_align::{AlignOptions, Alignment, ansi_align_with_options};
2use clap::{Parser, ValueEnum};
3use colored::*;
4use std::fs;
5use std::io::{self, Read};
6
7/// A beautiful CLI tool for aligning text with ANSI and Unicode support
8#[derive(Parser)]
9#[command(
10    name = "ansi-align",
11    version = "0.2.0",
12    about = "🎨 Beautiful text alignment with ANSI escape sequences and Unicode support",
13    long_about = "A powerful CLI tool that aligns text while preserving ANSI colors and properly handling Unicode characters including CJK. Perfect for creating beautiful terminal output, aligning code, or formatting data."
14)]
15struct Cli {
16    /// Text to align (use '-' to read from stdin)
17    #[arg(value_name = "TEXT")]
18    text: Option<String>,
19
20    /// Alignment direction
21    #[arg(short, long, default_value = "center")]
22    align: AlignmentArg,
23
24    /// Character to use for padding
25    #[arg(short, long, default_value = " ")]
26    pad: char,
27
28    /// String to split lines on
29    #[arg(short, long, default_value = "\\n")]
30    split: String,
31
32    /// Read from file instead of argument
33    #[arg(short, long, value_name = "FILE")]
34    file: Option<String>,
35
36    /// Add colorful borders around the output
37    #[arg(short, long)]
38    border: bool,
39
40    /// Show example usage with colors
41    #[arg(long)]
42    demo: bool,
43
44    /// Quiet mode - no decorative output
45    #[arg(short, long)]
46    quiet: bool,
47}
48
49#[derive(Clone, Copy, ValueEnum)]
50enum AlignmentArg {
51    Left,
52    Center,
53    Right,
54}
55
56impl From<AlignmentArg> for Alignment {
57    fn from(arg: AlignmentArg) -> Self {
58        match arg {
59            AlignmentArg::Left => Alignment::Left,
60            AlignmentArg::Center => Alignment::Center,
61            AlignmentArg::Right => Alignment::Right,
62        }
63    }
64}
65
66fn main() -> Result<(), Box<dyn std::error::Error>> {
67    let cli = Cli::parse();
68
69    if cli.demo {
70        show_demo();
71        return Ok(());
72    }
73
74    let input_text = get_input_text(&cli)?;
75
76    if input_text.trim().is_empty() {
77        if !cli.quiet {
78            eprintln!("{}", "⚠️  No input text provided".yellow());
79        }
80        return Ok(());
81    }
82
83    let split_str = cli.split.replace("\\n", "\n").replace("\\t", "\t");
84
85    let alignment = cli.align.into();
86    let options = AlignOptions::new(alignment)
87        .with_split(split_str)
88        .with_pad(cli.pad);
89
90    let aligned_text = ansi_align_with_options(&input_text, &options);
91
92    if cli.border {
93        print_with_border(&aligned_text, &cli);
94    } else {
95        println!("{}", aligned_text);
96    }
97
98    Ok(())
99}
100
101fn get_input_text(cli: &Cli) -> Result<String, Box<dyn std::error::Error>> {
102    if let Some(file_path) = &cli.file {
103        // Read from file
104        Ok(fs::read_to_string(file_path)?)
105    } else if let Some(text) = &cli.text {
106        if text == "-" {
107            // Read from stdin
108            let mut buffer = String::new();
109            io::stdin().read_to_string(&mut buffer)?;
110            Ok(buffer)
111        } else {
112            // Process escape sequences in command line text
113            Ok(text.replace("\\n", "\n").replace("\\t", "\t"))
114        }
115    } else {
116        // Read from stdin if no text provided
117        let mut buffer = String::new();
118        io::stdin().read_to_string(&mut buffer)?;
119        Ok(buffer)
120    }
121}
122
123fn print_with_border(text: &str, cli: &Cli) {
124    let lines: Vec<&str> = text.split('\n').collect();
125    let max_width = lines
126        .iter()
127        .map(|line| string_width::string_width(line))
128        .max()
129        .unwrap_or(0);
130
131    let border_width = max_width + 2; // 1 space padding on each side
132
133    if !cli.quiet {
134        // Top border
135        println!(
136            "{}{}{}",
137            "β”Œ".cyan().bold(),
138            "─".repeat(border_width).cyan(),
139            "┐".cyan().bold()
140        );
141    }
142
143    // Content with side borders - text is already aligned
144    for line in lines {
145        if !cli.quiet {
146            print!("{}", "β”‚ ".cyan().bold());
147        }
148        print!("{}", line);
149        if !cli.quiet {
150            print!("{}", " β”‚".cyan().bold());
151        }
152        println!();
153    }
154
155    if !cli.quiet {
156        // Bottom border
157        println!(
158            "{}{}{}",
159            "β””".cyan().bold(),
160            "─".repeat(border_width).cyan(),
161            "β”˜".cyan().bold()
162        );
163    }
164}
165
166fn show_demo() {
167    println!(
168        "{}",
169        "🎨 ansi-align Demo - Beautiful Text Alignment"
170            .bold()
171            .magenta()
172    );
173    println!();
174
175    // Demo 1: Basic alignment
176    println!("{}", "πŸ“ Basic Alignment:".bold().blue());
177    let basic_text = "Hello\nWorld\nRust";
178
179    println!("\n{}", "Left:".green());
180    println!(
181        "{}",
182        ansi_align_with_options(basic_text, &AlignOptions::new(Alignment::Left))
183    );
184
185    println!("\n{}", "Center:".green());
186    println!(
187        "{}",
188        ansi_align_with_options(basic_text, &AlignOptions::new(Alignment::Center))
189    );
190
191    println!("\n{}", "Right:".green());
192    println!(
193        "{}",
194        ansi_align_with_options(basic_text, &AlignOptions::new(Alignment::Right))
195    );
196
197    println!("\n{}", "─".repeat(50).dimmed());
198
199    // Demo 2: ANSI colors
200    println!("\n{}", "🌈 ANSI Color Support:".bold().blue());
201    let colored_text = format!(
202        "{}\n{}\n{}",
203        "Red Text".red().bold(),
204        "Green Text".green().italic(),
205        "Blue Text".blue().underline()
206    );
207
208    println!("\n{}", "Center aligned with colors:".green());
209    println!(
210        "{}",
211        ansi_align_with_options(&colored_text, &AlignOptions::new(Alignment::Center))
212    );
213
214    println!("\n{}", "─".repeat(50).dimmed());
215
216    // Demo 3: Unicode support
217    println!("\n{}", "🌏 Unicode Support:".bold().blue());
218    let unicode_text = "叀\n叀叀叀\nHello δΈ–η•Œ";
219
220    println!("\n{}", "Right aligned Unicode:".green());
221    println!(
222        "{}",
223        ansi_align_with_options(unicode_text, &AlignOptions::new(Alignment::Right))
224    );
225
226    println!("\n{}", "─".repeat(50).dimmed());
227
228    // Demo 4: Custom options
229    println!("\n{}", "βš™οΈ  Custom Options:".bold().blue());
230    let custom_text = "Name|Age|Location";
231    let options = AlignOptions::new(Alignment::Center)
232        .with_split("|")
233        .with_pad('.');
234
235    println!("\n{}", "Custom separator '|' and padding '.':".green());
236    println!("{}", ansi_align_with_options(custom_text, &options));
237
238    println!("\n{}", "─".repeat(50).dimmed());
239
240    // Demo 5: Menu example
241    println!("\n{}", "πŸ“‹ Menu Example:".bold().blue());
242    let menu = format!(
243        "{}\n{}\n{}\n{}",
244        "🏠 Home".cyan(),
245        "πŸ“‹ About Us".yellow(),
246        "πŸ“ž Contact".green(),
247        "βš™οΈ Settings".magenta()
248    );
249
250    println!("\n{}", "Center aligned menu:".green());
251    println!(
252        "{}",
253        ansi_align_with_options(&menu, &AlignOptions::new(Alignment::Center))
254    );
255
256    println!(
257        "\n{}",
258        "✨ Try it yourself with different options!".bold().green()
259    );
260    println!("{}", "Examples:".dimmed());
261    println!(
262        "{}",
263        "  cargo run --example cli_tool -- \"Hello\\nWorld\" --align center".dimmed()
264    );
265    println!(
266        "{}",
267        "  echo \"Line 1\\nLonger Line 2\" | cargo run --example cli_tool -- - --border".dimmed()
268    );
269    println!(
270        "{}",
271        "  cargo run --example cli_tool -- --file README.md --align right --pad '.'".dimmed()
272    );
273}