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#[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 #[arg(value_name = "TEXT")]
18 text: Option<String>,
19
20 #[arg(short, long, default_value = "center")]
22 align: AlignmentArg,
23
24 #[arg(short, long, default_value = " ")]
26 pad: char,
27
28 #[arg(short, long, default_value = "\\n")]
30 split: String,
31
32 #[arg(short, long, value_name = "FILE")]
34 file: Option<String>,
35
36 #[arg(short, long)]
38 border: bool,
39
40 #[arg(long)]
42 demo: bool,
43
44 #[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 Ok(fs::read_to_string(file_path)?)
105 } else if let Some(text) = &cli.text {
106 if text == "-" {
107 let mut buffer = String::new();
109 io::stdin().read_to_string(&mut buffer)?;
110 Ok(buffer)
111 } else {
112 Ok(text.replace("\\n", "\n").replace("\\t", "\t"))
114 }
115 } else {
116 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; if !cli.quiet {
134 println!(
136 "{}{}{}",
137 "β".cyan().bold(),
138 "β".repeat(border_width).cyan(),
139 "β".cyan().bold()
140 );
141 }
142
143 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 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 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 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 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 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 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}