fast_rich/lib.rs
1//! # fast-rich
2//!
3//! A Rust port of Python's [Rich](https://github.com/Textualize/rich) library
4//! for beautiful terminal formatting.
5//!
6//! ## Features
7//!
8//! - **Rich text** with colors, styles, and markup
9//! - **Tables** with Unicode borders and auto-sizing
10//! - **Progress bars** with multiple tasks, spinners, and customizable columns
11//! - **Live Display** for flicker-free auto-updating content
12//! - **Logging** handler for colorful structured logs
13//! - **Tree views** for hierarchical data
14//! - **Panels** and **Rules** for visual organization
15//! - **Markdown** rendering (optional)
16//! - **Syntax highlighting** (optional)
17//! - **Pretty tracebacks** for better error display
18//!
19//! ## Quick Start
20//!
21//! ### Drop-in Print Replacement
22//!
23//! ```no_run
24//! // Shadow standard print macros with rich versions
25//! use fast_rich::{print, println};
26//!
27//! println!("[bold magenta]Hello, World![/]");
28//! println!("Player {} scored [yellow]{}[/] points", "Alice", 100);
29//! ```
30//!
31//! ### Using Console Directly
32//!
33//! ```no_run
34//! use fast_rich::prelude::*;
35//!
36//! let console = Console::new();
37//!
38//! // Simple styled output
39//! console.print("Hello, [bold magenta]World[/]!");
40//!
41//! // Tables
42//! let mut table = Table::new();
43//! table.add_column("Name");
44//! table.add_column("Age");
45//! table.add_row_strs(&["Alice", "30"]);
46//! console.print_renderable(&table);
47//! ```
48//!
49//! ## Markup Syntax
50//!
51//! The print macros support Rich markup syntax:
52//!
53//! - `[bold]text[/]` - Bold text
54//! - `[red]text[/]` - Colored text
55//! - `[bold red on blue]text[/]` - Combined styles
56//! - `[[` and `]]` - Escaped brackets (literal `[` and `]`)
57//!
58//! ## Important: Bracket Handling
59//!
60//! The markup parser uses a smart heuristic to distinguish between style tags
61//! and data brackets:
62//!
63//! - **Valid Tags**: `[bold]`, `[red]`, `[link=url]` -> Parsed as style
64//! - **Data**: `[1, 2, 3]`, `[Unknown]` -> Printed literally as text
65//!
66//! This means standard debug output usually works out of the box:
67//!
68//! ```no_run
69//! use fast_rich::println;
70//!
71//! let data = vec![1, 2, 3];
72//! println!("Data: {:?}", data); // Prints: Data: [1, 2, 3]
73//! ```
74//!
75//! However, there is a **Known Limitation**: if your data looks exactly like a style tag,
76//! the parser will consume it.
77//!
78//! ```no_run
79//! // ⚠️ Unintended Tag Collision
80//! let colors = vec!["red", "blue"];
81//! // Parser sees "[red", parses it as color tag!
82//! // println!("Colors: {:?}", colors);
83//! ```
84//!
85//! For untrusted input or strict correctness, always use the `_raw` macros:
86//!
87//! ```no_run
88//! use fast_rich::println_raw;
89//!
90//! let colors = vec!["red", "blue"];
91//! println_raw!("Colors: {:?}", colors); // ✅ Safe: Prints ["red", "blue"]
92//! ```
93//!
94//! ### Macro Summary
95//!
96//! | Macro | Markup | Use Case |
97//! |-------|--------|----------|
98//! | `print!` / `println!` | ✅ Smart | Styled output & most debug data |
99//! | `print_raw!` / `println_raw!` | ❌ Skipped | Strict raw data output |
100//! | `rprint!` / `rprintln!` | ✅ Smart | Alias (when you need both std and rich) |
101//!
102//! ### Macro Summary
103//!
104//! | Macro | Markup | Use Case |
105//! |-------|--------|----------|
106//! | `print!` / `println!` | ✅ Parsed | Styled output you control |
107//! | `print_raw!` / `println_raw!` | ❌ Skipped | Data output, debug values |
108//! | `rprint!` / `rprintln!` | ✅ Parsed | Alias (when you need both std and rich) |
109
110use std::cell::RefCell;
111
112// Core modules
113pub mod align;
114pub mod bar;
115pub mod box_drawing;
116pub mod console;
117pub mod emoji;
118pub mod group;
119pub mod highlighter;
120pub mod markup;
121pub mod measure;
122pub mod nested_progress;
123pub mod padding;
124pub mod pager;
125pub mod renderable;
126pub mod screen;
127pub mod style;
128pub mod text;
129pub mod theme;
130
131// Renderables
132pub mod columns;
133pub mod filesize;
134pub mod layout;
135pub mod live;
136pub mod log;
137pub mod panel;
138pub mod rule;
139pub mod table;
140pub mod tree;
141
142// Progress
143pub mod progress;
144
145// Utilities
146pub mod inspect;
147pub mod prompt;
148pub mod traceback;
149
150// Optional feature-gated modules
151#[cfg(feature = "markdown")]
152pub mod markdown;
153
154#[cfg(feature = "syntax")]
155pub mod syntax;
156
157// Re-exports for convenience
158pub use console::Console;
159pub use layout::Layout;
160pub use live::Live;
161pub use panel::{BorderStyle, Panel};
162pub use renderable::Renderable;
163pub use rule::Rule;
164pub use style::{Color, Style};
165pub use table::{Column, ColumnAlign, Table};
166pub use text::{Alignment, Text};
167pub use tree::{Tree, TreeNode};
168
169// ============================================================================
170// Thread-local Console for Print Macros
171// ============================================================================
172
173thread_local! {
174 static STDOUT_CONSOLE: RefCell<Console> = RefCell::new(Console::new());
175 static STDERR_CONSOLE: RefCell<Console> = RefCell::new(Console::stderr());
176 // Raw consoles have markup parsing disabled - for data output
177 static STDOUT_RAW_CONSOLE: RefCell<Console> = RefCell::new(Console::new().markup(false));
178 static STDERR_RAW_CONSOLE: RefCell<Console> = RefCell::new(Console::stderr().markup(false));
179}
180
181/// Internal helper for print macros - DO NOT USE DIRECTLY.
182#[doc(hidden)]
183pub fn __internal_print(content: String, newline: bool) {
184 STDOUT_CONSOLE.with(|c| {
185 let console = c.borrow();
186 if newline {
187 console.println(&content);
188 } else {
189 console.print(&content);
190 }
191 });
192}
193
194/// Internal helper for eprint macros - DO NOT USE DIRECTLY.
195#[doc(hidden)]
196pub fn __internal_eprint(content: String, newline: bool) {
197 STDERR_CONSOLE.with(|c| {
198 let console = c.borrow();
199 if newline {
200 console.println(&content);
201 } else {
202 console.print(&content);
203 }
204 });
205}
206
207/// Internal helper for raw print macros (no markup parsing) - DO NOT USE DIRECTLY.
208#[doc(hidden)]
209pub fn __internal_print_raw(content: String, newline: bool) {
210 STDOUT_RAW_CONSOLE.with(|c| {
211 let console = c.borrow();
212 if newline {
213 console.println(&content);
214 } else {
215 console.print(&content);
216 }
217 });
218}
219
220/// Internal helper for raw eprint macros (no markup parsing) - DO NOT USE DIRECTLY.
221#[doc(hidden)]
222pub fn __internal_eprint_raw(content: String, newline: bool) {
223 STDERR_RAW_CONSOLE.with(|c| {
224 let console = c.borrow();
225 if newline {
226 console.println(&content);
227 } else {
228 console.print(&content);
229 }
230 });
231}
232
233// ============================================================================
234// Print Macros - Drop-in replacements for std::print! and std::println!
235// ============================================================================
236
237/// Print formatted text with Rich markup to stdout (no newline).
238///
239/// This macro is a drop-in replacement for `std::print!` that adds
240/// Rich markup support for colors, styles, and formatting.
241///
242/// # Example
243///
244/// ```no_run
245/// use fast_rich::print;
246///
247/// print!("[bold blue]Status:[/] checking... ");
248/// print!("Value: [yellow]{}[/]", 42);
249/// ```
250#[macro_export]
251macro_rules! print {
252 ($($arg:tt)*) => {{
253 $crate::__internal_print(format!($($arg)*), false);
254 }};
255}
256
257/// Print formatted text with Rich markup to stdout (with newline).
258///
259/// This macro is a drop-in replacement for `std::println!` that adds
260/// Rich markup support for colors, styles, and formatting.
261///
262/// # Example
263///
264/// ```no_run
265/// use fast_rich::println;
266///
267/// println!("[bold green]Success![/] All tests passed.");
268/// println!("Player {} scored [yellow]{}[/] points", "Alice", 100);
269/// println!(); // Empty line
270/// ```
271#[macro_export]
272macro_rules! println {
273 () => {{
274 $crate::__internal_print(String::new(), true);
275 }};
276 ($($arg:tt)*) => {{
277 $crate::__internal_print(format!($($arg)*), true);
278 }};
279}
280
281/// Print formatted text with Rich markup to stderr (no newline).
282///
283/// This macro is a drop-in replacement for `std::eprint!` that adds
284/// Rich markup support for colors, styles, and formatting.
285///
286/// # Example
287///
288/// ```no_run
289/// use fast_rich::eprint;
290///
291/// eprint!("[red]Error:[/] ");
292/// ```
293#[macro_export]
294macro_rules! eprint {
295 ($($arg:tt)*) => {{
296 $crate::__internal_eprint(format!($($arg)*), false);
297 }};
298}
299
300/// Print formatted text with Rich markup to stderr (with newline).
301///
302/// This macro is a drop-in replacement for `std::eprintln!` that adds
303/// Rich markup support for colors, styles, and formatting.
304///
305/// # Example
306///
307/// ```no_run
308/// use fast_rich::eprintln;
309///
310/// eprintln!("[bold red]Error:[/] Something went wrong!");
311/// eprintln!("[yellow]Warning:[/] {} items skipped", 5);
312/// ```
313#[macro_export]
314macro_rules! eprintln {
315 () => {{
316 $crate::__internal_eprint(String::new(), true);
317 }};
318 ($($arg:tt)*) => {{
319 $crate::__internal_eprint(format!($($arg)*), true);
320 }};
321}
322
323// ============================================================================
324// Raw Print Macros - No markup parsing, safe for data output
325// ============================================================================
326
327/// Print to stdout without markup parsing (no newline).
328///
329/// Use this for data output that may contain brackets like `[1, 2, 3]`
330/// which would otherwise be interpreted as style tags.
331///
332/// # Example
333///
334/// ```no_run
335/// use fast_rich::print_raw;
336///
337/// let data = vec![1, 2, 3];
338/// print_raw!("Data: {:?}", data); // Brackets printed literally
339/// ```
340#[macro_export]
341macro_rules! print_raw {
342 ($($arg:tt)*) => {{
343 $crate::__internal_print_raw(format!($($arg)*), false);
344 }};
345}
346
347/// Print to stdout without markup parsing (with newline).
348///
349/// Use this for data output that may contain brackets like `[1, 2, 3]`
350/// which would otherwise be interpreted as style tags.
351///
352/// # Example
353///
354/// ```no_run
355/// use fast_rich::println_raw;
356///
357/// let items = vec!["a", "b", "c"];
358/// println_raw!("Items: {:?}", items); // Brackets printed literally
359/// ```
360#[macro_export]
361macro_rules! println_raw {
362 () => {{
363 $crate::__internal_print_raw(String::new(), true);
364 }};
365 ($($arg:tt)*) => {{
366 $crate::__internal_print_raw(format!($($arg)*), true);
367 }};
368}
369
370/// Print to stderr without markup parsing (no newline).
371#[macro_export]
372macro_rules! eprint_raw {
373 ($($arg:tt)*) => {{
374 $crate::__internal_eprint_raw(format!($($arg)*), false);
375 }};
376}
377
378/// Print to stderr without markup parsing (with newline).
379#[macro_export]
380macro_rules! eprintln_raw {
381 () => {{
382 $crate::__internal_eprint_raw(String::new(), true);
383 }};
384 ($($arg:tt)*) => {{
385 $crate::__internal_eprint_raw(format!($($arg)*), true);
386 }};
387}
388
389// ============================================================================
390// Aliases - For users who want both standard and rich macros
391// ============================================================================
392
393/// Alias for `print!` - use when you need both `std::print!` and rich print.
394///
395/// # Example
396///
397/// ```no_run
398/// use fast_rich::rprint;
399///
400/// std::print!("Standard: no markup [bold]");
401/// rprint!("Rich: [bold]this is bold[/]");
402/// ```
403#[macro_export]
404macro_rules! rprint {
405 ($($arg:tt)*) => {{
406 $crate::print!($($arg)*);
407 }};
408}
409
410/// Alias for `println!` - use when you need both `std::println!` and rich println.
411///
412/// # Example
413///
414/// ```no_run
415/// use fast_rich::rprintln;
416///
417/// std::println!("Standard: no markup [bold]");
418/// rprintln!("Rich: [bold]this is bold[/]");
419/// ```
420#[macro_export]
421macro_rules! rprintln {
422 () => {{
423 $crate::println!();
424 }};
425 ($($arg:tt)*) => {{
426 $crate::println!($($arg)*);
427 }};
428}
429
430/// Prelude module for convenient imports.
431///
432/// ## Print Macros
433///
434/// The print macros are NOT included in the prelude to avoid conflicts with `std`.
435/// Import them explicitly if you want drop-in shadowing:
436///
437/// ```no_run
438/// use fast_rich::{print, println};
439/// println!("[bold green]Hello![/]");
440/// ```
441///
442/// The following ARE included in the prelude:
443/// - `rprint!` / `rprintln!` - Aliases that don't conflict with std
444/// - `print_raw!` / `println_raw!` - Raw output without markup parsing
445pub mod prelude {
446 // Aliases that don't conflict with std
447 pub use crate::{rprint, rprintln};
448
449 // Raw print macros for data output (no markup parsing, no std conflicts)
450 pub use crate::{eprint_raw, eprintln_raw, print_raw, println_raw};
451
452 pub use crate::columns::Columns;
453 pub use crate::console::Console;
454 pub use crate::inspect::{inspect, InspectConfig};
455 pub use crate::log::ConsoleLog;
456 pub use crate::panel::{BorderStyle, Panel};
457 pub use crate::progress::{track, Progress, ProgressBar, Spinner, SpinnerStyle, Status};
458 pub use crate::renderable::Renderable;
459 pub use crate::rule::Rule;
460 pub use crate::style::{Color, Style};
461 pub use crate::table::{Column, ColumnAlign, Table};
462 pub use crate::text::{Alignment, Text};
463 pub use crate::traceback::install_panic_hook;
464 pub use crate::tree::{GuideStyle, Tree, TreeNode};
465}
466
467#[cfg(test)]
468mod tests {
469 use super::*;
470
471 #[test]
472 fn test_console_creation() {
473 let console = Console::new();
474 assert!(console.get_width() > 0);
475 }
476
477 #[test]
478 fn test_style_builder() {
479 let style = Style::new().foreground(Color::Red).bold().underline();
480
481 assert!(style.bold);
482 assert!(style.underline);
483 }
484
485 #[test]
486 fn test_text_creation() {
487 let text = Text::plain("Hello, World!");
488 assert_eq!(text.plain_text(), "Hello, World!");
489 }
490
491 #[test]
492 fn test_table_creation() {
493 let mut table = Table::new();
494 table.add_column("Col1");
495 table.add_column("Col2");
496 table.add_row_strs(&["a", "b"]);
497
498 // Table should have columns and rows
499 assert!(!table
500 .render(&console::RenderContext {
501 width: 40,
502 height: None
503 })
504 .is_empty());
505 }
506}